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

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

#include "ecore_internal.h"

typedef struct _Efl_Ui_View_Model_Data Efl_Ui_View_Model_Data;
typedef struct _Efl_Ui_View_Model_Bind Efl_Ui_View_Model_Bind;
typedef struct _Efl_Ui_View_Model_Text Efl_Ui_View_Model_Text;
typedef struct _Efl_Ui_View_Model_Logic Efl_Ui_View_Model_Logic;
typedef struct _Efl_Ui_View_Model_Property_Ref Efl_Ui_View_Model_Property_Ref;

struct _Efl_Ui_View_Model_Data
{
   // FIXME: If parent is set, always access parent... recursively?
   Efl_Ui_View_Model_Data *parent;

   Eina_Hash *bound; // Stringhash of Efl_Ui_View_Model_Bind
   Eina_Hash *logics; // Stringhash of Efl_Ui_View_Model_Logic
   Eina_Hash *texts; // Stringhash of Efl_Ui_View_Model_Text

   Eina_Hash *deduplication; // Stringhash of Efl_Ui_View_Model_Property_Ref

   struct {
      Eina_Bool property_changed : 1;
      Eina_Bool child_added : 1;
      Eina_Bool child_removed : 1;
   } propagating; // Boolean to prevent reentrance event emission on the same object
   Eina_Bool finalized : 1;
   Eina_Bool children_bind : 1; // Define if child object should be automatically bound
};

struct _Efl_Ui_View_Model_Text
{
   Eina_Stringshare *name;
   Eina_Stringshare *definition;
   Eina_Stringshare *not_ready;
   Eina_Stringshare *on_error;
   Efl_Model *self;
};

struct _Efl_Ui_View_Model_Bind
{
   Eina_Stringshare *source;
   Eina_List *destinations;
};

struct _Efl_Ui_View_Model_Logic
{
   struct {
      EflUiViewModelPropertyGet fct;
      Eina_Free_Cb free_cb;
      void *data;
   } get;
   struct {
      EflUiViewModelPropertySet fct;
      Eina_Free_Cb free_cb;
      void *data;
   } set;

   Efl_Object *object;
   Eina_List *sources;
   Eina_Stringshare *property;
};

struct _Efl_Ui_View_Model_Property_Ref
{
   EINA_REFCOUNT;
   Eina_Stringshare *property;
};

static void
_ref_free(void *data)
{
   Efl_Ui_View_Model_Property_Ref *r = data;

   eina_stringshare_del(r->property);
   free(r);
}

static void
_ref_add(Efl_Ui_View_Model_Data *pd, Eina_Stringshare *property)
{
   Efl_Ui_View_Model_Property_Ref *r;

   r = eina_hash_find(pd->deduplication, property);
   if (!r)
     {
        r = calloc(1, sizeof (Efl_Ui_View_Model_Property_Ref));
        if (!r) return ;
        r->property = eina_stringshare_ref(property);

        eina_hash_direct_add(pd->deduplication, r->property, r);
     }

   EINA_REFCOUNT_REF(r);
}

static void
_ref_del(Efl_Ui_View_Model_Data *pd, Eina_Stringshare *property)
{
   Efl_Ui_View_Model_Property_Ref *r;

   r = eina_hash_find(pd->deduplication, property);
   if (!r) return ;

   EINA_REFCOUNT_UNREF(r)
     eina_hash_del(pd->deduplication, property, r);
}

static void
_logic_free(void *data)
{
   Efl_Ui_View_Model_Logic *logic = data;
   Eina_Stringshare *source;

   if (logic->get.free_cb) logic->get.free_cb(logic->get.data);
   if (logic->set.free_cb) logic->set.free_cb(logic->set.data);
   EINA_LIST_FREE(logic->sources, source)
     {
        efl_ui_view_model_property_unbind(logic->object, source, logic->property);
        eina_stringshare_del(source);
     }
   eina_stringshare_del(logic->property);
   free(logic);
}

static Eina_Value *
_efl_ui_view_model_property_dummy_get(void *data EINA_UNUSED,
                                   const Efl_Ui_View_Model *view_model EINA_UNUSED,
                                   Eina_Stringshare *property EINA_UNUSED)
{
   return eina_value_error_new(EFL_MODEL_ERROR_NOT_SUPPORTED);
}

