/*
 * Copyright 2010, 2011, 2013 Canonical, Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of either or both of the following licenses:
 *
 * 1) the GNU Lesser General Public License version 3, as published by the
 * Free Software Foundation; and/or
 * 2) the GNU Lesser General Public License version 2.1, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the applicable version of the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of both the GNU Lesser General Public
 * License version 3 and version 2.1 along with this program.  If not, see
 * <http://www.gnu.org/licenses/>
 *
 * Authors:
 *    Cody Russell <crussell@canonical.com>
 */

#include "gripgesturemanager.h"

#include <gdk/gdkx.h>
#include <geis/geis.h>
#include "gripinputdevice.h"

typedef struct _GripGestureBinding      GripGestureBinding;
typedef struct _GripRegistrationRequest GripRegistrationRequest;

typedef GPtrArray GripDevices;

/*
 * GripGestureClassMap:
 * @grip_type: the libgrip gesture type
 * @geis_class: the geis gesture class
 *
 * Provides a mapping between a grip gesture type and a geis gesture class.
 */
typedef struct _GripGestureClassMap
{
  GripGestureType  grip_type;
  GeisGestureClass geis_class;
} GripGestureClassMap;

/*
 * GripGestureManagerPrivate:
 * @geis: the GEIS instance
 * @is_initialized: GEIS async initialization has been successfully completed
 * @iochannel: connects the geis object with the main loop
 * @iochannel_id: the identifier for the iochannel
 * @devices: a collection of recognized input devices
 * @classes: a list of grip type to geis class mappings
 * @requests: a list of pending binding requests
 * @bindings: a list of current gesture bindings
 */
struct _GripGestureManagerPrivate
{
  Geis              geis;
  gboolean          is_initialized;
  GIOChannel       *iochannel;
  int               iochannel_id;
  GripDevices      *devices;
  GList            *classes;
  GList            *requests;
  GList            *bindings;
};

G_DEFINE_TYPE (GripGestureManager, grip_gesture_manager, G_TYPE_OBJECT)

#define GRIP_GESTURE_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GRIP_TYPE_GESTURE_MANAGER, GripGestureManagerPrivate))

/*
 * GripGestureBinding:
 * @type:
 * @widget:
 * @touches:
 * @callback:
 * @data:
 * @destroy:
 *
 * The mapping between a GEIS subscription and a GTK widget.  Sort of.
 */
struct _GripGestureBinding
{
  GripGestureType      type;
  GtkWidget           *widget;
  guint                touches;
  GeisSubscription     subscription;
  GripGestureCallback  callback;
  gpointer             data;
  GDestroyNotify       destroy;
};


/*
 * GripRegistrationRequest:
 * @manager: the @GripGestureManager
 * @widget: the GtkWidget to be bound
 * @gesture_type: the type of gesture to subscribe for
 * @device_type: the type of input device to subscribe for
 * @touch_points: the number of touches making the gesture
 * @callback: the callback to be invoked on gesture events
 * @user_data: the callback context to be passed back on gesture events
 * @destroy: function to destroy the callback context (NULL for no function)
 *
 * A collection of information required to register a callback for a
 * particulatr type of gestures on a window.
 */
struct _GripRegistrationRequest
{
  GripGestureManager *manager;
  GtkWidget          *widget;
  GripGestureType     gesture_type;
  GripDeviceType      device_type;
  gint                touch_points;
  GripGestureCallback callback;
  gpointer            user_data;
  GDestroyNotify      destroy;
};

enum
{
  DEVICE_AVAILABLE,
  DEVICE_UNAVAILABLE,
  SIGNAL_LAST
};

static guint signals[SIGNAL_LAST];

static void process_gesture_events (GripGestureManager *manager,
                                    GeisEvent geis_event);

static void toplevel_mapped_cb (GtkWidget       *toplevel,
                                GdkEvent        *event,
                                gpointer         user_data);


static void
grip_geis_device_added(GripGestureManager *manager G_GNUC_UNUSED, GeisEvent geis_event)
{
  GeisAttr attr = geis_event_attr_by_name(geis_event, GEIS_EVENT_ATTRIBUTE_DEVICE);
  g_return_if_fail (attr);

  GeisDevice device = geis_attr_value_to_pointer(attr);
  g_return_if_fail (device);

  switch (geis_event_type(geis_event))
    {
      case GEIS_EVENT_DEVICE_AVAILABLE:
        {
          GripInputDevice *input_device = g_object_new (GRIP_TYPE_INPUT_DEVICE,
                                                        "geis-device", device,
                                                        NULL);

          g_ptr_array_add(manager->priv->devices, input_device);
          g_signal_emit (manager, signals[DEVICE_AVAILABLE], 0, input_device);
        }
        break;

      case GEIS_EVENT_DEVICE_UNAVAILABLE:
        {
          guint device_id = geis_device_id (device);
          GripDevices *devices = manager->priv->devices;
          guint i;
          for (i = 0; i < devices->len; ++i)
          {
            GripInputDevice *input_device = g_ptr_array_index (devices, i);
            if (device_id == grip_input_device_get_id (input_device))
            {
              g_signal_emit (manager, signals[DEVICE_UNAVAILABLE], 0,
                             input_device);
              g_ptr_array_remove_index (devices, i);
              break;
            }
          }
        }
        break;

      default:
        break;
    }
}


