#ifdef HAVE_CONFIG_H
# include "elementary_config.h"
#endif

#include <Elementary.h>
#include "elm_priv.h"
#include "elm_gesture_layer_eo.h"

#define MY_CLASS ELM_GESTURE_LAYER_CLASS

#define MY_CLASS_NAME "Elm_Gesture_Layer"
#define MY_CLASS_NAME_LEGACY "elm_gesture_layer"

/* Some defaults */
#define ELM_MOUSE_DEVICE             0
/* ELM_GESTURE_NEGATIVE_ANGLE - magic number says we didn't compute this yet */
#define ELM_GESTURE_NEGATIVE_ANGLE   (-1.0) /* Magic number */
#define ELM_GESTURE_MOMENTUM_DELAY   25
#define ELM_GESTURE_MOMENTUM_TIMEOUT 50
#define ELM_GESTURE_MULTI_TIMEOUT    50
#define ELM_GESTURE_MINIMUM_MOMENTUM 0.001

/* Some Trigo values */
#define RAD_90DEG                    M_PI_2
#define RAD_180DEG                   M_PI
#define RAD_270DEG                   (M_PI_2 * 3)
#define RAD_360DEG                   (M_PI * 2)

#define RAD2DEG(x) ((x) * 57.295779513)
#define DEG2RAD(x) ((x) / 57.295779513)

static void *
_glayer_buf_dup(void *buf, size_t size)
{
   void *p;

   p = malloc(size);
   memcpy(p, buf, size);

   return p;
}

#define COPY_EVENT_INFO(EV) _glayer_buf_dup(EV, sizeof(*EV))

#define SET_TEST_BIT(P)                               \
  do {                                                \
       P->test = P->cbs[ELM_GESTURE_STATE_START] || \
         P->cbs[ELM_GESTURE_STATE_MOVE] ||          \
         P->cbs[ELM_GESTURE_STATE_END] ||           \
         P->cbs[ELM_GESTURE_STATE_ABORT];           \
    } while (0)

#define IS_TESTED_GESTURE(gesture) \
  ((gesture) ? (gesture)->test : EINA_FALSE)

#define IS_TESTED(T) \
  ((sd->gesture[T]) ? sd->gesture[T]->test : EINA_FALSE)

#define ELM_GESTURE_LAYER_DATA_GET(o, sd) \
  Elm_Gesture_Layer_Data * sd = efl_data_scope_get(o, MY_CLASS)

#define ELM_GESTURE_LAYER_DATA_GET_OR_RETURN(o, ptr) \
  ELM_GESTURE_LAYER_DATA_GET(o, ptr);                \
  if (!ptr)                                          \
    {                                                \
       ERR("No widget data for object %p (%s)", \
                o, evas_object_type_get(o));         \
       return;                                       \
    }

#define ELM_GESTURE_LAYER_DATA_GET_OR_RETURN_VAL(o, ptr, val) \
  ELM_GESTURE_LAYER_DATA_GET(o, ptr);                         \
  if (!ptr)                                                   \
    {                                                         \
       ERR("No widget data for object %p (%s)",          \
                o, evas_object_type_get(o));                  \
       return val;                                            \
    }

#define ELM_GESTURE_LAYER_CHECK(obj)                                      \
  if (!obj || !efl_isa(obj, MY_CLASS)) \
    return

/**
 * @internal
 *
 * @struct _Pointer_Event
 * Struct holds pointer-event info
 * This is a generic pointer event structure
 *
 * @ingroup Elm_Gesture_Layer
 */
struct _Pointer_Event
{
   Evas_Coord         x, y;
   unsigned int       timestamp;
   int                device;
   Evas_Callback_Type event_type;
};

/**
 * @internal
 *
 * @typedef Pointer_Event
 * Type for generic pointer event structure
 *
 * @ingroup Elm_Gesture_Layer
 */
typedef struct _Pointer_Event Pointer_Event;

/**
 * @internal
 *
 * @struct _Func_Data
 * Struct holds callback information.
 *
 * @ingroup Elm_Gesture_Layer
 */
struct _Func_Data
{
   EINA_INLIST;
   void                *user_data; /**< Holds user data to CB (like sd) */
   Elm_Gesture_Event_Cb cb;
};

/**
 * @internal
 *
 * @typedef Func_Data
 * type for callback information
 *
 * @ingroup Elm_Gesture_Layer
 */
typedef struct _Func_Data Func_Data;

/**
 * @internal
 *
 * @struct _Gesture_Info
 * Struct holds gesture info
 *
 * @ingroup Elm_Gesture_Layer
 */
struct _Gesture_Info
{
   Evas_Object      *obj;
   void             *data; /**< Holds gesture intermediate processing data */
   Eina_Inlist      *cbs[ELM_GESTURE_STATE_ABORT + 1]; /**< Callback info (Func_Data) for states */
   Elm_Gesture_Type  g_type; /**< gesture type */
   Elm_Gesture_State state; /**< gesture state */
   void             *info; /**< Data for the state callback */
   Eina_Bool         test; /**< if true this gesture should be tested on input */
};

/**
 * @internal
 *
 * @typedef Gesture_Info
 * Type for _Gesture_Info
 *
 * @ingroup Elm_Gesture_Layer
 */
typedef struct _Gesture_Info Gesture_Info;

typedef struct
{
   void (*test)(Evas_Object *obj, Pointer_Event *pe,
                void *event_info, Evas_Callback_Type event_type,
                Elm_Gesture_Type g_type);
   void (*reset)(Gesture_Info *gesture);
   void (*cont_reset)(Gesture_Info *gesture); /* Can be NULL. */
} Tests_Array_Funcs;

/* functions referred by _glayer_tests_array */
static void _tap_gesture_test(Evas_Object *obj,
                              Pointer_Event *pe,
                              void *event_info,
                              Evas_Callback_Type event_type,
                              Elm_Gesture_Type g_type);
static void _tap_gestures_test_reset(Gesture_Info *gesture);
static void _n_long_tap_test(Evas_Object *obj,
                             Pointer_Event *pe,
                             void *event_info,
                             Evas_Callback_Type event_type,
                             Elm_Gesture_Type g_type);
static void _n_long_tap_test_reset(Gesture_Info *gesture);
static void _momentum_test(Evas_Object *obj,
                           Pointer_Event *pe,
                           void *event_info,
                           Evas_Callback_Type event_type,
                           Elm_Gesture_Type g_type);
static void _momentum_test_reset(Gesture_Info *gesture);
static void _n_line_test(Evas_Object *obj,
                         Pointer_Event *pe,
                         void *event_info,
                         Evas_Callback_Type event_type,
                         Elm_Gesture_Type g_type);
static void _line_test_reset(Gesture_Info *gesture);
static void _zoom_test(Evas_Object *obj,
                       Pointer_Event *pe,
                       void *event_info,
                       Evas_Callback_Type event_type,
                       Elm_Gesture_Type g_type);
static void _zoom_test_reset(Gesture_Info *gesture);
static void _rotate_test(Evas_Object *obj,
                         Pointer_Event *pe,
                         void *event_info,
                         Evas_Callback_Type event_type,
                         Elm_Gesture_Type g_type);
static void _rotate_test_reset(Gesture_Info *gesture);

static void _event_process(void *data,
                           Evas_Object *obj,
                           void *event_info,
                           Evas_Callback_Type event_type);

static void _callbacks_unregister(Evas_Object *obj);

/* Should be the same order as _Elm_Gesture_Type */
static Tests_Array_Funcs _glayer_tests_array[] = {
   { NULL, NULL, NULL },     /** Because someone made an awful mistake. */
   { _tap_gesture_test, _tap_gestures_test_reset, NULL },
   /* ELM_GESTURE_N_TAPS */
   { _n_long_tap_test, _n_long_tap_test_reset, NULL },
   /* ELM_GESTURE_N_LONG_TAPS */
   { _tap_gesture_test, _tap_gestures_test_reset, NULL },
   /* ELM_GESTURE_N_DOUBLE_TAPS */
   { _tap_gesture_test, _tap_gestures_test_reset, NULL },
   /* ELM_GESTURE_N_TRIPLE_TAPS */
   { _momentum_test, _momentum_test_reset, _momentum_test_reset },
   /* ELM_GESTURE_MOMENTUM */
   { _n_line_test, _line_test_reset, _line_test_reset },
   /* ELM_GESTURE_N_LINES */
   { _n_line_test, _line_test_reset, _line_test_reset },
   /* ELM_GESTURE_N_FLICKS */
   { _zoom_test, _zoom_test_reset, _zoom_test_reset },
   /* ELM_GESTURE_ZOOM */
   { _rotate_test, _rotate_test_reset, _rotate_test_reset },
   /* ELM_GESTURE_ROTATE */
   { NULL, NULL, NULL }
};

/**
 * @internal
 *
 * @struct _Event_History
 * Struct holds event history.
 * These events are repeated if no gesture found.
 *
 * @ingroup Elm_Gesture_Layer
 */
struct _Event_History
{
   EINA_INLIST;
   void              *event;
   Evas_Callback_Type event_type;
};

/**
 * @internal
 *
 * @typedef Event_History
 * Type for _Event_History
 *
 * @ingroup Elm_Gesture_Layer
 */
typedef struct _Event_History Event_History;

/* All *Type structs hold result for the user in 'info' field
 * The rest is gesture processing intermediate data.
 * NOTE: info field must be FIRST in the struct.
 * This is used when reporting ABORT in _event_history_clear() */
struct _Taps_Type
{
   Elm_Gesture_Taps_Info info;
   unsigned int          sum_x;
   unsigned int          sum_y;
   unsigned int          n_taps_needed;
   unsigned int          n_taps;
   Eina_List            *l;
};
typedef struct _Taps_Type Taps_Type;

struct _Long_Tap_Type
{
   Elm_Gesture_Taps_Info info;
   Evas_Coord            center_x;
   Evas_Coord            center_y;
   Ecore_Timer          *timeout; /* When this expires, long tap STARTed */
   Eina_List            *touched;
};
typedef struct _Long_Tap_Type Long_Tap_Type;

struct _Momentum_Type /* Fields used by _line_test() */
{
   Elm_Gesture_Momentum_Info info;
   Evas_Coord_Point          line_st;
   Evas_Coord_Point          line_end;
   unsigned int              t_st_x; /* Time start on X */
   unsigned int              t_st_y; /* Time start on Y */
   unsigned int              t_end; /* Time end        */
   unsigned int              t_up; /* Recent up event time */
   int                       xdir, ydir;
};
typedef struct _Momentum_Type Momentum_Type;

struct _Line_Data
{
   Evas_Coord_Point line_st;
   Evas_Coord_Point line_end;
   Evas_Coord       line_length;
   unsigned int     t_st; /* Time start */
   unsigned int     t_end; /* Time end   */
   int              device;
   double           line_angle; /* Current angle of line */
};
typedef struct _Line_Data Line_Data;

struct _Line_Type /* Fields used by _line_test() */
{
   Elm_Gesture_Line_Info info;
   Eina_List            *list; /* List of Line_Data */
};
typedef struct _Line_Type Line_Type;

struct _Zoom_Type /* Fields used by _zoom_test() */
{
   Elm_Gesture_Zoom_Info   info;
   Pointer_Event           zoom_st;
   Pointer_Event           zoom_mv;
   Pointer_Event           zoom_st1;
   Pointer_Event           zoom_mv1;
   Evas_Event_Mouse_Wheel *zoom_wheel;
   Evas_Coord              zoom_base; /* Holds gap between fingers on
                                       * zoom-start  */
   Evas_Coord              zoom_distance_tolerance;
   unsigned int            m_st_tm; /* momentum start time */
   unsigned int            m_prev_tm; /* momentum prev time  */
   int                     dir; /* Direction: 1=zoom-in, (-1)=zoom-out */
   double                  m_base; /* zoom value when momentum starts */
   double                  next_step;
};
typedef struct _Zoom_Type Zoom_Type;

struct _Rotate_Type /* Fields used by _rotation_test() */
{
   Elm_Gesture_Rotate_Info info;
   Pointer_Event           rotate_st;
   Pointer_Event           rotate_mv;
   Pointer_Event           rotate_st1;
   Pointer_Event           rotate_mv1;
   unsigned int            prev_momentum_tm; /* timestamp of prev_momentum */
   double                  prev_momentum; /* Snapshot of momentum 0.01
                                           * sec ago */
   double                  accum_momentum;
   double                  rotate_angular_tolerance;
   double                  next_step;
};
typedef struct _Rotate_Type                  Rotate_Type;

typedef struct _Elm_Gesture_Layer_Data Elm_Gesture_Layer_Data;
struct _Elm_Gesture_Layer_Data
{
   Evas_Object          *target; /* Target Widget */
   Event_History        *event_history_list;

   int                   line_min_length;
   Evas_Coord            zoom_distance_tolerance;
   Evas_Coord            line_distance_tolerance;
   double                line_angular_tolerance;
   double                zoom_wheel_factor; /* mouse wheel zoom steps */
   double                zoom_finger_factor; /* used for zoom factor */
   double                rotate_angular_tolerance;
   unsigned int          flick_time_limit_ms;
   double                long_tap_start_timeout;
   Eina_Bool             glayer_continues_enable;
   double                double_tap_timeout;

   double                zoom_step;
   double                rotate_step;

   Gesture_Info         *gesture[ELM_GESTURE_LAST];
   Eina_List            *pending; /* List of devices need to refeed
                                   * *UP event */
   Eina_List            *touched; /* Information  of touched devices */

   /* Taps Gestures */
   Evas_Coord           tap_finger_size;    /* Default from Config */
   Ecore_Timer          *gest_taps_timeout; /* When this expires, dbl
                                             * click/taps ABORTed  */

   Eina_Bool             repeat_events : 1;
};

/* START - Functions to manage touched-device list */
/**
 * @internal
 * This function is used to find if device is touched
 *
 * @ingroup Elm_Gesture_Layer
 */
static int
_device_compare(const void *data1,
                const void *data2)
{
   /* Compare the two device numbers */
   return ((Pointer_Event *)data1)->device - ((Pointer_Event *)data2)->device;
}

/**
 * @internal
 *
 * Remove Pointer Event from touched device list
 * @param list Pointer to touched device list.
 * @param Pointer_Event Pointer to PE.
 *
 * @ingroup Elm_Gesture_Layer
 */
static Eina_List *
_touched_device_remove(Eina_List *list,
                       Pointer_Event *pe)
{
   Eina_List *lst = NULL;
   Pointer_Event *p = eina_list_search_unsorted(list, _device_compare, pe);
   if (p)
     {
        lst = eina_list_remove(list, p);
        free(p);
        return lst;
     }

   return list;
}

/**
 * @internal
 *
 * Recoed Pointer Event in touched device list
 * Note: This fuction allocates memory for PE event
 * This memory is released in _touched_device_remove()
 * @param list Pointer to touched device list.
 * @param Pointer_Event Pointer to PE.
 *
 * @ingroup Elm_Gesture_Layer
 */