static Eina_Future *
_efl_ui_view_model_property_dummy_set(void *data EINA_UNUSED,
                                   Efl_Ui_View_Model *view_model,
                                   Eina_Stringshare *property EINA_UNUSED,
                                   Eina_Value *value EINA_UNUSED)
{
   return efl_loop_future_rejected(view_model, EFL_MODEL_ERROR_READ_ONLY);
}

static Eina_Error
_efl_ui_view_model_property_logic_add(Eo *obj, Efl_Ui_View_Model_Data *pd,
                                   const char *property,
                                   void *get_data, EflUiViewModelPropertyGet get, Eina_Free_Cb get_free_cb,
                                   void *set_data, EflUiViewModelPropertySet set, Eina_Free_Cb set_free_cb,
                                   Eina_Iterator *bound)
{
   Efl_Ui_View_Model_Logic *logic;
   Eina_Stringshare *prop;
   const char *source;

   prop = eina_stringshare_add(property);

   if (eina_hash_find(pd->logics, prop))
     {
        eina_stringshare_del(prop);
        return EFL_MODEL_ERROR_INCORRECT_VALUE;
     }

   logic = calloc(1, sizeof (Efl_Ui_View_Model_Logic));
   if (!logic) return ENOMEM;

   logic->object = obj;
   logic->property = prop;
   logic->get.fct = get ? get : _efl_ui_view_model_property_dummy_get;
   logic->get.free_cb = get_free_cb;
   logic->get.data = get_data;
   logic->set.fct = set ? set : _efl_ui_view_model_property_dummy_set;
   logic->set.free_cb = set_free_cb;
   logic->set.data = set_data;

   eina_hash_direct_add(pd->logics, prop, logic);

   EINA_ITERATOR_FOREACH(bound, source)
     {
        logic->sources = eina_list_append(logic->sources, eina_stringshare_add(source));
        efl_ui_view_model_property_bind(obj, source, property);
     }
   eina_iterator_free(bound);

   return 0;
}

static Eina_Error
_efl_ui_view_model_property_logic_del(Eo *obj EINA_UNUSED, Efl_Ui_View_Model_Data *pd,
                                   const char *property)
{
   Efl_Ui_View_Model_Logic *logic;

   logic = eina_hash_find(pd->logics, property);
   if (!logic) return EFL_MODEL_ERROR_INCORRECT_VALUE;
   eina_hash_del(pd->logics, property, logic);
   return 0;
}

static int
_lookup_next_token(const char *definition,
                   Eina_Slstr **text,
                   Eina_Slstr **property)
{
   const char *lookup_text;
   const char *lookup_property;

   if (!definition) return 0;

   *text = NULL;
   *property = NULL;

   lookup_text = strstr(definition, "${");
   if (!lookup_text) goto on_error;
   lookup_text += 2;

   lookup_property = strchr(lookup_text, '}');
   if (!lookup_property) goto on_error;

   *text = eina_slstr_copy_new_length(definition, lookup_text - definition - 2);
   *property = eina_slstr_copy_new_length(lookup_text, lookup_property - lookup_text);

   return lookup_property + 1 - definition;

 on_error:
   if (strlen(definition) == 0) return 0;
   *text = eina_slstr_copy_new(definition);
   return strlen(definition);
}

