#define EFL_CANVAS_GROUP_PROTECTED
#define EFL_PART_PROTECTED

#include "edje_private.h"

#include "canvas/evas_canvas_eo.h"

#ifdef MY_CLASS
# undef MY_CLASS
#endif

#define MY_CLASS             EFL_CANVAS_LAYOUT_CLASS

#define MY_CLASS_NAME        "Edje"
#define MY_CLASS_NAME_LEGACY "edje"

Eina_Inlist *_edje_edjes = NULL;

/************************** API Routines **************************/

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

EOLIAN static Eo *
_efl_canvas_layout_efl_object_constructor(Eo *obj, Edje *ed)
{
   Eo *parent;
   Evas *e;
   void *tmp;

   efl_canvas_group_clipped_set(obj, EINA_TRUE);
   obj = efl_constructor(efl_super(obj, MY_CLASS));
   efl_canvas_object_type_set(obj, MY_CLASS_NAME_LEGACY);
   ed->base.evas = evas_object_evas_get(obj);
   ed->base.clipper = (Evas_Object *) efl_canvas_group_clipper_get(obj);
   ed->duration_scale = 1.0;
   _edje_lib_ref();

   parent = efl_parent_get(obj);
   e = evas_object_evas_get(parent);
   tmp = ecore_evas_ecore_evas_get(e);

   ed->canvas_animator = !!tmp;

   return obj;
}


EOLIAN static void
_efl_canvas_layout_efl_object_invalidate(Eo *obj, Edje *ed)
{
   _edje_file_callbacks_del(ed, NULL);

   efl_invalidate(efl_super(obj, MY_CLASS));

   //invalidate is done, this means the legacy evas deletion event is called.
   for (int i = 0; i < ed->table_parts_size; ++i)
     {
        Edje_Real_Part *rp = ed->table_parts[i];
        switch(rp->type)
          {
            case EDJE_RP_TYPE_SWALLOW:
              _edje_real_part_swallow_clear(ed, rp);
            break;

            case EDJE_RP_TYPE_CONTAINER:
              if (rp->part->type == EDJE_PART_TYPE_BOX)
                _edje_real_part_box_remove_all(ed, rp, 0);
              else if (rp->part->type == EDJE_PART_TYPE_TABLE)
                _edje_real_part_table_clear(ed, rp, 0);
            break;
          }
     }
}

EOLIAN static void
_efl_canvas_layout_efl_object_debug_name_override(Eo *obj, Edje *ed, Eina_Strbuf *sb)
{
   efl_debug_name_override(efl_super(obj, MY_CLASS), sb);
   eina_strbuf_append_printf(sb, ":file='%s':group='%s'",
                             ed->file ? eina_file_filename_get(ed->file->f) : NULL,
                             ed->group);
}

EOLIAN static void
_efl_canvas_layout_efl_object_dbg_info_get(Eo *eo_obj, Edje *_pd EINA_UNUSED, Efl_Dbg_Info *root) EINA_ARG_NONNULL(3)
{
   efl_dbg_info_get(efl_super(eo_obj, MY_CLASS), root);
   Efl_Dbg_Info *group = EFL_DBG_INFO_LIST_APPEND(root, MY_CLASS_NAME);
   Edje_Load_Error error;

   const char *file, *edje_group;
   efl_file_simple_get(eo_obj, &file, &edje_group);
   EFL_DBG_INFO_APPEND(group, "File", EINA_VALUE_TYPE_STRING, file);
   EFL_DBG_INFO_APPEND(group, "Group", EINA_VALUE_TYPE_STRING, edje_group);

   error = edje_object_load_error_get(eo_obj);
   if (error != EDJE_LOAD_ERROR_NONE)
     {
        EFL_DBG_INFO_APPEND(group, "Error", EINA_VALUE_TYPE_STRING,
                           edje_load_error_str(error));
     }
}

static void
_edje_color_class_free(void *data)
{
   Edje_Color_Class *cc = data;

   if (cc->name) eina_stringshare_del(cc->name);
   free(cc);
}