static void
process_class_event(GripGestureManagerPrivate *priv G_GNUC_UNUSED, GeisEvent geis_event)
{
  GeisAttr attr = geis_event_attr_by_name (geis_event,
                                           GEIS_EVENT_ATTRIBUTE_CLASS);
  g_return_if_fail (attr);

  GeisGestureClass gesture_class = geis_attr_value_to_pointer (attr);
  g_return_if_fail (gesture_class);

  switch (geis_event_type (geis_event))
    {
      case GEIS_EVENT_CLASS_AVAILABLE:
        {
          gchar const *class_name = geis_gesture_class_name (gesture_class);
          /* check for dups */
          GList *l;
          for (l = priv->classes; l != NULL; l = l->next)
            {
              GripGestureClassMap *p = (GripGestureClassMap *)l->data;
              if (0 == g_strcmp0 (geis_gesture_class_name (p->geis_class),
                                  class_name))
                {
                  g_warning ("multiple class definitions for '%s' received",
                             class_name);
                  return;
                }
            }

          GripGestureClassMap *m = g_new0 (GripGestureClassMap, 1);
          if (0 == g_strcmp0 (class_name, "Drag"))
            m->grip_type = GRIP_GESTURE_DRAG;
          else if (0 == g_strcmp0 (class_name, "Pinch"))
            m->grip_type = GRIP_GESTURE_PINCH;
          else if (0 == g_strcmp0 (class_name, "Rotate"))
            m->grip_type = GRIP_GESTURE_ROTATE;
          else if (0 == g_strcmp0 (class_name, "Tap"))
            m->grip_type = GRIP_GESTURE_TAP;
          else
            m->grip_type = (GripGestureType)100;
          m->geis_class = gesture_class;
          priv->classes = g_list_append (priv->classes, m);
        }
        break;

      default:
        /* @todo handle class-changed and class-unavailable events */
        g_warning ("gesture class event ignored");
        break;
    }
}


static void
grip_geis_event_callback(Geis geis G_GNUC_UNUSED, GeisEvent geis_event, void *context)
{
  GripGestureManager *self = GRIP_GESTURE_MANAGER (context);
  GripGestureManagerPrivate *priv = GRIP_GESTURE_MANAGER_GET_PRIVATE (self);

  switch (geis_event_type(geis_event))
  {
    case GEIS_EVENT_DEVICE_AVAILABLE:
    case GEIS_EVENT_DEVICE_UNAVAILABLE:
      grip_geis_device_added (self, geis_event);
      break;

    case GEIS_EVENT_CLASS_AVAILABLE:
    case GEIS_EVENT_CLASS_CHANGED:
    case GEIS_EVENT_CLASS_UNAVAILABLE:
      process_class_event(priv, geis_event);
      break;

    case GEIS_EVENT_INIT_COMPLETE:
      priv->is_initialized = TRUE;
      break;

    case GEIS_EVENT_GESTURE_BEGIN:
    case GEIS_EVENT_GESTURE_UPDATE:
    case GEIS_EVENT_GESTURE_END:
      process_gesture_events(self, geis_event);
      break;

    default:
      g_warning ("event ignored");
      break;
  }

  geis_event_delete(geis_event);
  return;
}


static void
grip_gesture_manager_dispose (GObject *object G_GNUC_UNUSED)
{
}


static void
grip_gesture_manager_finalize (GObject *object)
{
  GripGestureManagerPrivate *priv = GRIP_GESTURE_MANAGER_GET_PRIVATE (object);

  g_list_foreach(priv->classes, (GFunc) g_free, NULL);
  g_list_free(priv->classes);

  g_ptr_array_foreach (priv->devices, (GFunc) g_object_unref, NULL);
  g_ptr_array_unref (priv->devices);
}


static GObject *
grip_gesture_manager_constructor (GType                  type,
                                  guint                  n_params,
                                  GObjectConstructParam *params)
{
  static GObject *self = NULL;
  
  if (self == NULL)
  {
    self = G_OBJECT_CLASS (grip_gesture_manager_parent_class)->
                              constructor (type, n_params, params);
    g_object_add_weak_pointer (self, (gpointer) &self);
  }

  return g_object_ref (self);
}


