#include "e.h"

E_API int E_EVENT_FM_OP_REGISTRY_ADD = 0;
E_API int E_EVENT_FM_OP_REGISTRY_DEL = 0;
E_API int E_EVENT_FM_OP_REGISTRY_CHANGED = 0;

static Eina_Hash *_e_fm2_op_registry = NULL;
static unsigned int _e_fm2_init_count = 0;

typedef struct _E_Fm2_Op_Registry_Entry_Listener E_Fm2_Op_Registry_Entry_Listener;
typedef struct _E_Fm2_Op_Registry_Entry_Internal E_Fm2_Op_Registry_Entry_Internal;

struct _E_Fm2_Op_Registry_Entry_Listener
{
   EINA_INLIST;
   void  (*cb)(void *data, const E_Fm2_Op_Registry_Entry *entry);
   void *data;
   void  (*free_data)(void *data);
};

struct _E_Fm2_Op_Registry_Entry_Internal
{
   E_Fm2_Op_Registry_Entry entry;
   Eina_Inlist            *listeners;
   int                     references;
   Ecore_Event            *changed_event;
};

static void
_e_fm2_op_registry_entry_e_fm_deleted(void *data, Evas *evas EINA_UNUSED, Evas_Object *e_fm EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Fm2_Op_Registry_Entry *entry = data;

   entry->e_fm = NULL;
   e_fm2_op_registry_entry_changed(entry);
}

static void
_e_fm2_op_registry_entry_e_fm_monitor_start(const E_Fm2_Op_Registry_Entry *entry)
{
   if (!entry->e_fm) return;
   evas_object_event_callback_add
     (entry->e_fm, EVAS_CALLBACK_DEL,
     _e_fm2_op_registry_entry_e_fm_deleted, entry);
}

static void
_e_fm2_op_registry_entry_e_fm_monitor_stop(const E_Fm2_Op_Registry_Entry *entry)
{
   if (!entry->e_fm) return;
   evas_object_event_callback_del_full
     (entry->e_fm, EVAS_CALLBACK_DEL,
     _e_fm2_op_registry_entry_e_fm_deleted, entry);
}

static inline E_Fm2_Op_Registry_Entry_Internal *
_e_fm2_op_registry_entry_internal_get(const E_Fm2_Op_Registry_Entry *entry)
{
   return (E_Fm2_Op_Registry_Entry_Internal *)entry;
}

static void
_e_fm2_op_registry_entry_internal_free(E_Fm2_Op_Registry_Entry_Internal *e)
{
   _e_fm2_op_registry_entry_e_fm_monitor_stop(&(e->entry));

   while (e->listeners)
     {
        E_Fm2_Op_Registry_Entry_Listener *listener = (void *)e->listeners;
        e->listeners = eina_inlist_remove(e->listeners, e->listeners);

        if (listener->free_data) listener->free_data(listener->data);
        free(listener);
     }

   eina_stringshare_del(e->entry.src);
   eina_stringshare_del(e->entry.dst);
   free(e);
}

static inline int
_e_fm2_op_registry_entry_internal_unref(E_Fm2_Op_Registry_Entry_Internal *e)
{
   if (e->references < 1)
     return 0;

   e->references--;
   if (e->references > 0)
     return e->references;

   _e_fm2_op_registry_entry_internal_free(e);
   return 0;
}

static inline int
_e_fm2_op_registry_entry_internal_ref(E_Fm2_Op_Registry_Entry_Internal *e)
{
   e->references++;
   return e->references;
}

static void
_e_fm2_op_registry_entry_listeners_call(const E_Fm2_Op_Registry_Entry_Internal *e)
{
   E_Fm2_Op_Registry_Entry_Listener *listener;
   Eina_Inlist *l;

   if (!e->listeners) return;

   EINA_INLIST_FOREACH_SAFE(e->listeners, l, listener)
     listener->cb(listener->data, &e->entry);
}

