#include "efl_ui_table_private.h"

#define MY_CLASS EFL_UI_TABLE_CLASS
#define MY_CLASS_NAME "Efl.Ui.Table"

#define EFL_UI_TABLE_DATA_GET(o, pd) \
   Efl_Ui_Table_Data *pd = efl_data_scope_get(o, EFL_UI_TABLE_CLASS)

inline static Table_Item *
_efl_ui_table_item_date_get(Eo *obj, Efl_Ui_Table_Data *pd, Efl_Gfx_Entity *subobj)
{
   Table_Item *gi;
   if (!efl_invalidated_get(subobj) &&
       (obj != efl_canvas_object_render_parent_get(subobj)))
     {
        ERR("%p is not a child of %p", subobj, obj);
        return NULL;
     }
   gi = efl_key_data_get(subobj, TABLE_ITEM_KEY);
   if (!gi)
     {
        WRN("item %p has no table internal data", subobj);
        EINA_INLIST_FOREACH(EINA_INLIST_GET(pd->items), gi)
          {
             if (gi->object == subobj)
               break;
          }
        if (!gi)
          {
             ERR("item %p was not found in this table", subobj);
             return NULL;
          }
     }
   return gi;
}

static void
_on_child_size_changed(void *data, const Efl_Event *event EINA_UNUSED)
{
   Eo *table = data;
   efl_pack_layout_request(table);
}

static void
_on_child_hints_changed(void *data, const Efl_Event *event EINA_UNUSED)
{
   Eo *table = data;
   efl_pack_layout_request(table);
}

static void
_on_child_del(void *data, const Efl_Event *event)
{
   Eo *table = data;
   Table_Item *gi;
   EFL_UI_TABLE_DATA_GET(table, pd);

   gi = _efl_ui_table_item_date_get(table, pd, event->object);
   if (!gi) return;

   if ((gi->col == pd->last_col) && (gi->row == pd->last_row))
     pd->linear_recalc = EINA_TRUE;

   if (gi->col + gi->col_span >= pd->cols)
     pd->cols_recalc = EINA_TRUE;
   if (gi->row + gi->row_span >= pd->rows)
     pd->rows_recalc = EINA_TRUE;

   pd->items = (Table_Item *)
         eina_inlist_remove(EINA_INLIST_GET(pd->items), EINA_INLIST_GET(gi));
   free(gi);

   pd->count--;
   efl_key_data_set(event->object, TABLE_ITEM_KEY, NULL);

   efl_pack_layout_request(table);
}

EFL_CALLBACKS_ARRAY_DEFINE(efl_ui_table_callbacks,
  { EFL_GFX_ENTITY_EVENT_SIZE_CHANGED, _on_child_size_changed },
  { EFL_GFX_ENTITY_EVENT_HINTS_CHANGED, _on_child_hints_changed },
  { EFL_EVENT_DEL, _on_child_del }
);

static void
_efl_ui_table_last_position_get(Eo * obj, Efl_Ui_Table_Data *pd, int *last_col, int *last_row)
{
   Table_Item *gi;
   int col = -1, row  = -1;
   int req_cols, req_rows;
   int item_row, item_col;

   if (!pd->linear_recalc)
     {
        *last_col = pd->last_col;
        *last_row = pd->last_row;
        return;
     }

   efl_pack_table_size_get(obj, &req_cols, &req_rows);

   if (efl_ui_layout_orientation_is_horizontal(pd->fill_dir, EINA_TRUE))
     {
        EINA_INLIST_REVERSE_FOREACH(EINA_INLIST_GET(pd->items), gi)
          {
             item_row = gi->row + gi->row_span - 1;
             item_col = gi->col + gi->col_span - 1;
             if ((item_row < row) || (req_cols < item_col) ||
                 (req_rows < item_row))
               continue;

             if (item_row > row)
               {
                  row = item_row;
                  col = item_col;
               }
             else if (item_col > col)
               {
                  col = item_col;
               }
          }
     }
   else
     {
        EINA_INLIST_REVERSE_FOREACH(EINA_INLIST_GET(pd->items), gi)
          {
             item_row = gi->row + gi->row_span - 1;
             item_col = gi->col + gi->col_span - 1;
             if ((item_col < col) || (req_cols < item_col) ||
                 (req_rows < item_row))
               continue;

             if (item_col > col)
               {
                  col = item_col;
                  row = item_row;
               }
             else if (item_row > row)
               {
                  row = item_row;
               }
          }
     }
   *last_col = col;
   *last_row = row;
   pd->linear_recalc = EINA_FALSE;
}