static void
grip_gesture_manager_class_init (GripGestureManagerClass *class)
{
  GObjectClass     *gobject_class;

  gobject_class = G_OBJECT_CLASS (class);

  grip_gesture_manager_parent_class = g_type_class_peek_parent (class);

  g_type_class_add_private (gobject_class, sizeof (GripGestureManagerPrivate));

  gobject_class->constructor = grip_gesture_manager_constructor;
  gobject_class->dispose     = grip_gesture_manager_dispose;
  gobject_class->finalize    = grip_gesture_manager_finalize;

  /**
   * GripGestureManager::device-available:
   * @gesture_manager: the #GripGestureManager sending the signal
   * @input_device: the new #GripInputDevice just added.
   *
   * Signals the availability of a new gesture-capable input device.
   */
  signals[DEVICE_AVAILABLE] = g_signal_new ("device-available",
                                            GRIP_TYPE_GESTURE_MANAGER,
                                            G_SIGNAL_RUN_LAST,
                                            0,
                                            NULL, NULL,
                                            g_cclosure_marshal_VOID__OBJECT,
                                            G_TYPE_NONE,
                                            1,
                                            GRIP_TYPE_INPUT_DEVICE);

  /**
   * GripGestureManager::device-unavailable:
   * @gesture_manager: the #GripGestureManager sending the signal
   * @input_device: the new #GripInputDevice just added.
   *
   * Signals the unavailability of a gesture-capable input device.
   */
  signals[DEVICE_UNAVAILABLE] = g_signal_new ("device-unavailable",
                                              GRIP_TYPE_GESTURE_MANAGER,
                                              G_SIGNAL_RUN_LAST,
                                              0,
                                              NULL, NULL,
                                              g_cclosure_marshal_VOID__OBJECT,
                                              G_TYPE_NONE,
                                              1,
                                              GRIP_TYPE_INPUT_DEVICE);
}


/*
 * device_id_to_input_device:
 * @gesture_manager: the #GripGestureManager
 * @device_id: the device ID
 *
 * Finds a known #GripInputDevice given a GEIS device identifier.
 *
 * Returns: a pointer to a #GripInputDevice or %NULL if not found.
 */
static GripInputDevice *
device_id_to_input_device (GripGestureManager *gesture_manager,
                           GeisInputDeviceId   device_id)
{
  guint j;
  GripInputDevice *input_device = NULL;

  for (j = 0; j < gesture_manager->priv->devices->len; ++j)
  {
    GripInputDevice *device;

    device = g_ptr_array_index (gesture_manager->priv->devices, j);
    if (grip_input_device_get_id (device) == device_id)
    {
      input_device = device;
      break;
    }
  }
  return input_device;
}


static gint
pinch_gesture_handle_properties (GripEventGesturePinch *event,
                                 GeisFrame             frame)
{
  guint attr_count = geis_frame_attr_count (frame);
  gint num_touches = 0;
  guint i;
  for (i = 0; i < attr_count; ++i)
    {
      GeisAttr attr = geis_frame_attr(frame, i);
      GeisString attr_name = geis_attr_name(attr);

      if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_TOUCHES) == 0)
          num_touches = geis_attr_value_to_integer(attr);
      if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_TIMESTAMP) == 0)
          event->timestamp = geis_attr_value_to_integer(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_FOCUS_X) == 0)
          event->focus_x = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_FOCUS_Y) == 0)
          event->focus_y = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_RADIUS_DELTA) == 0)
          event->radius_delta = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_RADIAL_VELOCITY) == 0)
          event->radial_velocity = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_RADIUS) == 0)
          event->radius = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_POSITION_X) == 0)
          event->position_x = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_POSITION_Y) == 0)
          event->position_y = geis_attr_value_to_float(attr);
   }
  return num_touches;
}

static gint
drag_gesture_handle_properties (GripEventGestureDrag *event,
                                GeisFrame             frame)
{
  guint attr_count = geis_frame_attr_count (frame);
  gint num_touches = 0;
  guint i;
  for (i = 0; i < attr_count; ++i)
    {
      GeisAttr attr = geis_frame_attr(frame, i);
      GeisString attr_name = geis_attr_name(attr);

      if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_TOUCHES) == 0)
          num_touches = geis_attr_value_to_integer(attr);
      if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_TIMESTAMP) == 0)
          event->timestamp = geis_attr_value_to_integer(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_FOCUS_X) == 0)
          event->focus_x = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_FOCUS_Y) == 0)
          event->focus_y = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_DELTA_X) == 0)
          event->delta_x = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_DELTA_Y) == 0)
          event->delta_y = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_VELOCITY_X) == 0)
          event->velocity_x = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_VELOCITY_Y) == 0)
          event->velocity_y = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_POSITION_X) == 0)
          event->position_x = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_POSITION_Y) == 0)
          event->position_y = geis_attr_value_to_float(attr);
   }
  return num_touches;
}