static void
_e_fm2_op_registry_entry_internal_unref_on_event(void *data, void *event EINA_UNUSED)
{
   E_Fm2_Op_Registry_Entry_Internal *e = data;
   _e_fm2_op_registry_entry_internal_unref(e);
}

static void
_e_fm2_op_registry_entry_internal_event(E_Fm2_Op_Registry_Entry_Internal *e, int event_type)
{
   _e_fm2_op_registry_entry_internal_ref(e);
   ecore_event_add(event_type, &(e->entry),
                   _e_fm2_op_registry_entry_internal_unref_on_event, e);
}

Eina_Bool
e_fm2_op_registry_entry_add(int id, Evas_Object *e_fm, E_Fm_Op_Type op, E_Fm2_Op_Registry_Abort_Func abrt)
{
   E_Fm2_Op_Registry_Entry_Internal *e;

   e = E_NEW(E_Fm2_Op_Registry_Entry_Internal, 1);
   if (!e) return 0;

   e->entry.id = id;
   e->entry.e_fm = e_fm;
   e->entry.start_time = ecore_loop_time_get();
   e->entry.op = op;
   e->entry.status = E_FM2_OP_STATUS_IN_PROGRESS;
   e->entry.func.abort = abrt;
   e->references = 1;

   if (!eina_hash_add(_e_fm2_op_registry, &id, e))
     {
        free(e);
        return 0;
     }

   _e_fm2_op_registry_entry_e_fm_monitor_start(&(e->entry));
   _e_fm2_op_registry_entry_internal_event(e, E_EVENT_FM_OP_REGISTRY_ADD);

   return 1;
}

Eina_Bool
e_fm2_op_registry_entry_del(int id)
{
   E_Fm2_Op_Registry_Entry_Internal *e;

   e = eina_hash_find(_e_fm2_op_registry, &id);
   if (!e) return 0;
   eina_hash_del_by_key(_e_fm2_op_registry, &id);

   _e_fm2_op_registry_entry_internal_event(e, E_EVENT_FM_OP_REGISTRY_DEL);
   _e_fm2_op_registry_entry_internal_unref(e);

   return 1;
}

static void
_e_fm2_op_registry_entry_internal_unref_on_changed_event(void *data, void *event EINA_UNUSED)
{
   E_Fm2_Op_Registry_Entry_Internal *e = data;
   e->changed_event = NULL;
   _e_fm2_op_registry_entry_internal_unref(e);
}

void
e_fm2_op_registry_entry_changed(const E_Fm2_Op_Registry_Entry *entry)
{
   E_Fm2_Op_Registry_Entry_Internal *e;

   if (!entry) return;
   e = _e_fm2_op_registry_entry_internal_get(entry);

   _e_fm2_op_registry_entry_listeners_call(e);

   if (e->changed_event) return;
   _e_fm2_op_registry_entry_internal_ref(e);
   e->changed_event = ecore_event_add
       (E_EVENT_FM_OP_REGISTRY_CHANGED, &(e->entry),
       _e_fm2_op_registry_entry_internal_unref_on_changed_event, e);
}

/**
 * Set the new e_fm for this operation.
 *
 * Use this call instead of directly setting in order to have the
 * object to be monitored, when it is gone, the pointer will be made
 * NULL.
 *
 * @note: it will not call any listener or add any event, please use
 * e_fm2_op_registry_entry_changed().
 */
void
e_fm2_op_registry_entry_e_fm_set(E_Fm2_Op_Registry_Entry *entry, Evas_Object *e_fm)
{
   if (!entry) return;
   _e_fm2_op_registry_entry_e_fm_monitor_stop(entry);
   entry->e_fm = e_fm;
   _e_fm2_op_registry_entry_e_fm_monitor_start(entry);
}

/**
 * Set the new files for this operation.
 *
 * Use this call instead of directly setting in order to have
 * stringshare references right.
 *
 * @note: it will not call any listener or add any event, please use
 * e_fm2_op_registry_entry_changed().
 */
void
e_fm2_op_registry_entry_files_set(E_Fm2_Op_Registry_Entry *entry, const char *src, const char *dst)
{
   if (!entry) return;

   eina_stringshare_replace(&entry->src, src);
   eina_stringshare_replace(&entry->dst, dst);
}