static Eina_Error
_efl_ui_view_model_property_string_add(Eo *obj, Efl_Ui_View_Model_Data *pd,
                                    const char *name,
                                    const char *definition,
                                    const char *not_ready,
                                    const char *on_error)
{
   Efl_Ui_View_Model_Text *text;
   Eina_Stringshare *sn;
   Eina_Slstr *st = NULL;
   Eina_Slstr *sp = NULL;
   int lookup;
   Eina_Error err = ENOMEM;

   if (!name || !definition) return EFL_MODEL_ERROR_INCORRECT_VALUE;
   if (!strlen(name)) return EFL_MODEL_ERROR_INCORRECT_VALUE;
   if (!strlen(definition)) return EFL_MODEL_ERROR_INCORRECT_VALUE;
   sn = eina_stringshare_add(name);

   // Lookup if there is an existing property defined and undo it first
   text = eina_hash_find(pd->texts, sn);
   if (text) efl_ui_view_model_property_string_del(obj, sn);

   text = calloc(1, sizeof (Efl_Ui_View_Model_Text));
   if (!text) goto on_error;

   err = EFL_MODEL_ERROR_INCORRECT_VALUE;

   text->name = eina_stringshare_add(name);
   text->definition = eina_stringshare_add(definition);
   text->not_ready = not_ready ? eina_stringshare_add(not_ready) : NULL;
   text->on_error = on_error ? eina_stringshare_add(on_error) : NULL;
   text->self = obj;

   for (lookup = _lookup_next_token(definition, &st, &sp);
        lookup;
        definition += lookup, lookup = _lookup_next_token(definition, &st, &sp))
     {
        if (sp) efl_ui_view_model_property_bind(obj, sp, name);
     }

   for (lookup = _lookup_next_token(not_ready, &st, &sp);
        lookup;
        not_ready += lookup, lookup = _lookup_next_token(not_ready, &st, &sp))
     {
        if (sp) efl_ui_view_model_property_bind(obj, sp, name);
     }

   for (lookup = _lookup_next_token(on_error, &st, &sp);
        lookup;
        on_error += lookup, lookup = _lookup_next_token(on_error, &st, &sp))
     {
        if (sp) efl_ui_view_model_property_bind(obj, sp, name);
     }

   eina_hash_direct_add(pd->texts, text->name, text);

   return 0;

 on_error:
   eina_stringshare_del(sn);
   free(text);
   return err;
}

static void
_text_free(void *data)
{
   Efl_Ui_View_Model_Text *text = data;
   Eina_Stringshare *st;
   Eina_Stringshare *sp;
   int lookup;
   const char *tmp;

   tmp = text->definition;
   for (lookup = _lookup_next_token(tmp, &st, &sp);
        lookup;
        tmp += lookup, lookup = _lookup_next_token(tmp, &st, &sp))
     {
        if (sp) efl_ui_view_model_property_unbind(text->self, sp, text->name);
     }

   tmp = text->not_ready;
   for (lookup = _lookup_next_token(tmp, &st, &sp);
        lookup;
        tmp += lookup, lookup = _lookup_next_token(tmp, &st, &sp))
     {
        if (sp) efl_ui_view_model_property_unbind(text->self, sp, text->name);
     }

   tmp = text->on_error;
   for (lookup = _lookup_next_token(tmp, &st, &sp);
        lookup;
        tmp += lookup, lookup = _lookup_next_token(tmp, &st, &sp))
     {
        if (sp) efl_ui_view_model_property_unbind(text->self, sp, text->name);
     }

   eina_stringshare_del(text->name);
   eina_stringshare_del(text->not_ready);
   eina_stringshare_del(text->on_error);
   free(text);
}

static Eina_Error
_efl_ui_view_model_property_string_del(Eo *obj EINA_UNUSED,
                                    Efl_Ui_View_Model_Data *pd,
                                    const char *name)
{
   Efl_Ui_View_Model_Text *text;
   Eina_Stringshare *sn;
   Eina_Error err = EFL_MODEL_ERROR_INCORRECT_VALUE;

   if (!name) return EFL_MODEL_ERROR_INCORRECT_VALUE;

   sn = eina_stringshare_add(name);
   text = eina_hash_find(pd->texts, sn);
   if (!text) goto on_error;
   eina_hash_del(pd->texts, sn, text);
   err = 0;

 on_error:
   eina_stringshare_del(sn);
   return err;
}

