#include "evas_common_private.h"
#include "evas_private.h"
#include "efl_canvas_event_grabber.eo.h"

#define MY_CLASS EFL_CANVAS_EVENT_GRABBER_CLASS
#define MY_CLASS_NAME "Efl_Object_Event_Grabber"
#define MY_CLASS_NAME_LEGACY "evas_object_event_grabber"


struct _Efl_Object_Event_Grabber_Data
{
   Eo *rect;
   Eina_List *contained;

   Eina_Bool vis : 1;
   Eina_Bool freeze : 1;
   Eina_Bool restack : 1;
};

typedef struct Efl_Object_Event_Grabber_Iterator
{
   Eina_Iterator iterator;

   Eina_Iterator *itl;
   Eo *parent;
} Efl_Object_Event_Grabber_Iterator;


static Eina_Bool
_efl_canvas_group_group_iterator_next(Efl_Object_Event_Grabber_Iterator *it, void **data)
{
   return eina_iterator_next(it->itl, data);
}

static Evas_Object *
_efl_canvas_group_group_iterator_get_container(Efl_Object_Event_Grabber_Iterator *it)
{
   return it->parent;
}

static void
_efl_canvas_group_group_iterator_free(Efl_Object_Event_Grabber_Iterator *it)
{
   efl_unref(it->parent);
   free(it);
}

EOLIAN static Eina_Iterator*
_efl_canvas_event_grabber_efl_canvas_group_group_members_iterate(const Eo *eo_obj, Efl_Object_Event_Grabber_Data *pd)
{
   Efl_Object_Event_Grabber_Iterator *it;

   if (!pd->contained) return NULL;

   it = calloc(1, sizeof(Efl_Object_Event_Grabber_Iterator));
   if (!it) return NULL;

   EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR);
   it->parent = efl_ref(eo_obj);
   it->itl = eina_list_iterator_new(pd->contained);

   it->iterator.next = FUNC_ITERATOR_NEXT(_efl_canvas_group_group_iterator_next);
   it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER(_efl_canvas_group_group_iterator_get_container);
   it->iterator.free = FUNC_ITERATOR_FREE(_efl_canvas_group_group_iterator_free);

   return &it->iterator;
}

EOLIAN static Eina_Bool
_efl_canvas_event_grabber_efl_canvas_group_group_member_is(const Eo *eo_obj, Efl_Object_Event_Grabber_Data *pd EINA_UNUSED, const Eo *sub_obj)
{
   Evas_Object_Protected_Data *obj = efl_data_scope_get(eo_obj, EFL_CANVAS_OBJECT_CLASS);
   Evas_Object_Protected_Data *sub = efl_data_scope_safe_get(sub_obj, EFL_CANVAS_OBJECT_CLASS);

   evas_object_async_block(obj);

   if (!sub) return EINA_FALSE;
   return (sub->events->parent == eo_obj);
}

static void
_stacking_verify(Efl_Object_Event_Grabber_Data *pd, Evas_Object_Protected_Data *obj)
{
   Eo *grabber;
   Evas_Object_Protected_Data *gobj, *i;

   grabber = efl_parent_get(pd->rect);
   gobj = efl_data_scope_get(grabber, EFL_CANVAS_OBJECT_CLASS);
   if (obj->layer->layer > gobj->layer->layer)
     {
        CRI("Cannot stack child object above event grabber object!");
        return;
     }
   if (obj->layer != gobj->layer) return;

   EINA_INLIST_REVERSE_FOREACH(EINA_INLIST_GET(gobj->layer->objects), i)
     {
        if (i == gobj) break;
        if (i == obj)
          {
             CRI("Cannot stack child object above event grabber object!");
             return;
          }
     }
}