static gint
rotate_gesture_handle_properties (GripEventGestureRotate *event,
                                  GeisFrame               frame)
{
  guint attr_count = geis_frame_attr_count (frame);
  gint num_touches = 0;
  guint i;
  for (i = 0; i < attr_count; ++i)
    {
      GeisAttr attr = geis_frame_attr(frame, i);
      GeisString attr_name = geis_attr_name(attr);

      if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_TOUCHES) == 0)
          num_touches = geis_attr_value_to_integer(attr);
      if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_TIMESTAMP) == 0)
          event->timestamp = geis_attr_value_to_integer(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_FOCUS_X) == 0)
          event->focus_x = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_FOCUS_Y) == 0)
          event->focus_y = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_ANGLE_DELTA) == 0)
          event->angle_delta = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_ANGULAR_VELOCITY) == 0)
          event->angular_velocity = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_ANGLE) == 0)
          event->angle = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_POSITION_X) == 0)
          event->position_x = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_POSITION_Y) == 0)
          event->position_y = geis_attr_value_to_float(attr);
   }
  return num_touches;
}


static gint
tap_gesture_handle_properties (GripEventGestureTap *event,
                               GeisFrame            frame)
{
  guint attr_count = geis_frame_attr_count (frame);
  gint num_touches = 0;
  guint i;
  for (i = 0; i < attr_count; ++i)
    {
      GeisAttr attr = geis_frame_attr(frame, i);
      GeisString attr_name = geis_attr_name(attr);

      if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_TOUCHES) == 0)
          num_touches = geis_attr_value_to_integer(attr);
      if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_TIMESTAMP) == 0)
          event->timestamp = geis_attr_value_to_integer(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_FOCUS_X) == 0)
          event->focus_x = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_FOCUS_Y) == 0)
          event->focus_y = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_POSITION_X) == 0)
          event->position_x = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_POSITION_Y) == 0)
          event->position_y = geis_attr_value_to_float(attr);
      else if (g_strcmp0 (attr_name, GEIS_GESTURE_ATTRIBUTE_TAP_TIME) == 0)
          event->tap_time = geis_attr_value_to_float(attr);
   }
  return num_touches;
}