static Eina_List *
_touched_device_add(Eina_List *list,
                    Pointer_Event *pe)
{
   Pointer_Event *p = eina_list_search_unsorted(list, _device_compare, pe);

   if (p) /* We like to track device touch-position, overwrite info */
     {
        memcpy(p, pe, sizeof(Pointer_Event));
        return list;
     }

   if ((pe->event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
       (pe->event_type == EVAS_CALLBACK_MULTI_DOWN)) /* Add touched
                                                      * device on DOWN
                                                      * event only */
     {
        p = malloc(sizeof(Pointer_Event));
        /* Freed in _touched_device_remove()    */
        memcpy(p, pe, sizeof(Pointer_Event));
        return eina_list_append(list, p);
     }

   return list;
}

/* END   - Functions to manage touched-device list */

/**
 * @internal
 *
 * Get event flag
 * @param event_info pointer to event.
 *
 * @ingroup Elm_Gesture_Layer
 */
static Evas_Event_Flags
_event_flag_get(void *event_info,
                Evas_Callback_Type event_type)
{
   switch (event_type)
     {
      case EVAS_CALLBACK_MOUSE_IN:
        return ((Evas_Event_Mouse_In *)event_info)->event_flags;

      case EVAS_CALLBACK_MOUSE_OUT:
        return ((Evas_Event_Mouse_Out *)event_info)->event_flags;

      case EVAS_CALLBACK_MOUSE_DOWN:
        return ((Evas_Event_Mouse_Down *)event_info)->event_flags;

      case EVAS_CALLBACK_MOUSE_MOVE:
        return ((Evas_Event_Mouse_Move *)event_info)->event_flags;

      case EVAS_CALLBACK_MOUSE_UP:
        return ((Evas_Event_Mouse_Up *)event_info)->event_flags;

      case EVAS_CALLBACK_MOUSE_WHEEL:
        return ((Evas_Event_Mouse_Wheel *)event_info)->event_flags;

      case EVAS_CALLBACK_MULTI_DOWN:
        return ((Evas_Event_Multi_Down *)event_info)->event_flags;

      case EVAS_CALLBACK_MULTI_MOVE:
        return ((Evas_Event_Multi_Move *)event_info)->event_flags;

      case EVAS_CALLBACK_MULTI_UP:
        return ((Evas_Event_Multi_Up *)event_info)->event_flags;

      case EVAS_CALLBACK_KEY_DOWN:
        return ((Evas_Event_Key_Down *)event_info)->event_flags;

      case EVAS_CALLBACK_KEY_UP:
        return ((Evas_Event_Key_Up *)event_info)->event_flags;

      default:
        return EVAS_EVENT_FLAG_NONE;
     }
}

/**
 * @internal
 *
 * Sets event flag to value returned from user callback
 * @param sd Widget Data
 * @param event_info pointer to event.
 * @param event_type what type was ev (mouse down, etc...)
 * @param ev_flags event flags
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_event_consume(Elm_Gesture_Layer_Data *sd,
               void *event_info,
               Evas_Callback_Type event_type,
               Evas_Event_Flags ev_flags)
{
   /* Mark EVAS_EVENT_FLAG_ON_HOLD on events that are used by gesture layer */
   /* ev_flags != EVAS_EVENT_FLAG_NONE means target used event and g-layer  */
   /* should not refeed this event.  */
   if (!event_info)
     return;  /* This happens when restarting gestures  */

   if (!sd->repeat_events) ev_flags |= EVAS_EVENT_FLAG_ON_HOLD;

   if (ev_flags)
     {
        switch (event_type)
          {
           case EVAS_CALLBACK_MOUSE_DOWN:
             ((Evas_Event_Mouse_Down *)event_info)->event_flags |= ev_flags;
             break;

           case EVAS_CALLBACK_MOUSE_MOVE:
             ((Evas_Event_Mouse_Move *)event_info)->event_flags |= ev_flags;
             break;

           case EVAS_CALLBACK_MOUSE_UP:
             ((Evas_Event_Mouse_Up *)event_info)->event_flags |= ev_flags;
             break;

           case EVAS_CALLBACK_MOUSE_WHEEL:
             ((Evas_Event_Mouse_Wheel *)event_info)->event_flags |= ev_flags;
             break;

           case EVAS_CALLBACK_MULTI_DOWN:
             ((Evas_Event_Multi_Down *)event_info)->event_flags |= ev_flags;
             break;

           case EVAS_CALLBACK_MULTI_MOVE:
             ((Evas_Event_Multi_Move *)event_info)->event_flags |= ev_flags;
             break;

           case EVAS_CALLBACK_MULTI_UP:
             ((Evas_Event_Multi_Up *)event_info)->event_flags |= ev_flags;
             break;

           case EVAS_CALLBACK_KEY_DOWN:
             ((Evas_Event_Key_Down *)event_info)->event_flags |= ev_flags;
             break;

           case EVAS_CALLBACK_KEY_UP:
             ((Evas_Event_Key_Up *)event_info)->event_flags |= ev_flags;
             break;

           default:
             return;
          }
     }
}

/**
 * @internal
 *
 * Report current state of a gesture by calling user callback.
 * @param gesture what gesture state we report.
 * @param info inforamtion for user callback
 *
 * @ingroup Elm_Gesture_Layer
 */
static Evas_Event_Flags
_state_report(Gesture_Info *gesture,
              void *info)
{
   Evas_Event_Flags flags = EVAS_EVENT_FLAG_NONE;
   /* We report current state (START, MOVE, END, ABORT), once */
   if ((gesture->state != ELM_GESTURE_STATE_UNDEFINED) &&
       (gesture->cbs[gesture->state])) /* Fill state-info struct and
                                        * send ptr to user
                                        * callback */
     {
        Eina_Inlist *itr;
        Func_Data *cb_info;
        /* elm_gesture_layer_cb_del can be called in Elm_Gesture_Event_Cb cb */
        EINA_INLIST_FOREACH_SAFE(gesture->cbs[gesture->state], itr, cb_info)
           flags |= cb_info->cb(cb_info->user_data, info);
     }

   return flags;
}

/**
 * @internal
 *
 * Update state for a given gesture.
 * We may update gesture state to:
 * - @c UNDEFINED - current input did not start gesture yet.
 * - @c START - gesture started according to input.
 * - @c MOVE - gesture in progress.
 * - @c END - gesture completed according to input.
 * - @c ABORT - input does not matches gesture.
 * note that we may move from UNDEFINED to ABORT
 * because we may detect that gesture will not START
 * with a given input.
 *
 * @param g given gesture to change state.
 * @param s gesture new state.
 * @param info buffer to be sent to user callback on report_state.
 * @param force makes report_state to report the new-state even
 * if its same as current state. Works for MOVE - gesture in progress.
 *
 * @ingroup Elm_Gesture_Layer
 */
static Evas_Event_Flags
_state_set(Gesture_Info *g,
           Elm_Gesture_State s,
           void *info,
           Eina_Bool force)
{
   Elm_Gesture_State old_state;

   if ((g->state == s) && (!force))
     return EVAS_EVENT_FLAG_NONE;

   old_state = g->state;

   g->state = s;
   g->info = info;  /* Information for user callback */
   if ((g->state == ELM_GESTURE_STATE_ABORT) ||
       (g->state == ELM_GESTURE_STATE_END))
     g->test = EINA_FALSE;

   if ((g->state != ELM_GESTURE_STATE_UNDEFINED) &&
       (!((old_state == ELM_GESTURE_STATE_UNDEFINED) &&
          (s == ELM_GESTURE_STATE_ABORT))))
     return _state_report(g, g->info);

   return EVAS_EVENT_FLAG_NONE;
}

/**
 * @internal
 *
 * This resets all gesture states and sets test-bit.
 * this is used for restarting gestures to listen to input.
 * happens after we complete a gesture or no gesture was detected.
 * @param sd Widget data of the gesture-layer object.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_states_reset(Elm_Gesture_Layer_Data *sd)
{
   int i;
   Gesture_Info *p;

   for (i = ELM_GESTURE_FIRST; i < ELM_GESTURE_LAST; i++)
     {
        p = sd->gesture[i];
        if (p)
          {
             _state_set(p, ELM_GESTURE_STATE_UNDEFINED, NULL, EINA_FALSE);
             SET_TEST_BIT(p);
          }
     }
}

/**
 * @internal
 *
 * This function is used to save input events in an abstract struct
 * to be used later by getsure-testing functions.
 *
 * @param data The gesture-layer object.
 * @param event_info Pointer to recent input event.
 * @param event_type Recent input event type.
 * @param pe The abstract data-struct (output).
 *
 * @ingroup Elm_Gesture_Layer
 */
static Eina_Bool
_pointer_event_make(void *data EINA_UNUSED,
                    void *event_info,
                    Evas_Callback_Type event_type,
                    Pointer_Event *pe)
{
   memset(pe, '\0', sizeof(*pe));
   switch (event_type)
     {
      case EVAS_CALLBACK_MOUSE_DOWN:
        pe->x = ((Evas_Event_Mouse_Down *)event_info)->canvas.x;
        pe->y = ((Evas_Event_Mouse_Down *)event_info)->canvas.y;
        pe->timestamp = ((Evas_Event_Mouse_Down *)event_info)->timestamp;
        pe->device = ELM_MOUSE_DEVICE;
        break;

      case EVAS_CALLBACK_MOUSE_UP:
        pe->x = ((Evas_Event_Mouse_Up *)event_info)->canvas.x;
        pe->y = ((Evas_Event_Mouse_Up *)event_info)->canvas.y;
        pe->timestamp = ((Evas_Event_Mouse_Up *)event_info)->timestamp;
        pe->device = ELM_MOUSE_DEVICE;
        break;

      case EVAS_CALLBACK_MOUSE_MOVE:
        pe->x = ((Evas_Event_Mouse_Move *)event_info)->cur.canvas.x;
        pe->y = ((Evas_Event_Mouse_Move *)event_info)->cur.canvas.y;
        pe->timestamp = ((Evas_Event_Mouse_Move *)event_info)->timestamp;
        pe->device = ELM_MOUSE_DEVICE;
        break;

      case EVAS_CALLBACK_MULTI_DOWN:
        pe->x = ((Evas_Event_Multi_Down *)event_info)->canvas.x;
        pe->y = ((Evas_Event_Multi_Down *)event_info)->canvas.y;
        pe->timestamp = ((Evas_Event_Multi_Down *)event_info)->timestamp;
        pe->device = ((Evas_Event_Multi_Down *)event_info)->device;
        break;

      case EVAS_CALLBACK_MULTI_UP:
        pe->x = ((Evas_Event_Multi_Up *)event_info)->canvas.x;
        pe->y = ((Evas_Event_Multi_Up *)event_info)->canvas.y;
        pe->timestamp = ((Evas_Event_Multi_Up *)event_info)->timestamp;
        pe->device = ((Evas_Event_Multi_Up *)event_info)->device;
        break;

      case EVAS_CALLBACK_MULTI_MOVE:
        pe->x = ((Evas_Event_Multi_Move *)event_info)->cur.canvas.x;
        pe->y = ((Evas_Event_Multi_Move *)event_info)->cur.canvas.y;
        pe->timestamp = ((Evas_Event_Multi_Move *)event_info)->timestamp;
        pe->device = ((Evas_Event_Multi_Move *)event_info)->device;
        break;

      default:
        return EINA_FALSE;
     }

   pe->event_type = event_type;
   return EINA_TRUE;
}

/**
 * @internal
 *
 * This function copies input events.
 * We copy event info before adding it to history.
 * The memory is freed when we clear history.
 *
 * @param event the event to copy
 * @param event_type event type to copy
 *
 * @ingroup Elm_Gesture_Layer
 */
static void *
_event_info_copy(void *event,
                 Evas_Callback_Type event_type)
{
   switch (event_type)
     {
      case EVAS_CALLBACK_MOUSE_DOWN:
        return COPY_EVENT_INFO((Evas_Event_Mouse_Down *)event);
        break;

      case EVAS_CALLBACK_MOUSE_MOVE:
        return COPY_EVENT_INFO((Evas_Event_Mouse_Move *)event);
        break;

      case EVAS_CALLBACK_MOUSE_UP:
        return COPY_EVENT_INFO((Evas_Event_Mouse_Up *)event);
        break;

      case EVAS_CALLBACK_MOUSE_WHEEL:
        return COPY_EVENT_INFO((Evas_Event_Mouse_Wheel *)event);
        break;

      case EVAS_CALLBACK_MULTI_DOWN:
        return COPY_EVENT_INFO((Evas_Event_Multi_Down *)event);
        break;

      case EVAS_CALLBACK_MULTI_MOVE:
        return COPY_EVENT_INFO((Evas_Event_Multi_Move *)event);
        break;

      case EVAS_CALLBACK_MULTI_UP:
        return COPY_EVENT_INFO((Evas_Event_Multi_Up *)event);
        break;

      case EVAS_CALLBACK_KEY_DOWN:
        return COPY_EVENT_INFO((Evas_Event_Key_Down *)event);
        break;

      case EVAS_CALLBACK_KEY_UP:
        return COPY_EVENT_INFO((Evas_Event_Key_Up *)event);
        break;

      default:
        return NULL;
     }
}

static Eina_Bool
_event_history_add(Evas_Object *obj,
                   void *event,
                   Evas_Callback_Type event_type)
{
   Event_History *ev;

   ELM_GESTURE_LAYER_DATA_GET(obj, sd);

   ev = malloc(sizeof(Event_History));
   if (!ev) return EINA_FALSE;

   ev->event = _event_info_copy(event, event_type);  /* Freed on
                                                      * _event_history_clear */
   ev->event_type = event_type;
   sd->event_history_list = (Event_History *)eina_inlist_append(
       EINA_INLIST_GET(sd->event_history_list), EINA_INLIST_GET(ev));

   return EINA_TRUE;
}

/**
 * For all _mouse_* / multi_* functions wethen send this event to
 * _event_process function.
 *
 * @param data The gesture-layer object.
 * @param event_info Pointer to recent input event.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_mouse_down_cb(void *data,
               Evas *e EINA_UNUSED,
               Evas_Object *obj EINA_UNUSED,
               void *event_info)
{
   if (((Evas_Event_Mouse_Down *)event_info)->button != 1)
     return;  /* We only process left-click at the moment */

   _event_process(data, obj, event_info, EVAS_CALLBACK_MOUSE_DOWN);
}

static void
_mouse_move_cb(void *data,
               Evas *e EINA_UNUSED,
               Evas_Object *obj EINA_UNUSED,
               void *event_info)
{
   _event_process(data, obj, event_info, EVAS_CALLBACK_MOUSE_MOVE);
}

static void
_key_down_cb(void *data,
             Evas *e EINA_UNUSED,
             Evas_Object *obj EINA_UNUSED,
             void *event_info)
{
   _event_process(data, obj, event_info, EVAS_CALLBACK_KEY_DOWN);
}

static void
_key_up_cb(void *data,
           Evas *e EINA_UNUSED,
           Evas_Object *obj EINA_UNUSED,
           void *event_info)
{
   _event_process(data, obj, event_info, EVAS_CALLBACK_KEY_UP);
}

static void
_mouse_up_cb(void *data,
             Evas *e EINA_UNUSED,
             Evas_Object *obj EINA_UNUSED,
             void *event_info)
{
   if (((Evas_Event_Mouse_Up *)event_info)->button != 1)
     return;  /* We only process left-click at the moment */

   _event_process(data, obj, event_info, EVAS_CALLBACK_MOUSE_UP);
}

static void
_mouse_wheel_cb(void *data,
                Evas *e EINA_UNUSED,
                Evas_Object *obj EINA_UNUSED,
                void *event_info)
{
   _event_process(data, obj, event_info, EVAS_CALLBACK_MOUSE_WHEEL);
}

static void
_multi_down_cb(void *data,
               Evas *e EINA_UNUSED,
               Evas_Object *obj EINA_UNUSED,
               void *event_info)
{
   /* Skip the mouse duplicates. */
   if (((Evas_Event_Multi_Down *) event_info)->device == 0) return;

   _event_process(data, obj, event_info, EVAS_CALLBACK_MULTI_DOWN);
}

static void
_multi_move_cb(void *data,
               Evas *e EINA_UNUSED,
               Evas_Object *obj EINA_UNUSED,
               void *event_info)
{
   /* Skip the mouse duplicates. */
   if (((Evas_Event_Multi_Move *) event_info)->device == 0) return;

   _event_process(data, obj, event_info, EVAS_CALLBACK_MULTI_MOVE);
}

static void
_multi_up_cb(void *data,
             Evas *e EINA_UNUSED,
             Evas_Object *obj EINA_UNUSED,
             void *event_info)
{
   /* Skip the mouse duplicates. */
   if (((Evas_Event_Multi_Up *) event_info)->device == 0) return;

   _event_process(data, obj, event_info, EVAS_CALLBACK_MULTI_UP);
}

static void
_target_del_cb(void *data,
               Evas *e EINA_UNUSED,
               Evas_Object *obj EINA_UNUSED,
               void *event_info EINA_UNUSED)
{
   _callbacks_unregister(data);
   ELM_GESTURE_LAYER_DATA_GET(data, sd);
   sd->target = NULL;
}

