#include "e_efx_private.h"

#define PAN_FUNC_CHECK Smart_Data *sd; sd = evas_object_smart_data_get(obj); if ((!obj) || (!sd) || (evas_object_type_get(obj) && strcmp(evas_object_type_get(obj), "e_efx_pan")))
#define PAN_CB_SETUP Smart_Data *sd; sd = evas_object_smart_data_get(obj); if (!sd) return

typedef struct Smart_Data
{
   E_EFX *e;
   Evas_Object *smart_obj;
   Evas_Object *child_obj;
   Evas_Coord   x, y, w, h;
   Evas_Coord   child_w, child_h, px, py, dx, dy;
} Smart_Data;

typedef struct E_Efx_Pan_Data
{
   E_EFX *e;
   Evas_Object *pan;
   Ecore_Animator *anim;
   E_Efx_Effect_Speed speed;
   Evas_Point change;
   Evas_Point current;
   int degrees;
   E_Efx_End_Cb cb;
   void *data;
} E_Efx_Pan_Data;

static void _smart_pan_child_set(Evas_Object *obj, Evas_Object *child);

/* local subsystem functions */
static void _smart_child_del_hook(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _smart_child_resize_hook(void *data, Evas *e, Evas_Object *obj, void *event_info);

static void _smart_reconfigure(Smart_Data *sd);
static void _smart_add(Evas_Object *obj);
static void _smart_del(Evas_Object *obj);
static void _smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void _smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void _smart_show(Evas_Object *obj);
static void _smart_hide(Evas_Object *obj);
static void _smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void _smart_clip_set(Evas_Object *obj, Evas_Object * clip);
static void _smart_clip_unset(Evas_Object *obj);
static void _smart_init(void);

/* local subsystem globals */
static Evas_Smart *_smart = NULL;

/* local subsystem functions */
static void
_smart_child_del_hook(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Smart_Data *sd;

   sd = data;
   sd->child_obj = NULL;
}

static void
_smart_child_resize_hook(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Smart_Data *sd;
   Evas_Coord w, h;

   sd = data;
   evas_object_geometry_get(sd->child_obj, NULL, NULL, &w, &h);
   if ((w != sd->child_w) || (h != sd->child_h))
     {
        sd->child_w = w;
        sd->child_h = h;
        _smart_reconfigure(sd);
     }
}

static void
_smart_reconfigure(Smart_Data *sd)
{
   Eina_List *l;
   E_EFX *e;
   Evas_Coord x, y;

   evas_object_move(sd->child_obj, sd->x - sd->px, sd->y - sd->py);
   e_efx_maps_apply(sd->e, sd->child_obj, NULL, E_EFX_MAPS_APPLY_ALL);
   //DBG("DELTA: (%d,%d)", sd->dx, sd->dy);
   EINA_LIST_FOREACH(sd->e->followers, l, e)
     {
        evas_object_geometry_get(e->obj, &x, &y, NULL, NULL);
        evas_object_move(e->obj, x - sd->dx, y - sd->dy);
        e_efx_maps_apply(e, e->obj, NULL, E_EFX_MAPS_APPLY_ALL);
//        _size_debug(e->obj);
     }
}

static void
_smart_add(Evas_Object *obj)
{
   Smart_Data *sd;

   sd = calloc(1, sizeof(Smart_Data));
   if (!sd) return;
   sd->smart_obj = obj;
   sd->x = 0;
   sd->y = 0;
   sd->w = 0;
   sd->h = 0;
   evas_object_smart_data_set(obj, sd);
}

static void
_smart_del(Evas_Object *obj)
{
   PAN_CB_SETUP;
   _smart_pan_child_set(obj, NULL);
   free(sd);
}

static void
_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   PAN_CB_SETUP;
   sd->x = x;
   sd->y = y;
   _smart_reconfigure(sd);
}

static void
_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   PAN_CB_SETUP;
   sd->w = w;
   sd->h = h;
   _smart_reconfigure(sd);
}

