/***
 *  This file is part of Clutter-Gesture.
 *
 *  Copyright 2009 (c) Intel Corp.
 *  Author: Long Bu    (long.bu@intel.com)
 *
 *  Referenced clutter code
 *  Copyright (C) 2006 OpenedHand
 *
 *  Clutter-Gesture 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.1 of the License,
 *  or (at your option) any later version.
 *
 *  Clutter-Gesture 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 ClutterGesture; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 *  USA.
 ***/
#include <clutter/clutter.h>
#include "math.h"
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
#include <GL/gl.h>
#include <glib-object.h>

#include "clutter-gesture.h"
#include "marshal.h"
#include "engine.h"

G_DEFINE_TYPE (ClutterGesture, clutter_gesture, G_TYPE_OBJECT);

#define CLUTTER_GESTURE_GET_PRIVATE(obj)          \
  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_GESTURE, ClutterGesturePrivate))

enum
{
  GEST_SLIDE,
  GEST_HOLD,
  GEST_PINCH,
  GEST_ROTATE,
  GEST_NAVIGATE,
  GEST_ANY,

  LAST_SIGNAL
};

static guint  gesture_signals[LAST_SIGNAL] = { 0, };

enum
{
  PROP_0,

  PROP_GESTURE_ACTOR,
  PROP_GESTURE_MASK,
  PROP_TOUCH_HOLD_TIMEOUT,
  PROP_TOUCH_HOLD_RADIUS
};

struct _ClutterGesturePrivate
{
  ClutterActor *root_actor;
  gulong handler_id;
  handle_t gesture_handle;
  GSList *actors;  /* interesting list of actores */
};

static gboolean
recognize_cb (ClutterActor *target_actor,
              event_type_t  event_type,
              guint         flag,
              GSList       *event_list,
              gint          number,
              void         *data);

static gboolean
captured_event_cb (ClutterActor *root_actor, ClutterEvent *event, gpointer data);


static GMutex *lock = NULL;
static GSList *root_actors = NULL;

static gboolean
is_a_child(ClutterActor *parent, ClutterActor *child)
{
  ClutterActor *t;
  if (!parent || !child)
    return FALSE;

  for (t = child; t; t = clutter_actor_get_parent(t))
  {
    if (t == parent)
      return TRUE;
  }

  return FALSE;
}

static ClutterActor *
find_actor(GSList *list, ClutterActor *actor)
{
  GSList *l;

  for (l = list; l; l = l->next)
    {
      ClutterActor *stored_actor = l->data;

      if (stored_actor == actor)
        return actor;
    }

  return NULL;
}

/* which actor is interested in which gestures */
gboolean
clutter_gesture_set_gesture_mask (ClutterGesture *gesture,
                                  ClutterActor   *actor,
                                  gint mask)
{
  ClutterGesturePrivate *priv;

  g_return_val_if_fail (IS_CLUTTER_GESTURE (gesture), FALSE);

  priv = gesture->priv;

  if (!actor)
    return FALSE;

  /* The actor must be a child of the root_actor.
   * Otherwise, no event can be captured for the actor */
  if (!is_a_child(priv->root_actor, actor))
    return FALSE;

  if (find_actor (priv->actors, actor) == NULL)
    priv->actors = g_slist_append(priv->actors, actor);

  if (set_gesture_mask (priv->gesture_handle, actor, mask) == 0)
    return TRUE;
  else
    return FALSE;
}