static void
_efl_ui_view_model_property_bind(Eo *obj EINA_UNUSED, Efl_Ui_View_Model_Data *pd,
                              const char *source, const char *destination)
{
   Efl_Ui_View_Model_Bind *bind;
   Eina_Stringshare *src;
   Eina_Stringshare *dst;

   if (!source || !destination) return ;

   src = eina_stringshare_add(source);
   bind = eina_hash_find(pd->bound, src);
   if (!bind)
     {
        bind = calloc(1, sizeof (Efl_Ui_View_Model_Bind));
        if (!bind) goto on_error;
        bind->source = eina_stringshare_ref(src);

        eina_hash_direct_add(pd->bound, bind->source, bind);
     }

   dst = eina_stringshare_add(destination);
   bind->destinations = eina_list_append(bind->destinations, dst);
   _ref_add(pd, dst);

 on_error:
   eina_stringshare_del(src);
}

static void
_efl_ui_view_model_property_unbind(Eo *obj EINA_UNUSED, Efl_Ui_View_Model_Data *pd,
                                const char *source, const char *destination)
{
   Efl_Ui_View_Model_Bind *bind;
   Eina_Stringshare *src;
   Eina_Stringshare *dst;
   Eina_Stringshare *cmp;
   Eina_List *l;

   if (!source || !destination) return ;
   src = eina_stringshare_add(source);
   bind = eina_hash_find(pd->bound, src);
   if (!bind) goto on_error;

   dst = eina_stringshare_add(destination);

   EINA_LIST_FOREACH(bind->destinations, l, cmp)
     if (cmp == dst)
       {
          bind->destinations = eina_list_remove_list(bind->destinations, l);
          break;
       }

   if (!bind->destinations)
     eina_hash_del(pd->bound, dst, bind);

   _ref_del(pd, dst);
   eina_stringshare_del(dst);

 on_error:
   eina_stringshare_del(src);
}

static void
_bind_free(void *data)
{
   Efl_Ui_View_Model_Bind *bind = data;
   Eina_Stringshare *dst;

   eina_stringshare_del(bind->source);

   EINA_LIST_FREE(bind->destinations, dst)
     eina_stringshare_del(dst);

   free(bind);
}

static void
_efl_ui_view_model_property_bind_lookup(Eina_Array *changed_properties,
                                     Efl_Ui_View_Model_Data *pd,
                                     Eina_Stringshare *src)
{
   Efl_Ui_View_Model_Bind *bind;

   if (!pd) return ;
   bind = eina_hash_find(pd->bound, src);
   if (bind)
     {
        Eina_Stringshare *dest;
        Eina_List *l;

        EINA_LIST_FOREACH(bind->destinations, l, dest)
          {
             // Check for duplicated entry first to avoid infinite recursion
             Eina_Stringshare *dup = NULL;
             Eina_Array_Iterator iterator;
             unsigned int i;

             EINA_ARRAY_ITER_NEXT(changed_properties, i, dup, iterator)
               if (dup == dest) break;
             if (dup == dest) continue ;

             eina_array_push(changed_properties, dest);
             _efl_ui_view_model_property_bind_lookup(changed_properties, pd, dest);
          }
     }
   _efl_ui_view_model_property_bind_lookup(changed_properties, pd->parent, src);
}

static void
_efl_ui_view_model_property_changed(void *data, const Efl_Event *event)
{
   Efl_Ui_View_Model_Data *pd = data;
   Efl_Model_Property_Event *ev = event->info;
   Efl_Model_Property_Event nev = { 0 };
   const char *property;
   Eina_Stringshare *src;
   Eina_Array_Iterator iterator;
   unsigned int i;

   if (pd->propagating.property_changed) return ;
   pd->propagating.property_changed = EINA_TRUE;

   // Our strategy is to rebuild a new Property_Event and cancel the current one.
   efl_event_callback_stop(event->object);

   nev.changed_properties = eina_array_new(1);

   EINA_ARRAY_ITER_NEXT(ev->changed_properties, i, property, iterator)
     {
        eina_array_push(nev.changed_properties, property);

        src = eina_stringshare_ref(property);
        _efl_ui_view_model_property_bind_lookup(nev.changed_properties, pd, src);
     }

   efl_event_callback_call(event->object, EFL_MODEL_EVENT_PROPERTIES_CHANGED, &nev);

   eina_array_free(nev.changed_properties);

   pd->propagating.property_changed = EINA_FALSE;
}