static void
_efl_ui_table_size_hints_changed_cb(void *data EINA_UNUSED, const Efl_Event *ev)
{
   efl_pack_layout_request(ev->object);
}

EOLIAN static void
_efl_ui_table_homogeneous_set(Eo *obj, Efl_Ui_Table_Data *pd, Eina_Bool homogeneoush, Eina_Bool homogeneousv)
{
   homogeneoush = !!homogeneoush;
   homogeneousv = !!homogeneousv;

   if ((pd->homogeneoush == homogeneoush) && (pd->homogeneousv == homogeneousv))
     return;

   pd->homogeneoush = homogeneoush;
   pd->homogeneousv = homogeneousv;
   efl_pack_layout_request(obj);
}

EOLIAN static void
_efl_ui_table_homogeneous_get(const Eo *obj EINA_UNUSED, Efl_Ui_Table_Data *pd, Eina_Bool *homogeneoush, Eina_Bool *homogeneousv)
{
   if (homogeneoush) *homogeneoush = pd->homogeneoush;
   if (homogeneousv) *homogeneousv = pd->homogeneousv;
}

EOLIAN static void
_efl_ui_table_efl_pack_layout_layout_update(Eo *obj, Efl_Ui_Table_Data *pd)
{
   _efl_ui_table_custom_layout(obj, pd);
}

EOLIAN void
_efl_ui_table_efl_canvas_group_group_calculate(Eo *obj, Efl_Ui_Table_Data *_pd EINA_UNUSED)
{
   efl_canvas_group_need_recalculate_set(obj, EINA_FALSE);
   efl_pack_layout_update(obj);
}

EOLIAN static void
_efl_ui_table_efl_gfx_entity_size_set(Eo *obj, Efl_Ui_Table_Data *_pd EINA_UNUSED, Eina_Size2D sz)
{
   efl_gfx_entity_size_set(efl_super(obj, MY_CLASS), sz);
   efl_pack_layout_request(obj);
}

EOLIAN static void
_efl_ui_table_efl_gfx_entity_position_set(Eo *obj, Efl_Ui_Table_Data *_pd EINA_UNUSED, Eina_Position2D pos)
{
   efl_gfx_entity_position_set(efl_super(obj, MY_CLASS), pos);
   efl_canvas_group_change(obj);
}

EOLIAN static void
_efl_ui_table_efl_canvas_group_group_add(Eo *obj, Efl_Ui_Table_Data *pd)
{
   pd->clipper = efl_add(EFL_CANVAS_RECTANGLE_CLASS, obj);
   evas_object_static_clip_set(pd->clipper, EINA_TRUE);
   efl_gfx_entity_geometry_set(pd->clipper, EINA_RECT(-49999, -49999, 99999, 99999));
   efl_canvas_group_member_add(obj, pd->clipper);
   efl_ui_widget_sub_object_add(obj, pd->clipper);

   efl_canvas_group_add(efl_super(obj, MY_CLASS));
   elm_widget_sub_object_parent_add(obj);

   efl_ui_widget_focus_allow_set(obj, EINA_FALSE);
   elm_widget_highlight_ignore_set(obj, EINA_FALSE);

   efl_event_callback_add(obj, EFL_GFX_ENTITY_EVENT_HINTS_CHANGED,
                          _efl_ui_table_size_hints_changed_cb, NULL);
}

EOLIAN static void
_efl_ui_table_efl_canvas_group_group_del(Eo *obj, Efl_Ui_Table_Data *_pd EINA_UNUSED)
{
   efl_event_callback_del(obj, EFL_GFX_ENTITY_EVENT_HINTS_CHANGED,
                          _efl_ui_table_size_hints_changed_cb, NULL);

   efl_canvas_group_del(efl_super(obj, MY_CLASS));
}

