/*
   (c) Copyright 2001-2009  The world wide DirectFB Open Source Community (directfb.org)
   (c) Copyright 2000-2004  Convergence (integrated media) GmbH

   All rights reserved.

   Written by Denis Oliver Kropp <dok@directfb.org>,
              Andreas Hundt <andi@fischlustig.de>,
              Sven Neumann <neo@directfb.org>,
              Ville Syrjälä <syrjala@sci.fi> and
              Claudio Ciccani <klan@users.sf.net>.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

#include <config.h>

#include <directfb.h>

#include <direct/debug.h>
#include <direct/messages.h>
#include <direct/util.h>

#include <fusion/shmalloc.h>
#include <fusion/vector.h>

#include <core/core.h>
#include <core/input.h>
#include <core/layers_internal.h>
#include <core/windows_internal.h>

#include <misc/util.h>

#include <unique/device.h>
#include <unique/internal.h>


#define MAX_CLASSES 16


D_DEBUG_DOMAIN( UniQuE_Device, "UniQuE/Device", "UniQuE's Devices" );


static const ReactionFunc unique_device_globals[] = {
     _unique_input_switch_device_listener,
     _unique_cursor_device_listener,
     NULL
};

/**************************************************************************************************/

typedef struct {
     DirectLink       link;

     int              magic;

     DFBInputDeviceID source;

     GlobalReaction   reaction;
} DeviceConnection;

/**************************************************************************************************/

static void purge_connection( UniqueDevice     *device,
                              DeviceConnection *connection );

/**************************************************************************************************/

static const UniqueDeviceClass *classes[MAX_CLASSES] = { NULL };

static pthread_mutex_t          classes_lock  = PTHREAD_MUTEX_INITIALIZER;
static int                      classes_count = 0;

/**************************************************************************************************/

DFBResult
unique_device_class_register( const UniqueDeviceClass *clazz,
                              UniqueDeviceClassID     *ret_id )
{
     int i;

     D_DEBUG_AT( UniQuE_Device, "unique_device_class_register( %p )\n", clazz );

     D_ASSERT( clazz != NULL );
     D_ASSERT( ret_id != NULL );

     pthread_mutex_lock( &classes_lock );

     if (classes_count == MAX_CLASSES) {
          D_WARN( "too many classes" );
          pthread_mutex_unlock( &classes_lock );
          return DFB_LIMITEXCEEDED;
     }

     classes_count++;

     for (i=0; i<MAX_CLASSES; i++) {
          if (!classes[i]) {
               classes[i] = clazz;
               break;
          }
     }

     D_DEBUG_AT( UniQuE_Device, "    -> New class ID is %d.\n", i );

     D_ASSERT( i < MAX_CLASSES );

     *ret_id = i;

     pthread_mutex_unlock( &classes_lock );

     return DFB_OK;
}

DFBResult
unique_device_class_unregister( UniqueDeviceClassID id )
{
     D_DEBUG_AT( UniQuE_Device, "unique_device_class_unregister( %d )\n", id );

     pthread_mutex_lock( &classes_lock );

     D_ASSERT( id >= 0 );
     D_ASSERT( id < MAX_CLASSES );
     D_ASSERT( classes[id] != NULL );

     classes[id] = NULL;

     classes_count--;

     pthread_mutex_unlock( &classes_lock );

     return DFB_OK;
}

/**************************************************************************************************/