static void
_edje_text_class_free(void *data)
{
   Edje_Text_Class *tc = data;

   if (tc->name) eina_stringshare_del(tc->name);
   if (tc->font) eina_stringshare_del(tc->font);
   free(tc);
}

static void
_edje_size_class_free(void *data)
{
   Edje_Size_Class *sc = data;

   if (sc->name) eina_stringshare_del(sc->name);
   free(sc);
}

/* Private Routines */
EOLIAN static void
_efl_canvas_layout_efl_canvas_group_group_add(Eo *obj, Edje *ed)
{
   Evas *tev = evas_object_evas_get(obj);

   evas_event_freeze(tev);

   efl_canvas_group_add(efl_super(obj, MY_CLASS));

   ed->is_rtl = EINA_FALSE;
   ed->have_objects = EINA_TRUE;
   ed->references = 1;
   ed->user_defined = NULL;
   ed->color_classes = eina_hash_string_small_new(_edje_color_class_free);
   ed->text_classes = eina_hash_string_small_new(_edje_text_class_free);
   ed->size_classes = eina_hash_string_small_new(_edje_size_class_free);

   evas_object_geometry_get(obj, &(ed->x), &(ed->y), &(ed->w), &(ed->h));
   ed->obj = obj;
   _edje_edjes = eina_inlist_append(_edje_edjes, EINA_INLIST_GET(ed));
   evas_event_thaw(tev);
   evas_event_thaw_eval(tev);
}

EOLIAN static void
_efl_canvas_layout_efl_canvas_group_group_del(Eo *obj, Edje *ed)
{
   _edje_block_violate(ed);
   ed->delete_me = 1;
   _edje_edjes = eina_inlist_remove(_edje_edjes, EINA_INLIST_GET(ed));
   if (_edje_lua_script_only(ed)) _edje_lua_script_only_shutdown(ed);
#ifdef HAVE_EPHYSICS
   /* clear physics world  / shutdown ephysics */
   if ((ed->collection) && (ed->collection->physics_enabled) && (ed->world))
     {
        if (EPH_LOAD())
          {
             EPH_CALL(ephysics_world_del)(ed->world);
             EPH_CALL(ephysics_shutdown)();
          }
     }
#endif
   if (ed->persp) edje_object_perspective_set(obj, NULL);
   _edje_file_del(ed);
   _edje_unref(ed);
   _edje_lib_unref();
   efl_canvas_group_del(efl_super(obj, MY_CLASS));
}

EOLIAN static void
_efl_canvas_layout_efl_gfx_entity_position_set(Eo *obj, Edje *ed, Eina_Position2D pos)
{
   unsigned short i;

   if (_evas_object_intercept_call(obj, EVAS_OBJECT_INTERCEPT_CB_MOVE, 0, pos.x, pos.y))
     return;

   efl_gfx_entity_position_set(efl_super(obj, MY_CLASS), pos);

   if ((ed->x == pos.x) && (ed->y == pos.y)) return;
   ed->x = pos.x;
   ed->y = pos.y;
//   evas_object_move(ed->clipper, ed->x, ed->y);

   if (_edje_lua_script_only(ed))
     {
        _edje_lua_script_only_move(ed);
        return;
     }

   for (i = 0; i < ed->table_parts_size; i++)
     {
        Edje_Real_Part *ep;

        ep = ed->table_parts[i];
        if ((ep->type == EDJE_RP_TYPE_TEXT) && (ep->typedata.text))
          {
             if (ep->object)
               evas_object_move(ep->object,
                                ed->x + ep->x + ep->typedata.text->offset.x,
                                ed->y + ep->y + ep->typedata.text->offset.y);
             else if (ep->type != EFL_CANVAS_LAYOUT_PART_TYPE_NONE)
               WRN("No object for part '%s' in group '%s'",
                   ep->part ? ep->part->name : "<invalid>", ed->group);
          }
        else
          {
             if (ep->object)
               evas_object_move(ep->object, ed->x + ep->x, ed->y + ep->y);
             else if (ep->type != EFL_CANVAS_LAYOUT_PART_TYPE_NONE)
               WRN("No object for part '%s' in group '%s'",
                   ep->part ? ep->part->name : "<invalid>", ed->group);
             if ((ep->type == EDJE_RP_TYPE_SWALLOW) &&
                 (ep->typedata.swallow))
               {
                  if (ep->typedata.swallow->swallowed_object)
                    evas_object_move
                      (ep->typedata.swallow->swallowed_object,
                      ed->x + ep->x,
                      ed->y + ep->y);
               }
          }
        if (ep->part->entry_mode > EDJE_ENTRY_EDIT_MODE_NONE)
          _edje_entry_real_part_configure(ed, ep);
     }

   if (ed->have_mapped_part)
     {
        ed->dirty = EINA_TRUE;
        ed->have_mapped_part = EINA_FALSE;
        ed->need_map_update = EINA_TRUE;
        _edje_recalc_do(ed);
        ed->need_map_update = EINA_FALSE;
     }

//   _edje_emit(ed, "move", NULL);
}