static void
_efl_ui_view_model_children_bind_set(Eo *obj EINA_UNUSED, Efl_Ui_View_Model_Data *pd, Eina_Bool enable)
{
   if (pd->finalized) return;

   pd->children_bind = enable;
}

static Eina_Bool
_efl_ui_view_model_children_bind_get(const Eo *obj EINA_UNUSED, Efl_Ui_View_Model_Data *pd)
{
   return pd->children_bind;
}

static void
_efl_ui_view_model_parent_data(Efl_Ui_View_Model *child, Efl_Ui_View_Model_Data *ppd)
{
   Efl_Ui_View_Model_Data *cpd;

   cpd = efl_data_scope_get(child, EFL_UI_VIEW_MODEL_CLASS);
   cpd->parent = ppd;
   cpd->propagating = ppd->propagating;
}

static Efl_Ui_View_Model *
_efl_ui_view_model_child_lookup(Efl_Ui_View_Model_Data *pd, Efl_Object *parent, Efl_Model *view)
{
   EFL_COMPOSITE_LOOKUP_RETURN(co, parent, view, "_efl.ui.view_model");

   co = efl_add(EFL_UI_VIEW_MODEL_CLASS, parent,
                efl_ui_view_model_set(efl_added, view),
                _efl_ui_view_model_parent_data(efl_added, pd));
   if (!co) return NULL;

   EFL_COMPOSITE_REMEMBER_RETURN(co, view);
}

static void
_efl_ui_view_model_child_added(void *data, const Efl_Event *event)
{
   Efl_Model_Children_Event *ev = event->info;
   Efl_Model_Children_Event nevt = { 0 };
   Efl_Ui_View_Model_Data *pd = data;
   Efl_Ui_View_Model *co;

   if (pd->propagating.child_added) return ;
   if (!pd->children_bind) return;
   if (!ev->child) return;

   pd->propagating.child_added = EINA_TRUE;

   // Our strategy is to rebuild a new Child_Add and cancel the current one.
   efl_event_callback_stop(event->object);

   co = _efl_ui_view_model_child_lookup(pd, event->object, ev->child);
   if (!co) return;

   nevt.index = ev->index;
   nevt.child = co;

   efl_event_callback_call(event->object, EFL_MODEL_EVENT_CHILD_ADDED, &nevt);

   pd->propagating.child_added = EINA_FALSE;
}

static void
_efl_ui_view_model_child_removed(void *data, const Efl_Event *event)
{
   Efl_Model_Children_Event *ev = event->info;
   Efl_Model_Children_Event nevt = { 0 };
   Efl_Ui_View_Model_Data *pd = data;
   Efl_Ui_View_Model *co;

   if (pd->propagating.child_removed) return ;
   if (!pd->children_bind) return;
   if (!ev->child) return;

   pd->propagating.child_removed = EINA_TRUE;

   // Our strategy is to rebuild a new Child_Add and cancel the current one.
   efl_event_callback_stop(event->object);

   co = _efl_ui_view_model_child_lookup(pd, event->object, ev->child);
   if (!co) return;

   nevt.index = ev->index;
   nevt.child = co;

   efl_event_callback_call(event->object, EFL_MODEL_EVENT_CHILD_REMOVED, &nevt);

   // The object is being destroyed, there is no point in us keeping the ViewModel proxy alive.
   efl_del(co);

   pd->propagating.child_removed = EINA_FALSE;
}

EFL_CALLBACKS_ARRAY_DEFINE(efl_ui_view_model_intercept,
                           { EFL_MODEL_EVENT_PROPERTIES_CHANGED, _efl_ui_view_model_property_changed },
                           { EFL_MODEL_EVENT_CHILD_ADDED, _efl_ui_view_model_child_added },
                           { EFL_MODEL_EVENT_CHILD_REMOVED, _efl_ui_view_model_child_removed })

