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

#define EFL_UI_FOCUS_COMPOSITION_PROTECTED
#define EFL_UI_FOCUS_OBJECT_PROTECTED

#include <Elementary.h>
#include "elm_priv.h"
#include "efl_ui_focus_composition_adapter.eo.h"

#define MY_CLASS EFL_UI_FOCUS_COMPOSITION_MIXIN

typedef struct {
   Eina_List *order, *targets_ordered;
   Eina_List *register_target, *registered_targets;
   Efl_Ui_Focus_Manager *registered, *old_manager;
   Eina_Bool dirty;
   Eina_Bool logical;
} Efl_Ui_Focus_Composition_Data;

static void
_state_apply(Eo *obj, Efl_Ui_Focus_Composition_Data *pd)
{
   Efl_Ui_Focus_Manager *manager;

   //Legacy code compatibility, only update the custom chain of elements if legacy was NOT messing with it.
   if (elm_widget_is_legacy(obj) && elm_object_focus_custom_chain_get(obj)) return;

   manager = pd->registered;

   if (manager)
     {
        Eina_List *n;
        Eina_List *safed = NULL;
        Efl_Ui_Focus_Object *o;

        pd->old_manager = manager;
        //remove all of them
        EINA_LIST_FREE(pd->registered_targets, o)
          {
             if (!!eina_list_data_find(pd->register_target, o))
               safed = eina_list_append(safed, o);
             else
               efl_ui_focus_manager_calc_unregister(manager, o);

             if (efl_isa(o, EFL_UI_FOCUS_COMPOSITION_ADAPTER_CLASS))
               {
                  efl_ui_focus_composition_adapter_focus_manager_parent_set(o, NULL);
                  efl_ui_focus_composition_adapter_focus_manager_object_set(o, NULL);
               }
          }
        pd->registered_targets = safed;

        EINA_LIST_FOREACH(pd->register_target, n, o)
          {
             if (!!eina_list_data_find(pd->registered_targets, o)) continue;

             if (!pd->logical)
               efl_ui_focus_manager_calc_register(manager, o, obj, NULL);
             else
               efl_ui_focus_manager_calc_register_logical(manager, o, obj, NULL);

             if (efl_isa(o, EFL_UI_FOCUS_COMPOSITION_ADAPTER_CLASS))
               {
                  efl_ui_focus_composition_adapter_focus_manager_parent_set(o, obj);
                  efl_ui_focus_composition_adapter_focus_manager_object_set(o, manager);
               }

             pd->registered_targets = eina_list_append(pd->registered_targets, o);
          }

        efl_ui_focus_manager_calc_update_order(manager, obj, eina_list_clone(pd->targets_ordered));
     }
   else
     {
        Efl_Ui_Focus_Object *o;

        EINA_LIST_FREE(pd->registered_targets, o)
          {
             efl_ui_focus_manager_calc_unregister(pd->old_manager, o);

             if (efl_isa(o, EFL_UI_FOCUS_COMPOSITION_ADAPTER_CLASS))
               {
                  efl_ui_focus_composition_adapter_focus_manager_parent_set(o, NULL);
                  efl_ui_focus_composition_adapter_focus_manager_object_set(o, NULL);
               }
          }
     }
}
static void
_invalidate(void *data, const Efl_Event *ev)
{
   Efl_Ui_Focus_Composition_Data *pd = efl_data_scope_get(data, EFL_UI_FOCUS_COMPOSITION_MIXIN);
   pd->register_target = eina_list_remove(pd->register_target, ev->object);
   pd->registered_targets = eina_list_remove(pd->registered_targets, ev->object);
}