static void
_edje_limit_emit(Edje *ed, const char *limit_name, Eina_Bool over)
{
   char *buffer;
   unsigned int length;

   if (!limit_name) return;

   length = strlen(limit_name) + 13;
   buffer = alloca(length);
   snprintf(buffer, length, "limit,%s,%s", limit_name, over ? "over" : "below");
   _edje_emit(ed, buffer, NULL);
}

static void
_edje_limit_get(Edje *ed, Edje_Limit **limits, unsigned int length, Evas_Coord size_current, Evas_Coord size_next)
{
   unsigned int i;

   if (size_next == size_current) return;

   for (i = 0; i < length; ++i)
     {
        if ((size_current <= limits[i]->value) && (limits[i]->value < size_next))
          {
             _edje_limit_emit(ed, limits[i]->name, EINA_TRUE);
          }
        else if ((size_next <= limits[i]->value) && (limits[i]->value < size_current))
          {
             _edje_limit_emit(ed, limits[i]->name, EINA_FALSE);
          }
     }
}

EOLIAN static void
_efl_canvas_layout_efl_gfx_entity_size_set(Eo *obj, Edje *ed, Eina_Size2D sz)
{
   if (_evas_object_intercept_call(obj, EVAS_OBJECT_INTERCEPT_CB_RESIZE, 0, sz.w, sz.h))
     return;

   if ((sz.w == ed->w) && (sz.h == ed->h)) goto super;
   if (ed->collection)
     {
        _edje_limit_get(ed, ed->collection->limits.horizontal, ed->collection->limits.horizontal_count, ed->w, sz.w);
        _edje_limit_get(ed, ed->collection->limits.vertical, ed->collection->limits.vertical_count, ed->h, sz.h);
     }
   ed->w = sz.w;
   ed->h = sz.h;
#ifdef HAVE_EPHYSICS
   if ((ed->collection) && (ed->world))
     {
        if (EPH_LOAD())
          EPH_CALL(ephysics_world_render_geometry_set)
            (ed->world, ed->x, ed->y, ed->collection->physics.world.z,
             ed->w, ed->h, ed->collection->physics.world.depth);
     }
#endif
#ifdef EDJE_CALC_CACHE
   ed->all_part_change = EINA_TRUE;
#endif
   if (_edje_lua_script_only(ed))
     {
        _edje_lua_script_only_resize(ed);
        goto super;
     }
//   evas_object_resize(ed->clipper, ed->w, ed->h);
   ed->dirty = EINA_TRUE;
   _edje_recalc_do(ed);
   _edje_emit(ed, "resize", NULL);

super:
   efl_gfx_entity_size_set(efl_super(obj, MY_CLASS), sz);
}