DFBResult
unique_device_create( CoreDFB              *core,
                      UniqueContext        *context,
                      UniqueDeviceClassID   class_id,
                      void                 *ctx,
                      UniqueDevice        **ret_device )
{
     DFBResult                ret;
     UniqueDevice            *device;
     const UniqueDeviceClass *clazz;

     D_DEBUG_AT( UniQuE_Device, "unique_device_create( class %d )\n", class_id );

     D_MAGIC_ASSERT( context, UniqueContext );

     D_ASSERT( class_id >= 0 );
     D_ASSERT( class_id < MAX_CLASSES );

     clazz = classes[class_id];

     D_ASSERT( clazz != NULL );
     D_ASSERT( ret_device != NULL );

     /* Allocate device data. */
     device = SHCALLOC( context->shmpool, 1, sizeof(UniqueDevice) );
     if (!device) {
          D_WARN( "out of (shared) memory" );
          return D_OOSHM();
     }

     /* Initialize device data. */
     device->context = context;
     device->clazz   = class_id;
     device->ctx     = ctx;

     /* Allocate private device data. */
     if (clazz->data_size) {
          device->data = SHCALLOC( context->shmpool, 1, clazz->data_size );
          if (!device->data) {
               ret = D_OOSHM();
               goto error;
          }
     }

     /* Create reactor for dispatching generated events. */
     device->reactor = fusion_reactor_new( sizeof(UniqueInputEvent),
                                           "UniQuE Device", dfb_core_world(core) );
     if (!device->reactor) {
          ret = DFB_FUSION;
          goto error;
     }

     fusion_reactor_set_lock( device->reactor, &context->stack->context->lock );

     D_MAGIC_SET( device, UniqueDevice );

     if (clazz->Initialize) {
          ret = clazz->Initialize( device, device->data, device->ctx );
          if (ret) {
               D_MAGIC_CLEAR( device );
               goto error;
          }
     }

     D_DEBUG_AT( UniQuE_Device, "  -> device created (%p)\n", device );

     *ret_device = device;

     return DFB_OK;


error:
     if (device->reactor)
          fusion_reactor_free( device->reactor );

     if (device->data)
          SHFREE( context->shmpool, device->data );

     SHFREE( context->shmpool, device );

     return ret;
}

DFBResult
unique_device_destroy( UniqueDevice *device )
{
     DirectLink              *n;
     DeviceConnection        *connection;
     UniqueContext           *context;
     const UniqueDeviceClass *clazz;

     D_MAGIC_ASSERT( device, UniqueDevice );

     context = device->context;

     D_MAGIC_ASSERT( context, UniqueContext );

     D_ASSERT( device->clazz >= 0 );
     D_ASSERT( device->clazz < MAX_CLASSES );

     clazz = classes[device->clazz];

     D_ASSERT( clazz != NULL );

     D_DEBUG_AT( UniQuE_Device, "unique_device_destroy( %p )\n", device );

     direct_list_foreach_safe (connection, n, device->connections) {
          D_MAGIC_ASSERT( connection, DeviceConnection );

          purge_connection( device, connection );
     }

     D_ASSERT( device->connections == NULL );

     if (clazz->Shutdown)
          clazz->Shutdown( device, device->data, device->ctx );

     fusion_reactor_free( device->reactor );

     if (device->data)
          SHFREE( context->shmpool, device->data );

     D_MAGIC_CLEAR( device );

     SHFREE( context->shmpool, device );

     return DFB_OK;
}

DFBResult
unique_device_connect( UniqueDevice    *device,
                       CoreInputDevice *source )
{
     DFBResult         ret;
     WMShared         *shared;
     UniqueContext    *context;
     DeviceConnection *connection;

     D_MAGIC_ASSERT( device, UniqueDevice );

     D_ASSERT( source != NULL );

     D_ASSERT( device->clazz >= 0 );
     D_ASSERT( device->clazz < MAX_CLASSES );
     D_ASSERT( classes[device->clazz] != NULL );

     context = device->context;

     D_MAGIC_ASSERT( context, UniqueContext );

     shared = context->shared;

     D_MAGIC_ASSERT( shared, WMShared );

     D_DEBUG_AT( UniQuE_Device, "unique_device_connect( %p, %p, ID %d )\n",
                 device, source, dfb_input_device_id( source ) );

     /* Allocate connection structure. */
     connection = SHCALLOC( context->shmpool, 1, sizeof(DeviceConnection) );
     if (!connection)
          return D_OOSHM();

     /* Initialize connection structure. */
     connection->source = dfb_input_device_id( source );

     /* Attach global reaction for processing events. */
     ret = dfb_input_attach_global( source, shared->device_listener,
                                    device, &connection->reaction );
     if (ret) {
          SHFREE( context->shmpool, connection );
          return ret;
     }

     /* Add the new connection to the list. */
     direct_list_append( &device->connections, &connection->link );

     D_MAGIC_SET( connection, DeviceConnection );

     if (classes[device->clazz]->Connected)
          classes[device->clazz]->Connected( device, device->data, device->ctx, source );

     return DFB_OK;
}