/**
 * @internal
 *
 * We register callbacks when gesture layer is attached to an object
 * or when its enabled after disable.
 *
 * @param obj The gesture-layer object.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_callbacks_register(Evas_Object *obj)
{
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);

   if (!sd->target) return;

   evas_object_event_callback_add
     (sd->target, EVAS_CALLBACK_MOUSE_DOWN, _mouse_down_cb, obj);
   evas_object_event_callback_add
     (sd->target, EVAS_CALLBACK_MOUSE_MOVE, _mouse_move_cb, obj);
   evas_object_event_callback_add
     (sd->target, EVAS_CALLBACK_MOUSE_UP, _mouse_up_cb, obj);

   evas_object_event_callback_add
     (sd->target, EVAS_CALLBACK_MOUSE_WHEEL, _mouse_wheel_cb, obj);

   evas_object_event_callback_add
     (sd->target, EVAS_CALLBACK_MULTI_DOWN, _multi_down_cb, obj);
   evas_object_event_callback_add
     (sd->target, EVAS_CALLBACK_MULTI_MOVE, _multi_move_cb, obj);
   evas_object_event_callback_add
     (sd->target, EVAS_CALLBACK_MULTI_UP, _multi_up_cb, obj);

   evas_object_event_callback_add
     (sd->target, EVAS_CALLBACK_KEY_DOWN, _key_down_cb, obj);
   evas_object_event_callback_add
     (sd->target, EVAS_CALLBACK_KEY_UP, _key_up_cb, obj);

   evas_object_event_callback_add
     (sd->target, EVAS_CALLBACK_DEL, _target_del_cb, obj);
}

/**
 * @internal
 *
 * We unregister callbacks when gesture layer is disabled.
 *
 * @param obj The gesture-layer object.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_callbacks_unregister(Evas_Object *obj)
{
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);

   if (!sd->target) return;

   evas_object_event_callback_del_full
     (sd->target, EVAS_CALLBACK_MOUSE_DOWN, _mouse_down_cb, obj);
   evas_object_event_callback_del_full
     (sd->target, EVAS_CALLBACK_MOUSE_MOVE, _mouse_move_cb, obj);
   evas_object_event_callback_del_full
     (sd->target, EVAS_CALLBACK_MOUSE_UP, _mouse_up_cb, obj);

   evas_object_event_callback_del_full
     (sd->target, EVAS_CALLBACK_MOUSE_WHEEL, _mouse_wheel_cb, obj);

   evas_object_event_callback_del_full
     (sd->target, EVAS_CALLBACK_MULTI_DOWN, _multi_down_cb, obj);

   evas_object_event_callback_del_full
     (sd->target, EVAS_CALLBACK_MULTI_MOVE, _multi_move_cb, obj);

   evas_object_event_callback_del_full
     (sd->target, EVAS_CALLBACK_MULTI_UP, _multi_up_cb, obj);

   evas_object_event_callback_del_full
     (sd->target, EVAS_CALLBACK_KEY_DOWN, _key_down_cb, obj);
   evas_object_event_callback_del_full
     (sd->target, EVAS_CALLBACK_KEY_UP, _key_up_cb, obj);

   evas_object_event_callback_del_full
     (sd->target, EVAS_CALLBACK_DEL, _target_del_cb, obj);
}

/**
 * @internal
 * This function is used to find if device number
 * is found in a list of devices.
 * The list contains devices for refeeding *UP event
 *
 * @ingroup Elm_Gesture_Layer
 */
static int
_device_in_pending_cmp(const void *data1,
                       const void *data2)
{
   /* Compare the two device numbers */
   return ((intptr_t)data1) - ((intptr_t)data2);
}

/**
 * @internal
 *
 * This functions returns pending-device node
 * @ingroup Elm_Gesture_Layer
 */
static Eina_List *
_device_is_pending(Eina_List *list,
                   void *event,
                   Evas_Callback_Type event_type)
{
   int device = ELM_MOUSE_DEVICE;

   switch (event_type)
     {
      case EVAS_CALLBACK_MOUSE_UP:
        break;

      case EVAS_CALLBACK_MULTI_UP:
        device = ((Evas_Event_Multi_Up *)event)->device;
        break;

      default:
        return NULL;
     }

   return eina_list_search_unsorted_list
            (list, _device_in_pending_cmp, (void *)(intptr_t)device);
}

/**
 * @internal
 *
 * This functions adds device to refeed-pending device list
 * @ingroup Elm_Gesture_Layer
 */
static Eina_List *
_pending_device_add(Eina_List *list,
                    void *event,
                    Evas_Callback_Type event_type)
{
   int device = ELM_MOUSE_DEVICE;

   switch (event_type)
     {
      case EVAS_CALLBACK_MOUSE_DOWN:
        break;

      case EVAS_CALLBACK_MULTI_DOWN:
        device = ((Evas_Event_Multi_Down *)event)->device;
        break;

      default:
        return list;
     }

   if (!eina_list_search_unsorted_list
         (list, _device_in_pending_cmp, (void *)(intptr_t)device))
     {
        return eina_list_append(list, (void *)(intptr_t)device);
     }

   return list;
}

/**
 * @internal
 *
 * This function reports ABORT to all none-detected gestures
 * Then resets test bits for all desired gestures
 * and clears input-events history.
 * note: if no gesture was detected, events from history list
 * are streamed to the widget because it's unused by layer.
 * user may cancel refeed of events by setting repeat events.
 *
 * @param obj The gesture-layer object.
 * @param need_reset Clear all gestures data or not.
 *
 * @ingroup Elm_Gesture_Layer
 */
static Eina_Bool
_event_history_clear(Evas_Object *obj, Eina_Bool need_reset)
{
   int i;
   Gesture_Info *p;
   Evas *e = evas_object_evas_get(obj);
   Eina_Bool gesture_found = EINA_FALSE;

   ELM_GESTURE_LAYER_DATA_GET(obj, sd);

   for (i = ELM_GESTURE_FIRST; i < ELM_GESTURE_LAST; i++)
     {
        p = sd->gesture[i];
        if (p)
          {
             if (p->state == ELM_GESTURE_STATE_END)
               {
                  gesture_found = EINA_TRUE;
               }
             else
               {  /* Report ABORT to all gestures that still not finished */
                  if (sd->target)
                    _state_set(p, ELM_GESTURE_STATE_ABORT,
                          sd->gesture[i]->info, EINA_FALSE);
               }
          }
     }

   _states_reset(sd); /* we are ready to start testing for gestures again */

   /* Clear all gestures intermediate data */
   if (need_reset)
     {
        /* FIXME: +1 because of the mistake in the enum. */
        Gesture_Info **gitr = sd->gesture + 1;
        Tests_Array_Funcs *fitr = _glayer_tests_array + 1;
        for (; fitr->reset; fitr++, gitr++)
          {
             if (IS_TESTED_GESTURE(*gitr))
               fitr->reset(*gitr);
          }
     }

   /* Disable gesture layer so refeeded events won't be consumed by it */
   _callbacks_unregister(obj);
   while (sd->event_history_list)
     {
        Event_History *t;
        t = sd->event_history_list;
        Eina_List *pending = _device_is_pending
            (sd->pending, sd->event_history_list->event,
            sd->event_history_list->event_type);

        /* Refeed events if no gesture matched input */
        if (pending || ((!gesture_found) && (!sd->repeat_events)))
          {
             evas_event_refeed_event(e, sd->event_history_list->event,
                                     sd->event_history_list->event_type);

             if (pending)
               {
                  sd->pending = eina_list_remove_list(sd->pending, pending);
               }
             else
               {
                  sd->pending = _pending_device_add
                      (sd->pending, sd->event_history_list->event,
                      sd->event_history_list->event_type);
               }
          }

        free(sd->event_history_list->event);
        sd->event_history_list = (Event_History *)eina_inlist_remove(
            EINA_INLIST_GET(sd->event_history_list),
            EINA_INLIST_GET(sd->event_history_list));
        free(t);
     }
   _callbacks_register(obj);
   return EINA_TRUE;
}

/**
 * @internal
 *
 * if gesture was NOT detected AND we only have gestures in ABORT state
 * we clear history immediately to be ready for input.
 *
 * @param obj The gesture-layer object.
 * @return TRUE on event history_clear
 *
 * @ingroup Elm_Gesture_Layer
 */
static Eina_Bool
_clear_if_finished(Evas_Object *obj)
{
   int i;
   Eina_Bool reset_s = EINA_TRUE, all_undefined = EINA_TRUE;

   ELM_GESTURE_LAYER_DATA_GET(obj, sd);

   /* Clear history if all we have aborted gestures */
   for (i = ELM_GESTURE_FIRST; i < ELM_GESTURE_LAST; i++)
     {  /* If no gesture started and all we have aborted gestures, reset all */
       Gesture_Info *p = sd->gesture[i];

       if ((p) && (p->state != ELM_GESTURE_STATE_UNDEFINED))
         {
            if ((p->state == ELM_GESTURE_STATE_START) ||
                (p->state == ELM_GESTURE_STATE_MOVE))
              reset_s = EINA_FALSE;

            all_undefined = EINA_FALSE;
         }
     }

   if (reset_s && (!all_undefined))
     return _event_history_clear(obj, EINA_TRUE);

   return EINA_FALSE;
}

/**
 * @internal
 *
 * This function restarts line, flick, zoom and rotate gestures
 * when gesture-layer continues-gestures enabled.
 * Example of continues-gesture:
 * When doing a line, user stops moving finger but keeps fingers on touch.
 * This will cause line-end, then as user continues moving his finger
 * it re-starts line gesture.
 * When continue mode is disabled, user has to lift finger from touch
 * to end a gesture. Them touch-again to start a new one.
 *
 * @param data The gesture-layer object.
 * @param sd gesture layer widget data.
 * @param states_reset flag that marks gestures were reset in history clear.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_continues_gestures_restart(void *data,
                            Eina_Bool states_reset)
{
   ELM_GESTURE_LAYER_DATA_GET(data, sd);

   /* Test all the gestures */
   {
      /* FIXME: +1 because of the mistake in the enum. */
      Gesture_Info **gitr = sd->gesture + 1;
      Tests_Array_Funcs *fitr = _glayer_tests_array + 1;
      for (; fitr->test; fitr++, gitr++)
        {
           Gesture_Info *g = *gitr;
           Eina_Bool tmp = (g) ?
             ((states_reset) || ((g->state != ELM_GESTURE_STATE_START)
                                 && (g->state != ELM_GESTURE_STATE_MOVE)))
             : EINA_FALSE;
           if (tmp && fitr->cont_reset)
             {
                fitr->cont_reset(g);
                _state_set(g, ELM_GESTURE_STATE_UNDEFINED, NULL, EINA_FALSE);
                SET_TEST_BIT(g);
             }
        }
   }
}

/**
 * @internal
 *
 * This function the core-function where input handling is done.
 * Here we get user input and stream it to gesture testing.
 * We notify user about any gestures with new state:
 * Valid states are:
 * START - gesture started.
 * MOVE - gesture is ongoing.
 * END - gesture was completed.
 * ABORT - gesture was aborted after START, MOVE (will NOT be completed)
 *
 * We also check if a gesture was detected, then reset event history
 * If no gestures were found we reset gesture test flag
 * after streaming event-history to widget.
 * (stream to the widget all events not consumed as a gesture)
 *
 * @param data The gesture-layer object.
 * @param event_info Pointer to recent input event.
 * @param event_type Recent input event type.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_event_process(void *data,
               Evas_Object *obj EINA_UNUSED,
               void *event_info,
               Evas_Callback_Type event_type)
{
   Pointer_Event _pe;
   Pointer_Event *pe = NULL;

   ELM_GESTURE_LAYER_DATA_GET(data, sd);

   evas_object_ref(sd->target);
   /* Start testing candidate gesture from here */
   if (_pointer_event_make(data, event_info, event_type, &_pe))
     pe = &_pe;

   /* Test all the gestures */
   {
      /* FIXME: +1 because of the mistake in the enum. */
      Gesture_Info **gitr = sd->gesture + 1;
      Tests_Array_Funcs *fitr = _glayer_tests_array + 1;
      for (; fitr->test; fitr++, gitr++)
        {
           if (IS_TESTED_GESTURE(*gitr))
             fitr->test(data, pe, event_info, event_type, (*gitr)->g_type);
        }
   }

   if (_event_flag_get(event_info, event_type) & EVAS_EVENT_FLAG_ON_HOLD)
     _event_history_add(data, event_info, event_type);

   /* we maintain list of touched devices              */
   /* We also use move to track current device x.y pos */
   if ((event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
       (event_type == EVAS_CALLBACK_MULTI_DOWN) ||
       (event_type == EVAS_CALLBACK_MOUSE_MOVE) ||
       (event_type == EVAS_CALLBACK_MULTI_MOVE))
     {
        sd->touched = _touched_device_add(sd->touched, pe);
     }
   else if ((event_type == EVAS_CALLBACK_MOUSE_UP) ||
            (event_type == EVAS_CALLBACK_MULTI_UP))
     {
        sd->touched = _touched_device_remove(sd->touched, pe);
     }

   /* Report current states and clear history if needed */
   Eina_Bool states_reset = _clear_if_finished(data);
   if (sd->glayer_continues_enable)
     _continues_gestures_restart(data, states_reset);

   evas_object_unref(sd->target);
}

static Eina_Bool
_inside(Evas_Coord xx1,
        Evas_Coord yy1,
        Evas_Coord xx2,
        Evas_Coord yy2,
        Evas_Coord w)
{
   w >>= 1; /* Use half the distance, from center to all directions */
   if (!w)  /* use system default instead */
     w = elm_config_finger_size_get() >> 1; /* Finger size divided by 2 */

   if (xx1 < (xx2 - w))
     return EINA_FALSE;

   if (xx1 > (xx2 + w))
     return EINA_FALSE;

   if (yy1 < (yy2 - w))
     return EINA_FALSE;

   if (yy1 > (yy2 + w))
     return EINA_FALSE;

   return EINA_TRUE;
}

/* All *test_reset() funcs are called to clear
 * gesture intermediate data.
 * This happens when we need to reset our tests.
 * for example when gesture is detected or all ABORTed. */
static void
_tap_gestures_test_reset(Gesture_Info *gesture)
{
   Eina_List *data;
   Pointer_Event *pe;

   EINA_SAFETY_ON_NULL_RETURN(gesture);
   ELM_GESTURE_LAYER_DATA_GET(gesture->obj, sd);

   ELM_SAFE_FREE(sd->gest_taps_timeout, ecore_timer_del);

   if (!gesture->data)
     return;

   EINA_LIST_FREE(((Taps_Type *)gesture->data)->l, data)
     EINA_LIST_FREE(data, pe)
       free(pe);

   memset(gesture->data, 0, sizeof(Taps_Type));
}

/* All *test_reset() funcs are called to clear
 * gesture intermediate data.
 * This happens when we need to reset our tests.
 * for example when gesture is detected or all ABORTed. */
static void
_n_long_tap_test_reset(Gesture_Info *gesture)
{
   Pointer_Event *p;
   Long_Tap_Type *st;

   EINA_SAFETY_ON_NULL_RETURN(gesture);
   if (!gesture->data) return;

   st = gesture->data;

   EINA_LIST_FREE(st->touched, p)
     free(p);
   st->touched = NULL;

   ELM_SAFE_FREE(st->timeout, ecore_timer_del);
   memset(gesture->data, 0, sizeof(Long_Tap_Type));
}

static void
_momentum_test_reset(Gesture_Info *gesture)
{
   EINA_SAFETY_ON_NULL_RETURN(gesture);
   if (!gesture->data) return;

   memset(gesture->data, 0, sizeof(Momentum_Type));
}

static void
_line_data_reset(Line_Data *st)
{
   if (!st)
     return;

   memset(st, 0, sizeof(Line_Data));
   st->line_angle = ELM_GESTURE_NEGATIVE_ANGLE;
}

static void
_line_test_reset(Gesture_Info *gesture)
{
   Line_Type *st;
   Line_Data *t_line;

   EINA_SAFETY_ON_NULL_RETURN(gesture);
   if (!gesture->data) return;

   st = gesture->data;

   EINA_LIST_FREE(st->list, t_line)
     free(t_line);
   st->list = NULL;
}

static void
_zoom_test_reset(Gesture_Info *gesture)
{
   Zoom_Type *st;

   EINA_SAFETY_ON_NULL_RETURN(gesture);
   if (!gesture->data) return;
   ELM_GESTURE_LAYER_DATA_GET(gesture->obj, sd);

   st = gesture->data;
   efl_canvas_object_key_ungrab(sd->target, "Control_L", EFL_INPUT_MODIFIER_CONTROL, 0);
   efl_canvas_object_key_ungrab(sd->target, "Control_R", EFL_INPUT_MODIFIER_CONTROL, 0);

   memset(st, 0, sizeof(Zoom_Type));
   st->zoom_distance_tolerance = sd->zoom_distance_tolerance;
   st->info.zoom = 1.0;
}

static void
_rotate_test_reset(Gesture_Info *gesture)
{
   Rotate_Type *st;

   EINA_SAFETY_ON_NULL_RETURN(gesture);
   if (!gesture->data) return;

   ELM_GESTURE_LAYER_DATA_GET(gesture->obj, sd);
   st = gesture->data;

   memset(st, 0, sizeof(Rotate_Type));
   st->info.base_angle = ELM_GESTURE_NEGATIVE_ANGLE;
   st->rotate_angular_tolerance = sd->rotate_angular_tolerance;
}

static Eina_List *
_match_fingers_compare(Eina_List *list,
                       Pointer_Event *pe1,
                       Evas_Coord w)
{
    /* Compare coords of first item in list to cur coords */
   Eina_List *pe_list;
   Eina_List *l;

   EINA_LIST_FOREACH(list, l, pe_list)
     {
        Pointer_Event *pe2 = eina_list_data_get(pe_list);

        if (_inside(pe1->x, pe1->y, pe2->x, pe2->y, w))
          return pe_list;
     }

  return NULL;
}

static int
_pe_device_compare(const void *data1,
                   const void *data2)
{
   /* Compare device of first item in list to our pe device */
   const Pointer_Event *pe1 = eina_list_data_get(data1);
   const Pointer_Event *pe2 = data2;

   if (pe1->device == pe2->device)
     return 0;
   else if (pe1->device < pe2->device)
     return -1;
   else
     return 1;
}