/**
 * Adds a reference to given entry.
 *
 * @return: new number of references after operation or -1 on error.
 */
E_API int
e_fm2_op_registry_entry_ref(E_Fm2_Op_Registry_Entry *entry)
{
   E_Fm2_Op_Registry_Entry_Internal *e;

   if (!entry) return -1;

   e = _e_fm2_op_registry_entry_internal_get(entry);
   return _e_fm2_op_registry_entry_internal_ref(e);
}

/**
 * Releases a reference to given entry.
 *
 * @return: new number of references after operation or -1 on error,
 *    if 0 the entry was freed and pointer is then invalid.
 */
E_API int
e_fm2_op_registry_entry_unref(E_Fm2_Op_Registry_Entry *entry)
{
   E_Fm2_Op_Registry_Entry_Internal *e;

   if (!entry) return -1;

   e = _e_fm2_op_registry_entry_internal_get(entry);
   return _e_fm2_op_registry_entry_internal_unref(e);
}

/**
 * Returns the X window associated to this operation.
 *
 * This will handle all bureaucracy to get X window based on e_fm evas
 * object.
 *
 * @return: 0 if no window, window identifier otherwise.
 */
E_API Ecore_X_Window
e_fm2_op_registry_entry_xwin_get(const E_Fm2_Op_Registry_Entry *entry)
{
   Evas *e;
   Ecore_Evas *ee;

   if (!entry) return 0;
   if (!entry->e_fm) return 0;

   e = evas_object_evas_get(entry->e_fm);
   if (!e) return 0;

   ee = evas_data_attach_get(e);
   if (!ee) return 0;

   return (Ecore_X_Window)(long)ecore_evas_window_get(ee);
}

/**
 * Discover entry based on its identifier.
 *
 * @note: does not increment reference.
 */
E_API E_Fm2_Op_Registry_Entry *
e_fm2_op_registry_entry_get(int id)
{
   return eina_hash_find(_e_fm2_op_registry, &id);
}

/**
 * Adds a function to be called when entry changes.
 *
 * When entry changes any attribute this function will be called.
 *
 * @param: entry entry to operate on.
 * @param: cb function to callback on changes.
 * @param: data extra data to give to @p cb
 * @param: free_data function to call when listener is removed, entry
 *    is deleted or any error occur in this function and listener
 *    cannot be added.
 *
 * @note: does not increment reference.
 * @note: on errors, @p free_data will be called.
 */
E_API void
e_fm2_op_registry_entry_listener_add(E_Fm2_Op_Registry_Entry *entry, void (*cb)(void *data, const E_Fm2_Op_Registry_Entry *entry), const void *data, void (*free_data)(void *data))
{
   E_Fm2_Op_Registry_Entry_Internal *e;
   E_Fm2_Op_Registry_Entry_Listener *listener;

   if ((!entry) || (!cb))
     {
        if (free_data) free_data((void *)data);
        return;
     }

   listener = malloc(sizeof(*listener));
   if (!listener)
     {
        if (free_data) free_data((void *)data);
        return;
     }
   listener->cb = cb;
   listener->data = (void *)data;
   listener->free_data = free_data;

   e = _e_fm2_op_registry_entry_internal_get(entry);
   e->listeners = eina_inlist_append(e->listeners, EINA_INLIST_GET(listener));
}

/**
 * Removes the function to be called when entry changes.
 *
 * @param: entry entry to operate on.
 * @param: cb function to callback on changes.
 * @param: data extra data to give to @p cb
 *
 * @note: does not decrement reference.
 * @see: e_fm2_op_registry_entry_listener_add()
 */