EOLIAN static Eo *
_efl_ui_table_efl_object_constructor(Eo *obj, Efl_Ui_Table_Data *pd)
{
   obj = efl_constructor(efl_super(obj, MY_CLASS));
   efl_canvas_object_type_set(obj, MY_CLASS_NAME);
   efl_access_object_access_type_set(obj, EFL_ACCESS_TYPE_SKIPPED);
   efl_access_object_role_set(obj, EFL_ACCESS_ROLE_FILLER);

   pd->fill_dir = EFL_UI_LAYOUT_ORIENTATION_HORIZONTAL;
   pd->last_col = -1;
   pd->last_row = -1;
   pd->req_cols = 0;
   pd->req_rows = 0;
   pd->cols = 0;
   pd->rows = 0;
   pd->align.h = 0.5;
   pd->align.v = 0.5;

   return obj;
}

EOLIAN static void
_efl_ui_table_efl_object_invalidate(Eo *obj, Efl_Ui_Table_Data *pd)
{
   Table_Item *gi;

   efl_invalidate(efl_super(obj, MY_CLASS));

   EINA_INLIST_FREE(EINA_INLIST_GET(pd->items), gi)
     {
        efl_event_callback_array_del(gi->object, efl_ui_table_callbacks(), obj);

        pd->items = (Table_Item *)
            eina_inlist_remove(EINA_INLIST_GET(pd->items), EINA_INLIST_GET(gi));
        free(gi);
     }
}

EOLIAN static void
_efl_ui_table_efl_gfx_arrangement_content_padding_set(Eo *obj, Efl_Ui_Table_Data *pd, unsigned int h, unsigned int v)
{
   if (pd->pad.h == h && pd->pad.v == v)
     return;

   pd->pad.h = h;
   pd->pad.v = v;

   efl_pack_layout_request(obj);
}

EOLIAN static void
_efl_ui_table_efl_gfx_arrangement_content_padding_get(const Eo *obj EINA_UNUSED, Efl_Ui_Table_Data *pd, unsigned int *h, unsigned int *v)
{
   if (h) *h = pd->pad.h;
   if (v) *v = pd->pad.v;
}

EOLIAN static void
_efl_ui_table_efl_gfx_arrangement_content_align_set(Eo *obj, Efl_Ui_Table_Data *pd, double h, double v)
{
   if (h < 0) h = -1;
   else if (h > 1) h = 1;
   if (v < 0) v = -1;
   else if (v > 1) v = 1;

   if (EINA_DBL_EQ(pd->align.h, h) && EINA_DBL_EQ(pd->align.v, v))
     return;

   pd->align.h = h;
   pd->align.v = v;

   efl_pack_layout_request(obj);
}

EOLIAN static void
_efl_ui_table_efl_gfx_arrangement_content_align_get(const Eo *obj EINA_UNUSED, Efl_Ui_Table_Data *pd, double *h, double *v)
{
   if (h) *h = pd->align.h;
   if (v) *v = pd->align.v;
}