static void
_edje_object_show(Eo *obj, Edje *ed)
{
   Eina_List *l;
   Edje *edg;

   efl_gfx_entity_visible_set(efl_super(obj, MY_CLASS), EINA_TRUE);
   if (_edje_lua_script_only(ed))
     {
        _edje_lua_script_only_show(ed);
        return;
     }
   if (eina_list_count(ed->groups) > 1)
     {
        EINA_LIST_FOREACH(ed->groups, l, edg)
          {
             Edje_Real_Part *rp;

             if (edg == ed) continue;
             rp = evas_object_data_get(edg->obj, "\377 edje.part_obj");
             if ((rp) && (rp->chosen_description->visible))
               evas_object_show(edg->obj);
          }
     }
   _edje_emit(ed, "show", NULL);
}

static void
_edje_object_hide(Eo *obj, Edje *ed)
{
   Eina_List *l;
   Edje *edg;

   efl_gfx_entity_visible_set(efl_super(obj, MY_CLASS), EINA_FALSE);
   if (_edje_lua_script_only(ed))
     {
        _edje_lua_script_only_hide(ed);
        return;
     }
   EINA_LIST_FOREACH(ed->groups, l, edg)
     if (edg != ed) evas_object_hide(edg->obj);
   _edje_emit(ed, "hide", NULL);
}

EOLIAN static void
_efl_canvas_layout_efl_gfx_entity_visible_set(Eo *obj, Edje *ed, Eina_Bool vis)
{
   if (_evas_object_intercept_call(obj, EVAS_OBJECT_INTERCEPT_CB_VISIBLE, 0, vis))
     return;

   if (vis) _edje_object_show(obj, ed);
   else _edje_object_hide(obj, ed);
}

EOLIAN static void
_efl_canvas_layout_efl_canvas_object_no_render_set(Eo *obj, Edje *ed, Eina_Bool enable)
{
   Eina_List *l;
   Edje *edg;

   enable = !!enable;
   if (efl_canvas_object_no_render_get(obj) == enable) return;
   efl_canvas_object_no_render_set(efl_super(obj, MY_CLASS), enable);

   EINA_LIST_FOREACH(ed->groups, l, edg)
     if (edg != ed) efl_canvas_object_no_render_set(edg->obj, enable);
}

EOLIAN static void
_efl_canvas_layout_efl_canvas_group_group_calculate(Eo *obj EINA_UNUSED, Edje *ed)
{
   _edje_recalc_do(ed);
}

EOLIAN static void
_efl_canvas_layout_efl_file_unload(Eo *obj, Edje *ed)
{
   efl_file_unload(efl_super(obj, MY_CLASS));
   if (_edje_lua_script_only(ed)) _edje_lua_script_only_shutdown(ed);
#ifdef HAVE_EPHYSICS
   /* clear physics world  / shutdown ephysics */
   if ((ed->collection) && (ed->collection->physics_enabled) && (ed->world))
     {
        if (EPH_LOAD())
          {
             EPH_CALL(ephysics_world_del)(ed->world);
             EPH_CALL(ephysics_shutdown)();
          }
     }
#endif
   if (ed->persp) edje_object_perspective_set(obj, NULL);
   _edje_file_del(ed);
   eina_stringshare_replace(&ed->path, NULL);
   eina_stringshare_replace(&ed->group, NULL);
   eina_stringshare_replace(&ed->parent, NULL);
}

EOLIAN static Eina_Error
_efl_canvas_layout_efl_file_load(Eo *obj, Edje *ed)
{
   Eina_Error err;
   Eina_Array *nested;

   if (efl_file_loaded_get(obj)) return 0;

   err = efl_file_load(efl_super(obj, MY_CLASS));
   if (err)
     {
        if (err == ENOENT)
          ed->load_error = EDJE_LOAD_ERROR_DOES_NOT_EXIST;
        else if (err == ENOMEM)
          ed->load_error = EDJE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
        else if ((err == EPERM) || (err == EACCES))
          ed->load_error = EDJE_LOAD_ERROR_PERMISSION_DENIED;
        else
          ed->load_error = EDJE_LOAD_ERROR_GENERIC;
        return err;
     }

   nested = eina_array_new(8);

   err = _edje_object_file_set_internal(obj, efl_file_mmap_get(obj), efl_file_key_get(obj), NULL, NULL, nested);

   eina_array_free(nested);
   _edje_object_orientation_inform(obj);

   return err;
}