static void
_child_insert(Efl_Object_Event_Grabber_Data *pd, Evas_Object_Protected_Data *obj)
{
   Evas_Object_Protected_Data *a, *i;
   Eina_List *l;
   Eina_Bool found = EINA_FALSE;

   if (pd->vis) _stacking_verify(pd, obj);

   EINA_LIST_REVERSE_FOREACH(pd->contained, l, a)
     {
        if (a->object == pd->rect)
          {
             found = EINA_TRUE;
             break;
          }
        if (a->layer->layer > obj->layer->layer) continue;
        if (a->layer->layer < obj->layer->layer)
          {
             /* new object is higher layer than 'a' */
             found = EINA_TRUE;
             break;
          }
        EINA_INLIST_FOREACH(EINA_INLIST_GET(a->layer->objects), i)
          {
             if (obj == i)
               {
                  /* new object is below 'a' */
                  pd->contained = eina_list_prepend_relative(pd->contained, obj, a);
                  return;
               }
             else if (a == i)
               {
                  /* new object is above 'a' */
                  found = EINA_TRUE;
                  break;
               }
          }
        if (found) break;
     }

   if (found)
     pd->contained = eina_list_append_relative(pd->contained, obj, a);
   else
     pd->contained = eina_list_prepend(pd->contained, obj);
}

static void
_full_restack(Efl_Object_Event_Grabber_Data *pd)
{
   Evas_Object_Protected_Data *obj;
   Evas_Object_Protected_Data *root = NULL;
   Eina_List *list = NULL;
   EINA_LIST_FREE(pd->contained, obj)
     {
        if (obj->object == pd->rect)
          {
             root = obj;
             continue;
          }
        list = eina_list_append(list, obj);
     }

   pd->contained = eina_list_append(pd->contained, root);
   EINA_LIST_FREE(list, obj)
     _child_insert(pd, obj);
   pd->restack = 0;
}

static void
_efl_canvas_object_event_grabber_child_restack(void *data, const Efl_Event *event)
{
   Efl_Object_Event_Grabber_Data *pd = data;
   Evas_Object_Protected_Data *obj = efl_data_scope_get(event->object, EFL_CANVAS_OBJECT_CLASS);

   if (pd->vis && pd->freeze)
     {
        pd->restack = 1;
        return;
     }
   pd->contained = eina_list_remove(pd->contained, obj);
   _child_insert(pd, obj);
}

static void
_efl_canvas_object_event_grabber_child_invalidate(void *data, const Efl_Event *event)
{
   Efl_Object_Event_Grabber_Data *pd = data;

   efl_canvas_group_member_remove(efl_parent_get(pd->rect), event->object);
}

EOLIAN static void
_efl_canvas_event_grabber_efl_canvas_group_group_member_add(Eo *eo_obj, Efl_Object_Event_Grabber_Data *pd, Eo *eo_sub)
{
   Evas_Object_Protected_Data *sub = efl_data_scope_safe_get(eo_sub, EFL_CANVAS_OBJECT_CLASS);
   Evas_Object_Protected_Data *obj = efl_data_scope_get(eo_obj, EFL_CANVAS_OBJECT_CLASS);

   EINA_SAFETY_ON_NULL_RETURN(sub);
   EINA_SAFETY_ON_NULL_RETURN(obj);

   if (eo_sub != pd->rect)
     {
        if (sub->delete_me)
          {
             CRI("Can not add deleted member %p to event grabber %p", eo_sub, eo_obj);
             return;
          }
        if (obj->delete_me)
          {
             CRI("Can not add object %p to deleted event grabber %p", eo_sub, eo_obj);
             return;
          }
        if (!obj->layer)
          {
             CRI("Can not add object %p to event grabber %p: event grabber has "
                 "no associated canvas.", eo_sub, eo_obj);
             return;
          }
        if (!sub->layer)
          {
             CRI("Can not add object %p to event grabber %p: member has "
                 "no associated canvas.", eo_sub, eo_obj);
             return;
          }
        if ((sub->layer && obj->layer) &&
            (sub->layer->evas != obj->layer->evas))
          {
             CRI("Can not add object %p to event grabber %p: objects belong to "
                 "different canvases.", eo_sub, eo_obj);
             return;
          }
     }
   if (sub->events->parent == eo_obj) return;

   if (sub->smart.parent || sub->events->parent) evas_object_smart_member_del(eo_sub);
   EINA_COW_WRITE_BEGIN(evas_object_events_cow, sub->events, Evas_Object_Events_Data, events)
     events->parent = eo_obj;
   EINA_COW_WRITE_END(evas_object_events_cow, sub->events, events);
   _child_insert(pd, sub);
   efl_event_callback_add(eo_sub, EFL_EVENT_INVALIDATE, _efl_canvas_object_event_grabber_child_invalidate, pd);
   if (eo_sub != pd->rect)
     efl_event_callback_add(eo_sub, EFL_GFX_ENTITY_EVENT_STACKING_CHANGED, _efl_canvas_object_event_grabber_child_restack, pd);
}