static Eina_Bool
_pack_at(Eo *obj, Efl_Ui_Table_Data *pd, Efl_Gfx_Entity *subobj, int col, int row, int colspan, int rowspan)
{
   Table_Item *gi;

   if (efl_key_data_get(subobj, TABLE_ITEM_KEY))
     {
        if (efl_canvas_object_render_parent_get(subobj) == obj)
          ERR("subobj %p %s is already added to this", subobj, efl_class_name_get(subobj));
        else
          ERR("subobj %p %s currently belongs to different table", subobj, efl_class_name_get(subobj));

        return EINA_FALSE;
     }

   if (col < 0) col = 0;
   if (row < 0) row = 0;

   // note: we could have colspan = -1 mean "full width" if req_cols is set?
   if (colspan < 1) colspan = 1;
   if (rowspan < 1) rowspan = 1;

   if (((int64_t) col + (int64_t) colspan) > (int64_t) INT_MAX)
     colspan = INT_MAX - col;

   if (((int64_t) row + (int64_t) rowspan) > (int64_t) INT_MAX)
     rowspan = INT_MAX - row;

   if ((pd->req_cols && ((col + colspan) > pd->req_cols)) ||
       (pd->req_rows && ((row + rowspan) > pd->req_rows)))
     {
        ERR("table requested size exceeded! packing in extra cell at "
            "%d,%d %dx%d (table: %dx%d)",
            col, row, colspan, rowspan, pd->req_cols, pd->req_rows);
     }

   if (!efl_ui_widget_sub_object_add(obj, subobj))
     return EINA_FALSE;

   gi = calloc(1, sizeof(*gi));
   if (!gi) return EINA_FALSE;
   gi->col = col;
   gi->row = row;
   gi->col_span = colspan;
   gi->row_span = rowspan;
   gi->object = subobj;
   pd->count++;
   pd->items = (Table_Item *)
       eina_inlist_append(EINA_INLIST_GET(pd->items), EINA_INLIST_GET(gi));

   efl_key_data_set(subobj, TABLE_ITEM_KEY, gi);
   efl_key_data_set(subobj, "_elm_leaveme", obj);
   efl_canvas_object_clipper_set(subobj, pd->clipper);
   efl_event_callback_array_add(subobj, efl_ui_table_callbacks(), obj);

   efl_canvas_group_member_add(obj, subobj);
   efl_event_callback_call(obj, EFL_CONTAINER_EVENT_CONTENT_ADDED, subobj);

   if ((gi->col > pd->last_col) || (gi->row > pd->last_row))
     pd->linear_recalc = EINA_TRUE;

   if (pd->cols < gi->col + gi->col_span)
     pd->cols = gi->col + gi->col_span;
   if (pd->rows < gi->row + gi->row_span)
     pd->rows = gi->row + gi->row_span;

   efl_pack_layout_request(obj);

   return EINA_TRUE;
}

EOLIAN static Eina_Bool
_efl_ui_table_efl_pack_table_pack_table(Eo *obj, Efl_Ui_Table_Data *pd,
                                     Efl_Gfx_Entity *subobj,
                                     int col, int row, int colspan, int rowspan)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(subobj, EINA_FALSE);

   return _pack_at(obj, pd, subobj, col, row, colspan, rowspan);
}

EOLIAN static Eina_Bool
_efl_ui_table_efl_pack_table_table_cell_column_get(const Eo *obj, Efl_Ui_Table_Data *pd, Evas_Object *subobj, int *col, int *colspan)
{
   int c = -1, cs = 0;
   Table_Item *gi;
   Eina_Bool ret = EINA_FALSE;

   gi = _efl_ui_table_item_date_get((Eo *)obj, pd, subobj);
   if (gi)
     {
        c = gi->col;
        cs = gi->col_span;
        ret = EINA_TRUE;
     }

   if (col) *col = c;
   if (colspan) *colspan = cs;
   return ret;
}

EOLIAN static void
_efl_ui_table_efl_pack_table_table_cell_column_set(Eo *obj, Efl_Ui_Table_Data *pd, Evas_Object *subobj, int col, int colspan)
{
   Table_Item *gi;

   gi = _efl_ui_table_item_date_get((Eo *)obj, pd, subobj);
   if (!gi)
     return;

   if (col < 0) col = 0;
   if (colspan < 1) colspan = 1;

   if (((int64_t) col + (int64_t) colspan) > (int64_t) INT_MAX)
     colspan = INT_MAX - col;

   if (pd->req_cols && ((col + colspan) > pd->req_cols))
     {
        ERR("table requested size exceeded! packing in extra cell at "
            "%d+%d (table cols: %d)", col, colspan, pd->req_cols);
     }

   gi->col = col;
   gi->col_span = colspan;

   if (gi->col > pd->last_col)
     pd->linear_recalc = EINA_TRUE;

   if (pd->cols < gi->col + gi->col_span)
     pd->cols = gi->col + gi->col_span;

   efl_pack_layout_request(obj);
}

EOLIAN static Eina_Bool
_efl_ui_table_efl_pack_table_table_cell_row_get(const Eo *obj, Efl_Ui_Table_Data *pd, Evas_Object *subobj, int *row, int *rowspan)
{
   int r = -1, rs = 0;
   Table_Item *gi;
   Eina_Bool ret = EINA_FALSE;

   gi = _efl_ui_table_item_date_get((Eo *)obj, pd, subobj);
   if (gi)
     {
        r = gi->row;
        rs = gi->row_span;
        ret = EINA_TRUE;
     }

   if (row) *row = r;
   if (rowspan) *rowspan = rs;
   return ret;
}

