#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <Eina.h>
#include <Efl.h>
#include <Ecore.h>

#include "ecore_private.h"
#include "efl_composite_model.eo.h"

typedef struct _Efl_Composite_Model_Data Efl_Composite_Model_Data;

struct _Efl_Composite_Model_Data
{
   EINA_RBTREE;

   Efl_Composite_Model *self;
   Efl_Model *source;
   Eina_Rbtree *indexed;

   unsigned int index;

   Eina_Bool need_index : 1;
   Eina_Bool set_index : 1;
   Eina_Bool inserted : 1;
};

static Eina_Rbtree_Direction
_children_indexed_cmp(const Efl_Composite_Model_Data *left,
                      const Efl_Composite_Model_Data *right,
                      void *data EINA_UNUSED)
{
   if (left->index < right->index)
     return EINA_RBTREE_LEFT;
   return EINA_RBTREE_RIGHT;
}

static int
_children_indexed_key(const Efl_Composite_Model_Data *node,
                      const int *key, int length EINA_UNUSED, void *data EINA_UNUSED)
{
   if (node->index > (unsigned int) *key) return 1;
   if (node->index < (unsigned int) *key) return -1;
   return 0;
}

static void
_mark_greater(Efl_Composite_Model_Data *root, Eina_Array *mark, const unsigned int upper)
{
   if (!root) return ;

   if (root->index > upper)
     {
        eina_array_push(mark, root);
        _mark_greater((void*) EINA_RBTREE_GET(root)->son[0], mark, upper);
        _mark_greater((void*) EINA_RBTREE_GET(root)->son[1], mark, upper);
     }
   else
     {
        _mark_greater((void*) EINA_RBTREE_GET(root)->son[0], mark, upper);
     }
}

static void
_efl_composite_model_efl_object_invalidate(Eo *obj, Efl_Composite_Model_Data *pd)
{
   if (pd->inserted)
     {
        Eo *parent;

        parent = efl_parent_get(obj);
        if (efl_isa(parent, EFL_COMPOSITE_MODEL_CLASS))
          {
             Efl_Composite_Model_Data *ppd;

             ppd = efl_data_scope_get(parent, EFL_COMPOSITE_MODEL_CLASS);
             ppd->indexed = eina_rbtree_inline_remove(ppd->indexed, EINA_RBTREE_GET(pd),
                                                      EINA_RBTREE_CMP_NODE_CB(_children_indexed_cmp), NULL);
             pd->inserted = EINA_FALSE;
          }
        else
          {
             ERR("Unexpected parent change during the life of object: %s this might lead to crash.", efl_debug_name_get(obj));
          }
     }

   efl_invalidate(efl_super(obj, EFL_COMPOSITE_MODEL_CLASS));
}

static Efl_Object *
_efl_composite_model_efl_object_finalize(Eo *obj, Efl_Composite_Model_Data *pd)
{
   Eo *parent;

   if (pd->source == NULL)
     {
        ERR("Source of the composite model wasn't defined at construction time.");
        return NULL;
     }

   pd->self = obj;

   parent = efl_parent_get(obj);
   if (efl_isa(parent, EFL_COMPOSITE_MODEL_CLASS) && !pd->inserted)
     {
        Efl_Composite_Model_Data *ppd = efl_data_scope_get(parent, EFL_COMPOSITE_MODEL_CLASS);
        Efl_Composite_Model_Data *lookup;

        lookup = (void*) eina_rbtree_inline_lookup(ppd->indexed, &pd->index, sizeof (int),
                                                   EINA_RBTREE_CMP_KEY_CB(_children_indexed_key), NULL);
        if (lookup)
          {
             // There is already an object at this index, we should not
             // build anything different than what exist. Returning existing one.
             return lookup->self;
          }
        else
          {
             ppd->indexed = eina_rbtree_inline_insert(ppd->indexed, EINA_RBTREE_GET(pd),
                                                      EINA_RBTREE_CMP_NODE_CB(_children_indexed_cmp), NULL);

             pd->inserted = EINA_TRUE;
          }
     }

   return obj;
}

static void
_efl_composite_model_index_set(Eo *obj EINA_UNUSED, Efl_Composite_Model_Data *pd, unsigned int index)
{
   if (pd->set_index || !pd->source)
     return ;
   pd->index = index;
   pd->set_index = EINA_TRUE;
}