static void
process_gesture_events(GripGestureManager *manager, GeisEvent geis_event)
{
  GripGestureManagerPrivate *priv = manager->priv;

  GripTimeType time_type = GRIP_TIME_UPDATE;
  if (geis_event_type(geis_event) == GEIS_EVENT_GESTURE_BEGIN)
    time_type = GRIP_TIME_START;
  else if (geis_event_type(geis_event) == GEIS_EVENT_GESTURE_END)
    time_type = GRIP_TIME_END;

  GeisAttr attr = geis_event_attr_by_name (geis_event, GEIS_EVENT_ATTRIBUTE_GROUPSET);
  g_return_if_fail (attr);

  GeisGroupSet groupset = geis_attr_value_to_pointer (attr);
  g_return_if_fail (groupset);

  GeisSize i;
  for (i = 0; i < geis_groupset_group_count (groupset); ++i)
    {
      GeisGroup group = geis_groupset_group (groupset, i);
      g_return_if_fail (group);

      GeisSize j;
      for (j = 0; j < geis_group_frame_count (group); ++j)
        {
          GeisFrame frame = geis_group_frame(group, j);
          g_return_if_fail (frame);

          GeisAttr window_attr = geis_frame_attr_by_name(frame, GEIS_GESTURE_ATTRIBUTE_EVENT_WINDOW_ID);
          guint window_id = geis_attr_value_to_integer(window_attr);

          GeisAttr device_attr = geis_frame_attr_by_name(frame, GEIS_GESTURE_ATTRIBUTE_DEVICE_ID);
          guint device_id = geis_attr_value_to_integer(device_attr);
          GripInputDevice *input_device = device_id_to_input_device (manager, device_id);

          GList *l;
          for (l = priv->bindings; l != NULL; l = l->next)
            {
              GripGestureBinding *binding = (GripGestureBinding *)l->data;
              guint widget_window_id = GDK_WINDOW_XID (gtk_widget_get_window (binding->widget));
              if (widget_window_id == window_id)
                {
                  GList *class_list;
                  for (class_list = priv->classes; class_list != NULL; class_list = class_list->next)
                    {
                      GripGestureClassMap *p = (GripGestureClassMap *)class_list->data;
                      if (p->grip_type == binding->type)
                        {
                          if (geis_frame_is_class(frame, p->geis_class))
                            {
                              GripGestureEvent *grip_event = grip_gesture_event_new (p->grip_type);
                              if (p->grip_type == GRIP_GESTURE_DRAG)
                                {
                                  GripEventGestureDrag *drag = (GripEventGestureDrag *)grip_event;
                                  drag->type    = p->grip_type;
                                  drag->id      = geis_frame_id(frame);
                                  drag->input_device = input_device;
                                  drag->fingers = drag_gesture_handle_properties (drag, frame);
                                  binding->callback (binding->widget,
                                                     time_type,
                                                     grip_event,
                                                     binding->data);
                                }
                              else if (p->grip_type == GRIP_GESTURE_PINCH)
                                {
                                  GripEventGesturePinch *pinch = (GripEventGesturePinch *)grip_event;
                                  pinch->type    = p->grip_type;
                                  pinch->id      = geis_frame_id(frame);
                                  pinch->input_device = input_device;
                                  pinch->fingers = pinch_gesture_handle_properties (pinch, frame);
                                  binding->callback (binding->widget,
                                                     time_type,
                                                     grip_event,
                                                     binding->data);
                                }
                              else if (p->grip_type == GRIP_GESTURE_ROTATE)
                                {
                                  GripEventGestureRotate *rotate = (GripEventGestureRotate *)grip_event;
                                  rotate->type    = p->grip_type;
                                  rotate->id      = geis_frame_id(frame);
                                  rotate->input_device = input_device;
                                  rotate->fingers = rotate_gesture_handle_properties (rotate, frame);
                                  binding->callback (binding->widget,
                                                     time_type,
                                                     grip_event,
                                                     binding->data);
                                }
                              else if (p->grip_type == GRIP_GESTURE_TAP)
                                {
                                  GripEventGestureTap *tap = (GripEventGestureTap *)grip_event;
                                  tap->type    = p->grip_type;
                                  tap->id      = geis_frame_id(frame);
                                  tap->input_device = input_device;
                                  tap->fingers = tap_gesture_handle_properties (tap, frame);
                                  binding->callback (binding->widget,
                                                     time_type,
                                                     grip_event,
                                                     binding->data);
                                }
                              grip_gesture_event_free (grip_event);
                            }
                        }
                    }
                }
            }
        }
    }
}


static gboolean
grip_geis_event_ready (GIOChannel   *source G_GNUC_UNUSED,
                       GIOCondition  condition G_GNUC_UNUSED,
                       gpointer      context)
{
  Geis geis = (Geis)context;
  GeisStatus status G_GNUC_UNUSED = geis_dispatch_events(geis);
  return TRUE;
}


static void
grip_gesture_manager_init (GripGestureManager *self)
{
  GripGestureManagerPrivate *priv = self->priv = GRIP_GESTURE_MANAGER_GET_PRIVATE (self);
  priv->devices = g_ptr_array_new ();

  priv->is_initialized = FALSE;
  priv->geis = geis_new (GEIS_INIT_TRACK_DEVICES, GEIS_INIT_TRACK_GESTURE_CLASSES, NULL);
  g_return_if_fail (priv->geis);

  int fd;
  geis_get_configuration(priv->geis, GEIS_CONFIGURATION_FD, &fd);
  priv->iochannel = g_io_channel_unix_new (fd);
  priv->iochannel_id = g_io_add_watch (priv->iochannel, G_IO_IN, grip_geis_event_ready, priv->geis);
  geis_register_device_callback (priv->geis, grip_geis_event_callback, self);
  geis_register_class_callback (priv->geis, grip_geis_event_callback, self);
  geis_register_event_callback (priv->geis, grip_geis_event_callback, self);
}


static void
window_destroyed_cb (GtkWidget *object,
                     gpointer   user_data)
{
  GripGestureManager *manager = (GripGestureManager *)user_data;
  GList *l = manager->priv->bindings;
  while (l != NULL)
    {
      GList *next = l->next;
      GripGestureBinding *binding = l->data;
      if (gtk_widget_get_toplevel (binding->widget) == object)
        {
          geis_subscription_delete (binding->subscription);

          if (binding->destroy)
            binding->destroy (binding->data);
          g_free (binding);
          manager->priv->bindings = g_list_delete_link (manager->priv->bindings, l);
        }
      l = next;
    }
}


/* Public API */
GripGestureManager *
grip_gesture_manager_get (void)
{
  return g_object_new (GRIP_TYPE_GESTURE_MANAGER, NULL);
}