static void
_smart_show(Evas_Object *obj)
{
   PAN_CB_SETUP;
   if (sd->child_obj)
     evas_object_show(sd->child_obj);
}

static void
_smart_hide(Evas_Object *obj)
{
   PAN_CB_SETUP;
   if (sd->child_obj)
     evas_object_hide(sd->child_obj);
}

static void
_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   PAN_CB_SETUP;
   if (sd->child_obj)
     evas_object_color_set(sd->child_obj, r, g, b, a);
}

static void
_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   PAN_CB_SETUP;
   if (sd->child_obj)
     evas_object_clip_set(sd->child_obj, clip);
}

static void
_smart_clip_unset(Evas_Object *obj)
{
   PAN_CB_SETUP;
   if (sd->child_obj)
     evas_object_clip_unset(sd->child_obj);
}

/* never need to touch this */

static void
_smart_init(void)
{
   if (_smart) return;
   {
      static const Evas_Smart_Class sc =
        {
           "e_efx_pan",
           EVAS_SMART_CLASS_VERSION,
           _smart_add,
           _smart_del,
           _smart_move,
           _smart_resize,
           _smart_show,
           _smart_hide,
           _smart_color_set,
           _smart_clip_set,
           _smart_clip_unset,
           NULL,
           NULL,
           NULL,
           NULL,
           NULL,
           NULL,
           NULL
        };
      _smart = evas_smart_class_new(&sc);
   }
}


static Evas_Object *
_smart_pan_add(Evas *evas)
{
   _smart_init();
   return evas_object_smart_add(evas, _smart);
}

static void
_smart_pan_child_set(Evas_Object *obj, Evas_Object *child)
{
   PAN_FUNC_CHECK return;
   if (child == sd->child_obj) return;
   if (sd->child_obj)
     {
        evas_object_clip_unset(sd->child_obj);
        evas_object_smart_member_del(sd->child_obj);
        evas_object_event_callback_del_full(sd->child_obj, EVAS_CALLBACK_FREE, _smart_child_del_hook, sd);
        evas_object_event_callback_del_full(sd->child_obj, EVAS_CALLBACK_RESIZE, _smart_child_resize_hook, sd);
        sd->child_obj = NULL;
     }
   if (child)
     {
        Evas_Coord w, h;
        int r, g, b, a;

        sd->child_obj = child;
        evas_object_smart_member_add(sd->child_obj, sd->smart_obj);
        evas_object_geometry_get(sd->child_obj, NULL, NULL, &w, &h);
        sd->child_w = w;
        sd->child_h = h;
        evas_object_event_callback_add(child, EVAS_CALLBACK_FREE, _smart_child_del_hook, sd);
        evas_object_event_callback_add(child, EVAS_CALLBACK_RESIZE, _smart_child_resize_hook, sd);
        evas_object_color_get(sd->smart_obj, &r, &g, &b, &a);
        evas_object_color_set(sd->child_obj, r, g, b, a);
        evas_object_clip_set(sd->child_obj, evas_object_clip_get(sd->smart_obj));
        if (evas_object_visible_get(sd->smart_obj)) evas_object_show(sd->child_obj);
        else evas_object_hide(sd->child_obj);
        _smart_reconfigure(sd);
     }
}

static void
_smart_pan_set(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   PAN_FUNC_CHECK return;
   if ((x == sd->px) && (y == sd->py)) return;
   sd->dx = x - sd->px;
   sd->dy = y - sd->py;
   sd->px = x;
   sd->py = y;
   _smart_reconfigure(sd);
}

static void
_smart_pan_get(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y)
{
   PAN_FUNC_CHECK return;
   if (x) *x = sd->px;
   if (y) *y = sd->py;
}

/*
static void
_smart_pan_max_get(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y)
{
   PAN_FUNC_CHECK return;
   if (x)
     {
        if (sd->w < sd->child_w) *x = sd->child_w - sd->w;
        else *x = 0;
     }
   if (y)
     {
        if (sd->h < sd->child_h) *y = sd->child_h - sd->h;
        else *y = 0;
     }
}
*/