static unsigned int
_efl_composite_model_index_get(const Eo *obj, Efl_Composite_Model_Data *pd)
{
   Eina_Value *fetch = NULL;
   unsigned int r = 0xFFFFFFFF;

   if (pd->set_index)
     return pd->index;
   if (pd->need_index)
     return 0xFFFFFFFF;

   fetch = efl_model_property_get(obj, EFL_COMPOSITE_MODEL_CHILD_INDEX);
   if (!eina_value_uint_convert(fetch, &r))
     return 0xFFFFFFFF;
   eina_value_free(fetch);

   return r;
}

static void
_efl_composite_model_child_event(Efl_Composite_Model_Data *pd,
                                 const Efl_Model_Children_Event *ev,
                                 const Efl_Event_Description *description)
{
   Efl_Composite_Model_Data *cpd;
   Efl_Model_Children_Event cev = { 0 };
   Eina_Array mark;
   Eina_Array_Iterator iterator;
   unsigned int i;

   cev.index = ev->index;
   if (ev->child)
     {
        cev.child = _efl_composite_lookup(efl_class_get(pd->self),
                                          pd->self, ev->child, ev->index);
     }
   else
     {
        cpd = (void*) eina_rbtree_inline_lookup(pd->indexed, &cev.index, sizeof (unsigned int),
                                                EINA_RBTREE_CMP_KEY_CB(_children_indexed_key), NULL);
        if (cpd) cev.child = efl_ref(cpd->self);
     }

   if (cev.child && description == EFL_MODEL_EVENT_CHILD_REMOVED)
     {
        cpd = efl_data_scope_get(cev.child, EFL_COMPOSITE_MODEL_CLASS);

        // Remove child from lookup tree if it exist before triggering anything further
        pd->indexed = eina_rbtree_inline_remove(pd->indexed, EINA_RBTREE_GET(cpd),
                                                EINA_RBTREE_CMP_NODE_CB(_children_indexed_cmp), NULL);
        cpd->inserted = EINA_FALSE;
        efl_replace(&cpd->source, NULL);
     }

   // Update all index above this one if necessaryy
   eina_array_step_set(&mark, sizeof (Eina_Array), 8);
   _mark_greater((void*) pd->indexed, &mark, cev.index);

   // Correct index of the object stored that need to
   // There is no need to remove and reinsert them as their relative order will not change.
   EINA_ARRAY_ITER_NEXT(&mark, i, cpd, iterator)
     {
        if (description == EFL_MODEL_EVENT_CHILD_REMOVED) cpd->index--;
        else cpd->index++;

        efl_ref(cpd->self);
     }

   efl_event_callback_call(pd->self, description, &cev);

   // Notify of the index change only after notifying of the removal top avoid overlap
   EINA_ARRAY_ITER_NEXT(&mark, i, cpd, iterator)
      {
         efl_model_properties_changed(cpd->self, EFL_COMPOSITE_MODEL_CHILD_INDEX);
         efl_unref(cpd->self);
      }
   eina_array_flush(&mark);

   efl_unref(cev.child);
}

static void
_efl_composite_model_child_added(void *data, const Efl_Event *event)
{
   Efl_Composite_Model_Data *pd = data;
   Efl_Model_Children_Event *ev = event->info;

   _efl_composite_model_child_event(pd, ev, EFL_MODEL_EVENT_CHILD_ADDED);
}

static void
_efl_composite_model_child_removed(void *data, const Efl_Event *event)
{
   Efl_Composite_Model_Data *pd = data;
   Efl_Model_Children_Event *ev = event->info;

   _efl_composite_model_child_event(pd, ev, EFL_MODEL_EVENT_CHILD_REMOVED);
}

EFL_CALLBACKS_ARRAY_DEFINE(composite_callbacks,
                           { EFL_MODEL_EVENT_CHILD_ADDED, _efl_composite_model_child_added },
                           { EFL_MODEL_EVENT_CHILD_REMOVED, _efl_composite_model_child_removed });