EOLIAN static void
_efl_ui_table_efl_pack_table_table_cell_row_set(Eo *obj, Efl_Ui_Table_Data *pd, Evas_Object *subobj, int row, int rowspan)
{
   Table_Item *gi;

   gi = _efl_ui_table_item_date_get((Eo *)obj, pd, subobj);
   if (!gi)
     return;

   if (row < 0) row = 0;
   if (rowspan < 1) rowspan = 1;

   if (((int64_t) row + (int64_t) rowspan) > (int64_t) INT_MAX)
     rowspan = INT_MAX - row;

   if (pd->req_rows && ((row + rowspan) > pd->req_rows))
     {
        ERR("table requested size exceeded! packing in extra cell at "
            "%d+%d (table rows: %d)", row, rowspan, pd->req_rows);
     }

   gi->row = row;
   gi->row_span = rowspan;

   if (gi->row > pd->last_row)
     pd->linear_recalc = EINA_TRUE;

   if (pd->rows < gi->row + gi->row_span)
     pd->rows = gi->row + gi->row_span;

   efl_pack_layout_request(obj);
}

EOLIAN static Efl_Gfx_Entity *
_efl_ui_table_efl_pack_table_table_content_get(Eo *obj EINA_UNUSED, Efl_Ui_Table_Data *pd, int col, int row)
{
   Table_Item *gi;
   EINA_INLIST_FOREACH(EINA_INLIST_GET(pd->items), gi)
     {
        if (gi->col == col && gi->row == row)
          return gi->object;
     }

   return NULL;
}

static Eina_Bool
_item_remove(Efl_Ui_Table *obj, Efl_Ui_Table_Data *pd, Table_Item *gi)
{
   Efl_Gfx_Entity *subobj = gi->object;

   if (!subobj || !_elm_widget_sub_object_redirect_to_top(obj, subobj))
     return EINA_FALSE;

   efl_canvas_object_clipper_set(subobj, NULL);
   efl_key_data_set(subobj, "_elm_leaveme", NULL);
   efl_key_data_set(subobj, TABLE_ITEM_KEY, NULL);
   efl_event_callback_array_del(subobj, efl_ui_table_callbacks(), obj);

   efl_canvas_group_member_remove(obj, subobj);
   efl_event_callback_call(obj, EFL_CONTAINER_EVENT_CONTENT_REMOVED, subobj);

   if ((gi->col == pd->last_col) && (gi->row == pd->last_row))
     pd->linear_recalc = EINA_TRUE;

   if (gi->col + gi->col_span >= pd->cols)
     pd->cols_recalc = EINA_TRUE;
   if (gi->row + gi->row_span >= pd->rows)
     pd->rows_recalc = EINA_TRUE;

   pd->items = (Table_Item *)
         eina_inlist_remove(EINA_INLIST_GET(pd->items), EINA_INLIST_GET(gi));
   free(gi);

   pd->count--;
   efl_pack_layout_request(obj);

   return EINA_TRUE;
}

EOLIAN static Eina_Bool
_efl_ui_table_efl_pack_unpack(Eo *obj, Efl_Ui_Table_Data *pd, Efl_Gfx_Entity *subobj)
{
   Table_Item *gi = _efl_ui_table_item_date_get(obj, pd, subobj);
   if (!gi) return EINA_FALSE;

   return _item_remove(obj, pd, gi);
}

EOLIAN static Eina_Bool
_efl_ui_table_efl_pack_pack_clear(Eo *obj, Efl_Ui_Table_Data *pd)
{
   Table_Item *gi;

   EINA_INLIST_FREE(EINA_INLIST_GET(pd->items), gi)
     {
        efl_event_callback_array_del(gi->object, efl_ui_table_callbacks(), obj);
        efl_del(gi->object);

        pd->items = (Table_Item *)
            eina_inlist_remove(EINA_INLIST_GET(pd->items), EINA_INLIST_GET(gi));
        free(gi);
     }

   pd->linear_recalc = EINA_TRUE;
   pd->cols_recalc = EINA_TRUE;
   pd->rows_recalc = EINA_TRUE;

   pd->last_col = -1;
   pd->last_row = -1;
   pd->cols = 0;
   pd->rows = 0;
   pd->count = 0;
   efl_pack_layout_request(obj);

   return EINA_TRUE;
}