/*
 * processed_mapped_window_request:
 * @reg: a #GripRegistrationRequest (removed from the GripGestureManager
 * requests listand freed).
 *
 * Creates a subscription for a widget mapped to a window.
 */
static void
processed_mapped_window_request (GripRegistrationRequest *req)
{
  g_return_if_fail (GRIP_IS_GESTURE_MANAGER (req->manager));
  g_return_if_fail (GTK_IS_WIDGET (req->widget));

  GtkWidget *toplevel = gtk_widget_get_toplevel (req->widget);
  g_return_if_fail (GTK_IS_WINDOW (toplevel));

  GripGestureManagerPrivate *priv = req->manager->priv;
  if (!priv->is_initialized)
    return;

  guint window_id = GDK_WINDOW_XID (gtk_widget_get_window(toplevel));

  /* XXX - check for duplicates in reg->bindings first */
  GripGestureBinding *binding = g_new0 (GripGestureBinding, 1);
  binding->type     = req->gesture_type;
  binding->widget   = req->widget;
  binding->touches  = req->touch_points;
  binding->callback = req->callback;
  binding->data     = req->user_data;
  binding->destroy  = req->destroy;

  char sub_name[48];
  snprintf(sub_name, 48, "sub-%x-%x-%x", window_id,  req->gesture_type, req->touch_points);
  binding->subscription = geis_subscription_new (priv->geis, sub_name, GEIS_SUBSCRIPTION_CONT);
  g_return_if_fail (binding->subscription);

  char  filter_id[20];
  snprintf(filter_id, 20, "grip-%x", window_id);
  GeisFilter window_filter = geis_filter_new(priv->geis, filter_id);
  geis_filter_add_term(window_filter, GEIS_FILTER_REGION,
                       GEIS_REGION_ATTRIBUTE_WINDOWID, GEIS_FILTER_OP_EQ, window_id,
                       NULL);

  switch (req->gesture_type)
  {
    case GRIP_GESTURE_DRAG:
      geis_filter_add_term(window_filter, GEIS_FILTER_CLASS,
                           GEIS_CLASS_ATTRIBUTE_NAME, GEIS_FILTER_OP_EQ, GEIS_GESTURE_DRAG,
                           GEIS_GESTURE_ATTRIBUTE_TOUCHES, GEIS_FILTER_OP_EQ, req->touch_points,
                           NULL);
      break;
    case GRIP_GESTURE_PINCH:
      geis_filter_add_term(window_filter, GEIS_FILTER_CLASS,
                           GEIS_CLASS_ATTRIBUTE_NAME, GEIS_FILTER_OP_EQ, GEIS_GESTURE_PINCH,
                           GEIS_GESTURE_ATTRIBUTE_TOUCHES, GEIS_FILTER_OP_EQ, req->touch_points,
                           NULL);
      break;
    case GRIP_GESTURE_ROTATE:
      geis_filter_add_term(window_filter, GEIS_FILTER_CLASS,
                           GEIS_CLASS_ATTRIBUTE_NAME, GEIS_FILTER_OP_EQ, GEIS_GESTURE_ROTATE,
                           GEIS_GESTURE_ATTRIBUTE_TOUCHES, GEIS_FILTER_OP_EQ, req->touch_points,
                           NULL);
      break;
    case GRIP_GESTURE_TAP:
      geis_filter_add_term(window_filter, GEIS_FILTER_CLASS,
                           GEIS_CLASS_ATTRIBUTE_NAME, GEIS_FILTER_OP_EQ, GEIS_GESTURE_TAP,
                           GEIS_GESTURE_ATTRIBUTE_TOUCHES, GEIS_FILTER_OP_EQ, req->touch_points,
                           NULL);
      break;
  }

  if (req->device_type & GRIP_DEVICE_TOUCHSCREEN)
    {
      char filter_name[32];
      snprintf(filter_name, 32, "%s/touchscreen", filter_id);
      GeisFilter touchscreen_filter = geis_filter_clone (window_filter, filter_name);
      geis_filter_add_term(touchscreen_filter, GEIS_FILTER_DEVICE,
                           GEIS_DEVICE_ATTRIBUTE_DIRECT_TOUCH, GEIS_FILTER_OP_EQ, GEIS_TRUE,
                           NULL);
      GeisStatus status = geis_subscription_add_filter(binding->subscription, touchscreen_filter);
      g_return_if_fail (status == GEIS_STATUS_SUCCESS);
    }
  if (req->device_type & GRIP_DEVICE_TOUCHPAD)
    {
      char filter_name[32];
      snprintf(filter_name, 32, "%s/touchpad", filter_id);
      GeisFilter touchpad_filter = geis_filter_clone (window_filter, filter_name);
      geis_filter_add_term(touchpad_filter, GEIS_FILTER_DEVICE,
                           GEIS_DEVICE_ATTRIBUTE_DIRECT_TOUCH, GEIS_FILTER_OP_EQ, GEIS_FALSE,
                           NULL);
      GeisStatus status = geis_subscription_add_filter(binding->subscription, touchpad_filter);
      g_return_if_fail (status == GEIS_STATUS_SUCCESS);
    }
  if (req->device_type & GRIP_DEVICE_INDEPENDENT)
    {
      char filter_name[32];
      snprintf(filter_name, 32, "%s/indep", filter_id);
      GeisFilter indep_filter = geis_filter_clone (window_filter, filter_name);
      geis_filter_add_term(indep_filter, GEIS_FILTER_DEVICE,
                           GEIS_DEVICE_ATTRIBUTE_INDEPENDENT_TOUCH, GEIS_FILTER_OP_EQ, GEIS_TRUE,
                           NULL);
      GeisStatus status = geis_subscription_add_filter(binding->subscription, indep_filter);
      g_return_if_fail (status == GEIS_STATUS_SUCCESS);
    }

  /*
   * If any device types were specified, the window_filter was cloned and needs
   * to be disposed of, otehrwise it's _the_ filter.
   */
  if (req->device_type)
    {
      geis_filter_delete(window_filter);
    }
  else
    {
      GeisStatus status = geis_subscription_add_filter(binding->subscription, window_filter);
      g_return_if_fail (status == GEIS_STATUS_SUCCESS);
    }


  GeisStatus status = geis_subscription_activate (binding->subscription);
  g_return_if_fail (status == GEIS_STATUS_SUCCESS);

  priv->bindings = g_list_append (priv->bindings, binding);

  g_signal_connect (toplevel,
                    "destroy",
                    G_CALLBACK (window_destroyed_cb),
                    req->manager);

  /* remove the request from the watch list */
  g_free (req);
  priv->requests = g_list_remove (priv->requests, req);
}


