#include "e_efx_private.h"

typedef struct E_Efx_Fade_Data
{
   E_EFX *e;
   E_Efx_Effect_Speed speed;
   Ecore_Animator *anim;
   Evas_Object *clip;
   E_Efx_Color start;
   E_Efx_Color color;
   unsigned char alpha[2];
   E_Efx_End_Cb cb;
   void *data;
} E_Efx_Fade_Data;


static void
_clip_setup(void *data EINA_UNUSED, Evas *evas EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_EFX *e;
   E_Efx_Fade_Data *efd;

   e = evas_object_data_get(obj, "e_efx-data");
   if (!e) return;
   efd = e->fade_data;
   e_efx_clip_setup(obj, efd->clip);
}

static void
_obj_del(E_Efx_Fade_Data *efd, Evas *evas EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   if (efd->anim) ecore_animator_del(efd->anim);
   evas_object_event_callback_del_full(efd->e->obj, EVAS_CALLBACK_RESIZE, (Evas_Object_Event_Cb)_clip_setup, efd);
   evas_object_event_callback_del_full(efd->e->obj, EVAS_CALLBACK_MOVE, (Evas_Object_Event_Cb)_clip_setup, efd);
   if (efd->clip)
     {
        Evas_Object *clip;

        clip = evas_object_clip_get(efd->clip);
        evas_object_clip_unset(efd->e->obj);
        evas_object_del(efd->clip);
        efd->clip = NULL;
        if (clip && (!obj)) //obj is only passed during actual del
          evas_object_clip_set(efd->e->obj, clip);
     }
   efd->e->fade_data = NULL;
   if ((!efd->e->owner) && (!efd->e->followers)) e_efx_free(efd->e);
   free(efd);
}