DFBResult
unique_device_disconnect( UniqueDevice    *device,
                          CoreInputDevice *source )
{
     DFBInputDeviceID  source_id;
     DeviceConnection *connection;

     D_MAGIC_ASSERT( device, UniqueDevice );

     D_ASSERT( source != NULL );

     D_ASSERT( device->clazz >= 0 );
     D_ASSERT( device->clazz < MAX_CLASSES );
     D_ASSERT( classes[device->clazz] != NULL );

     source_id = dfb_input_device_id( source );

     direct_list_foreach (connection, device->connections) {
          D_MAGIC_ASSERT( connection, DeviceConnection );

          if (connection->source == source_id)
               break;
     }

     if (!connection) {
          D_WARN( "source not found amoung connections" );
          return DFB_ITEMNOTFOUND;
     }

     purge_connection( device, connection );

     return DFB_OK;
}

DFBResult
unique_device_attach( UniqueDevice *device,
                      ReactionFunc  func,
                      void         *ctx,
                      Reaction     *reaction )
{
     D_MAGIC_ASSERT( device, UniqueDevice );

     return fusion_reactor_attach( device->reactor, func, ctx, reaction );
}

DFBResult
unique_device_detach( UniqueDevice *device,
                      Reaction     *reaction )
{
     D_MAGIC_ASSERT( device, UniqueDevice );

     return fusion_reactor_detach( device->reactor, reaction );
}

DFBResult
unique_device_attach_global( UniqueDevice   *device,
                             int             index,
                             void           *ctx,
                             GlobalReaction *reaction )
{
     D_MAGIC_ASSERT( device, UniqueDevice );

     return fusion_reactor_attach_global( device->reactor, index, ctx, reaction );
}

DFBResult
unique_device_detach_global( UniqueDevice   *device,
                             GlobalReaction *reaction )
{
     D_MAGIC_ASSERT( device, UniqueDevice );

     return fusion_reactor_detach_global( device->reactor, reaction );
}

DFBResult
unique_device_dispatch( UniqueDevice           *device,
                        const UniqueInputEvent *event )
{
     D_MAGIC_ASSERT( device, UniqueDevice );

     return fusion_reactor_dispatch( device->reactor, event, true, unique_device_globals );
}

bool
unique_device_filter ( UniqueDeviceClassID     class_id,
                       const UniqueInputEvent *event,
                       const UniqueInputEvent *filter )
{
     const UniqueDeviceClass *clazz;

     D_ASSERT( class_id >= 0 );
     D_ASSERT( class_id < MAX_CLASSES );

     D_ASSERT( event != NULL );
     D_ASSERT( filter != NULL );

     clazz = classes[class_id];

     D_ASSERT( clazz != NULL );

     if (clazz->FilterEvent)
          return clazz->FilterEvent( event, filter );

     return false;
}

/**************************************************************************************************/

ReactionResult
_unique_device_listener( const void *msg_data,
                         void       *ctx )
{
     const DFBInputEvent *event  = msg_data;
     UniqueDevice        *device = ctx;

     D_ASSERT( event != NULL );

     D_MAGIC_ASSERT( device, UniqueDevice );

     D_ASSERT( device->clazz >= 0 );
     D_ASSERT( device->clazz < MAX_CLASSES );
     D_ASSERT( classes[device->clazz] != NULL );
     D_ASSERT( classes[device->clazz]->ProcessEvent != NULL );

     classes[device->clazz]->ProcessEvent( device, device->data, device->ctx, event );

     return RS_OK;
}

/**************************************************************************************************/

static void
purge_connection( UniqueDevice     *device,
                  DeviceConnection *connection )
{
     CoreInputDevice *source;
     UniqueContext   *context;

     D_MAGIC_ASSERT( device, UniqueDevice );
     D_MAGIC_ASSERT( connection, DeviceConnection );

     context = device->context;

     D_MAGIC_ASSERT( context, UniqueContext );

     source = dfb_input_device_at( connection->source );

     D_ASSERT( source != NULL );

     /* Detach global reaction for processing events. */
     dfb_input_detach_global( source, &connection->reaction );

     direct_list_remove( &device->connections, &connection->link );

     if (classes[device->clazz]->Disconnected)
          classes[device->clazz]->Disconnected( device, device->data, device->ctx, source );

     D_MAGIC_CLEAR( connection );

     SHFREE( context->shmpool, connection );
}