EOLIAN static Eina_Bool
_efl_ui_table_efl_pack_unpack_all(Eo *obj, Efl_Ui_Table_Data *pd)
{
   Table_Item *gi;
   Eina_Bool ret = EINA_TRUE;

   EINA_INLIST_FREE(EINA_INLIST_GET(pd->items), gi)
     ret &= _item_remove(obj, pd, gi);

   pd->linear_recalc = EINA_TRUE;
   pd->cols_recalc = EINA_TRUE;
   pd->rows_recalc = EINA_TRUE;

   pd->last_col = -1;
   pd->last_row = -1;
   pd->cols = 0;
   pd->rows = 0;

   return ret;
}

EOLIAN static void
_efl_ui_table_efl_pack_layout_layout_request(Eo *obj, Efl_Ui_Table_Data *pd)
{
   pd->full_recalc = EINA_TRUE;
   efl_canvas_group_need_recalculate_set(obj, EINA_TRUE);
}

static Eina_Bool
_efl_ui_table_item_iterator_next(Table_Item_Iterator *it, void **data)
{
   Table_Item *gi;

   if (!it->cur)
     return EINA_FALSE;

   if (data)
     {
        gi = EINA_INLIST_CONTAINER_GET(it->cur, Table_Item);
        *data = gi->object;
     }

   it->cur = it->cur->next;

   return EINA_TRUE;
}

static Eo *
_efl_ui_table_item_iterator_get_container(Table_Item_Iterator *it)
{
   return it->object;
}

static void
_efl_ui_table_item_iterator_free(Table_Item_Iterator *it)
{
   free(it);
}

EOLIAN static Eina_Iterator *
_efl_ui_table_efl_container_content_iterate(Eo *obj, Efl_Ui_Table_Data *pd)
{
   Table_Item_Iterator *it;

   if (!pd->count)
     return NULL;

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

   EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR);

   it->object = obj;
   it->cur = EINA_INLIST_GET(pd->items);

   it->iterator.version = EINA_ITERATOR_VERSION;
   it->iterator.next = FUNC_ITERATOR_NEXT(_efl_ui_table_item_iterator_next);
   it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER(
     _efl_ui_table_item_iterator_get_container);
   it->iterator.free = FUNC_ITERATOR_FREE(_efl_ui_table_item_iterator_free);

   return &it->iterator;
}

EOLIAN static int
_efl_ui_table_efl_container_content_count(Eo *obj EINA_UNUSED, Efl_Ui_Table_Data *pd)
{
   return pd->count;
}

EOLIAN static Eina_Iterator *
_efl_ui_table_efl_pack_table_table_contents_get(Eo *obj EINA_UNUSED, Efl_Ui_Table_Data *pd, int col, int row, Eina_Bool below)
{
   Table_Item *gi;
   Eina_List *atlist = NULL;

   EINA_INLIST_FOREACH(EINA_INLIST_GET(pd->items), gi)
     {
        if ((gi->col == col) && (gi->row == row))
          atlist = eina_list_append(atlist, gi->object);
        else if (below)
          {
             if ((gi->col <= col) && (gi->col + gi->col_span >= col) &&
                 (gi->row <= row) && (gi->row + gi->row_span >= row))
               atlist = eina_list_append(atlist, gi->object);
          }
     }

   return eina_list_iterator_new(atlist);
}

EOLIAN static void
_efl_ui_table_efl_ui_layout_orientable_orientation_set(Eo *obj, Efl_Ui_Table_Data *pd, Efl_Ui_Layout_Orientation dir)
{
   if (pd->fill_dir == dir)
     return;

   if (dir == EFL_UI_LAYOUT_ORIENTATION_DEFAULT)
     dir = EFL_UI_LAYOUT_ORIENTATION_HORIZONTAL;

   pd->fill_dir = dir;

   efl_pack_layout_request(obj);
}

EOLIAN static Efl_Ui_Layout_Orientation
_efl_ui_table_efl_ui_layout_orientable_orientation_get(const Eo *obj EINA_UNUSED, Efl_Ui_Table_Data *pd)
{
   return pd->fill_dir;
}