EOLIAN static void
_efl_ui_focus_composition_composition_elements_set(Eo *obj, Efl_Ui_Focus_Composition_Data *pd, Eina_List *logical_order)
{
   Evas_Object *elem;
   Eina_List *n;

   pd->targets_ordered = eina_list_free(pd->targets_ordered);
   EINA_LIST_FREE(pd->register_target, elem)
     {
        efl_event_callback_del(elem, EFL_EVENT_INVALIDATE, _invalidate, obj);
     }

   pd->order = eina_list_free(pd->order);
   pd->order = logical_order;

   //now build a composition_elements list
   EINA_LIST_FOREACH(logical_order, n, elem)
     {
        Efl_Ui_Focus_Object *o = elem;

        EINA_SAFETY_ON_NULL_GOTO(elem, cont);

        if (!efl_isa(elem, EFL_UI_WIDGET_CLASS))
          {
             if (efl_isa(elem, EFL_UI_FOCUS_OBJECT_MIXIN))
               {
                  pd->register_target = eina_list_append(pd->register_target , o);
                  efl_event_callback_add(o, EFL_EVENT_INVALIDATE, _invalidate, obj);
               }
             else
               {
                  EINA_SAFETY_ERROR("List contains element that is not EFL_UI_FOCUS_OBJECT_MIXIN or EFL_GFX_ENTITY_INTERFACE or EFL_UI_WIDGET_CLASS");
                  continue;
               }
          }

        pd->targets_ordered = eina_list_append(pd->targets_ordered, o);
cont:
        continue;
     }
   _state_apply(obj, pd);
}

EOLIAN static Eina_List*
_efl_ui_focus_composition_composition_elements_get(const Eo *obj EINA_UNUSED, Efl_Ui_Focus_Composition_Data *pd)
{
   return eina_list_clone(pd->order);
}

EOLIAN static Eina_Bool
_efl_ui_focus_composition_efl_ui_widget_focus_state_apply(Eo *obj, Efl_Ui_Focus_Composition_Data *pd, Efl_Ui_Widget_Focus_State current_state, Efl_Ui_Widget_Focus_State *configured_state, Efl_Ui_Widget *redirect)
{
   Eina_Bool registered;

   configured_state->logical = EINA_TRUE;

   //shortcut for having the same configurations
   if (current_state.manager == configured_state->manager && !current_state.manager)
     return !!current_state.manager;

   if (configured_state->logical == current_state.logical &&
       configured_state->manager == current_state.manager &&
       configured_state->parent == current_state.parent)
     return !!current_state.manager;

   registered = efl_ui_widget_focus_state_apply(efl_super(obj, MY_CLASS), current_state, configured_state, redirect);

   if (registered)
     {
        if (efl_isa(obj, EFL_UI_FOCUS_MANAGER_INTERFACE))
          pd->registered = obj;
        else
          pd->registered = configured_state->manager;
     }
   else
     pd->registered = NULL;

   _state_apply(obj, pd);

   return registered;
}

EOLIAN static void
_efl_ui_focus_composition_dirty(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Composition_Data *pd)
{
   pd->dirty = EINA_TRUE;
}

EOLIAN static void
_efl_ui_focus_composition_efl_ui_focus_object_setup_order_non_recursive(Eo *obj, Efl_Ui_Focus_Composition_Data *pd EINA_UNUSED)
{
   if (pd->dirty)
     {
        efl_ui_focus_composition_prepare(obj);
        pd->dirty = EINA_FALSE;
     }

   efl_ui_focus_object_setup_order_non_recursive(efl_super(obj, MY_CLASS));
}

EOLIAN static void
_efl_ui_focus_composition_logical_mode_set(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Composition_Data *pd, Eina_Bool logical_mode)
{
   pd->logical = logical_mode;
}

EOLIAN static Eina_Bool
_efl_ui_focus_composition_logical_mode_get(const Eo *obj EINA_UNUSED, Efl_Ui_Focus_Composition_Data *pd)
{
   return pd->logical;
}

EOLIAN static void
_efl_ui_focus_composition_efl_object_invalidate(Eo *obj, Efl_Ui_Focus_Composition_Data *pd EINA_UNUSED)
{
   efl_invalidate(efl_super(obj, MY_CLASS));

   efl_ui_focus_composition_elements_set(obj, NULL);
}


#include "efl_ui_focus_composition.eo.c"