static Eina_Bool
_fade_cb(E_Efx_Fade_Data *efd, double pos)
{
   double factor;
   unsigned char r, g, b, a;

   if (pos < 1.0)
     {
        r = efd->start.r;
        g = efd->start.g;
        b = efd->start.b;
        a = efd->alpha[0];
        factor = ecore_animator_pos_map(pos, efd->speed, 0, 0);
        if (efd->color.r != efd->start.r)
          r -= lround(factor * ((int)efd->start.r - (int)efd->color.r));
        if (efd->color.g != efd->start.g)
          g -= lround(factor * ((int)efd->start.g - (int)efd->color.g));
        if (efd->color.b != efd->start.b)
          b -= lround(factor * ((int)efd->start.b - (int)efd->color.b));
        if (efd->alpha[0] != efd->alpha[1])
          a -= lround(factor * ((int)efd->alpha[0] - (int)efd->alpha[1]));
        evas_object_color_set(efd->clip, MIN(r, a), MIN(g, a), MIN(b, a), a);
//        _color_debug(efd->clip);
        return EINA_TRUE;
     }
   else
     /* lround will usually be off by 1 at the end, so we manually set this here */
     evas_object_color_set(efd->clip, MIN(efd->color.r, efd->alpha[1]), MIN(efd->color.g, efd->alpha[1]), MIN(efd->color.b, efd->alpha[1]), efd->alpha[1]);

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

static void
_fade_stop(Evas_Object *obj, Eina_Bool reset)
{
   E_EFX *e;
   E_Efx_Fade_Data *efd;

   e = evas_object_data_get(obj, "e_efx-data");
   if ((!e) || (!e->fade_data)) return;
   efd = e->fade_data;
   if (reset)
     {
        evas_object_event_callback_del_full(obj, EVAS_CALLBACK_FREE, (Evas_Object_Event_Cb)_obj_del, efd);
        evas_object_event_callback_del_full(obj, EVAS_CALLBACK_RESIZE, (Evas_Object_Event_Cb)_clip_setup, efd);
        evas_object_event_callback_del_full(obj, EVAS_CALLBACK_MOVE, (Evas_Object_Event_Cb)_clip_setup, efd);
        if (e_efx_queue_complete(efd->e, efd))
          e_efx_queue_process(efd->e);
        _obj_del(efd, NULL, NULL, NULL);
        INF("reset faded object %p", obj);
     }
   else
     {
        INF("stopped faded object %p", obj);
        if (efd->anim) ecore_animator_del(efd->anim);
        efd->anim = NULL;
        if (e_efx_queue_complete(efd->e, efd))
          e_efx_queue_process(efd->e);
     }
}

void
e_efx_fade_reclip(void *fade_data)
{
   E_Efx_Fade_Data *efd = fade_data;
   Evas_Object *clip;

   clip = evas_object_clip_get(efd->e->obj);
   if (clip == efd->clip) return;
   evas_object_clip_set(efd->e->obj, efd->clip);
   if (clip)
     evas_object_clip_set(efd->clip, clip);
   e_efx_clip_setup(efd->e->obj, efd->clip);
}

EAPI Eina_Bool
e_efx_fade(Evas_Object *obj, E_Efx_Effect_Speed speed, E_Efx_Color *ec, unsigned char alpha, double total_time, E_Efx_End_Cb cb, const void *data)
{
   E_EFX *e;
   E_Efx_Fade_Data *efd;
   int r, g, b, a;

   EINA_SAFETY_ON_NULL_RETURN_VAL(obj, EINA_FALSE);
   e = evas_object_data_get(obj, "e_efx-data");
   if (!e) e = e_efx_new(obj);
   EINA_SAFETY_ON_NULL_RETURN_VAL(e, EINA_FALSE);
   efd = e->fade_data;
   if (!efd)
     {
        e->fade_data = efd = calloc(1, sizeof(E_Efx_Fade_Data));
        EINA_SAFETY_ON_NULL_RETURN_VAL(efd, EINA_FALSE);
        evas_object_event_callback_add(obj, EVAS_CALLBACK_FREE, (Evas_Object_Event_Cb)_obj_del, e->fade_data);
        evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE, (Evas_Object_Event_Cb)_clip_setup, e->fade_data);
        evas_object_event_callback_add(obj, EVAS_CALLBACK_MOVE, (Evas_Object_Event_Cb)_clip_setup, e->fade_data);
        efd->clip = evas_object_rectangle_add(evas_object_evas_get(obj));
     }

   efd->e = e;
   e_efx_fade_reclip(efd);
   evas_object_show(efd->clip);
   efd->alpha[1] = alpha;
   efd->cb = cb;
   efd->data = (void*)data;
   evas_object_color_get(efd->clip, &r, &g, &b, &a);
   efd->start.r = r;
   efd->start.g = g;
   efd->start.b = b;
   efd->alpha[0] = a;
   if (ec)
     {
        efd->color.r = ec->r;
        efd->color.g = ec->g;
        efd->color.b = ec->b;
     }
   else efd->color = (E_Efx_Color){255, 255, 255};
   INF("fade: %p || %d/%d/%d/%d => %d/%d/%d/%d %s over %gs", obj, efd->start.r, efd->start.g, efd->start.b, efd->alpha[0], efd->color.r, efd->color.g, efd->color.b, efd->alpha[1], e_efx_speed_str[speed], total_time);
   if (efd->anim) ecore_animator_del(efd->anim);
   efd->anim = NULL;
   if (!eina_dbl_exact(total_time, 0))
     efd->anim = ecore_animator_timeline_add(total_time, (Ecore_Timeline_Cb)_fade_cb, efd);
   else
     _fade_cb(efd, 1.0);

   return EINA_TRUE;
}

EAPI void
e_efx_fade_reset(Evas_Object *obj)
{
   _fade_stop(obj, EINA_TRUE);
}

EAPI void
e_efx_fade_stop(Evas_Object *obj)
{
   _fade_stop(obj, EINA_FALSE);
}