EOLIAN static void
_efl_canvas_event_grabber_efl_canvas_group_group_member_remove(Eo *eo_obj EINA_UNUSED, Efl_Object_Event_Grabber_Data *pd, Eo *member)
{
   Evas_Object_Protected_Data *obj = efl_data_scope_get(member, EFL_CANVAS_OBJECT_CLASS);

   efl_event_callback_del(member, EFL_EVENT_INVALIDATE, _efl_canvas_object_event_grabber_child_invalidate, pd);
   efl_event_callback_del(member, EFL_GFX_ENTITY_EVENT_STACKING_CHANGED, _efl_canvas_object_event_grabber_child_restack, pd);
   pd->contained = eina_list_remove(pd->contained, obj);

   EINA_COW_WRITE_BEGIN(evas_object_events_cow, obj->events, Evas_Object_Events_Data, events)
     events->parent = NULL;
   EINA_COW_WRITE_END(evas_object_events_cow, obj->events, events);
}

EOLIAN static void
_efl_canvas_event_grabber_efl_canvas_group_group_change(Eo *eo_obj EINA_UNUSED, Efl_Object_Event_Grabber_Data *pd EINA_UNUSED)
{}

EOLIAN static void
_efl_canvas_event_grabber_efl_canvas_group_group_calculate(Eo *eo_obj EINA_UNUSED, Efl_Object_Event_Grabber_Data *pd EINA_UNUSED)
{}

EOLIAN static void
_efl_canvas_event_grabber_efl_canvas_group_group_need_recalculate_set(Eo *eo_obj EINA_UNUSED, Efl_Object_Event_Grabber_Data *pd EINA_UNUSED, Eina_Bool set EINA_UNUSED)
{}

EOLIAN static Eina_Bool
_efl_canvas_event_grabber_efl_canvas_group_group_need_recalculate_get(const Eo *eo_obj EINA_UNUSED, Efl_Object_Event_Grabber_Data *pd EINA_UNUSED)
{
   return EINA_FALSE;
}

EOLIAN static void
_efl_canvas_event_grabber_efl_gfx_entity_position_set(Eo *eo_obj, Efl_Object_Event_Grabber_Data *pd, Eina_Position2D pos)
{
   efl_gfx_entity_position_set(efl_super(eo_obj, MY_CLASS), pos);
   efl_gfx_entity_position_set(pd->rect, pos);
}

EOLIAN static void
_efl_canvas_event_grabber_efl_gfx_entity_size_set(Eo *eo_obj, Efl_Object_Event_Grabber_Data *pd, Eina_Size2D sz)
{
   efl_gfx_entity_size_set(efl_super(eo_obj, MY_CLASS), sz);
   efl_gfx_entity_size_set(pd->rect, sz);
}

EOLIAN static Eina_Bool
_efl_canvas_event_grabber_efl_gfx_entity_visible_get(const Eo *eo_obj EINA_UNUSED, Efl_Object_Event_Grabber_Data *pd)
{
   return pd->vis;
}

EOLIAN static void
_efl_canvas_event_grabber_efl_gfx_entity_visible_set(Eo *eo_obj EINA_UNUSED, Efl_Object_Event_Grabber_Data *pd, Eina_Bool set)
{
   if (set)
     {
        Evas_Object_Protected_Data *obj;
        Eina_List *l;

        EINA_LIST_FOREACH(pd->contained, l, obj)
          if (obj->object != pd->rect) _stacking_verify(pd, obj);
     }
   pd->vis = !!set;
   efl_gfx_entity_visible_set(pd->rect, set);
   if (pd->restack && pd->freeze && (!set))
     _full_restack(pd);
}