static Eina_List *
_pointer_event_record(Taps_Type *st,
                      Eina_List *pe_list,
                      Pointer_Event *pe,
                      Elm_Gesture_Layer_Data *sd,
                      void *event_info,
                      Evas_Callback_Type event_type)
{
   /* Keep copy of pe and record it in list */
   Pointer_Event *p = malloc(sizeof(Pointer_Event));

   memcpy(p, pe, sizeof(Pointer_Event));
   _event_consume(sd, event_info, event_type, EVAS_EVENT_FLAG_NONE);

   st->sum_x += pe->x;
   st->sum_y += pe->y;
   st->n_taps++;

   /* This will also update middle-point to report to user later */
   st->info.x = st->sum_x / st->n_taps;
   st->info.y = st->sum_y / st->n_taps;
   st->info.timestamp = pe->timestamp;

   if (!pe_list)
     {
        pe_list = eina_list_append(pe_list, p);
        st->l = eina_list_append(st->l, pe_list);
     }
   else
     pe_list = eina_list_append(pe_list, p);

   return pe_list;
}

/**
 * @internal
 *
 * This function computes minimum rect to bound taps at idx index
 *
 * @param taps [in] List of lists containing taps info.
 * @param idx [in] index of events taken from lists.
 * @param r [out] rect object to save info
 * @return EINA_TRUE if managed to compute rect.
 *
 * @ingroup Elm_Gesture_Layer
 */
static Eina_Bool
_taps_rect_get(Eina_List *taps, int idx, Eina_Rectangle *r)
{  /* Build a rect bounding all taps at index idx */
   Eina_List *l;
   Evas_Coord bx = 0, by = 0;
   Eina_List *pe_list;
   Eina_Bool was_init = EINA_FALSE;

   EINA_LIST_FOREACH(taps, l, pe_list)
     {
        Pointer_Event *pe = eina_list_nth(pe_list, idx);
        if (!pe) continue;  /* Not suppose to happen */

        if (was_init)
          {
             if (pe->x < r->x) r->x = pe->x;
             if (pe->y < r->y) r->y = pe->y;
             if (pe->x > bx) bx = pe->x;
             if (pe->y > by) by = pe->y;
          }
        else
          {
             r->x = bx = pe->x;
             r->y = by = pe->y;
             was_init = EINA_TRUE;
          }
     }

   r->w = bx - r->x;
   r->h = by - r->y;
   return was_init;
}

/**
 * @internal
 *
 * This function checks if the tap gesture is done.
 *
 * @param data gesture info pointer
 * @return EINA_TRUE if it is done.
 *
 * @ingroup Elm_Gesture_Layer
 */
static Eina_Bool
_tap_gesture_check_finish(Gesture_Info *gesture)
{
   /* Here we check if taps-gesture was completed successfully */
   /* Count how many taps were received on each device then   */
   /* determine if it matches n_taps_needed defined on START  */
   unsigned int i;
   Taps_Type *st = gesture->data;
   Eina_List *l;
   Eina_List *pe_list;
   Eina_Rectangle base = {0, 0, 0, 0};
   Eina_Rectangle tmp = {0, 0, 0, 0};

   ELM_GESTURE_LAYER_DATA_GET(gesture->obj, sd);

   if (!st->l) return EINA_FALSE;
   EINA_LIST_FOREACH(st->l, l, pe_list)
     {
        /* No match taps number on device, ABORT */
        if (eina_list_count(pe_list) != st->n_taps_needed)
          {
             return EINA_FALSE;
          }
     }

   /* Now bound each tap touches in a rect, compare diff within tolerance */
   /* Get rect based on first DOWN events for all devices */
   if (!_taps_rect_get(st->l, 0, &base))
     return EINA_FALSE;  /* Should not happen */

   for (i = 1; i < st->n_taps_needed; i++)
     {  /* Compare all other rects to base, tolerance is finger size */
        if (_taps_rect_get(st->l, i, &tmp))
          {
             if (abs(tmp.x - base.x) > sd->tap_finger_size)
               return EINA_FALSE;

             if (abs(tmp.y - base.y) > sd->tap_finger_size)
               return EINA_FALSE;

             if (abs((tmp.x + tmp.w) - (base.x + base.w)) > sd->tap_finger_size)
               return EINA_FALSE;

             if (abs((tmp.y + tmp.h) - (base.y + base.h)) > sd->tap_finger_size)
               return EINA_FALSE;
          }
     }

   return EINA_TRUE;
}

/**
 * @internal
 *
 * This function sets state a tap-gesture to END or ABORT
 *
 * @param data gesture info pointer
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_tap_gesture_finish(void *data)
{
   /* This function will test each tap gesture when timer expires */
   Elm_Gesture_State s = ELM_GESTURE_STATE_ABORT;
   Gesture_Info *gesture = data;
   Taps_Type *st = gesture->data;

   if (_tap_gesture_check_finish(gesture))
     {
        s = ELM_GESTURE_STATE_END;
     }

   st->info.n = eina_list_count(st->l);
   _state_set(gesture, s, gesture->info, EINA_FALSE);
   _tap_gestures_test_reset(gesture);
}

/**
 * @internal
 *
 * when this timer expires we finish tap gestures.
 *
 * @param data The gesture-layer object.
 * @return cancels callback for this timer.
 *
 * @ingroup Elm_Gesture_Layer
 */
static Eina_Bool
_multi_tap_timeout(void *data)
{
   ELM_GESTURE_LAYER_DATA_GET(data, sd);

   if (IS_TESTED(ELM_GESTURE_N_TAPS))
     _tap_gesture_finish(sd->gesture[ELM_GESTURE_N_TAPS]);

   if (IS_TESTED(ELM_GESTURE_N_DOUBLE_TAPS))
     _tap_gesture_finish(sd->gesture[ELM_GESTURE_N_DOUBLE_TAPS]);

   if (IS_TESTED(ELM_GESTURE_N_TRIPLE_TAPS))
     _tap_gesture_finish(sd->gesture[ELM_GESTURE_N_TRIPLE_TAPS]);

   _clear_if_finished(data);
   sd->gest_taps_timeout = NULL;

   return ECORE_CALLBACK_CANCEL;
}

/**
 * @internal
 *
 * when this timer expires we START long tap gesture
 *
 * @param data The gesture-layer object.
 * @return cancels callback for this timer.
 *
 * @ingroup Elm_Gesture_Layer
 */
static Eina_Bool
_long_tap_timeout(void *data)
{
   Gesture_Info *gesture = data;
   Long_Tap_Type *st = gesture->data;

   st->info.timestamp = ecore_time_get() * 1000;

   _state_set(gesture, ELM_GESTURE_STATE_MOVE,
              gesture->data, EINA_TRUE);

   return ECORE_CALLBACK_RENEW;
}

/**
 * @internal
 *
 * This function checks the state of a tap gesture.
 *
 * @param sd Gesture Layer Widget Data.
 * @param pe The recent input event as stored in pe struct.
 * @param event_info Original input event pointer.
 * @param event_type Type of original input event.
 * @param gesture what gesture is tested
 * @param how many taps for this gesture (1, 2 or 3)
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_tap_gesture_test(Evas_Object *obj,
                  Pointer_Event *pe,
                  void *event_info,
                  Evas_Callback_Type event_type,
                  Elm_Gesture_Type g_type)
{
   int taps = 0;
   Taps_Type *st;
   Gesture_Info *gesture;
   Eina_List *pe_list = NULL;
   Pointer_Event *pe_last = NULL;
   Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;

   /* Here we fill Tap struct */
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);

   if (!pe)
     return;

   gesture = sd->gesture[g_type];
   if (!gesture) return;

   switch (g_type)
     {
      case ELM_GESTURE_N_TAPS:
         taps = 1;
         break;

      case ELM_GESTURE_N_DOUBLE_TAPS:
         taps = 2;
         break;

      case ELM_GESTURE_N_TRIPLE_TAPS:
         taps = 3;
         break;

      default:
         taps = 0;
         break;
     }

   st = gesture->data;
   if (!st) /* Allocated once on first time */
     {
        st = calloc(1, sizeof(Taps_Type));
        if (!st) return;

        gesture->data = st;
        _tap_gestures_test_reset(gesture);
     }

   switch (pe->event_type)
     {
      case EVAS_CALLBACK_MULTI_DOWN:
      case EVAS_CALLBACK_MOUSE_DOWN:
         /* Each device taps (DOWN, UP event) registered in same list    */
         /* Find list for this device or start a new list if not found   */
         pe_list = eina_list_search_unsorted(st->l, _pe_device_compare, pe);
         if (pe_list)
           {  /* This device touched before, verify that this tap is on  */
              /* top of a previous tap (including a tap of other device) */
              if (!_match_fingers_compare(st->l, pe, sd->tap_finger_size))
                {  /* New DOWN event is not on top of any prev touch     */
                   ev_flag = _state_set(gesture, ELM_GESTURE_STATE_ABORT,
                         &st->info, EINA_FALSE);
                   _event_consume(sd, event_info, event_type, ev_flag);

                   return;
                }
           }

         /* All tests are good, register this tap in device list */
         pe_list = _pointer_event_record
            (st, pe_list, pe, sd, event_info, event_type);

         if (!sd->gest_taps_timeout)
           {
              if (sd->double_tap_timeout > 0.0)
                {
                   sd->gest_taps_timeout =
                      ecore_timer_add(sd->double_tap_timeout,
                            _multi_tap_timeout, gesture->obj);
                }
           }
         else  /* We re-allocate gest_taps_timeout between taps */
           ecore_timer_reset(sd->gest_taps_timeout);

         if ((pe->device == 0) && (eina_list_count(pe_list) == 1))
           {  /* This is the first mouse down we got */
              ev_flag = _state_set(gesture, ELM_GESTURE_STATE_START,
                    &st->info, EINA_FALSE);
              _event_consume(sd, event_info, event_type, ev_flag);

              st->n_taps_needed = taps * 2;  /* count DOWN and UP */

              return;
           }
         else if (eina_list_count(pe_list) > st->n_taps_needed)
           {  /* If we already got too many touches for this gesture. */
              _state_set(gesture, ELM_GESTURE_STATE_ABORT,
                    &st->info, EINA_FALSE);
           }

         if (gesture->state == ELM_GESTURE_STATE_MOVE)
           {  /* Report MOVE if all devices have same DOWN/UP count */
              /* Should be in MOVE state from last UP event */
              Eina_List *l;
              Eina_Bool move = EINA_TRUE;
              unsigned int n = 0;

              EINA_LIST_FOREACH(st->l, l, pe_list)
                {
                   if (n == 0)
                     {
                        n = eina_list_count(pe_list);
                     }
                   else if (n != eina_list_count(pe_list))
                     {
                        move = EINA_FALSE;
                     }
                }

              if (move && (n > 0))
                {
                   _state_set(gesture, ELM_GESTURE_STATE_MOVE,
                         &st->info, EINA_TRUE);
                }
           }

         break;

      case EVAS_CALLBACK_MULTI_UP:
      case EVAS_CALLBACK_MOUSE_UP:
         pe_list = eina_list_search_unsorted(st->l, _pe_device_compare, pe);
         if (!pe_list) return;

         _pointer_event_record(st, pe_list, pe, sd, event_info, event_type);

         if (((gesture->g_type == ELM_GESTURE_N_TAPS) &&
                  !IS_TESTED(ELM_GESTURE_N_DOUBLE_TAPS) &&
                  !IS_TESTED(ELM_GESTURE_N_TRIPLE_TAPS)) ||
               ((gesture->g_type == ELM_GESTURE_N_DOUBLE_TAPS) &&
                !IS_TESTED(ELM_GESTURE_N_TRIPLE_TAPS)))
           {  /* Test for finish immediately, not waiting for timeout */
              if (_tap_gesture_check_finish(gesture))
                {
                   _tap_gesture_finish(gesture);
                   return;
                }
           }

         if ((gesture->state == ELM_GESTURE_STATE_START) ||
               (gesture->state == ELM_GESTURE_STATE_MOVE))
           {  /* Tap gesture started, no finger on surface. Report MOVE */
              Eina_List *l;
              Eina_Bool move = EINA_TRUE;
              unsigned int n = 0;

              /* Report move only if all devices have same DOWN/UP count */
              EINA_LIST_FOREACH(st->l, l, pe_list)
                {
                   if (n == 0)
                     {
                        n = eina_list_count(pe_list);
                     }
                   else if (n != eina_list_count(pe_list))
                     {
                        move = EINA_FALSE;
                     }
                }

              if ((move && (n > 0)) && (n < st->n_taps_needed))
                {  /* Set number of fingers and report MOVE */
                   /* We don't report MOVE when (n >= st->n_taps_needed)
                      because will be END or ABORT at this stage */
                   st->info.n = eina_list_count(st->l);
                   _state_set(gesture, ELM_GESTURE_STATE_MOVE,
                         &st->info, EINA_TRUE);
                }
           }

         break;

      case EVAS_CALLBACK_MULTI_MOVE:
      case EVAS_CALLBACK_MOUSE_MOVE:
         /* Verify that user didn't move out of tap area before next tap */
         /* BUT: we need to skip some MOVE events coming before DOWN     */
         /* when user taps next tap. So fetch LAST recorded event for    */
         /* device (DOWN or UP event), ignore all MOVEs if last was UP   */
         pe_last = eina_list_data_get(eina_list_last(
                  eina_list_search_unsorted(st->l, _pe_device_compare, pe)));

         if (pe_last)
           {  /* pe_last is the last event recorded for this device */
              if ((pe_last->event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
                    (pe_last->event_type == EVAS_CALLBACK_MULTI_DOWN))
                {  /* Test only MOVE events that come after DOWN event */
                   if (!_inside(pe_last->x, pe_last->y, pe->x, pe->y,
                            sd->tap_finger_size))
                     {
                        ev_flag = _state_set(gesture, ELM_GESTURE_STATE_ABORT,
                              &st->info, EINA_FALSE);
                        _event_consume(sd, event_info, event_type, ev_flag);
                     }
                }
           }
         break;

      default:
         return;
     }
}

/**
 * @internal
 *
 * This function computes center-point for  long-tap gesture
 *
 * @param st Long Tap gesture info pointer
 * @param pe The recent input event as stored in pe struct.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_compute_taps_center(Long_Tap_Type *st,
                     Evas_Coord *x_out,
                     Evas_Coord *y_out,
                     Pointer_Event *pe)
{
   Eina_List *l;
   Pointer_Event *p;
   Evas_Coord x = 0, y = 0;

   if (!eina_list_count(st->touched))
     return;

   EINA_LIST_FOREACH(st->touched, l, p)
     {  /* Accumulate all then take average */
       if (p->device == pe->device) /* This will take care of values
                                     * coming from MOVE event */
         {
            x += pe->x;
            y += pe->y;
         }
       else
         {
            x += p->x;
            y += p->y;
         }
     }

   *x_out = x / eina_list_count(st->touched);
   *y_out = y / eina_list_count(st->touched);
}