static void
clutter_gesture_set_property (GObject      *object,
                              guint         prop_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
  ClutterGesture *gesture;
  ClutterGesturePrivate *priv;
  handle_t gesture_handle;
  ClutterActor *actor;

  gesture = CLUTTER_GESTURE (object);
  priv = gesture->priv;

  switch (prop_id)
  {
    case PROP_GESTURE_ACTOR:
      if (priv->root_actor != NULL)
        return;

      /*By default no gestures should be interesting */
      gesture_handle = engine_init (recognize_cb, gesture);
      if (!gesture_handle)
        {
          g_object_unref (gesture);
          return;
        }

      priv->gesture_handle = gesture_handle;

      actor = g_value_get_object (value);
      priv->root_actor = actor;
      priv->handler_id = g_signal_connect (G_OBJECT (actor), "captured-event",
                                           G_CALLBACK (captured_event_cb), gesture);

      root_actors = g_slist_prepend (root_actors, actor);
      break;

    case PROP_GESTURE_MASK:
      clutter_gesture_set_gesture_mask (gesture, priv->root_actor, g_value_get_uint (value));
      break;

    case PROP_TOUCH_HOLD_TIMEOUT:
      clutter_gesture_set_hold_timeout (gesture, priv->root_actor, g_value_get_uint (value));
      break;

    case PROP_TOUCH_HOLD_RADIUS:
      clutter_gesture_set_hold_radius (gesture, priv->root_actor, g_value_get_uint (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
clutter_gesture_get_property (GObject      *object,
                              guint         prop_id,
                              GValue       *value,
                              GParamSpec   *pspec)
{
  ClutterGesture *gesture;
  ClutterGesturePrivate *priv;
  guint mask, timeout, radius;

  gesture = CLUTTER_GESTURE (object);
  priv = gesture->priv;

  switch (prop_id)
  {
    case PROP_GESTURE_ACTOR:
      g_value_set_object (value, priv->root_actor);
      break;

    case PROP_GESTURE_MASK:
      get_gesture_mask (priv->gesture_handle, priv->root_actor, &mask);
      g_value_set_uint (value, mask);
      break;

    case PROP_TOUCH_HOLD_TIMEOUT:
      get_hold_timeout (priv->gesture_handle, priv->root_actor, &timeout);
      g_value_set_uint (value, timeout);
      break;

    case PROP_TOUCH_HOLD_RADIUS:
      get_hold_radius (priv->gesture_handle, priv->root_actor, &radius);
      g_value_set_uint (value, radius);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
clutter_gesture_finalize (GObject *object)
{
  ClutterGestureClass *klass;
  ClutterGesture *gesture;

  gesture = CLUTTER_GESTURE(object);
  klass = CLUTTER_GESTURE_GET_CLASS(gesture);
  if (klass) {
    ClutterGesturePrivate *priv;

    priv = gesture->priv;

    g_mutex_lock (lock);
    root_actors = g_slist_remove (root_actors, priv->root_actor);
    g_mutex_unlock (lock);

    g_slist_free (priv->actors);
    engine_shutdown (priv->gesture_handle);
    g_signal_handler_disconnect (priv->root_actor, priv->handler_id);
  }
  
  G_OBJECT_CLASS (clutter_gesture_parent_class)->finalize (object);
}

#if 0
  gboolean
_clutter_boolean_handled_accumulator (GSignalInvocationHint *ihint,
    GValue                *return_accu,
    const GValue          *handler_return,
    gpointer               dummy)
{
  gboolean continue_emission;
  gboolean signal_handled;

  signal_handled = g_value_get_boolean (handler_return);
  g_value_set_boolean (return_accu, signal_handled);
  continue_emission = !signal_handled;

  return continue_emission;
}
#endif

static void
clutter_gesture_class_init (ClutterGestureClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GParamSpec *pspec;

  object_class->finalize     = clutter_gesture_finalize;
  object_class->set_property = clutter_gesture_set_property;
  object_class->get_property = clutter_gesture_get_property;

  if (!g_thread_supported ())
    g_thread_init(NULL);
  lock = g_mutex_new();

  g_type_class_add_private (klass, sizeof (ClutterGesturePrivate));

  gesture_signals[GEST_SLIDE] = 
  g_signal_new ("gesture-slide-event",
                G_TYPE_FROM_CLASS (object_class),
                G_SIGNAL_RUN_LAST,
                G_STRUCT_OFFSET (ClutterGestureClass, gesture_slide_event),
                NULL,
                NULL,
                g_cclosure_marshal_VOID__POINTER,
                G_TYPE_NONE, 1,
                G_TYPE_POINTER);

  gesture_debug("slide signal:%d\n", gesture_signals[GEST_SLIDE]);

  gesture_signals[GEST_HOLD] =
  g_signal_new ("gesture-hold-event",
                G_TYPE_FROM_CLASS (object_class),
                G_SIGNAL_RUN_LAST,
                G_STRUCT_OFFSET (ClutterGestureClass, gesture_hold_event),
                NULL,
                NULL,
                _cluttergesture_marshal_BOOLEAN__POINTER,
                G_TYPE_BOOLEAN, 1,
                G_TYPE_POINTER);

  gesture_debug("hold signal:%d\n", gesture_signals[GEST_HOLD]);

  gesture_signals[GEST_PINCH] = 
    g_signal_new ("gesture-pinch-event",
        G_TYPE_FROM_CLASS (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ClutterGestureClass, gesture_pinch_event),
        NULL, NULL,
        g_cclosure_marshal_VOID__POINTER,
        G_TYPE_NONE, 1,
        G_TYPE_POINTER);

  gesture_debug("pinch signal:%d\n", gesture_signals[GEST_PINCH]);
  gesture_signals[GEST_ROTATE] = 
    g_signal_new ("gesture-rotate-event",
        G_TYPE_FROM_CLASS (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ClutterGestureClass, gesture_rotate_event),
        NULL, NULL,
        g_cclosure_marshal_VOID__POINTER,
        G_TYPE_NONE, 1,
        G_TYPE_POINTER);

  gesture_debug("rotate signal:%d\n", gesture_signals[GEST_ROTATE]);
  gesture_signals[GEST_NAVIGATE] = 
    g_signal_new ("gesture-navigate-event",
        G_TYPE_FROM_CLASS (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ClutterGestureClass, gesture_navigate_event),
        NULL, NULL,
        g_cclosure_marshal_VOID__POINTER,
        G_TYPE_NONE, 1,
        G_TYPE_POINTER);

  gesture_debug("navigate signal:%d\n", gesture_signals[GEST_NAVIGATE]);

  gesture_signals[GEST_ANY] = 
    g_signal_new ("gesture-any-event",
        G_TYPE_FROM_CLASS (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ClutterGestureClass, gesture_any_event),
        NULL, NULL,
        g_cclosure_marshal_VOID__POINTER,
        G_TYPE_NONE, 1,
        G_TYPE_POINTER);

  gesture_debug("any signal:%d\n", gesture_signals[GEST_ANY]);

  pspec = g_param_spec_object ("actor",
                               "actor",
                               "the actor on which we do gesture recognition",
                               CLUTTER_TYPE_ACTOR,
                               G_PARAM_READABLE | G_PARAM_WRITABLE |
                               G_PARAM_STATIC_NICK | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_GESTURE_ACTOR, pspec);

  pspec = g_param_spec_uint ("gesture-mask",
                             "gesture-mask",
                             "bit-map mask to enable/disable gestures",
                             0x0,
                             0xffffffff,
                             0x0,
                             G_PARAM_READABLE | G_PARAM_WRITABLE |
                             G_PARAM_STATIC_NICK | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_GESTURE_MASK, pspec);

  pspec = g_param_spec_uint ("touch-hold-timeout",
                             "touch-hold-timeout",
                             "timeout value for touch-hold in millisecond",
                             200,
                             2000,
                             DEFAULT_TOUCH_HOLD_INTERVAL,
                             G_PARAM_READABLE | G_PARAM_WRITABLE |
                             G_PARAM_STATIC_NICK | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_TOUCH_HOLD_TIMEOUT, pspec);

  pspec = g_param_spec_uint ("touch-hold-radius",
                             "touch-hold-radius",
                             "radius for touch-hold in pixel",
                             5,
                             100,
                             DEFAULT_TOUCH_HOLD_RADIUS,
                             G_PARAM_READABLE | G_PARAM_WRITABLE |
                             G_PARAM_STATIC_NICK | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB);
  g_object_class_install_property (object_class, PROP_TOUCH_HOLD_RADIUS, pspec);
}

static void
clutter_gesture_init (ClutterGesture *gesture)
{
  gesture->priv = G_TYPE_INSTANCE_GET_PRIVATE (gesture, CLUTTER_TYPE_GESTURE, ClutterGesturePrivate);
}

static gboolean
captured_event_cb (ClutterActor *root_actor, ClutterEvent *event, gpointer data)
{
  ClutterGesturePrivate *priv;
  ClutterGesture *gesture;
  GSList *node;
  gboolean ret;
  gesture_debug("root_captured_event_cb\n");

  gesture = (ClutterGesture *)data;
  priv = gesture->priv;

  {
    ClutterActor *stage = CLUTTER_ACTOR(event->any.stage);
    stage = NULL;
  }
  ret = FALSE;
  if (event->any.flags & (1<<31))
    return ret;  /* this event has been through the engine */

  /* Check if the event->any.source is a child of any actors in the list
   * of interesting actors */
  for (node = priv->actors; node; node = g_slist_next(node)) {
    ClutterActor *interesting_actor;
    if (!node->data)
      continue;

    interesting_actor = (ClutterActor *)(node->data);
    if (is_a_child(interesting_actor, event->any.source)) {
      ret = fill_event(priv->gesture_handle, interesting_actor, event);
      break;
    }
  }

  if (!ret) {
    /* The event is not eaten by the engine.
     * Pass it alone*/
    return FALSE;
  } else {
    /* Before we return true. We need to pass
     * the event to other instance of ClutterGesture.
     * Otherwise, the captured_event_cb of other ClutterGesture instance
     * may not have a change to be called */
    ClutterGestureClass *klass;
    gboolean dummy;
    klass = CLUTTER_GESTURE_GET_CLASS(gesture);
    g_mutex_lock(lock);
    for (node = root_actors; node; node = g_slist_next(node)) {
      ClutterActor *root_actor = (ClutterActor *)node->data;
      if (root_actor != priv->root_actor) {
        /*don't need to send this event to myself */
        g_signal_emit_by_name (root_actor, "catpured-event", event, &dummy);
      }
    }
    g_mutex_unlock(lock);

    return TRUE;
  }
}

static void
event_list_free (GSList *l)
{
  GSList *node;
  for (node = l; node; node = g_slist_next(node)) {
    ClutterEvent *event = (ClutterEvent *)(node->data);
    clutter_event_free(event);
  }

  g_slist_free(l);
}

static inline void
emit_event (ClutterEvent *event,
            gboolean      is_key_event)
{
  static gboolean      lock = FALSE;

  GPtrArray *event_tree = NULL;
  ClutterActor *actor;
  gint i = 0;

  if (!event->any.source)
  {
    g_warning("No source set, discarding event");
    return;
  }

  /* reentrancy check */
  if (lock != FALSE)
  {
    g_warning ("Tried emitting event during event delivery, bailing out.n");
    return;
  }

  lock = TRUE;

  event_tree = g_ptr_array_sized_new (64);

  actor = event->any.source;

  /* Build 'tree' of emitters for the event */
  while (actor)
  {
    ClutterActor *parent;

    parent = clutter_actor_get_parent (actor);
    if (clutter_actor_get_reactive (actor) ||
        parent == NULL ||         /* stage gets all events */
        is_key_event)             /* keyboard events are always emitted */
    {
      g_ptr_array_add (event_tree, g_object_ref (actor));
    }

    actor = parent;
  }

  /* Capture */
  for (i = event_tree->len - 1; i >= 0; i--)
    if (clutter_actor_event (g_ptr_array_index (event_tree, i), event, TRUE))
      goto done;

  /* Bubble */
  for (i = 0; i < event_tree->len; i++)
    if (clutter_actor_event (g_ptr_array_index (event_tree, i), event, FALSE))
      goto done;

done:
  for (i = 0; i < event_tree->len; i++)
    g_object_unref (g_ptr_array_index (event_tree, i));

  g_ptr_array_free (event_tree, TRUE);

  lock = FALSE;
}

typedef struct {
      GSList *event_list;
      guint flag;
} _event_param_t;

static gboolean
process_clutter_event (gpointer data)
{
  GSList *node;
  _event_param_t *event_param = (_event_param_t *)data;
  GSList *event_list = event_param->event_list;
  guint flag = event_param->flag;
  gesture_debug("process_clutter_event: type: CLUTTER_EVENT\n");
  for (node = event_list; node; node = g_slist_next(node)) {
    ClutterEvent *event = (ClutterEvent *)(node->data);

    event->any.flags |= (1<<31);  /* indicates that this event has been processed by the engine */
    if (flag & IN_MT_GESTURE)
      event->any.flags |= GESTURE_IN_PROGRESS;  

    gesture_debug("process_clutter_event: do_event\n");
    g_assert(event->type == CLUTTER_MOTION ||
             event->type == CLUTTER_BUTTON_PRESS ||
             event->type == CLUTTER_BUTTON_RELEASE ||
             event->type == CLUTTER_SCROLL);
    emit_event (event, FALSE);
//    clutter_do_event (event);
  }

  event_list_free(event_list);
  g_free(event_param);

  return FALSE;
}

static GSList *
event_list_copy (GSList *l)
{
  GSList *node;
  GSList *ret = NULL;
  for (node = l; node; node = g_slist_next(node)) {
    ClutterEvent *event = clutter_event_copy(node->data);
    ret = g_slist_append(ret, event);
  }
  return ret;
}

static gboolean
recognize_cb (ClutterActor *target_actor,
              event_type_t  event_type,
              guint         flag,
              GSList       *event_list,
              gint          number,
              void         *data)
{
  GSList *node;
  gboolean result = FALSE;
  ClutterGesture *gesture = (ClutterGesture *)data;
  gesture_debug("recognized_cb gets called, type:%d, gesture:%p\n", event_type, data);
  if (event_type == GESTURE_EVENT) {
    gesture_debug("recognized_cb: type: GESTURE_EVENT, number:%d\n", number);
    for (node = event_list; node; node = g_slist_next(node)) {
      ClutterGestureEvent *event = (ClutterGestureEvent *)(node->data);

      switch (event->type) {
        case GESTURE_PINCH:
          g_signal_emit (gesture,
                         gesture_signals[GEST_PINCH],
                         0,
                         event,
                         &result);
          break;
        case GESTURE_SLIDE:
          g_signal_emit (gesture,
                         gesture_signals[GEST_SLIDE],
                         0,
                         event,
                         &result);
          break;
        case GESTURE_HOLD:
          g_signal_emit (gesture,
                         gesture_signals[GEST_HOLD],
                         0,
                         event,
                         &result);
          break;
        case GESTURE_ROTATE:
          g_signal_emit (gesture,
                         gesture_signals[GEST_ROTATE],
                         0,
                         event,
                         &result);
          break;

        case GESTURE_NAVIGATE:
          g_signal_emit (gesture,
                         gesture_signals[GEST_NAVIGATE],
                         0,
                         event,
                         &result);
          break;
        default:
          break;
      }
/*
      if (event->type == GESTURE_PINCH || event->type == GESTURE_NAVIGATE ||
          event->type == GESTURE_SLIDE || event->type == GESTURE_ROTATE ||
          event->type == GESTURE_HOLD) {
        g_signal_emit (gesture, gesture_signals[GEST_ANY], 0,
                       event, &result); 

      }
*/
    }
  } else if (event_type == CLUTTER_EVENT) {
    /* The event_list will be free before the timeout_cb gets called */
    _event_param_t *event_param = g_new(_event_param_t, 1);
    event_param->event_list =  event_list_copy(event_list);
    event_param->flag = flag;
    g_timeout_add(0, process_clutter_event, event_param);
  }

  return result;
}

ClutterGesture *
clutter_gesture_new (ClutterActor *actor)
{
  if (!actor)
    return NULL;

  return g_object_new (CLUTTER_TYPE_GESTURE, "actor", actor, NULL);
}

gboolean
clutter_gesture_set_hold_timeout (ClutterGesture *gesture,
                                  ClutterActor *actor,
                                  guint interval)
{
  ClutterGesturePrivate *priv;

  g_return_val_if_fail (IS_CLUTTER_GESTURE (gesture), FALSE);

  priv = gesture->priv;

  if (!actor)
    return FALSE;

  /* The actor must be a child of the root_actor.
   * Otherwise, no event can be captured for the actor */
  if (!is_a_child(priv->root_actor, actor))
    return FALSE;

  if (find_actor (priv->actors, actor) == NULL)
    return FALSE;

  if (set_hold_timeout (priv->gesture_handle, actor, interval) == 0)
    return TRUE;

    return FALSE;
}

gboolean
clutter_gesture_set_hold_radius (ClutterGesture *gesture,
                                 ClutterActor *actor,
                                 guint radius)
{
  ClutterGesturePrivate *priv;

  g_return_val_if_fail (IS_CLUTTER_GESTURE (gesture), FALSE);

  priv = gesture->priv;

  if (!actor)
    return FALSE;

  /* The actor must be a child of the root_actor.
   * Otherwise, no event can be captured for the actor */
  if (!is_a_child(priv->root_actor, actor))
    return FALSE;

  if (find_actor (priv->actors, actor) == NULL)
    return FALSE;

  if (set_hold_radius (priv->gesture_handle, actor, radius) == 0)
    return TRUE;

    return FALSE;
}