EAPI Eina_Bool
edje_object_mmap_set(Edje_Object *obj, const Eina_File *file, const char *group)
{
   return efl_file_simple_mmap_load(obj, file, group);
}

EAPI Eina_Bool
edje_object_file_set(Edje_Object *obj, const char *file, const char *group)
{
   /* mtime checking of file is handled in efl.file mixin */
   return efl_file_simple_load(obj, file, group);
}

EAPI void
edje_object_file_get(const Edje_Object *obj, const char **file, const char **group)
{
   if (file) *file = efl_file_get(obj);
   if (group) *group = efl_file_key_get(obj);
}

EOLIAN static void
_efl_canvas_layout_efl_canvas_object_paragraph_direction_set(Eo *obj, Edje *ed, Efl_Text_Bidirectional_Type dir)
{
   efl_canvas_object_paragraph_direction_set(efl_super(obj, MY_CLASS), dir);

   /* Make it dirty to recalculate edje.
      It needs to move text objects according to new paragraph direction */
   ed->dirty = EINA_TRUE;
   efl_canvas_group_need_recalculate_set(obj, 1);
}

EOLIAN static void
_efl_canvas_layout_efl_observer_update(Eo *obj EINA_UNUSED, Edje *ed, Efl_Object *obs, const char *key, void *data)
{
   if (!obs) return;

   ed->dirty = EINA_TRUE;
   ed->recalc_call = EINA_TRUE;

   if ((obs == _edje_color_class_member) || (obs == _edje_size_class_member))
     {
#ifdef EDJE_CALC_CACHE
        ed->all_part_change = EINA_TRUE;
#endif
     }
   else if (obs == _edje_text_class_member)
     {
        if (data == ed)
          // Object level update
          _edje_object_textblock_style_all_update_text_class(ed, key);
        else if ((data == ed->file) || // File level update will pass ed->file
                 (!data)) // Global update will pass NULL
          _edje_file_textblock_styles_all_update_text_class(ed, key);
#ifdef EDJE_CALC_CACHE
        ed->text_part_change = EINA_TRUE;
#endif
     }

   _edje_recalc(ed);

   if (obs == _edje_color_class_member)
     {
        if (data)
          _edje_emit(ed, (const char *)data, key);

        if ((ed->file) && (ed->file->color_tree))
          {
             Edje_Color_Tree_Node *ctn = NULL;
             Eina_List *l = NULL;
             char *name;

             EINA_LIST_FOREACH(ed->file->color_tree, l, ctn)
               {
                  if (!strcmp(ctn->name, key) && (ctn->color_classes))
                    {
                       EINA_LIST_FOREACH(ctn->color_classes, l, name)
                         efl_observable_observers_update(_edje_color_class_member, name, data);

                       break;
                    }
               }
          }
     }
}

EOLIAN Eina_Bool
_efl_canvas_layout_efl_playable_playable_get(const Eo *obj EINA_UNUSED, Edje *pd EINA_UNUSED)
{
   return EINA_TRUE;
}