static void
_efl_composite_model_efl_ui_view_model_set(Eo *obj EINA_UNUSED, Efl_Composite_Model_Data *pd, Efl_Model *model)
{
   Eina_Iterator *properties;
   const char *property;

   if (pd->source != NULL)
     {
        ERR("Source already set for composite model. It can only be set once.");
        return ;
     }
   pd->source = efl_ref(model);

   efl_event_callback_array_add(model, composite_callbacks(), pd);
   efl_event_callback_forwarder_priority_add(model, EFL_MODEL_EVENT_CHILDREN_COUNT_CHANGED, EFL_CALLBACK_PRIORITY_BEFORE, obj);
   efl_event_callback_forwarder_priority_add(model, EFL_MODEL_EVENT_PROPERTIES_CHANGED, EFL_CALLBACK_PRIORITY_BEFORE, obj);

   pd->need_index = EINA_TRUE;

   properties = efl_model_properties_get(pd->source);
   EINA_ITERATOR_FOREACH(properties, property)
     {
        if (eina_streq(property, EFL_COMPOSITE_MODEL_CHILD_INDEX))
          {
             pd->need_index = EINA_FALSE;
             break;
          }
     }
   eina_iterator_free(properties);
}

static Efl_Model *
_efl_composite_model_efl_ui_view_model_get(const Eo *obj EINA_UNUSED, Efl_Composite_Model_Data *pd)
{
   return pd->source;
}

static Eina_Future *
_efl_composite_model_efl_model_property_set(Eo *obj, Efl_Composite_Model_Data *pd,
                                            const char *property, Eina_Value *value)
{
   if (pd->need_index && eina_streq(property, EFL_COMPOSITE_MODEL_CHILD_INDEX))
     {
        if (pd->set_index || !pd->source)
          return efl_loop_future_rejected(obj, EFL_MODEL_ERROR_READ_ONLY);
        if (!eina_value_uint_convert(value, &pd->index))
          return efl_loop_future_rejected(obj, EFL_MODEL_ERROR_UNKNOWN);
        pd->set_index = EINA_TRUE;
        return efl_loop_future_resolved(obj, eina_value_uint_init(pd->index));
     }
   return efl_model_property_set(pd->source, property, value);
}

static Eina_Value *
_efl_composite_model_efl_model_property_get(const Eo *obj EINA_UNUSED, Efl_Composite_Model_Data *pd,
                                            const char *property)
{
   Eina_Value *try;
   if (pd->need_index && eina_streq(property, EFL_COMPOSITE_MODEL_CHILD_INDEX))
     {
        if (pd->set_index)
          return eina_value_uint_new(pd->index);
        return eina_value_error_new(EAGAIN);
     }
   try = efl_model_property_get(efl_super(obj, EFL_COMPOSITE_MODEL_CLASS), property);
   if (eina_value_type_get(try) == EINA_VALUE_TYPE_ERROR)
     {
        Eina_Error err = EINA_ERROR_NOT_IMPLEMENTED;

        if (eina_value_error_get(try, &err) && (err == EINA_ERROR_NOT_IMPLEMENTED))
          {
             eina_value_free(try);
             return efl_model_property_get(pd->source, property);
          }
     }
   return try;
}

static Eina_Iterator *
_efl_composite_model_efl_model_properties_get(const Eo *obj EINA_UNUSED, Efl_Composite_Model_Data *pd)
{
   if (pd->need_index)
     {
        static const char *composite_properties[] = {
          EFL_COMPOSITE_MODEL_CHILD_INDEX
        };

        return eina_multi_iterator_new(efl_model_properties_get(pd->source),
                                       EINA_C_ARRAY_ITERATOR_NEW(composite_properties));
     }
   return efl_model_properties_get(pd->source);
}

static unsigned int
_efl_composite_model_efl_model_children_count_get(const Eo *obj EINA_UNUSED, Efl_Composite_Model_Data *pd)
{
   return efl_model_children_count_get(pd->source);
}

typedef struct _Efl_Composite_Model_Slice_Request Efl_Composite_Model_Slice_Request;
struct _Efl_Composite_Model_Slice_Request
{
   const Efl_Class *self;
   Eo *parent;
   unsigned int start;
   unsigned int dummy_need;
};

static Eina_Value
_efl_composite_model_then(Eo *o EINA_UNUSED, void *data, const Eina_Value v)
{
   Efl_Composite_Model_Slice_Request *req = data;
   unsigned int i, len;
   Eina_Value r = EINA_VALUE_EMPTY;
   Eo *target = NULL;

   eina_value_array_setup(&r, EINA_VALUE_TYPE_OBJECT, 4);

   EINA_VALUE_ARRAY_FOREACH(&v, len, i, target)
     {
        Eo *composite;

        // Fetch an existing composite model for this model or create a new one if none exist
        composite = _efl_composite_lookup(req->self, req->parent, target, req->start + i);
        if (!composite) continue;

        eina_value_array_append(&r, composite);
        // Dropping this scope reference
        efl_unref(composite);
     }

   while (req->dummy_need)
     {
        Eo *dummy, *dummy_proxy;

        // Create a dummy object and its proxy
        dummy = efl_add(EFL_GENERIC_MODEL_CLASS, req->parent);
        dummy_proxy = efl_add_ref(req->self, req->parent,
                                  efl_ui_view_model_set(efl_added, dummy),
                                  efl_composite_model_index_set(efl_added, req->start + i),
                                  efl_loop_model_volatile_make(efl_added));
        efl_parent_set(dummy, dummy_proxy);

        eina_value_array_append(&r, dummy_proxy);
        efl_unref(dummy_proxy);

        req->dummy_need--;
        i++;
     }

   return r;
}

