#define EFL_GFX_HINT_PROTECTED

#include "efl_ui_box_private.h"
#include "efl_ui_container_layout.h"

// FIXME: handle RTL? just invert the horizontal order?

typedef struct _Item_Calc Item_Calc;

struct _Item_Calc
{
   EINA_INLIST;

   Evas_Object *obj;
   double weight_factor;
   Efl_Ui_Container_Item_Hints hints[2]; /* 0 is x-axis, 1 is y-axis */
};

static int
_weight_sort_cb(const void *l1, const void *l2)
{
   Item_Calc *it1, *it2;

   it1 = EINA_INLIST_CONTAINER_GET(l1, Item_Calc);
   it2 = EINA_INLIST_CONTAINER_GET(l2, Item_Calc);

   return it2->weight_factor <= it1->weight_factor ? -1 : 1;
}

/* this function performs a simplified layout when the box has changed position
 * but no other changes have occurred, e.g., when a box is being scrolled
 */
static void
_efl_ui_box_layout_simple(Efl_Ui_Box *ui_box, Efl_Ui_Box_Data *pd)
{
   Eo *child;
   Eina_List *l;
   Eina_Position2D pos = efl_gfx_entity_position_get(ui_box);

   EINA_LIST_FOREACH(pd->children, l, child)
     {
        Eina_Position2D child_pos = efl_gfx_entity_position_get(child);

        efl_gfx_entity_position_set(child,
          EINA_POSITION2D(pos.x - pd->last_pos.x + child_pos.x,
                          pos.y - pd->last_pos.y + child_pos.y));
     }
   pd->last_pos = pos;
   efl_event_callback_call(ui_box, EFL_PACK_EVENT_LAYOUT_UPDATED, NULL);
}