/**
 * @internal
 *
 * This function checks N long-tap gesture.
 *
 * @param obj The gesture-layer object.
 * @param pe The recent input event as stored in pe struct.
 * @param event_info Original input event pointer.
 * @param event_type Type of original input event.
 * @param g_type what Gesture we are testing.
 * @param taps How many click/taps we test for.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_n_long_tap_test(Evas_Object *obj,
                 Pointer_Event *pe,
                 void *event_info,
                 Evas_Callback_Type event_type,
                 Elm_Gesture_Type g_type)
{
   Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;
   Gesture_Info *gesture;
   Long_Tap_Type *st;

   /* Here we fill Recent_Taps struct and fire-up click/tap timers */
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);

   if (!pe) /* this happens when unhandled event arrived */
     return;  /* see _make_pointer_event function */

   gesture = sd->gesture[g_type];
   if (!gesture) return;

   st = gesture->data;
   if (!st) /* Allocated once on first time */
     {
        st = calloc(1, sizeof(Long_Tap_Type));
        if (!st) return;

        gesture->data = st;
        _n_long_tap_test_reset(gesture);
     }

   switch (pe->event_type)
     {
      case EVAS_CALLBACK_MULTI_DOWN:
      case EVAS_CALLBACK_MOUSE_DOWN:
        st->touched = _touched_device_add(st->touched, pe);
        st->info.n = eina_list_count(st->touched);

        _event_consume(sd, event_info, event_type, ev_flag);
        _compute_taps_center(st, &st->info.x, &st->info.y, pe);
        st->center_x = st->info.x;  /* Update coords for */
        st->center_y = st->info.y;  /* reporting START  */
        st->info.timestamp = pe->timestamp;

        /* This is the first mouse down we got */
        if (eina_list_count(st->touched) == 1)
          {
             _state_set(gesture, ELM_GESTURE_STATE_START,
                   gesture->data, EINA_FALSE);

             /* To test long tap */
             /* When this timer expires, gesture STARTED */
             if ((!st->timeout) && (sd->long_tap_start_timeout > 0.0))
               st->timeout = ecore_timer_add(sd->long_tap_start_timeout,
                                             _long_tap_timeout, gesture);
          }
        else
          {
             if (st->timeout)
                ecore_timer_reset(st->timeout);
          }

        break;

      case EVAS_CALLBACK_MULTI_UP:
      case EVAS_CALLBACK_MOUSE_UP:
        st->touched = _touched_device_remove(st->touched, pe);
        _compute_taps_center(st, &st->center_x, &st->center_y, pe);
        st->info.timestamp = pe->timestamp;
        if (st->info.n)
          {
             if (gesture->state == ELM_GESTURE_STATE_MOVE)
               ev_flag = _state_set(gesture, ELM_GESTURE_STATE_END,
                                    &st->info, EINA_FALSE);
             else
               ev_flag = _state_set(gesture, ELM_GESTURE_STATE_ABORT,
                                    &st->info, EINA_FALSE);

             ELM_SAFE_FREE(st->timeout, ecore_timer_del);
             _event_consume(sd, event_info, event_type, ev_flag);
          }

        break;

      case EVAS_CALLBACK_MULTI_MOVE:
      case EVAS_CALLBACK_MOUSE_MOVE:
        if (st->info.n &&
            ((gesture->state == ELM_GESTURE_STATE_START) ||
             /* Report MOVE only if STARTED */
             (gesture->state == ELM_GESTURE_STATE_MOVE)))
          {
             Evas_Coord x = 0;
             Evas_Coord y = 0;

             _compute_taps_center(st, &x, &y, pe);
             st->info.timestamp = pe->timestamp;
             /* ABORT if user moved fingers out of tap area */
             if (!_inside(x, y, st->center_x, st->center_y,
                      sd->tap_finger_size))
               {
                  ELM_SAFE_FREE(st->timeout, ecore_timer_del);

                  /* Report MOVE if gesture started */
                  ev_flag = _state_set(gesture, ELM_GESTURE_STATE_ABORT,
                        &st->info, EINA_FALSE);
               }

             _event_consume(sd, event_info, event_type, ev_flag);
          }
        break;

      default:
        return;
     }
}

/**
 * @internal
 *
 * This function computes momentum for MOMENTUM, LINE and FLICK gestures
 * This momentum value will be sent to widget when gesture is completed.
 *
 * @param momentum pointer to buffer where we record momentum value.
 * @param xx1 x coord where user started gesture.
 * @param yy1 y coord where user started gesture.
 * @param xx2 x coord where user completed gesture.
 * @param yy2 y coord where user completed gesture.
 * @param t1x timestamp for X, when user started gesture.
 * @param t1y timestamp for Y, when user started gesture.
 * @param t2  timestamp when user completed gesture.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_momentum_set(Elm_Gesture_Momentum_Info *momentum,
              Evas_Coord xx1,
              Evas_Coord yy1,
              Evas_Coord xx2,
              Evas_Coord yy2,
              unsigned int t1x,
              unsigned int t1y,
              unsigned int t2)
{
   Evas_Coord velx = 0, vely = 0, vel;
   Evas_Coord dx = xx2 - xx1;
   Evas_Coord dy = yy2 - yy1;
   int dtx = t2 - t1x;
   int dty = t2 - t1y;

   if (dtx > 0)
     velx = (dx * 1000) / dtx;

   if (dty > 0)
     vely = (dy * 1000) / dty;

   vel = sqrt((velx * velx) + (vely * vely));

   if ((_elm_config->thumbscroll_friction > 0.0) &&
       (vel > _elm_config->thumbscroll_momentum_threshold)) /* report
                                                             * momentum */
     {
        momentum->mx = velx;
        momentum->my = vely;
     }
   else
     {
        momentum->mx = 0;
        momentum->my = 0;
     }
}

/**
 * @internal
 *
 * This function is used for computing rotation angle (DEG).
 *
 * @param xx1 first finger x location.
 * @param yy1 first finger y location.
 * @param xx2 second finger x location.
 * @param yy2 second finger y location.
 *
 * @return angle of the line between (xx1,yy1), (xx2,yy2) in Deg.
 * Angles now are given in DEG, not RAD.
 * ZERO angle at 12-oclock, growing clockwise.
 *
 * @ingroup Elm_Gesture_Layer
 */
static double
_angle_get(Evas_Coord xx1,
           Evas_Coord yy1,
           Evas_Coord xx2,
           Evas_Coord yy2)
{
   double a, xx, yy, rt = (-1);

   xx = abs(xx2 - xx1);
   yy = abs(yy2 - yy1);

   if (((int)xx) && ((int)yy))
     {
        rt = a = RAD2DEG(atan(yy / xx));
        if (xx1 < xx2)
          {
             if (yy1 < yy2) rt = 360 - a;
             else rt = a;
          }
        else
          {
             if (yy1 < yy2) rt = 180 + a;
             else rt = 180 - a;
          }
     }

   if (rt < 0) /* Do this only if rt is not set */
     {
        if (((int)xx)) /* Horizontal line */
          {
             if (xx2 < xx1) rt = 180;
             else rt = 0.0;
          }
        else
          {  /* Vertical line */
            if (yy2 < yy1) rt = 90;
            else rt = 270;
          }
     }

   /* Now we want to change from:
    *                      90                   0
    * original circle   180   0   We want:  270   90
    *                     270                 180
    */
   rt = 450 - rt;
   if (rt >= 360) rt -= 360;

   return rt;
}

/**
 * @internal
 *
 * This function is used for computing the magnitude and direction
 * of vector between two points.
 *
 * @param xx1 first finger x location.
 * @param yy1 first finger y location.
 * @param xx2 second finger x location.
 * @param yy2 second finger y location.
 * @param l length computed (output)
 * @param a angle computed (output)
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_vector_get(Evas_Coord xx1,
            Evas_Coord yy1,
            Evas_Coord xx2,
            Evas_Coord yy2,
            Evas_Coord *l,
            double *a)
{
   Evas_Coord xx, yy;

   xx = xx2 - xx1;
   yy = yy2 - yy1;
   *l = (Evas_Coord)sqrt((xx * xx) + (yy * yy));
   *a = _angle_get(xx1, yy1, xx2, yy2);
}

static int
_direction_get(Evas_Coord xx1,
               Evas_Coord xx2)
{
   if (xx2 < xx1) return -1;
   if (xx2 > xx1) return 1;

   return 0;
}

/**
 * @internal
 *
 * This function tests momentum gesture.
 * @param obj The gesture-layer object.
 * @param pe The recent input event as stored in pe struct.
 * @param event_info recent input event.
 * @param event_type recent event type.
 * @param g_type what Gesture we are testing.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_momentum_test(Evas_Object *obj,
               Pointer_Event *pe,
               void *event_info,
               Evas_Callback_Type event_type,
               Elm_Gesture_Type g_type)
{
   Eina_List *l;
   Pointer_Event *p;
   Momentum_Type *st;
   Gesture_Info *gesture;
   Pointer_Event pe_local;
   Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;
   Elm_Gesture_State state_to_report = ELM_GESTURE_STATE_MOVE;
   unsigned int cnt = 1; /* We start counter counting current pe event */

   ELM_GESTURE_LAYER_DATA_GET(obj, sd);

   gesture = sd->gesture[g_type];
   if (!gesture) return;

   /* When continues enable = TRUE a gesture may START on MOVE event */
   /* We don't allow this to happen with the if-statement below.     */
   /* When continues enable = FALSE a gesture may START on DOWN only */
   /* Therefor it would NOT start on MOVE event.                     */
   /* NOTE that touched list is updated AFTER this function returns  */
   /* so (count == 0) when we get here on first touch on surface.    */
   if ((sd->glayer_continues_enable) && (!eina_list_count(sd->touched)))
     return;  /* Got move on mouse-over move */

   st = gesture->data;
   if (!st) /* Allocated once on first time */
     {
        st = calloc(1, sizeof(Momentum_Type));
        if (!st) return;

        gesture->data = st;
        _momentum_test_reset(gesture);
     }

   if (!pe)
     return;

   /* First make average of all touched devices to determine center point */
   pe_local = *pe; /* Copy pe event info to local */
   EINA_LIST_FOREACH(sd->touched, l, p)
     if (p->device != pe_local.device)
       {
          pe_local.x += p->x;
          pe_local.y += p->y;
          cnt++;
       }

   /* Compute average to get center point */
   pe_local.x /= cnt;
   pe_local.y /= cnt;

   /* If user added finger - reset gesture */
   if ((st->info.n) && (st->info.n < cnt))
     state_to_report = ELM_GESTURE_STATE_ABORT;

   if (st->info.n < cnt)
     st->info.n = cnt;

   switch (event_type)
     {
      case EVAS_CALLBACK_MOUSE_DOWN:
      case EVAS_CALLBACK_MULTI_DOWN:
      case EVAS_CALLBACK_MOUSE_MOVE:
      case EVAS_CALLBACK_MULTI_MOVE:
        if (!st->t_st_x)
          {
             if ((event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
                 (event_type == EVAS_CALLBACK_MULTI_DOWN) ||
                 (sd->glayer_continues_enable))    /* start also on MOVE */
               {   /* We start on MOVE when cont-enabled only */
                 st->line_st.x = st->line_end.x = pe_local.x;
                 st->line_st.y = st->line_end.y = pe_local.y;
                 st->t_st_x = st->t_st_y = st->t_end = pe_local.timestamp;
                 st->xdir = st->ydir = 0;
                 st->info.x2 = st->info.x1 = pe_local.x;
                 st->info.y2 = st->info.y1 = pe_local.y;
                 st->info.tx = st->info.ty = pe_local.timestamp;
                 ev_flag = _state_set(gesture, ELM_GESTURE_STATE_START,
                                      &st->info, EINA_FALSE);
                 _event_consume(sd, event_info, event_type, ev_flag);
               }

             return;
          }

        if (st->t_up)
          {
             Eina_Bool force = EINA_TRUE;   /* for move state */

             /* ABORT if got DOWN or MOVE event after UP+timeout */
             if ((st->t_up + ELM_GESTURE_MULTI_TIMEOUT) < pe_local.timestamp)
               {
                  state_to_report = ELM_GESTURE_STATE_ABORT;
                  force = EINA_FALSE;
               }

             /* We report state but don't compute momentum now */
             ev_flag = _state_set(gesture, state_to_report, &st->info,
                                  force);
             _event_consume(sd, event_info, event_type, ev_flag);
             return;  /* Stop computing when user remove finger */
          }

        /*  Too long of a wait, reset all values */
        if ((pe_local.timestamp - ELM_GESTURE_MOMENTUM_TIMEOUT) > st->t_end)
          {
             st->line_st.x = pe_local.x;
             st->line_st.y = pe_local.y;
             st->t_st_y = st->t_st_x = pe_local.timestamp;
             st->info.tx = st->t_st_x;
             st->info.ty = st->t_st_y;
             st->xdir = st->ydir = 0;
          }
        else
          {
             int xdir, ydir;

             xdir = _direction_get(st->line_end.x, pe_local.x);
             ydir = _direction_get(st->line_end.y, pe_local.y);
             if (xdir && (xdir != st->xdir))
               {
                  st->line_st.x = st->line_end.x;
                  st->info.tx = st->t_st_x = st->t_end;
                  st->xdir = xdir;
               }

             if (ydir && (ydir != st->ydir))
               {
                  st->line_st.y = st->line_end.y;
                  st->info.ty = st->t_st_y = st->t_end;
                  st->ydir = ydir;
               }
          }

        st->info.x2 = st->line_end.x = pe_local.x;
        st->info.y2 = st->line_end.y = pe_local.y;
        st->t_end = pe_local.timestamp;
        _momentum_set(&st->info, st->line_st.x, st->line_st.y,
                      pe_local.x, pe_local.y, st->t_st_x, st->t_st_y,
                      pe_local.timestamp);

        ev_flag = _state_set(gesture, state_to_report, &st->info,
                             EINA_TRUE);
        _event_consume(sd, event_info, event_type, ev_flag);
        break;

      case EVAS_CALLBACK_MOUSE_UP:
      case EVAS_CALLBACK_MULTI_UP:
        st->t_up = pe_local.timestamp; /* Record recent up event time */
        if ((cnt > 1) || /* Ignore if more fingers touch surface        */
            (!st->t_st_x)) /* IGNORE if info was cleared, long press,move */
          return;

        /* Too long of a wait, reset all values */
        if ((pe_local.timestamp - ELM_GESTURE_MOMENTUM_TIMEOUT) > st->t_end)
          {
             st->line_st.x = pe_local.x;
             st->line_st.y = pe_local.y;
             st->t_st_y = st->t_st_x = pe_local.timestamp;
             st->xdir = st->ydir = 0;
          }

        st->info.x2 = pe_local.x;
        st->info.y2 = pe_local.y;
        st->line_end.x = pe_local.x;
        st->line_end.y = pe_local.y;
        st->t_end = pe_local.timestamp;

        // FIXME: mx,my are int while the momentum is float. Fishy logic here.
        if ((abs(st->info.mx) > ELM_GESTURE_MINIMUM_MOMENTUM) ||
            (abs(st->info.my) > ELM_GESTURE_MINIMUM_MOMENTUM))
          state_to_report = ELM_GESTURE_STATE_END;
        else
          state_to_report = ELM_GESTURE_STATE_ABORT;

        ev_flag = _state_set(gesture, state_to_report, &st->info,
                             EINA_FALSE);
        _event_consume(sd, event_info, event_type, ev_flag);
        return;

      default:
        return;
     }
}

static int
_line_device_compare(const void *data1,
                     const void *data2)
{
   /* Compare device component of line struct */
   const Line_Data *ln1 = data1;
   const int *device = data2;

   if (ln1->t_st) /* Compare only with lines that started */
     return ln1->device - (*device);

   return -1;
}

/**
 * @internal
 *
 * This function construct line struct from input.
 * @param info pointer to store line momentum.
 * @param st line info to store input data.
 * @param pe The recent input event as stored in pe struct.
 *
 * @ingroup Elm_Gesture_Layer
 */
static Eina_Bool
_single_line_process(Elm_Gesture_Line_Info *info,
                     Line_Data *st,
                     Pointer_Event *pe,
                     Evas_Callback_Type event_type)
{
   /* Record events and set momentum for line pointed by st */
   if (!pe)
     return EINA_FALSE;

   switch (event_type)
     {
      case EVAS_CALLBACK_MOUSE_DOWN:
      case EVAS_CALLBACK_MOUSE_MOVE:
      case EVAS_CALLBACK_MULTI_DOWN:
      case EVAS_CALLBACK_MULTI_MOVE:
        if (!st->t_st) /* This happens only when line starts */
          {
             st->line_st.x = pe->x;
             st->line_st.y = pe->y;
             st->t_st = pe->timestamp;
             st->device = pe->device;
             info->momentum.x1 = pe->x;
             info->momentum.y1 = pe->y;
             info->momentum.tx = pe->timestamp;
             info->momentum.ty = pe->timestamp;

             return EINA_TRUE;
          }

        break;

      case EVAS_CALLBACK_MOUSE_UP:
      case EVAS_CALLBACK_MULTI_UP:
        /* IGNORE if line info was cleared, like long press, move */
        if (!st->t_st)
          return EINA_FALSE;

        st->line_end.x = pe->x;
        st->line_end.y = pe->y;
        st->t_end = pe->timestamp;
        break;

      default:
        return EINA_FALSE;
     }

   if (!st->t_st)
     {
        _line_data_reset(st);
        return EINA_FALSE;
     }

   info->momentum.x2 = pe->x;
   info->momentum.y2 = pe->y;
   _momentum_set(&info->momentum, st->line_st.x, st->line_st.y, pe->x, pe->y,
                 st->t_st, st->t_st, pe->timestamp);

   return EINA_TRUE;
}