static Efl_Object *
_efl_ui_view_model_efl_object_constructor(Eo *obj, Efl_Ui_View_Model_Data *pd)
{
   obj = efl_constructor(efl_super(obj, EFL_UI_VIEW_MODEL_CLASS));

   pd->children_bind = EINA_TRUE;
   pd->bound = eina_hash_stringshared_new(_bind_free);
   pd->logics = eina_hash_stringshared_new(_logic_free);
   pd->deduplication = eina_hash_stringshared_new(_ref_free);
   pd->texts = eina_hash_stringshared_new(_text_free);

   efl_event_callback_array_priority_add(obj, efl_ui_view_model_intercept(), EFL_CALLBACK_PRIORITY_BEFORE, pd);

   return obj;
}

static Efl_Object *
_efl_ui_view_model_efl_object_finalize(Eo *obj, Efl_Ui_View_Model_Data *pd)
{
   pd->finalized = EINA_TRUE;

   return efl_finalize(efl_super(obj, EFL_UI_VIEW_MODEL_CLASS));
}

static void
_efl_ui_view_model_efl_object_destructor(Eo *obj, Efl_Ui_View_Model_Data *pd)
{
   efl_event_callback_array_del(obj, efl_ui_view_model_intercept(), pd);

   eina_hash_free(pd->bound);
   pd->bound = NULL;

   eina_hash_free(pd->logics);
   pd->logics = NULL;

   eina_hash_free(pd->texts);
   pd->texts = NULL;

   eina_hash_free(pd->deduplication);
   pd->deduplication = NULL;

   efl_destructor(efl_super(obj, EFL_UI_VIEW_MODEL_CLASS));
}

static Efl_Ui_View_Model_Logic *
_efl_ui_view_model_property_logic_lookup(Efl_Ui_View_Model_Data *pd, Eina_Stringshare *property)
{
   Efl_Ui_View_Model_Logic *logic;

   if (!pd) return NULL;
   logic = eina_hash_find(pd->logics, property);
   if (!logic) return _efl_ui_view_model_property_logic_lookup(pd->parent, property);
   return logic;
}

static Eina_Future *
_efl_ui_view_model_efl_model_property_set(Eo *obj, Efl_Ui_View_Model_Data *pd,
                                       const char *property, Eina_Value *value)
{
   Efl_Ui_View_Model_Logic *logic;
   Eina_Stringshare *prop;
   Eina_Future *f;

   prop = eina_stringshare_add(property);
   logic = _efl_ui_view_model_property_logic_lookup(pd, prop);
   if (logic)
     f = logic->set.fct(logic->get.data, obj, prop, value);
   else
     {
        if (eina_hash_find(pd->texts, prop))
          f = efl_loop_future_rejected(obj, EFL_MODEL_ERROR_READ_ONLY);
        else
          f = efl_model_property_set(efl_super(obj, EFL_UI_VIEW_MODEL_CLASS), property, value);
     }

   eina_stringshare_del(prop);
   return f;
}

static Eina_Value *
_efl_ui_view_model_text_generate(const Eo *obj,
                              Eina_Strbuf *out,
                              Eina_Stringshare *pattern,
                              Eina_Bool stop_on_error)
{
   Eina_Stringshare *st;
   Eina_Stringshare *sp;
   int lookup;

   for (lookup = _lookup_next_token(pattern, &st, &sp);
        lookup;
        pattern += lookup, lookup = _lookup_next_token(pattern, &st, &sp))
     {
        Eina_Value *request;
        char *sr;

        eina_strbuf_append(out, st);

        if (!sp) continue;

        request = efl_model_property_get(obj, sp);
        if (!request)
          {
             if (stop_on_error)
               return eina_value_error_new(EFL_MODEL_ERROR_NOT_SUPPORTED);
             eina_strbuf_append(out, "Unknown property");
             continue;
          }
        if (eina_value_type_get(request) == EINA_VALUE_TYPE_ERROR && stop_on_error)
          return request;

        sr = eina_value_to_string(request);
        eina_strbuf_append(out, sr);

        free(sr);
        eina_value_free(request);
     }

   return eina_value_string_new(eina_strbuf_string_get(out));
}