EOLIAN static void
_efl_ui_table_efl_pack_table_table_size_set(Eo *obj, Efl_Ui_Table_Data *pd, int cols, int rows)
{
   if (cols < 0) cols = 0;
   if (rows < 0) rows = 0;

   if ((pd->req_cols == cols) && (pd->req_rows == rows))
     return;

   pd->req_cols = cols;
   pd->req_rows = rows;
   pd->linear_recalc = EINA_TRUE;

   efl_pack_layout_request(obj);
}

EOLIAN static void
_efl_ui_table_efl_pack_table_table_size_get(const Eo *obj, Efl_Ui_Table_Data *pd EINA_UNUSED, int *cols, int *rows)
{
   if (cols) *cols = efl_pack_table_columns_get(obj);
   if (rows) *rows = efl_pack_table_rows_get(obj);
}

EOLIAN static void
_efl_ui_table_efl_pack_table_table_columns_set(Eo *obj, Efl_Ui_Table_Data *pd, int columns)
{
   if (pd->req_cols == columns)
     return;

   pd->req_cols = columns;
   pd->linear_recalc = EINA_TRUE;

   efl_pack_layout_request(obj);
}

EOLIAN static int
_efl_ui_table_efl_pack_table_table_columns_get(const Eo *obj EINA_UNUSED, Efl_Ui_Table_Data *pd)
{
   Table_Item *gi;
   if (pd->cols_recalc)
     {
        pd->cols = 0;
        EINA_INLIST_REVERSE_FOREACH(EINA_INLIST_GET(pd->items), gi)
          {
             if (pd->cols < gi->col + gi->col_span)
               pd->cols = gi->col+ gi->col_span;
          }
        pd->cols_recalc = EINA_FALSE;
     }
   return pd->req_cols ? : pd->cols;
}

EOLIAN static void
_efl_ui_table_efl_pack_table_table_rows_set(Eo *obj, Efl_Ui_Table_Data *pd, int rows)
{
   if (pd->req_rows == rows)
     return;

   pd->req_rows = rows;
   pd->linear_recalc = EINA_TRUE;

   efl_pack_layout_request(obj);
}

EOLIAN static int
_efl_ui_table_efl_pack_table_table_rows_get(const Eo *obj EINA_UNUSED, Efl_Ui_Table_Data *pd)
{
   Table_Item *gi;
   if (pd->rows_recalc)
     {
        pd->rows = 0;
        EINA_INLIST_REVERSE_FOREACH(EINA_INLIST_GET(pd->items), gi)
          {
             if (pd->rows < gi->row + gi->row_span)
               pd->rows = gi->row + gi->row_span;
          }
        pd->rows_recalc = EINA_FALSE;
     }
   return pd->req_rows ? : pd->rows;
}

EOLIAN static Eina_Bool
_efl_ui_table_efl_pack_pack(Eo *obj, Efl_Ui_Table_Data *pd, Efl_Gfx_Entity *subobj)
{
   int last_col, last_row;

   EINA_SAFETY_ON_NULL_RETURN_VAL(subobj, EINA_FALSE);

   _efl_ui_table_last_position_get(obj, pd, &last_col, &last_row);

   if (efl_ui_layout_orientation_is_horizontal(pd->fill_dir, EINA_TRUE))
     {
        last_col++;
        if (pd->req_cols && (last_col >= pd->req_cols))
          {
             last_col = 0;
             last_row++;
          }
        if (last_row < 0) last_row = 0;
     }
   else
     {
        last_row++;
        if (pd->req_rows && (last_row >= pd->req_rows))
          {
             last_row = 0;
             last_col++;
          }
        if (last_col < 0) last_col = 0;
     }

   pd->last_col = last_col;
   pd->last_row = last_row;

   DBG("packing new obj at %d,%d", last_col, last_row);
   return _pack_at(obj, pd, subobj, last_col, last_row, 1, 1);
}

/* Internal EO APIs and hidden overrides */

#define EFL_UI_TABLE_EXTRA_OPS \
   EFL_CANVAS_GROUP_ADD_DEL_OPS(efl_ui_table)

#include "efl_ui_table.eo.c"