static void
_obj_del(E_Efx_Pan_Data *epd, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   if (epd->anim) ecore_animator_del(epd->anim);
   if (epd->pan)
     {
        evas_object_del(epd->pan);
        epd->pan = NULL;
     }
   epd->e->pan_data = NULL;
   if ((!epd->e->owner) && (!epd->e->followers)) e_efx_free(epd->e);
   free(epd);
}

static Eina_Bool
_pan_cb(E_Efx_Pan_Data *epd, double pos)
{
   int x, y, px = 0, py = 0;
   double pct;

   pct = ecore_animator_pos_map(pos, (Ecore_Pos_Map)epd->speed, 0, 0);
   x = lround(pct * (double)epd->change.x) - epd->current.x;
   y = lround(pct * (double)epd->change.y) - epd->current.y;
   _smart_pan_get(epd->pan, &px, &py);
   //DBG("PAN: (%d,%d) += (%d,%d)", px, py, x, y);
   _smart_pan_set(epd->pan, px + x, py + y);
   epd->e->map_data.pan.x = px + x;
   epd->e->map_data.pan.y = py + y;

   epd->current.x += x;
   epd->current.y += y;
   if (pos < 1.0) return EINA_TRUE;

   epd->anim = NULL;
   E_EFX_QUEUE_CHECK(epd);
   return EINA_TRUE;
}

static E_EFX *
_e_efx_pan_init(Evas_Object *obj)
{
   E_EFX *e;
   E_Efx_Pan_Data *epd;
   Smart_Data *sd;

   EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
   e = evas_object_data_get(obj, "e_efx-data");
   if (!e) e = e_efx_new(obj);
   EINA_SAFETY_ON_NULL_RETURN_VAL(e, NULL);

   if (!e->pan_data)
     {
        e->pan_data = calloc(1, sizeof(E_Efx_Pan_Data));
        EINA_SAFETY_ON_NULL_RETURN_VAL(e->pan_data, NULL);
        evas_object_event_callback_add(obj, EVAS_CALLBACK_FREE, (Evas_Object_Event_Cb)_obj_del, e->pan_data);
        epd = e->pan_data;
        epd->pan = _smart_pan_add(evas_object_evas_get(obj));
        sd = evas_object_smart_data_get(epd->pan);
        sd->e = e;
        _smart_pan_child_set(epd->pan, obj);
        evas_object_show(epd->pan);
     }
   return e;
}

EAPI Eina_Bool
e_efx_pan_init(Evas_Object *obj)
{
   return !!_e_efx_pan_init(obj);
}

EAPI Eina_Bool
e_efx_pan(Evas_Object *obj, E_Efx_Effect_Speed speed, const Evas_Point *distance, double total_time, E_Efx_End_Cb cb, const void *data)
{
   E_EFX *e;
   E_Efx_Pan_Data *epd;
   Evas_Coord x = 0, y = 0;

   if (!distance) return EINA_FALSE;
   if (total_time < 0.0) return EINA_FALSE;
   if (speed > E_EFX_EFFECT_SPEED_SINUSOIDAL) return EINA_FALSE;

   e = _e_efx_pan_init(obj);
   if (!e) return EINA_FALSE;
   epd = e->pan_data;
   epd->e = e;
   _smart_pan_get(epd->pan, &x, &y);
   INF("pan: %p - (%d,%d) += (%d,%d) over %gs: %s", obj, x, y, distance->x, distance->y, total_time, e_efx_speed_str[speed]);
   if (eina_dbl_exact(total_time, 0))
     {
        _smart_pan_set(epd->pan, x + distance->x, y + distance->y);
        return EINA_TRUE;
     }

   epd->speed = speed;
   epd->change.x = distance->x;
   epd->change.y = distance->y;
   epd->current.x = epd->current.y = 0;
   epd->cb = cb;
   epd->data = (void*)data;
   if (epd->anim) ecore_animator_del(epd->anim);
   epd->anim = ecore_animator_timeline_add(total_time, (Ecore_Timeline_Cb)_pan_cb, epd);
   return EINA_TRUE;
}