static Eina_Value *
_efl_ui_view_model_text_property_get(const Eo *obj, Efl_Ui_View_Model_Data *pd, Eina_Stringshare *prop)
{
   Efl_Ui_View_Model_Text *lookup;
   Eina_Strbuf *buf;
   Eina_Value *r;
   Eina_Error err = 0;

   if (!pd) return NULL;
   lookup = eina_hash_find(pd->texts, prop);
   // Lookup for property definition in the parent, but property value will be fetched on
   // the child object doing the request.
   if (!lookup) return _efl_ui_view_model_text_property_get(obj, pd->parent, prop);

   buf = eina_strbuf_new();

   r = _efl_ui_view_model_text_generate(obj, buf,
                                     lookup->definition,
                                     !!(lookup->on_error || lookup->not_ready));
   if (eina_value_type_get(r) != EINA_VALUE_TYPE_ERROR)
     goto done;
   if (eina_value_error_get(r, &err) && err == EAGAIN && lookup->not_ready)
     {
        eina_strbuf_reset(buf);
        eina_value_free(r);

        r = _efl_ui_view_model_text_generate(obj, buf, lookup->not_ready, !!lookup->on_error);
        if (eina_value_type_get(r) != EINA_VALUE_TYPE_ERROR)
          goto done;
     }
   if (lookup->on_error)
     {
        eina_strbuf_reset(buf);
        eina_value_free(r);

        r = _efl_ui_view_model_text_generate(obj, buf, lookup->on_error, 0);
     }

 done:
   eina_strbuf_free(buf);

   return r;
}

static Eina_Value *
_efl_ui_view_model_efl_model_property_get(const Eo *obj, Efl_Ui_View_Model_Data *pd,
                                       const char *property)
{
   Efl_Ui_View_Model_Logic *logic;
   Eina_Stringshare *prop;
   Eina_Value *r;

   prop = eina_stringshare_add(property);
   logic = _efl_ui_view_model_property_logic_lookup(pd, prop);
   if (logic)
     r = logic->get.fct(logic->get.data, obj, prop);
   else
     {
        r = _efl_ui_view_model_text_property_get(obj, pd, prop);
        if (!r) r = efl_model_property_get(efl_super(obj, EFL_UI_VIEW_MODEL_CLASS), property);
     }

   eina_stringshare_del(prop);
   return r;
}

static Eina_Iterator *
_efl_ui_view_model_efl_model_properties_get(const Eo *obj, Efl_Ui_View_Model_Data *pd)
{
   EFL_COMPOSITE_MODEL_PROPERTIES_SUPER(props, obj, EFL_UI_VIEW_MODEL_CLASS,
                                        eina_hash_iterator_key_new(pd->deduplication));

   return props;
}

typedef struct _Efl_Ui_View_Model_Slice_Request Efl_Ui_View_Model_Slice_Request;
struct _Efl_Ui_View_Model_Slice_Request
{
   Efl_Ui_View_Model_Data *pd;
   unsigned int start;
};

static Eina_Value
_efl_ui_view_model_slice_then(Eo *o, void *data, const Eina_Value v)
{
   Efl_Ui_View_Model_Slice_Request *req = data;
   Eo *target;
   Eina_Value r = EINA_VALUE_EMPTY;
   unsigned int i, len;

   eina_value_array_setup(&r, EINA_VALUE_TYPE_OBJECT, 4);

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

        composite = _efl_ui_view_model_child_lookup(req->pd, o, target);
        eina_value_array_append(&r, composite);
     }

   return r;
}

static void
_efl_ui_view_model_slice_clean(Eo *o EINA_UNUSED, void *data, const Eina_Future *dead_future EINA_UNUSED)
{
   free(data);
}

static Eina_Future *
_efl_ui_view_model_efl_model_children_slice_get(Eo *obj, Efl_Ui_View_Model_Data *pd,
                                             unsigned int start, unsigned int count)
{
   Efl_Ui_View_Model_Slice_Request *req;
   Eina_Future *f;

   f = efl_model_children_slice_get(efl_super(obj, EFL_UI_VIEW_MODEL_CLASS), start, count);

   req = malloc(sizeof (Efl_Ui_View_Model_Slice_Request));
   if (!req)
     {
        eina_future_cancel(f);
        return efl_loop_future_rejected(obj, ENOMEM);
     }

   req->pd = pd;
   req->start = start;

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

#include "efl_ui_view_model.eo.c"