E_API void
e_fm2_op_registry_entry_listener_del(E_Fm2_Op_Registry_Entry *entry, void (*cb)(void *data, const E_Fm2_Op_Registry_Entry *entry), const void *data)
{
   E_Fm2_Op_Registry_Entry_Internal *e;
   E_Fm2_Op_Registry_Entry_Listener *l;

   if ((!entry) || (!cb)) return;
   e = _e_fm2_op_registry_entry_internal_get(entry);

   EINA_INLIST_FOREACH(e->listeners, l)
     if ((l->cb == cb) && (l->data == data))
       {
          e->listeners = eina_inlist_remove(e->listeners, EINA_INLIST_GET(l));
          if (l->free_data) l->free_data(l->data);
          free(l);
          return;
       }
}

/**
 * Returns an iterator over all the entries in the fm operations registry.
 *
 * @warning: this iterator is just valid until new entries are added
 *    or removed (usually happens from main loop). This is because
 *    when system is back to main loop it can report new events and
 *    operations can be added or removed from this registry. In other
 *    words, it is fine to call this function, immediately walk the
 *    iterator and do something, then free the iterator. You can use
 *    it to create a shadow list if you wish.
 *
 * @see e_fm2_op_registry_get_all()
 */
E_API Eina_Iterator *
e_fm2_op_registry_iterator_new(void)
{
   return eina_hash_iterator_data_new(_e_fm2_op_registry);
}

/**
 * Returns a shadow list with all entries in the registry.
 *
 * All entries will have references incremented, so you must free the
 * list with e_fm2_op_registry_get_all_free() to free the list and
 * release these references.
 *
 * @note: List is unsorted!
 * @note: if you need a simple, immediate walk, use
 *    e_fm2_op_registry_iterator_new()
 */
E_API Eina_List *
e_fm2_op_registry_get_all(void)
{
   Eina_List *list;
   Eina_Iterator *it;
   E_Fm2_Op_Registry_Entry_Internal *e;

   list = NULL;
   it = eina_hash_iterator_data_new(_e_fm2_op_registry);
   EINA_ITERATOR_FOREACH(it, e)
     {
        _e_fm2_op_registry_entry_internal_ref(e);
        list = eina_list_append(list, &(e->entry));
     }
   eina_iterator_free(it);

   return list;
}

E_API void
e_fm2_op_registry_get_all_free(Eina_List *list)
{
   E_Fm2_Op_Registry_Entry *entry;
   EINA_LIST_FREE(list, entry)
     e_fm2_op_registry_entry_unref(entry);
}

E_API Eina_Bool
e_fm2_op_registry_is_empty(void)
{
   return eina_hash_population(_e_fm2_op_registry) == 0;
}

E_API int
e_fm2_op_registry_count(void)
{
   return eina_hash_population(_e_fm2_op_registry);
}

EINTERN unsigned int
e_fm2_op_registry_init(void)
{
   _e_fm2_init_count++;
   if (_e_fm2_init_count > 1) return _e_fm2_init_count;

   _e_fm2_op_registry = eina_hash_int32_new(NULL);
   if (!_e_fm2_op_registry)
     {
        _e_fm2_init_count = 0;
        return 0;
     }

   if (E_EVENT_FM_OP_REGISTRY_ADD == 0)
     E_EVENT_FM_OP_REGISTRY_ADD = ecore_event_type_new();
   if (E_EVENT_FM_OP_REGISTRY_DEL == 0)
     E_EVENT_FM_OP_REGISTRY_DEL = ecore_event_type_new();
   if (E_EVENT_FM_OP_REGISTRY_CHANGED == 0)
     E_EVENT_FM_OP_REGISTRY_CHANGED = ecore_event_type_new();

   return 1;
}

EINTERN unsigned int
e_fm2_op_registry_shutdown(void)
{
   if (_e_fm2_init_count == 0) return 0;
   _e_fm2_init_count--;
   if (_e_fm2_init_count > 0) return _e_fm2_init_count;

   eina_hash_free(_e_fm2_op_registry);
   _e_fm2_op_registry = NULL;

   return 0;
}

E_API void
e_fm2_op_registry_entry_abort(E_Fm2_Op_Registry_Entry *entry)
{
   if (!entry) return;

   if (entry->func.abort)
     entry->func.abort(entry);
}