/**
 * @internal
 *
 * This function test for (n) line gesture.
 * @param obj The gesture-layer object.
 * @param pe The recent input event as stored in pe struct.
 * @param event_info Original input event pointer.
 * @param event_type Type of original input event.
 * @param g_type what Gesture we are testing.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_n_line_test(Evas_Object *obj,
             Pointer_Event *pe,
             void *event_info,
             Evas_Callback_Type event_type,
             Elm_Gesture_Type g_type)
{
   unsigned cnt;
   Line_Type *st;
   Eina_List *list;
   Gesture_Info *gesture;
   Line_Data *line = NULL;

   if (!pe)
     return;

   ELM_GESTURE_LAYER_DATA_GET(obj, sd);

   gesture = sd->gesture[g_type];
   if (!gesture ) return;

   /* When continues enable = TRUE a gesture may START on MOVE event */
   /* We don't allow this to happen with the if-statement below.     */
   /* When continues enable = FALSE a gesture may START on DOWN only */
   /* Therefor it would NOT start on MOVE event.                     */
   /* NOTE that touched list is updated AFTER this function returns  */
   /* so (count == 0) when we get here on first touch on surface.    */
   if ((sd->glayer_continues_enable) && (!eina_list_count(sd->touched)))
     return;  /* Got move on mouse-over move */

   st = gesture->data;
   if (!st)
     {
        st = calloc(1, sizeof(Line_Type));
        if (!st) return;

        gesture->data = st;
     }

   list = st->list;
   cnt = eina_list_count(list);

   if (cnt) /* list is not empty, locate this device on list */
     {
        line = (Line_Data *)eina_list_search_unsorted
            (st->list, _line_device_compare, &pe->device);
     }

   if (!line) /* List is empty or device not found, new line-struct on
               * START only */
     {
        if ((event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
            (event_type == EVAS_CALLBACK_MULTI_DOWN) ||
            ((sd->glayer_continues_enable) &&   /* START on MOVE also */
             ((event_type == EVAS_CALLBACK_MOUSE_MOVE) ||
              /* Allocate new item on START only */
              (event_type == EVAS_CALLBACK_MULTI_MOVE))))
          {
             line = calloc(1, sizeof(Line_Data));
             _line_data_reset(line);
             list = eina_list_append(list, line);
             st->list = list;
          }
     }

   if (!line) /* This may happen on MOVE that comes before DOWN      */
     return;  /* No line-struct to work with, can't continue testing */

   /* update st with input */
   if (_single_line_process(&st->info, line, pe, event_type))
     _event_consume(sd, event_info, event_type, EVAS_EVENT_FLAG_NONE);

   /* Get direction and magnitude of the line */
   double angle;
   _vector_get(line->line_st.x, line->line_st.y, pe->x, pe->y,
               &line->line_length, &angle);

   /* These are used later to compare lines length */
   Evas_Coord shortest_line_len = line->line_length;
   Evas_Coord longest_line_len = line->line_length;
   Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;

   /* Now update line-state */
   if (line->t_st) /* Analyze line only if line started */
     {
        if (line->line_angle >= 0.0) /* if line direction was set, we
                                      * test if broke tolerance */
          {
             double a = fabs(angle - line->line_angle);
             /* Distance from line */
             double d = (tan(DEG2RAD(a))) * line->line_length;
             /* Broke tolerance: abort line and start a new one */
             if ((d > sd->line_distance_tolerance) ||
                 (a > sd->line_angular_tolerance))
               {
                  ev_flag = _state_set(gesture, ELM_GESTURE_STATE_ABORT,
                                       &st->info, EINA_FALSE);
                  _event_consume(sd, event_info, event_type, ev_flag);
                  return;
               }

             /* We may finish line if momentum is zero */
             if (sd->glayer_continues_enable)
               {
                  /* This is for continues-gesture */
                  /* Finish line on zero momentum for continues gesture */
                  if ((!st->info.momentum.mx) && (!st->info.momentum.my))
                    {
                       line->line_end.x = pe->x;
                       line->line_end.y = pe->y;
                       line->t_end = pe->timestamp;
                    }
               }
          }
        else
          {  /* Record the line angle as it broke minimum length for line */
            if (line->line_length >= sd->line_min_length)
              st->info.angle = line->line_angle = angle;
          }

        if (line->t_end)
          {
             if (line->line_angle < 0.0) /* it's not a line, too short
                                          * more close to a tap */
               {
                  ev_flag = _state_set(gesture, ELM_GESTURE_STATE_ABORT,
                                       &st->info, EINA_FALSE);
                  _event_consume(sd, event_info, event_type, ev_flag);
                  return;
               }
          }
     }

   /* Count how many lines already started / ended */
   int started = 0;
   int ended = 0;
   unsigned int tm_start = pe->timestamp;
   unsigned int tm_end = pe->timestamp;
   Eina_List *l;
   Line_Data *t_line;
   double base_angle = ELM_GESTURE_NEGATIVE_ANGLE;
   Eina_Bool lines_parallel = EINA_TRUE;
   EINA_LIST_FOREACH(list, l, t_line)
     {
        if (base_angle < 0)
          base_angle = t_line->line_angle;
        else
          {
             if (t_line->line_angle >= 0) /* Compare angle only with
                                           * lines with direction
                                           * defined */
               {
                  if (fabs(base_angle - t_line->line_angle) >
                      sd->line_angular_tolerance)
                    lines_parallel = EINA_FALSE;
               }
          }

        if (t_line->line_length) /* update only if this line is used */
          {
             if (shortest_line_len > t_line->line_length)
               shortest_line_len = t_line->line_length;

             if (longest_line_len < t_line->line_length)
               longest_line_len = t_line->line_length;
          }

        if (t_line->t_st)
          {
             started++;
             if (t_line->t_st < tm_start)
               tm_start = t_line->t_st;
          }

        if (t_line->t_end)
          {
             ended++;
             if (t_line->t_end < tm_end)
               tm_end = t_line->t_end;
          }
     }

   st->info.momentum.n = started;

   if (ended &&
       ((event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
        /* user lift one finger then starts again without line-end - ABORT */
        (event_type == EVAS_CALLBACK_MULTI_DOWN)))
     {
        ev_flag = _state_set(gesture, ELM_GESTURE_STATE_ABORT, &st->info,
                             EINA_FALSE);
        _event_consume(sd, event_info, event_type, ev_flag);
        return;
     }

   if (!lines_parallel) /* Lines are NOT at same direction, abort this
                         * gesture */
     {
        ev_flag = _state_set(gesture, ELM_GESTURE_STATE_ABORT, &st->info,
                             EINA_FALSE);
        _event_consume(sd, event_info, event_type, ev_flag);
        return;
     }

   /* We report ABORT if lines length are NOT matching when fingers are up */
   if ((longest_line_len - shortest_line_len) >
       (elm_config_finger_size_get() * 2))
     {
        ev_flag = _state_set(gesture, ELM_GESTURE_STATE_ABORT, &st->info,
                             EINA_FALSE);
        _event_consume(sd, event_info, event_type, ev_flag);
        return;
     }

   /* We consider FLICK as a fast line.ABORT if take too long to finish */
   if ((g_type == ELM_GESTURE_N_FLICKS) && ((tm_end - tm_start) >
                                            sd->flick_time_limit_ms))
     {
        ev_flag = _state_set(gesture, ELM_GESTURE_STATE_ABORT, &st->info,
                             EINA_FALSE);
        _event_consume(sd, event_info, event_type, ev_flag);
        return;
     }

   switch (event_type)
     {
      case EVAS_CALLBACK_MOUSE_UP:
      case EVAS_CALLBACK_MULTI_UP:
        if ((started) && (started == ended))
          {
             ev_flag = _state_set(gesture, ELM_GESTURE_STATE_END,
                                  &st->info, EINA_FALSE);
             _event_consume(sd, event_info, event_type, ev_flag);
          }

        return;

      case EVAS_CALLBACK_MOUSE_DOWN:
      case EVAS_CALLBACK_MULTI_DOWN:
      case EVAS_CALLBACK_MOUSE_MOVE:
      case EVAS_CALLBACK_MULTI_MOVE:
        if (started)
          {
             /* For continues gesture */
             if (sd->glayer_continues_enable && (started == ended))
               {
                  ev_flag = _state_set(gesture, ELM_GESTURE_STATE_END,
                                       &st->info, EINA_FALSE);
                  _event_consume(sd, event_info, event_type, ev_flag);
               }
             else
               {   /* When continues, may START on MOVE event too */
                 Elm_Gesture_State s = ELM_GESTURE_STATE_MOVE;

                 /* This happens when: on n > 1 lines then one finger up */
                 /* caused abort, then put finger down.                  */
                 /* This will stop line from starting again.             */
                 /* Number of lines, MUST match touched-device in list   */
                 if ((!sd->glayer_continues_enable) &&
                     (eina_list_count(st->list) <
                      eina_list_count(sd->touched)))
                   s = ELM_GESTURE_STATE_ABORT;

                 if (gesture->state == ELM_GESTURE_STATE_UNDEFINED)
                   s = ELM_GESTURE_STATE_START;

                 ev_flag = _state_set(gesture, s, &st->info, EINA_TRUE);
                 _event_consume(sd, event_info, event_type, ev_flag);
               }
          }
        break;

      default:
        return;   /* Unhandeld event type */
     }
}

/**
 * @internal
 *
 * This function is used to check if rotation gesture started.
 * @param st Contains current rotation values from user input.
 * @return TRUE/FALSE if we need to set rotation START.
 *
 * @ingroup Elm_Gesture_Layer
 */
static Eina_Bool
_on_rotation_broke_tolerance(Rotate_Type *st)
{
   if (st->info.base_angle < 0)
     return EINA_FALSE;  /* Angle has to be computed first */

   if (st->rotate_angular_tolerance < 0)
     return EINA_TRUE;

   double low = st->info.base_angle - st->rotate_angular_tolerance;
   double high = st->info.base_angle + st->rotate_angular_tolerance;
   double t = st->info.angle;

   if (low < 0)
     {
        low += 180;
        high += 180;

        if (t < 180)
          t += 180;
        else
          t -= 180;
     }

   if (high > 360)
     {
        low -= 180;
        high -= 180;

        if (t < 180)
          t += 180;
        else
          t -= 180;
     }

   if ((t < low) || (t > high)) /* This marks that rotation action has
                                 * started */
     {
        st->rotate_angular_tolerance = ELM_GESTURE_NEGATIVE_ANGLE;
        st->info.base_angle = st->info.angle; /* Avoid jump in angle value */
        return EINA_TRUE;
     }

   return EINA_FALSE;
}

/**
 * @internal
 *
 * This function is used for computing the gap between fingers.
 * It returns the length and center point between fingers.
 *
 * @param xx1 first finger x location.
 * @param yy1 first finger y location.
 * @param xx2 second finger x location.
 * @param yy2 second finger y location.
 * @param x  Get center point x cord (output)
 * @param y  Get center point y cord (output)
 *
 * @return length of the line between (xx1,yy1), (xx2,yy2) in pixels.
 *
 * @ingroup Elm_Gesture_Layer
 */
static Evas_Coord
_finger_gap_length_get(Evas_Coord xx1,
                       Evas_Coord yy1,
                       Evas_Coord xx2,
                       Evas_Coord yy2,
                       Evas_Coord *x,
                       Evas_Coord *y)
{
   double a, b, xx, yy, gap;
   xx = abs(xx2 - xx1);
   yy = abs(yy2 - yy1);
   gap = sqrt((xx * xx) + (yy * yy));

   /* START - Compute zoom center point */
   /* The triangle defined as follows:
    *             B
    *           / |
    *          /  |
    *     gap /   | a
    *        /    |
    *       A-----C
    *          b
    * http://en.wikipedia.org/wiki/Trigonometric_functions
    *************************************/
   if (((int)xx) && ((int)yy))
     {
        double A = atan((yy / xx));
        a = (Evas_Coord)((gap / 2) * sin(A));
        b = (Evas_Coord)((gap / 2) * cos(A));
        *x = (Evas_Coord)((xx2 > xx1) ? (xx1 + b) : (xx2 + b));
        *y = (Evas_Coord)((yy2 > yy1) ? (yy1 + a) : (yy2 + a));
     }
   else
     {
        if ((int)xx) /* horiz line, take half width */
          {
             *x = (Evas_Coord)((xx1 + xx2) / 2);
             *y = (Evas_Coord)(yy1);
          }

        if ((int)yy) /* vert line, take half width */
          {
             *x = (Evas_Coord)(xx1);
             *y = (Evas_Coord)((yy1 + yy2) / 2);
          }
     }
   /* END   - Compute zoom center point */

   return (Evas_Coord)gap;
}

/**
 * @internal
 *
 * This function is used for computing zoom value.
 *
 * @param st Pointer to zoom data based on user input.
 * @param tm_end Recent input event timestamp.
 * @param zoom_val Current computed zoom value.
 *
 * @return zoom momentum
 *
 * @ingroup Elm_Gesture_Layer
 */
static double
_zoom_momentum_get(Zoom_Type *st,
                   unsigned int tm_end,
                   double zoom_val)
{
   unsigned int tm_total;
   if (!st->m_st_tm) /* Init, and we don't start computing momentum yet */
     {
        st->m_st_tm = st->m_prev_tm = tm_end;
        st->m_base = zoom_val;
        return 0.0;
     }

   if ((tm_end - ELM_GESTURE_MOMENTUM_DELAY) < st->m_st_tm)
     return 0.0;  /* we don't start to compute momentum yet */

   if (st->dir) /* if direction was already defined, check if changed */
     {
        if (((st->dir < 0) && (zoom_val > st->info.zoom)) ||
            /* Direction changed, reset momentum */
            ((st->dir > 0) && (zoom_val < st->info.zoom)))
          {
             st->m_st_tm = 0;
             st->dir = (-st->dir);
             return 0.0;
          }
     }
   else
     st->dir = (zoom_val > st->info.zoom) ? 1 : -1;  /* init */

   if ((tm_end - ELM_GESTURE_MOMENTUM_TIMEOUT) > st->m_prev_tm)
     {
        st->m_st_tm = 0; /* Rest momentum when waiting too long */
        return 0.0;
     }

   st->m_prev_tm = tm_end;
   tm_total = tm_end - st->m_st_tm;

   if (tm_total)
     return ((zoom_val - st->m_base) * 1000) / tm_total;
   else
     return 0.0;
}

/**
 * @internal
 *
 * This function is used for computing zoom value.
 *
 * @param st Pointer to zoom data based on user input.
 * @param xx1 first finger x location.
 * @param yy1 first finger y location.
 * @param xx2 second finger x location.
 * @param yy2 second finger y location.
 * @param factor zoom-factor, used to determine how fast zoom works.
 *
 * @return zoom value, when 1.0 means no zoom, 0.5 half size...
 *
 * @ingroup Elm_Gesture_Layer
 */
static double
_zoom_compute(Zoom_Type *st,
              Evas_Coord xx1,
              Evas_Coord yy1,
              Evas_Coord xx2,
              Evas_Coord yy2,
              double zoom_finger_factor)
{
   double rt = 1.0;
   unsigned int tm_end = (st->zoom_mv.timestamp > st->zoom_mv1.timestamp) ?
     st->zoom_mv.timestamp : st->zoom_mv1.timestamp;

   Evas_Coord diam = _finger_gap_length_get(xx1, yy1, xx2, yy2,
                                            &st->info.x, &st->info.y);

   st->info.radius = diam / 2;

   if (!st->zoom_base)
     {
        st->zoom_base = diam;
        return st->info.zoom;
     }

   if (st->zoom_distance_tolerance) /* zoom tolerance <> ZERO, means
                                    * zoom action NOT started yet */
     {
        /* avoid jump with zoom value when break tolerance */
        if (diam < (st->zoom_base - st->zoom_distance_tolerance))
          {
             st->zoom_base -= st->zoom_distance_tolerance;
             st->zoom_distance_tolerance = 0;
          }

        /* avoid jump with zoom value when break tolerance */
        if (diam > (st->zoom_base + st->zoom_distance_tolerance))
          {
             st->zoom_base += st->zoom_distance_tolerance;
             st->zoom_distance_tolerance = 0;
          }

        return rt;
     }

   /* We use factor only on the difference between gap-base   */
   /* if gap=120, base=100, we get ((120-100)/100)=0.2*factor */
   rt = ((1.0) + ((((float)diam - (float)st->zoom_base) /
                   (float)st->zoom_base) * zoom_finger_factor));

   /* Momentum: zoom per second: */
   st->info.momentum = _zoom_momentum_get(st, tm_end, rt);

   return rt;
}

/**
 * @internal
 *
 * This function handles zoom with mouse wheel.
 * that's a combination of wheel + CTRL key.
 * @param obj The gesture-layer object.
 * @param event_info Original input event pointer.
 * @param event_type Type of original input event.
 * @param g_type what Gesture we are testing.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_zoom_with_wheel_test(Evas_Object *obj,
                      void *event_info,
                      Evas_Callback_Type event_type,
                      Elm_Gesture_Type g_type)
{
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);

   if (!sd->gesture[g_type]) return;

   Gesture_Info *gesture_zoom = sd->gesture[g_type];
   Zoom_Type *st = gesture_zoom->data;
   Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;
   if (!st) /* Allocated once on first time, used for zoom intermediate data */
     {
        st = calloc(1, sizeof(Zoom_Type));
        if (!st) return;

        gesture_zoom->data = st;
        _zoom_test_reset(gesture_zoom);
     }

   switch (event_type)
     {
      case EVAS_CALLBACK_KEY_UP:
      {
         Evas_Event_Key_Up *p = event_info;
         if ((!strcmp(p->key, "Control_L")) ||
             /* Test if we ended a zoom gesture when releasing CTRL */
             (!strcmp(p->key, "Control_R")))
           {
              if ((st->zoom_wheel) &&
                  ((gesture_zoom->state == ELM_GESTURE_STATE_START) ||
                   /* User released CTRL after zooming */
                   (gesture_zoom->state == ELM_GESTURE_STATE_MOVE)))
                {
                   st->info.momentum = _zoom_momentum_get
                       (st, p->timestamp, st->info.zoom);

                   ev_flag = _state_set
                       (gesture_zoom, ELM_GESTURE_STATE_END, &st->info,
                       EINA_FALSE);
                   _event_consume(sd, event_info, event_type, ev_flag);

                   return;
                }
           }
         break;
      }

      case EVAS_CALLBACK_MOUSE_WHEEL:
      {
         Eina_Bool force;
         Elm_Gesture_State s;
         if (!evas_key_modifier_is_set(
               ((Evas_Event_Mouse_Wheel *)event_info)->modifiers,
               "Control")) /* if using wheel without CTRL after starting zoom */
           {
              if ((st->zoom_wheel) &&
                  ((gesture_zoom->state == ELM_GESTURE_STATE_START) ||
                   (gesture_zoom->state == ELM_GESTURE_STATE_MOVE)))
                {
                   ev_flag = _state_set
                       (gesture_zoom, ELM_GESTURE_STATE_END, &st->info,
                       EINA_FALSE);
                   _event_consume(sd, event_info, event_type, ev_flag);

                   return;
                }
              else
                return;  /* Ignore mouse-wheel without control */
           }

         /* Using mouse wheel with CTRL for zoom */
         /* (zoom_wheel == NULL) and (zoom_distance_tolerance == 0) we
          * continue a zoom gesture */
         if (st->zoom_wheel || (st->zoom_distance_tolerance == 0))
           {
              force = EINA_TRUE;
              s = ELM_GESTURE_STATE_MOVE;
           }
         else
           { /* On first wheel event, report START */
             force = EINA_FALSE;
             s = ELM_GESTURE_STATE_START;
             if (!efl_canvas_object_key_grab(sd->target, "Control_L",
                                             EFL_INPUT_MODIFIER_CONTROL, 0,
                                             EINA_FALSE))
               ERR("Failed to grab left Ctrl");
             if (!efl_canvas_object_key_grab(sd->target, "Control_R",
                                             EFL_INPUT_MODIFIER_CONTROL, 0,
                                             EINA_FALSE))
               ERR("Failed to grab right Ctrl");
           }

         st->zoom_distance_tolerance = 0; /* Cancel tolerance */
         st->zoom_wheel = (Evas_Event_Mouse_Wheel *)event_info;
         st->info.x = st->zoom_wheel->canvas.x;
         st->info.y = st->zoom_wheel->canvas.y;

         if (st->zoom_wheel->z < 0) /* zoom in */
           st->info.zoom += (sd->zoom_finger_factor * sd->zoom_wheel_factor);

         if (st->zoom_wheel->z > 0) /* zoom out */
           st->info.zoom -= (sd->zoom_finger_factor * sd->zoom_wheel_factor);

         st->info.momentum = _zoom_momentum_get
             (st, st->zoom_wheel->timestamp, st->info.zoom);

         ev_flag = _state_set(gesture_zoom, s, &st->info, force);
         _event_consume(sd, event_info, event_type, ev_flag);
         break;
      }

      default:
        return;
     }
}