static void
_efl_composite_model_clean(Eo *o EINA_UNUSED, void *data, const Eina_Future *dead_future EINA_UNUSED)
{
   Efl_Composite_Model_Slice_Request *req = data;

   efl_unref(req->parent);
   free(data);
}

static Eina_Future *
_efl_composite_model_efl_model_children_slice_get(Eo *obj,
                                                  Efl_Composite_Model_Data *pd,
                                                  unsigned int start,
                                                  unsigned int count)
{
   Efl_Composite_Model_Slice_Request *req;
   Eina_Future *f;
   unsigned int source_count, self_count;
   unsigned int req_count;

   source_count = efl_model_children_count_get(pd->source);
   self_count = efl_model_children_count_get(obj);

   if (start + count > source_count &&
       start + count > self_count)
     return efl_loop_future_rejected(obj, EFL_MODEL_ERROR_INCORRECT_VALUE);

   if (start > source_count)
     {
        Eina_Value r = EINA_VALUE_EMPTY;
        unsigned int i = 0;

        eina_value_array_setup(&r, EINA_VALUE_TYPE_OBJECT, 4);

        while (count)
          {
             Eo *dummy, *dummy_proxy;

             // Create a dummy object and its proxy
             dummy = efl_add(EFL_GENERIC_MODEL_CLASS, obj);
             dummy_proxy = efl_add_ref(efl_class_get(obj), obj,
                                       efl_ui_view_model_set(efl_added, dummy),
                                       efl_composite_model_index_set(efl_added, start + i),
                                       efl_loop_model_volatile_make(efl_added));
             efl_parent_set(dummy, dummy_proxy);

             eina_value_array_append(&r, dummy_proxy);
             efl_unref(dummy_proxy);

             count--;
             i++;
          }

        return efl_loop_future_resolved(obj, r);
     }

   req_count = start + count > source_count ? source_count - start : count;
   f = efl_model_children_slice_get(pd->source, start, req_count);
   if (!f) return NULL;

   req = malloc(sizeof (Efl_Composite_Model_Slice_Request));
   if (!req) return efl_loop_future_rejected(obj, ENOMEM);

   req->self = efl_class_get(obj);
   req->parent = efl_ref(obj);
   req->start = start;
   if (start + count < source_count)
     req->dummy_need = 0;
   else
     req->dummy_need = count - (source_count - start);

   return efl_future_then(obj, f, .success_type = EINA_VALUE_TYPE_ARRAY,
                          .success = _efl_composite_model_then,
                          .free = _efl_composite_model_clean,
                          .data = req);
}

static Efl_Object *
_efl_composite_model_efl_model_child_add(Eo *obj EINA_UNUSED,
                                         Efl_Composite_Model_Data *pd)
{
   return efl_model_child_add(pd->source);
}

static void
_efl_composite_model_efl_model_child_del(Eo *obj EINA_UNUSED,
                                         Efl_Composite_Model_Data *pd,
                                         Efl_Object *child)
{
   efl_model_child_del(pd->source, efl_ui_view_model_get(child));
}

static void
_efl_composite_model_efl_object_destructor(Eo *obj, Efl_Composite_Model_Data *pd)
{
   if (pd->source)
     {
        efl_event_callback_array_del(pd->source, composite_callbacks(), pd);
        efl_event_callback_forwarder_del(pd->source, EFL_MODEL_EVENT_CHILDREN_COUNT_CHANGED, obj);
        efl_event_callback_forwarder_del(pd->source, EFL_MODEL_EVENT_PROPERTIES_CHANGED, obj);

        efl_replace(&pd->source, NULL);
     }

   efl_destructor(efl_super(obj, EFL_COMPOSITE_MODEL_CLASS));
}

#include "efl_composite_model.eo.c"