/*
 * register_mapped_window:
 * @manager: a Grip gesture manager object
 * @toplevel: a top-level widget
 *
 * Processes all subscriptions requests for a newly-mapped window.
 */
static void
register_mapped_window (GripGestureManager *manager, GtkWidget *toplevel)
{
  g_return_if_fail (gtk_widget_is_toplevel (toplevel));

  GList *pending_request = manager->priv->requests;
  while (pending_request)
    {
      GList *next = pending_request->next;
      GripRegistrationRequest *req = pending_request->data;

      if (gtk_widget_get_toplevel (req->widget) == toplevel)
        {
              processed_mapped_window_request (req);
        }
      pending_request = next;
    }
}


/*
 * register_toplevel_widget:
 * @manager: a Grip gesture manager object
 * @toplevel: a top-level widget
 *
 * Handles the registration of a widget that has just become a top-level widget.
 *
 * If the top-level widget is mapped to a window, it;s handled right away.
 * Otherwise, a callback is queued for when it does become mapped to a window.
 */
static void
register_toplevel_widget (GripGestureManager *manager, GtkWidget *toplevel)
{
  g_return_if_fail (gtk_widget_is_toplevel (toplevel));

  if (gtk_widget_get_mapped (GTK_WIDGET (toplevel)))
    {
      register_mapped_window (manager, toplevel);
    }
  else
    {
      g_signal_connect (toplevel,
                        "map-event",
                        G_CALLBACK (toplevel_mapped_cb),
                        manager);
    }
}


/*
 * toplevel_notify_cb:
 *
 * Called when a widget property has changed.
 *
 * The only widget property we're interested in here is the "parent" property.
 * We can't subscribe to gestures on a window until the widget is mapped to a
 * window.  Only top-level widgets can be mapped to a window:  we just keep
 * queueing this callback on successive parent widgets until one is a top-level
 * widget, then process that one.
 */
static void
toplevel_notify_cb (GtkWidget    *widget,
                    GParamSpec   *pspec,
                    gpointer      user_data)
{
  if (pspec->name == g_intern_static_string ("parent"))
    {
      GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
      GripGestureManager *manager = (GripGestureManager *)user_data;

      g_signal_handlers_disconnect_by_func (widget,
                                            G_CALLBACK (toplevel_notify_cb),
                                            user_data);

      if (gtk_widget_is_toplevel (toplevel))
        {
          register_toplevel_widget (manager, toplevel);
        }
      else
        {
          g_signal_connect (G_OBJECT (toplevel),
                            "notify",
                            G_CALLBACK (toplevel_notify_cb),
                            user_data);
        }
    }
}