/**
 * @internal
 *
 * This function is used to test zoom gesture.
 * user may combine zoom, rotation together.
 * so its possible that both will be detected from input.
 * (both are two-finger movement-oriented gestures)
 *
 * @param obj The gesture-layer object.
 * @param event_info Pointer to recent input event.
 * @param event_type Recent input event type.
 * @param g_type what Gesture we are testing.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_zoom_test(Evas_Object *obj,
           Pointer_Event *pe,
           void *event_info,
           Evas_Callback_Type event_type,
           Elm_Gesture_Type g_type)
{
   /* Test for wheel zoom. */
   _zoom_with_wheel_test(obj, event_info, event_type, ELM_GESTURE_ZOOM);

   if (!_elm_config->glayer_zoom_finger_enable)
     return;

   if (!pe)
     return;
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);

   if (!sd->gesture[g_type]) return;

   Gesture_Info *gesture_zoom = sd->gesture[g_type];
   Zoom_Type *st = gesture_zoom->data;

   if (!st) /* Allocated once on first time, used for zoom data */
     {
        st = calloc(1, sizeof(Zoom_Type));
        if (!st) return;

        gesture_zoom->data = st;
        _zoom_test_reset(gesture_zoom);
     }

   /* Start - new zoom testing, letting all fingers start */
   Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;
   switch (event_type)
     {
      case EVAS_CALLBACK_MOUSE_MOVE:
      case EVAS_CALLBACK_MULTI_MOVE:
        /* if non-continues mode and gesture NOT started, ignore MOVE */
        if ((!sd->glayer_continues_enable) &&
            (!st->zoom_st.timestamp))
          return;
        EINA_FALLTHROUGH;
        // fallthrough is intentional
      case EVAS_CALLBACK_MOUSE_DOWN:
      case EVAS_CALLBACK_MULTI_DOWN:
      { /* Here we take care of zoom-start and zoom move */
        Eina_List *l;
        Pointer_Event *p;

        if (eina_list_count(sd->touched) > 2) /* Process zoom only
                                               * when 2 fingers on
                                               * surface */
          {
             ev_flag = _state_set
                 (gesture_zoom, ELM_GESTURE_STATE_ABORT, &st->info,
                 EINA_FALSE);
             _event_consume(sd, event_info, event_type, ev_flag);

             return;
          }

        if (!st->zoom_st.timestamp) /* Now scan touched-devices list
                                     * and find other finger */
          {
             EINA_LIST_FOREACH(sd->touched, l, p)
               { /* Device of other finger <> pe device */
                 if (p->device != pe->device)
                   break;
               }

             if (!p) /* Single finger on touch */
               return;

             /* Record down fingers */
             _event_consume(sd, event_info, event_type, ev_flag);
             memcpy(&st->zoom_st, pe, sizeof(Pointer_Event));
             memcpy(&st->zoom_st1, p, sizeof(Pointer_Event));

             /* Set mv field as well to be ready for MOVE events  */
             memcpy(&st->zoom_mv, pe, sizeof(Pointer_Event));
             memcpy(&st->zoom_mv1, p, sizeof(Pointer_Event));

             /* Here we have zoom_st, zoom_st1 set, report START  */
             /* Set zoom-base after BOTH down events  recorded    */
             /* Compute length of line between fingers zoom start */
             st->info.zoom = 1.0;
             st->zoom_base = _finger_gap_length_get
                 (st->zoom_st1.x, st->zoom_st1.y, st->zoom_st.x, st->zoom_st.y,
                 &st->info.x, &st->info.y);

             st->info.radius = st->zoom_base / 2;

             if ((gesture_zoom->state != ELM_GESTURE_STATE_START) &&
                 /* zoom started with mouse-wheel, don't report twice */
                 (gesture_zoom->state != ELM_GESTURE_STATE_MOVE))
               {
                  ev_flag = _state_set
                      (gesture_zoom, ELM_GESTURE_STATE_START, &st->info,
                      EINA_FALSE);
                  _event_consume(sd, event_info, event_type, ev_flag);
               }

             return; /* Zoom started */
          } /* End of ZOOM_START handling */

        /* if we got here, we have (exactly) two fingers on surface */
        /* we also after START, report MOVE */
        /* First detect which finger moved  */
        if (pe->device == st->zoom_mv.device)
          memcpy(&st->zoom_mv, pe, sizeof(Pointer_Event));
        else if (pe->device == st->zoom_mv1.device)
          memcpy(&st->zoom_mv1, pe, sizeof(Pointer_Event));

        /* Compute change in zoom as fingers move */
        st->info.zoom = _zoom_compute(st,
                                      st->zoom_mv.x, st->zoom_mv.y,
                                      st->zoom_mv1.x, st->zoom_mv1.y,
                                      sd->zoom_finger_factor);

        if (!st->zoom_distance_tolerance) /* Zoom broke tolerance,
                                           * report move */
          {
             double d = st->info.zoom - st->next_step;
             if (d < 0.0)
               d = (-d);

             if (d >= sd->zoom_step) /* Report move in steps */
               {
                  st->next_step = st->info.zoom;

                  ev_flag = _state_set(gesture_zoom,
                                       ELM_GESTURE_STATE_MOVE,
                                       &st->info, EINA_TRUE);
                  _event_consume(sd, event_info, event_type, ev_flag);
               }
          } /* End of ZOOM_MOVE handling */

        return;
      }

      case EVAS_CALLBACK_MOUSE_UP:
      case EVAS_CALLBACK_MULTI_UP:
        /* Reset timestamp of finger-up.This is used later
           by _zoom_test_reset() to retain finger-down data */
        _event_consume(sd, event_info, event_type, ev_flag);
        if (((st->zoom_wheel) || (st->zoom_base)) &&
            (st->zoom_distance_tolerance == 0))
          {
             ev_flag = _state_set(gesture_zoom, ELM_GESTURE_STATE_END,
                                  &st->info, EINA_FALSE);
             _event_consume(sd, event_info, event_type, ev_flag);

             return;
          }

        /* if we got here not a ZOOM */
        /* Must be != undefined, if gesture started */
        if (gesture_zoom->state != ELM_GESTURE_STATE_UNDEFINED)
          {
             ev_flag = _state_set
                 (gesture_zoom, ELM_GESTURE_STATE_ABORT, &st->info,
                 EINA_FALSE);
             _event_consume(sd, event_info, event_type, ev_flag);
          }

        _zoom_test_reset(gesture_zoom);

        return;

      default:
        return;
     }
}

static void
_rotate_properties_get(Rotate_Type *st,
                       Evas_Coord xx1,
                       Evas_Coord yy1,
                       Evas_Coord xx2,
                       Evas_Coord yy2,
                       double *angle)
{
   /* FIXME: Fix momentum computation, it's wrong */
   double prev_angle = *angle;

   st->info.radius = _finger_gap_length_get(xx1, yy1, xx2, yy2,
                                            &st->info.x, &st->info.y) / 2;

   *angle = _angle_get(xx1, yy1, xx2, yy2);

   if (angle == &st->info.angle) /* Fingers are moving, compute momentum */
     {
        unsigned int tm_start =
          (st->rotate_st.timestamp > st->rotate_st1.timestamp)
          ?  st->rotate_st.timestamp : st->rotate_st1.timestamp;
        unsigned int tm_end =
          (st->rotate_mv.timestamp > st->rotate_mv1.timestamp)
          ? st->rotate_mv.timestamp : st->rotate_mv1.timestamp;

        unsigned int tm_total = tm_end - tm_start;
        if (tm_total) /* Momentum computed as:
                         accumulated rotation angle (deg) divided by time */
          {
             double m = 0;
             if (((prev_angle < 90) && ((*angle) > 270)) ||
                 /* We circle passing ZERO point */
                 ((prev_angle > 270) && ((*angle) < 90)))
               {
                  prev_angle = (*angle);
               }
             else m = prev_angle - (*angle);

             st->accum_momentum += m;

             if ((tm_end - st->prev_momentum_tm) < 100)
               st->prev_momentum += m;
             else
               {
                  if (fabs(st->prev_momentum) < 0.002)
                    st->accum_momentum = 0.0;  /* reset momentum */

                  st->prev_momentum = 0.0; /* Start again    */
               }

             st->prev_momentum_tm = tm_end;
             st->info.momentum = (st->accum_momentum * 1000) / tm_total;
          }
     }
   else
     st->info.momentum = 0;
}

/**
 * @internal
 *
 * This function is used to test rotation gesture.
 * user may combine zoom, rotation together.
 * so its possible that both will be detected from input.
 * (both are two-finger movement-oriented gestures)
 *
 * @param obj The gesture-layer object.
 * @param event_info Pointer to recent input event.
 * @param event_type Recent input event type.
 * @param g_type what Gesture we are testing.
 *
 * @ingroup Elm_Gesture_Layer
 */
static void
_rotate_test(Evas_Object *obj,
             Pointer_Event *pe,
             void *event_info,
             Evas_Callback_Type event_type,
             Elm_Gesture_Type g_type)
{
   Evas_Event_Flags ev_flag = EVAS_EVENT_FLAG_NONE;
   Gesture_Info *gesture;
   Rotate_Type *st = NULL;

   if (EINA_DBL_EQ(_elm_config->glayer_rotate_finger_enable, 0))
     return;

   if (!pe)
     return;

   ELM_GESTURE_LAYER_DATA_GET(obj, sd);

   if (!sd->gesture[g_type]) return;

   gesture = sd->gesture[g_type];
   if (!gesture) return ;

   st = gesture->data;
   if (!st) /* Allocated once on first time */
     {
       st = calloc(1, sizeof(Rotate_Type));
       if (!st) return;

       gesture->data = st;
       _rotate_test_reset(gesture);
     }

   switch (event_type)
     {
      case EVAS_CALLBACK_MOUSE_MOVE:
      case EVAS_CALLBACK_MULTI_MOVE:
        /* if non-continues mode and gesture NOT started, ignore MOVE */
        if ((!sd->glayer_continues_enable) &&
            (!st->rotate_st.timestamp))
          return;
        EINA_FALLTHROUGH;
        // fallthrough is intentional
      case EVAS_CALLBACK_MOUSE_DOWN:
      case EVAS_CALLBACK_MULTI_DOWN:
      { /* Here we take care of rotate-start and rotate move */
        Eina_List *l;
        Pointer_Event *p;

        if (eina_list_count(sd->touched) > 2) /* Process rotate only
                                               * when 2 fingers on
                                               * surface */
          {
             ev_flag = _state_set
                 (gesture, ELM_GESTURE_STATE_ABORT, &st->info, EINA_FALSE);
             _event_consume(sd, event_info, event_type, ev_flag);

             return;
          }

        if (!st->rotate_st.timestamp) /* Now scan touched-devices list
                                       * and find other finger */
          {
             EINA_LIST_FOREACH(sd->touched, l, p)
               { /* Device of other finger <> pe device */
                 if (p->device != pe->device)
                   break;
               }

             if (!p)
               return;  /* Single finger on touch */

             /* Record down fingers */
             _event_consume(sd, event_info, event_type, ev_flag);
             memcpy(&st->rotate_st, pe, sizeof(Pointer_Event));
             memcpy(&st->rotate_st1, p, sizeof(Pointer_Event));

             /* Set mv field as well to be ready for MOVE events  */
             memcpy(&st->rotate_mv, pe, sizeof(Pointer_Event));
             memcpy(&st->rotate_mv1, p, sizeof(Pointer_Event));

             /* Here we have rotate_st, rotate_st1 set, report START  */
             /* Set rotate-base after BOTH down events  recorded    */
             /* Compute length of line between fingers rotate start */
             _rotate_properties_get(st,
                                    st->rotate_st.x, st->rotate_st.y,
                                    st->rotate_st1.x, st->rotate_st1.y,
                                    &st->info.base_angle);

             ev_flag = _state_set(gesture, ELM_GESTURE_STATE_START,
                                  &st->info, EINA_FALSE);
             _event_consume(sd, event_info, event_type, ev_flag);

             return; /* Rotate started */
          } /* End of ROTATE_START handling */

        /* if we got here, we have (exactly) two fingers on surface */
        /* we also after START, report MOVE */
        /* First detect which finger moved  */
        if (pe->device == st->rotate_mv.device)
          memcpy(&st->rotate_mv, pe, sizeof(Pointer_Event));
        else if (pe->device == st->rotate_mv1.device)
          memcpy(&st->rotate_mv1, pe, sizeof(Pointer_Event));

        /* Compute change in rotate as fingers move */
        _rotate_properties_get(st,
                               st->rotate_mv.x, st->rotate_mv.y,
                               st->rotate_mv1.x, st->rotate_mv1.y,
                               &st->info.angle);

        if (_on_rotation_broke_tolerance(st)) /* Rotation broke
                                               * tolerance, report
                                               * move */
          {
             double d = st->info.angle - st->next_step;
             if (d < 0)
               d = (-d);

             if (d >= sd->rotate_step) /* Report move in steps */
               {
                  st->next_step = st->info.angle;

                  ev_flag = _state_set
                      (gesture, ELM_GESTURE_STATE_MOVE, &st->info, EINA_TRUE);
                  _event_consume(sd, event_info, event_type, ev_flag);
               }
          } /* End of ROTATE_MOVE handling */

        return;
      }

      case EVAS_CALLBACK_MOUSE_UP:
      case EVAS_CALLBACK_MULTI_UP:
        _event_consume(sd, event_info, event_type, ev_flag);
        /* Reset timestamp of finger-up.This is used later
           by rotate_test_reset() to retain finger-down data */
        if (st->rotate_angular_tolerance < 0)
          {
             ev_flag = _state_set(gesture, ELM_GESTURE_STATE_END,
                                  &st->info, EINA_FALSE);
             _event_consume(sd, event_info, event_type, ev_flag);

             return;
          }

        /* Must be != undefined, if gesture started */
        if (gesture->state != ELM_GESTURE_STATE_UNDEFINED)
          {
             ev_flag = _state_set(gesture, ELM_GESTURE_STATE_ABORT,
                                  &st->info, EINA_FALSE);
             _event_consume(sd, event_info, event_type, ev_flag);
          }

        _rotate_test_reset(gesture);
        return;

      default:
        return;
     }
}