EOLIAN Eina_Bool
_efl_canvas_layout_efl_player_paused_set(Eo *obj EINA_UNUSED, Edje *ed, Eina_Bool paused)
{
   double t;
   Eina_List *l;
   Edje_Running_Program *runp;
   unsigned short i;

   if (!ed) return EINA_FALSE;
   if (ed->delete_me) return EINA_FALSE;
   paused = !!paused;
   if (ed->paused == paused) return EINA_TRUE;
   if (!paused)
     {
        t = ecore_time_get() - ed->paused_at;
        EINA_LIST_FOREACH(ed->actions, l, runp)
          runp->start_time += t;
     }
   else
     {
        ed->paused_at = ecore_time_get();
     }

   for (i = 0; i < ed->table_parts_size; i++)
     {
        Edje_Real_Part *rp;
        rp = ed->table_parts[i];
        if ((rp->part->type == EDJE_PART_TYPE_GROUP) &&
            ((rp->type == EDJE_RP_TYPE_SWALLOW) &&
             (rp->typedata.swallow)) &&
            (rp->typedata.swallow->swallowed_object))
          efl_player_paused_set(rp->typedata.swallow->swallowed_object, paused);
     }
   return EINA_TRUE;
}

EOLIAN Eina_Bool
_efl_canvas_layout_efl_player_paused_get(const Eo *obj EINA_UNUSED, Edje *ed)
{
   if (!ed) return EINA_FALSE;
   if (ed->delete_me) return EINA_FALSE;
   return ed->paused;
}

EOLIAN void
_efl_canvas_layout_efl_player_playback_speed_set(Eo *obj EINA_UNUSED, Edje *pd , double speed)
{
   if (speed <= 0.0) speed = 1.0;
   pd->duration_scale = 1.0/speed;
}

EOLIAN double
_efl_canvas_layout_efl_player_playback_speed_get(const Eo *obj EINA_UNUSED, Edje *pd)
{
   return 1.0/pd->duration_scale;
}

/* Internal EO APIs and hidden overrides */
EOLIAN static Eina_Bool
_efl_canvas_layout_efl_object_event_callback_priority_add(Eo *obj, Edje *pd, const Efl_Event_Description *desc, Efl_Callback_Priority priority, Efl_Event_Cb func, const void *user_data)
{
  if (desc == EFL_LAYOUT_EVENT_RECALC)
    {
       pd->has_recalc_event_cb = EINA_TRUE;
    }
  else if (desc == EFL_CONTENT_EVENT_CONTENT_CHANGED)
    {
       pd->has_content_change_event_cb = EINA_TRUE;
    }

  return efl_event_callback_priority_add(efl_super(obj, MY_CLASS), desc, priority, func, user_data);
}

EOLIAN static Eina_Bool
_efl_canvas_layout_efl_object_event_callback_array_priority_add(Eo *obj, Edje *pd, const Efl_Callback_Array_Item *array, Efl_Callback_Priority priority, const void *user_data)
{
   for (int i = 0; array[i].desc; ++i)
     {
        if (array[i].desc == EFL_LAYOUT_EVENT_RECALC)
          {
             pd->has_recalc_event_cb = EINA_TRUE;
          }
        else if (array[i].desc == EFL_CONTENT_EVENT_CONTENT_CHANGED)
          {
             pd->has_content_change_event_cb = EINA_TRUE;
          }
     }
   return efl_event_callback_array_priority_add(efl_super(obj, MY_CLASS), array, priority, user_data);
}
#define EFL_CANVAS_LAYOUT_EXTRA_OPS \
   EFL_CANVAS_GROUP_ADD_DEL_OPS(efl_canvas_layout), \
   EFL_OBJECT_OP_FUNC(efl_event_callback_priority_add, _efl_canvas_layout_efl_object_event_callback_priority_add), \
   EFL_OBJECT_OP_FUNC(efl_event_callback_array_priority_add, _efl_canvas_layout_efl_object_event_callback_array_priority_add), \
   EFL_OBJECT_OP_FUNC(efl_dbg_info_get, _efl_canvas_layout_efl_object_dbg_info_get)

#include "efl_canvas_layout.eo.c"
#include "efl_canvas_layout_eo.legacy.c"
#include "edje_global.eo.c"
#include "efl_layout_calc.eo.c"
#include "efl_layout_signal.eo.c"
#include "efl_layout_group.eo.c"
#include "efl_layout_group_eo.legacy.c"