typedef struct {
   Evas_Object *object;
   Efl_Ui_Focus_Manager *manager;
   Efl_Ui_Focus_Object *parent;
}  Efl_Ui_Focus_Composition_Adapter_Data;

static void
_canvas_object_deleted(void *data, const Efl_Event *ev EINA_UNUSED)
{
  efl_ui_focus_composition_adapter_canvas_object_set(data, NULL);
}

static void
_new_geom(void *data, const Efl_Event *event EINA_UNUSED)
{
   Eina_Rect rect = efl_ui_focus_object_focus_geometry_get(data);
   efl_event_callback_call(data, EFL_UI_FOCUS_OBJECT_EVENT_FOCUS_GEOMETRY_CHANGED, &rect);
}

EFL_CALLBACKS_ARRAY_DEFINE(canvas_obj,
    {EFL_GFX_ENTITY_EVENT_SIZE_CHANGED, _new_geom},
    {EFL_GFX_ENTITY_EVENT_POSITION_CHANGED, _new_geom},
    {EFL_EVENT_DEL, _canvas_object_deleted},
);

EOLIAN static void
_efl_ui_focus_composition_adapter_canvas_object_set(Eo *obj, Efl_Ui_Focus_Composition_Adapter_Data *pd, Efl_Canvas_Object *v)
{
   if (pd->object)
     {
        efl_event_callback_array_del(pd->object, canvas_obj(), obj);
     }
   pd->object = v;
   if (v)
     {
        efl_event_callback_array_add(pd->object, canvas_obj(), obj);
     }

}

EOLIAN static Efl_Canvas_Object*
_efl_ui_focus_composition_adapter_canvas_object_get(const Eo *obj EINA_UNUSED, Efl_Ui_Focus_Composition_Adapter_Data *pd)
{
   return pd->object;
}

EOLIAN static Eina_Rect
_efl_ui_focus_composition_adapter_efl_ui_focus_object_focus_geometry_get(const Eo *obj EINA_UNUSED, Efl_Ui_Focus_Composition_Adapter_Data *pd EINA_UNUSED)
{
   return efl_gfx_entity_geometry_get(pd->object);
}

EOLIAN static void
_efl_ui_focus_composition_adapter_efl_ui_focus_object_focus_set(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Composition_Adapter_Data *pd, Eina_Bool focus)
{
   efl_ui_focus_object_focus_set(efl_super(obj, EFL_UI_FOCUS_COMPOSITION_ADAPTER_CLASS), focus);

   evas_object_focus_set(pd->object, efl_ui_focus_object_focus_get(obj));
}

EOLIAN static void
_efl_ui_focus_composition_adapter_efl_object_destructor(Eo *obj, Efl_Ui_Focus_Composition_Adapter_Data *pd EINA_UNUSED)
{
   efl_ui_focus_composition_adapter_canvas_object_set(obj, NULL);

   efl_destructor(efl_super(obj, EFL_UI_FOCUS_COMPOSITION_ADAPTER_CLASS));
}

EOLIAN static void
_efl_ui_focus_composition_adapter_focus_manager_object_set(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Composition_Adapter_Data *pd, Efl_Ui_Focus_Manager *v)
{
   pd->manager = v;
}

EOLIAN static void
_efl_ui_focus_composition_adapter_focus_manager_parent_set(Eo *obj EINA_UNUSED, Efl_Ui_Focus_Composition_Adapter_Data *pd, Efl_Ui_Focus_Object *parent)
{
   pd->parent = parent;
}

EOLIAN static Efl_Ui_Focus_Object*
_efl_ui_focus_composition_adapter_efl_ui_focus_object_focus_parent_get(const Eo *obj EINA_UNUSED, Efl_Ui_Focus_Composition_Adapter_Data *pd)
{
   return pd->parent;
}

EOLIAN static Efl_Ui_Focus_Manager*
_efl_ui_focus_composition_adapter_efl_ui_focus_object_focus_manager_get(const Eo *obj EINA_UNUSED, Efl_Ui_Focus_Composition_Adapter_Data *pd)
{
   return pd->manager;
}


#include "efl_ui_focus_composition_adapter.eo.c"