/*
 * toplevel_mapped_cb:
 *
 * A callback invoked when a toplevel widget with one or more pending gesture
 * registration requests has been mapped to a window.
 *
 * This callback will in fact process all pending registration requests for the
 * newly-mapped window.
 */
static void
toplevel_mapped_cb (GtkWidget   *toplevel,
                    GdkEvent    *event G_GNUC_UNUSED,
                    gpointer     user_data)
{
  GripGestureManager *manager = (GripGestureManager *)user_data;
  register_mapped_window (manager, toplevel);
  g_signal_handlers_disconnect_by_func (toplevel,
                                        toplevel_mapped_cb,
                                        user_data);
}


/**
 * grip_gesture_manager_register_window:
 * @manager: A #GripGestureManager instance.
 * @widget: A #GtkWidget to register the gesture event for.
 * @gesture_type: The type of gesture event to register.
 * @device_type: The type of the device use to create the gesture.
 * @touch_points: Number of touch points for this gesture.
 * @callback: Called when a gesture starts, updates, or ends.
 * @user_data: User data
 * @destroy: Destroy callback for user data.
 *
 * Registers a widget to receive gesture events.
 *
 * The callback parameters provided will be called by the
 * #GripGestureManager whenever the user initiates a gesture
 * on the specified window.
 */
void
grip_gesture_manager_register_window (GripGestureManager  *manager,
                                      GtkWidget           *widget,
                                      GripGestureType      gesture_type,
                                      GripDeviceType       device_type,
                                      gint                 touch_points,
                                      GripGestureCallback  callback,
                                      gpointer             user_data,
                                      GDestroyNotify       destroy)
{
  g_return_if_fail (GRIP_IS_GESTURE_MANAGER (manager));
  g_return_if_fail (GTK_IS_WIDGET (widget));

  GtkWidget *toplevel = gtk_widget_get_toplevel (widget);

  GripRegistrationRequest *req = g_new0 (GripRegistrationRequest, 1);
  req->manager      = manager;
  req->widget       = widget;
  req->gesture_type = gesture_type;
  req->device_type  = device_type;
  req->touch_points = touch_points;
  req->callback     = callback;
  req->user_data    = user_data;
  req->destroy      = destroy;
  manager->priv->requests = g_list_append (manager->priv->requests, req);

  if (GTK_IS_WINDOW (toplevel))
    {
      register_toplevel_widget (manager, toplevel);
    }
  else
    {
      g_signal_connect (toplevel,
                        "notify",
                        G_CALLBACK (toplevel_notify_cb),
                        manager);
    }
}


void
grip_gesture_manager_unregister_window (GripGestureManager  *manager G_GNUC_UNUSED,
                                        GtkWidget           *toplevel G_GNUC_UNUSED)
{
  g_return_if_fail (GRIP_IS_GESTURE_MANAGER (manager));
  g_return_if_fail (GTK_IS_WINDOW (toplevel));

  window_destroyed_cb (toplevel, manager);
}


GType
grip_gesture_event_get_type (void)
{
  static GType type = 0;

  if (type == 0)
    {
      type = g_boxed_type_register_static (g_intern_static_string ("GripGestureEvent"),
					   (GBoxedCopyFunc)grip_gesture_event_copy,
					   (GBoxedFreeFunc)grip_gesture_event_free);
    }

  return type;
}

/**
 * grip_gesture_event_new:
 * @gesture_type: the type of the gesture
 *
 * Creates a new Grip gesture event.
 *
 * Returns: a new #GripGestureEvent
 */
GripGestureEvent *
grip_gesture_event_new (GripGestureType gesture_type)
{
  GripGestureEvent *event = g_slice_new0 (GripGestureEvent);

  event->any.type = gesture_type;

  return event;
}

/**
 * grip_gesture_event_copy:
 * @event: an existing #GripGestureEvent
 *
 * Creates a new #GripGestureEvent instance using a deep copy of and existing
 * event.
 *
 * Returns: a new #GripGestureEvent
 */
GripGestureEvent *
grip_gesture_event_copy (const GripGestureEvent *event)
{
  GripGestureEvent *new_event;

  g_return_val_if_fail (event != NULL, NULL);

  new_event = grip_gesture_event_new (event->type);
  *new_event = *event;

  return new_event;
}

/**
 * grip_gesture_event_free:
 * @event: a #GripGestureEvent
 *
 * Frees the resources allocated for a #GripGestureEvent.
 */
void
grip_gesture_event_free (GripGestureEvent *event)
{
  g_return_if_fail (event != NULL);

  g_slice_free (GripGestureEvent, event);
}