EOLIAN static void
_elm_gesture_layer_efl_ui_widget_disabled_set(Eo *obj, Elm_Gesture_Layer_Data *_pd EINA_UNUSED, Eina_Bool disabled)
{
   efl_ui_widget_disabled_set(efl_super(obj, MY_CLASS), disabled);

   if (efl_ui_widget_disabled_get(obj))
     _callbacks_unregister(obj);
   else
     _callbacks_register(obj);
}

EOLIAN static void
_elm_gesture_layer_efl_canvas_group_group_add(Eo *obj, Elm_Gesture_Layer_Data *priv)
{
   efl_canvas_group_add(efl_super(obj, MY_CLASS));

   priv->line_min_length =
     _elm_config->glayer_line_min_length * elm_config_finger_size_get();
   priv->zoom_distance_tolerance = _elm_config->glayer_zoom_distance_tolerance
     * elm_config_finger_size_get();
   priv->line_distance_tolerance = _elm_config->glayer_line_distance_tolerance
     * elm_config_finger_size_get();
   priv->zoom_finger_factor = _elm_config->glayer_zoom_finger_factor;
   /* mouse wheel zoom steps */
   priv->zoom_wheel_factor = _elm_config->glayer_zoom_wheel_factor;
   priv->rotate_angular_tolerance =
     _elm_config->glayer_rotate_angular_tolerance;
   priv->line_angular_tolerance = _elm_config->glayer_line_angular_tolerance;
   priv->flick_time_limit_ms = _elm_config->glayer_flick_time_limit_ms;
   priv->long_tap_start_timeout = _elm_config->glayer_long_tap_start_timeout;
   priv->repeat_events = EINA_TRUE;
   priv->glayer_continues_enable = _elm_config->glayer_continues_enable;
   priv->tap_finger_size = _elm_config->glayer_tap_finger_size;

   /* FIXME: Hack to get around old configs - if too small, enlarge. */
   if (_elm_config->glayer_double_tap_timeout < 0.00001)
     _elm_config->glayer_double_tap_timeout = 0.25;
   priv->double_tap_timeout = _elm_config->glayer_double_tap_timeout;

   memset(priv->gesture, 0, sizeof(priv->gesture));
}

static void _cbs_clean(Elm_Gesture_Layer_Data *sd, Elm_Gesture_Type idx, Elm_Gesture_State cb_type);

EOLIAN static void
_elm_gesture_layer_efl_canvas_group_group_del(Eo *obj, Elm_Gesture_Layer_Data *sd)
{
   Pointer_Event *data;
   int i;

   /* Clear all gestures intermediate data, stop any timers */
   {
      /* FIXME: +1 because of the mistake in the enum. */
      Gesture_Info **gitr = sd->gesture + 1;
      Tests_Array_Funcs *fitr = _glayer_tests_array + 1;
      for (; fitr->reset; fitr++, gitr++)
        {
           if (IS_TESTED_GESTURE(*gitr))
             fitr->reset(*gitr);
        }
   }

   /* First Free all gestures internal data structures */
   for (i = 0; i < ELM_GESTURE_LAST; i++)
     if (sd->gesture[i])
       {
          if (sd->gesture[i]->data)
            free(sd->gesture[i]->data);

          _cbs_clean(sd, i, ELM_GESTURE_STATE_START);
          _cbs_clean(sd, i, ELM_GESTURE_STATE_MOVE);
          _cbs_clean(sd, i, ELM_GESTURE_STATE_END);
          _cbs_clean(sd, i, ELM_GESTURE_STATE_ABORT);
          free(sd->gesture[i]);
          sd->gesture[i] = NULL; /* Referenced by _event_history_clear */
       }
   ecore_timer_del(sd->gest_taps_timeout);

   /* Then take care of clearing events */
   _event_history_clear(obj, EINA_FALSE);
   sd->pending = eina_list_free(sd->pending);

   EINA_LIST_FREE(sd->touched, data)
     free(data);

   if (!elm_widget_disabled_get(obj))
     _callbacks_unregister(obj);

   efl_canvas_group_del(efl_super(obj, MY_CLASS));
}

EAPI Evas_Object *
elm_gesture_layer_add(Evas_Object *parent)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(parent, NULL);
   return elm_legacy_add(MY_CLASS, parent);
}

EOLIAN static Eo *
_elm_gesture_layer_efl_object_constructor(Eo *obj, Elm_Gesture_Layer_Data *_pd EINA_UNUSED)
{
   obj = efl_constructor(efl_super(obj, MY_CLASS));
   efl_canvas_object_type_set(obj, MY_CLASS_NAME_LEGACY);

   return obj;
}

EOLIAN static Eina_Bool
_elm_gesture_layer_hold_events_get(const Eo *obj EINA_UNUSED, Elm_Gesture_Layer_Data *sd)
{
   return !sd->repeat_events;
}

EOLIAN static void
_elm_gesture_layer_hold_events_set(Eo *obj EINA_UNUSED, Elm_Gesture_Layer_Data *sd, Eina_Bool hold_events)
{
   sd->repeat_events = !(!!hold_events);
}

EOLIAN static double
_elm_gesture_layer_zoom_step_get(const Eo *obj EINA_UNUSED, Elm_Gesture_Layer_Data *sd)
{
   return sd->zoom_step;
}

EOLIAN static void
_elm_gesture_layer_zoom_step_set(Eo *obj EINA_UNUSED, Elm_Gesture_Layer_Data *sd, double step)
{
   if (step < 0) return;

   sd->zoom_step = step;
}

EOLIAN static double
_elm_gesture_layer_rotate_step_get(const Eo *obj EINA_UNUSED, Elm_Gesture_Layer_Data *sd)
{
   return sd->rotate_step;
}

EOLIAN static void
_elm_gesture_layer_rotate_step_set(Eo *obj EINA_UNUSED, Elm_Gesture_Layer_Data *sd, double step)
{
   if (step < 0) return;

   sd->rotate_step = step;
}

EOLIAN static Eina_Bool
_elm_gesture_layer_attach(Eo *obj, Elm_Gesture_Layer_Data *sd, Evas_Object *target)
{
   if (!target) return EINA_FALSE;

   /* if was attached before, unregister callbacks first */
   if (sd->target)
     _callbacks_unregister(obj);

   sd->target = target;

   _callbacks_register(obj);
   return EINA_TRUE;
}

static void
_cbs_clean(Elm_Gesture_Layer_Data *sd,
          Elm_Gesture_Type idx,
          Elm_Gesture_State cb_type)
{
   if (!sd->gesture[idx]) return;

   Func_Data *cb_info;
   EINA_INLIST_FREE(sd->gesture[idx]->cbs[cb_type], cb_info)
     {
        sd->gesture[idx]->cbs[cb_type] = eina_inlist_remove(
              sd->gesture[idx]->cbs[cb_type], EINA_INLIST_GET(cb_info));
        free(cb_info);
     }
   SET_TEST_BIT(sd->gesture[idx]);
}

EOLIAN static void
_elm_gesture_layer_cb_set(Eo *obj, Elm_Gesture_Layer_Data *sd, Elm_Gesture_Type idx, Elm_Gesture_State cb_type, Elm_Gesture_Event_Cb cb, void *data)
{
   /* Clear gesture intermediate data, stop any timers */
   if (IS_TESTED_GESTURE(sd->gesture[idx]))
     _glayer_tests_array[idx].reset(sd->gesture[idx]);

   _cbs_clean(sd, idx, cb_type); // for ABI compat.
   elm_obj_gesture_layer_cb_add(obj, idx, cb_type, cb, data);
}

EOLIAN static void
_elm_gesture_layer_cb_add(Eo *obj, Elm_Gesture_Layer_Data *sd, Elm_Gesture_Type idx, Elm_Gesture_State cb_type, Elm_Gesture_Event_Cb cb, void *data)
{
   if (!cb) return;

   Gesture_Info *p;

   if (!sd->gesture[idx])
     sd->gesture[idx] = calloc(1, sizeof(Gesture_Info));
   if (!sd->gesture[idx]) return;

   Func_Data *cb_info = calloc(1, sizeof(*cb_info));
   if (!cb_info) return;
   cb_info->cb = cb;
   cb_info->user_data = data;

   p = sd->gesture[idx];
   p->obj = obj;
   p->g_type = idx;
   p->cbs[cb_type] = eina_inlist_append(p->cbs[cb_type],
         EINA_INLIST_GET(cb_info));
   p->state = ELM_GESTURE_STATE_UNDEFINED;
   SET_TEST_BIT(p);
}

EOLIAN static void
_elm_gesture_layer_cb_del(Eo *obj EINA_UNUSED, Elm_Gesture_Layer_Data *sd, Elm_Gesture_Type idx, Elm_Gesture_State cb_type, Elm_Gesture_Event_Cb cb, void *data)
{
   if (!sd->gesture[idx]) return;

   Eina_Inlist *itr;
   Func_Data *cb_info;
   EINA_INLIST_FOREACH_SAFE(sd->gesture[idx]->cbs[cb_type], itr, cb_info)
     {
        if (cb_info->cb == cb && cb_info->user_data == data)
          {
             /* Clear gesture intermediate data, stop any timers */
             if (IS_TESTED_GESTURE(sd->gesture[idx]))
                _glayer_tests_array[idx].reset(sd->gesture[idx]);

             sd->gesture[idx]->cbs[cb_type] = eina_inlist_remove(
                   sd->gesture[idx]->cbs[cb_type], EINA_INLIST_GET(cb_info));
             free(cb_info);
             SET_TEST_BIT(sd->gesture[idx]);
             return;
          }
     }
}

EAPI void
elm_gesture_layer_line_min_length_set(Evas_Object *obj, int line_min_length)
{
   ELM_GESTURE_LAYER_CHECK(obj);
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   sd->line_min_length = line_min_length;
}

EAPI int
elm_gesture_layer_line_min_length_get(const Evas_Object *obj)
{
   ELM_GESTURE_LAYER_CHECK(obj) 0;
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   return sd->line_min_length;
}

EAPI void
elm_gesture_layer_zoom_distance_tolerance_set(Evas_Object *obj, Evas_Coord zoom_distance_tolerance)
{
   ELM_GESTURE_LAYER_CHECK(obj);
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   sd->zoom_distance_tolerance = zoom_distance_tolerance;
}

EAPI Evas_Coord
elm_gesture_layer_zoom_distance_tolerance_get(const Evas_Object *obj)
{
   ELM_GESTURE_LAYER_CHECK(obj) 0;
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   return sd->zoom_distance_tolerance;
}

EAPI void
elm_gesture_layer_line_distance_tolerance_set(Evas_Object *obj, Evas_Coord line_distance_tolerance)
{
   ELM_GESTURE_LAYER_CHECK(obj);
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   sd->line_distance_tolerance = line_distance_tolerance;
}

EAPI Evas_Coord
elm_gesture_layer_line_distance_tolerance_get(const Evas_Object *obj)
{
   ELM_GESTURE_LAYER_CHECK(obj) 0;
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   return sd->line_distance_tolerance;
}

EAPI void
elm_gesture_layer_line_angular_tolerance_set(Evas_Object *obj, double line_angular_tolerance)
{
   ELM_GESTURE_LAYER_CHECK(obj);
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   sd->line_angular_tolerance = line_angular_tolerance;
}

EAPI double
elm_gesture_layer_line_angular_tolerance_get(const Evas_Object *obj)
{
   ELM_GESTURE_LAYER_CHECK(obj) 0.0;
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   return sd->line_angular_tolerance;
}

EAPI void
elm_gesture_layer_zoom_wheel_factor_set(Evas_Object *obj, double zoom_wheel_factor)
{
   ELM_GESTURE_LAYER_CHECK(obj);
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   sd->zoom_wheel_factor = zoom_wheel_factor;
}

EAPI double
elm_gesture_layer_zoom_wheel_factor_get(const Evas_Object *obj)
{
   ELM_GESTURE_LAYER_CHECK(obj) 0.0;
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   return sd->zoom_wheel_factor;
}

EAPI void
elm_gesture_layer_zoom_finger_factor_set(Evas_Object *obj, double zoom_finger_factor)
{
   ELM_GESTURE_LAYER_CHECK(obj);
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   sd->zoom_finger_factor = zoom_finger_factor;
}

EAPI double
elm_gesture_layer_zoom_finger_factor_get(const Evas_Object *obj)
{
   ELM_GESTURE_LAYER_CHECK(obj) 0.0;
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   return sd->zoom_finger_factor;
}

EAPI void
elm_gesture_layer_rotate_angular_tolerance_set(Evas_Object *obj, double rotate_angular_tolerance)
{
   ELM_GESTURE_LAYER_CHECK(obj);
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   sd->rotate_angular_tolerance = rotate_angular_tolerance;
}

EAPI double
elm_gesture_layer_rotate_angular_tolerance_get(const Evas_Object *obj)
{
   ELM_GESTURE_LAYER_CHECK(obj) 0.0;
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   return sd->rotate_angular_tolerance;
}

EAPI void
elm_gesture_layer_flick_time_limit_ms_set(Evas_Object *obj, unsigned int flick_time_limit_ms)
{
   ELM_GESTURE_LAYER_CHECK(obj);
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   sd->flick_time_limit_ms = flick_time_limit_ms;
}

EAPI unsigned int
elm_gesture_layer_flick_time_limit_ms_get(const Evas_Object *obj)
{
   ELM_GESTURE_LAYER_CHECK(obj) 0;
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   return sd->flick_time_limit_ms;
}

EAPI void
elm_gesture_layer_long_tap_start_timeout_set(Evas_Object *obj, double long_tap_start_timeout)
{
   ELM_GESTURE_LAYER_CHECK(obj);
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   sd->long_tap_start_timeout = ((long_tap_start_timeout > 0) ? long_tap_start_timeout : 0);
}

EAPI double
elm_gesture_layer_long_tap_start_timeout_get(const Evas_Object *obj)
{
   ELM_GESTURE_LAYER_CHECK(obj) 0.0;
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   return sd->long_tap_start_timeout;
}

EAPI void
elm_gesture_layer_continues_enable_set(Evas_Object *obj, Eina_Bool continues_enable)
{
   ELM_GESTURE_LAYER_CHECK(obj);
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   sd->glayer_continues_enable = continues_enable;
}

EAPI Eina_Bool
elm_gesture_layer_continues_enable_get(const Evas_Object *obj)
{
   ELM_GESTURE_LAYER_CHECK(obj) 0.0;
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   return sd->glayer_continues_enable;
}

EAPI void
elm_gesture_layer_double_tap_timeout_set(Evas_Object *obj, double double_tap_timeout)
{
   ELM_GESTURE_LAYER_CHECK(obj);
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   sd->double_tap_timeout = ((double_tap_timeout > 0) ? double_tap_timeout : 0);
}

EAPI double
elm_gesture_layer_double_tap_timeout_get(const Evas_Object *obj)
{
   ELM_GESTURE_LAYER_CHECK(obj) 0.0;
   ELM_GESTURE_LAYER_DATA_GET(obj, sd);
   return sd->double_tap_timeout;
}

EOLIAN static void
_elm_gesture_layer_tap_finger_size_set(Eo *obj EINA_UNUSED, Elm_Gesture_Layer_Data *sd, Evas_Coord sz)
{
   if (sz < 0)
      sz = 0;  /* Should not be negative, will reset to system value */

   sd->tap_finger_size = sz;
}

EOLIAN static Evas_Coord
_elm_gesture_layer_tap_finger_size_get(const Eo *obj EINA_UNUSED, Elm_Gesture_Layer_Data *sd)
{
   return sd->tap_finger_size;
}

static void
_elm_gesture_layer_class_constructor(Efl_Class *klass)
{
   evas_smart_legacy_type_register(MY_CLASS_NAME_LEGACY, klass);
}

/* Internal EO APIs and hidden overrides */

#define ELM_GESTURE_LAYER_EXTRA_OPS \
   EFL_CANVAS_GROUP_ADD_DEL_OPS(elm_gesture_layer)

#include "elm_gesture_layer_eo.c"