EOLIAN static void
_efl_canvas_event_grabber_efl_gfx_stack_layer_set(Eo *eo_obj, Efl_Object_Event_Grabber_Data *pd, short l)
{
   efl_gfx_stack_layer_set(efl_super(eo_obj, MY_CLASS), l);
   efl_gfx_stack_layer_set(pd->rect, l);
}

static void
_efl_canvas_object_event_grabber_restack(void *data, const Efl_Event *event)
{
   Efl_Object_Event_Grabber_Data *pd = data;

   evas_object_layer_set(pd->rect, evas_object_layer_get(event->object));
   evas_object_stack_below(pd->rect, event->object);

   pd->restack = 1;
   if (pd->vis && pd->freeze) return;
   _full_restack(pd);
}

EOLIAN static Eo *
_efl_canvas_event_grabber_efl_object_constructor(Eo *eo_obj, Efl_Object_Event_Grabber_Data *pd)
{
   Evas_Object_Protected_Data *obj;

   efl_canvas_group_clipped_set(eo_obj, EINA_FALSE);
   eo_obj = efl_constructor(efl_super(eo_obj, MY_CLASS));
   efl_canvas_object_type_set(eo_obj, MY_CLASS_NAME_LEGACY);
   obj = efl_data_scope_get(eo_obj, EFL_CANVAS_OBJECT_CLASS);
   obj->is_event_parent = 1;
   obj->is_smart = 0;

   efl_event_callback_add(eo_obj, EFL_GFX_ENTITY_EVENT_STACKING_CHANGED, _efl_canvas_object_event_grabber_restack, pd);
   pd->rect = evas_object_rectangle_add(efl_parent_get(eo_obj));
   evas_object_pointer_mode_set(pd->rect, EVAS_OBJECT_POINTER_MODE_NOGRAB);
   efl_parent_set(pd->rect, eo_obj);
   efl_canvas_group_member_add(eo_obj, pd->rect);
   evas_object_color_set(pd->rect, 0, 0, 0, 0);
   return eo_obj;
}

EOLIAN static void
_efl_canvas_event_grabber_efl_object_destructor(Eo *eo_obj, Efl_Object_Event_Grabber_Data *pd)
{
   Evas_Object_Protected_Data *obj;
   Eina_List *l, *ln;

   EINA_LIST_FOREACH_SAFE(pd->contained, l, ln, obj)
     efl_canvas_group_member_remove(eo_obj, obj->object);
   efl_canvas_group_del(eo_obj);
   efl_destructor(efl_super(eo_obj, MY_CLASS));
}

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

const Eina_List *
evas_object_event_grabber_members_list(const Eo *eo_obj)
{
   Efl_Object_Event_Grabber_Data *pd = efl_data_scope_get(eo_obj, MY_CLASS);
   return pd->contained;
}

EOLIAN static void
_efl_canvas_event_grabber_freeze_when_visible_set(Eo *eo_obj EINA_UNUSED, Efl_Object_Event_Grabber_Data *pd, Eina_Bool set)
{
   set = !!set;
   if (pd->freeze == set) return;
   pd->freeze = set;
   if (pd->vis && pd->restack && (!set))
     _full_restack(pd);
}

EOLIAN static Eina_Bool
_efl_canvas_event_grabber_freeze_when_visible_get(const Eo *eo_obj EINA_UNUSED, Efl_Object_Event_Grabber_Data *pd)
{
   return pd->freeze;
}

EAPI Evas_Object *
evas_object_event_grabber_add(Evas *eo_e)
{
   eo_e = evas_find(eo_e);
   EINA_SAFETY_ON_FALSE_RETURN_VAL(efl_isa(eo_e, EVAS_CANVAS_CLASS), NULL);
   return efl_add(MY_CLASS, eo_e, efl_canvas_object_legacy_ctor(efl_added));
}

#include "efl_canvas_event_grabber.eo.c"
#include "efl_canvas_event_grabber_eo.legacy.c"