void
_efl_ui_box_custom_layout(Efl_Ui_Box *ui_box, Efl_Ui_Box_Data *pd)
{
   Eo *child;
   Eina_List *li;
   Eina_Inlist *inlist = NULL;
   Item_Calc *items, *item;
   Efl_Ui_Container_Item_Hints *hints, *hint;
   Eina_Bool axis = !efl_ui_layout_orientation_is_horizontal(pd->dir, EINA_FALSE);
   Eina_Bool r_axis = !axis;
   int want[2] = { 0, 0 };
   int count, i = 0;
   double cur_pos, mmin = 0, weight_sum = 0;
   Efl_Ui_Container_Layout_Calc box_calc[2]; /* 0 is x-axis, 1 is y-axis */


   count = eina_list_count(pd->children);
   if (!count)
     {
        efl_gfx_hint_size_restricted_min_set(ui_box, EINA_SIZE2D(0, 0));
        return;
     }
   if (!pd->full_recalc)
     {
        _efl_ui_box_layout_simple(ui_box, pd);
        return;
     }
   _efl_ui_container_layout_init(ui_box, box_calc);
   pd->last_pos.x = box_calc[0].pos - box_calc[0].margin[0];
   pd->last_pos.y = box_calc[1].pos - box_calc[1].margin[0];

   /* Item_Calc struct is currently 152 bytes.
    * this is pretty big to be allocating a huge number of, and we don't want to explode the stack
    */
   if (count >= 500)
     {
        items = malloc(count * sizeof(*items));
        EINA_SAFETY_ON_NULL_RETURN(items);
     }
   else
     items = alloca(count * sizeof(*items));
#ifdef DEBUG
   memset(items, 0, count * sizeof(*items));
#endif

   // scan all items, get their properties, calculate total weight & min size
   EINA_LIST_FOREACH(pd->children, li, child)
     {
        item = &items[i++];
        item->obj = child;
        hints = item->hints;

        _efl_ui_container_layout_item_init(item->obj, hints);

        if (pd->homogeneous || box_calc[0].fill)
          hints[0].weight = 1;
        else if (hints[0].weight < 0)
          hints[0].weight = 0;

        if (pd->homogeneous || box_calc[1].fill)
          hints[1].weight = 1;
        else if (hints[1].weight < 0)
          hints[1].weight = 0;

        weight_sum += hints[axis].weight;

        if (hints[r_axis].space > want[r_axis])
          want[r_axis] = hints[r_axis].space;

        if (pd->homogeneous)
          {
             if (hints[axis].space > mmin)
               mmin = hints[axis].space;
          }
        else
          {
             want[axis] += hints[axis].space;
          }
     }

   // total space & available space
   if (pd->homogeneous)
     want[axis] = mmin * count;

   if (box_calc[r_axis].size < want[r_axis])
     box_calc[r_axis].size = want[r_axis];

   // padding can not be squeezed (note: could make it an option)
   box_calc[axis].size -= (box_calc[axis].pad * (count - 1));
   box_calc[r_axis].pad = 0;
   cur_pos = box_calc[axis].pos;

   // calculate weight size
   if (!pd->homogeneous && (box_calc[axis].size > want[axis]) && (weight_sum > 0))
     {
        int orig_size, calc_size;
        double orig_weight = weight_sum;

        calc_size = orig_size = box_calc[axis].size;

        for (i = 0; i < count; i++)
          {
             double denom;
             hint = &items[i].hints[axis];

             denom = (hint->weight * orig_size) - (orig_weight * hint->space);
             if (denom > 0)
               {
                  items[i].weight_factor = (hint->weight * orig_size) / denom;
                  inlist = eina_inlist_sorted_insert(inlist, EINA_INLIST_GET(&items[i]),
                                                     _weight_sort_cb);

               }
             else
               {
                  calc_size -= hint->space;
                  weight_sum -= hint->weight;
               }
          }

        EINA_INLIST_FOREACH(inlist, item)
          {
             double weight_len;
             hint = &item->hints[axis];

             weight_len = (calc_size * hint->weight) / weight_sum;
             if (hint->space < weight_len)
               {
                  hint->space = weight_len;
               }
             else
               {
                  weight_sum -= hint->weight;
                  calc_size -= hint->space;
               }
          }
     }

   // calculate item geometry
     {
        int item_size[2], item_pos[2], sw, sh;

        if (box_calc[axis].size > want[axis])
          {
             if (pd->homogeneous)
               mmin = (double)box_calc[axis].size / count;
             else if (EINA_DBL_EQ(weight_sum, 0))
               cur_pos += (box_calc[axis].size - want[axis]) * box_calc[axis].align;
          }

        for (i = 0; i < count; i++)
          {
             hints = items[i].hints;

             if (pd->homogeneous)
               hints[axis].space = mmin;
             hints[r_axis].space = box_calc[r_axis].size;
             sw = hints[0].space - (hints[0].margin[0] + hints[0].margin[1]);
             sh = hints[1].space - (hints[1].margin[0] + hints[1].margin[1]);

             item_size[0] = ((hints[0].weight > 0) && hints[0].fill) ? sw : 0;
             item_size[1] = ((hints[1].weight > 0) && hints[1].fill) ? sh : 0;

             _efl_ui_container_layout_min_max_calc(hints, &item_size[0], &item_size[1],
                                (hints[0].aspect > 0) && (hints[1].aspect > 0));

             item_pos[axis] = cur_pos + 0.5;
             item_pos[r_axis] = box_calc[r_axis].pos;

             item_pos[0] += (hints[0].margin[0] +
                             ((sw - item_size[0]) * hints[0].align));
             item_pos[1] += (hints[1].margin[0] +
                             ((sh - item_size[1]) * hints[1].align));

             cur_pos += hints[axis].space + box_calc[axis].pad;

             efl_gfx_entity_geometry_set(items[i].obj,
                                         EINA_RECT(item_pos[0], item_pos[1],
                                                   item_size[0], item_size[1]));
          }
     }
   want[0] += (box_calc[0].margin[0] + box_calc[0].margin[1]) +
              (box_calc[0].pad * (count - 1));
   want[1] += (box_calc[1].margin[0] + box_calc[1].margin[1]) +
              (box_calc[1].pad * (count - 1));

   pd->full_recalc = EINA_FALSE;
   efl_gfx_hint_size_restricted_min_set(ui_box, EINA_SIZE2D(want[0], want[1]));

   efl_event_callback_call(ui_box, EFL_PACK_EVENT_LAYOUT_UPDATED, NULL);
   if (count >= 500) free(items);
}
