#include "e.h"

static Eina_Bool  _e_fm2_custom_file_hash_foreach_list(const Eina_Hash *hash, const void *key, void *data, void *fdata);
static Eina_List *_e_fm2_custom_hash_key_base_list(Eina_Hash *hash, const char *str);
//static Eina_Bool _e_fm2_custom_file_hash_foreach_sub_list(Eina_Hash *hash, const char *key, void *data, void *fdata);
//static Eina_List *_e_fm2_custom_hash_key_sub_list(Eina_Hash *hash, const char *str);
static Eina_Bool  _e_fm2_custom_file_hash_foreach(const Eina_Hash *hash, const void *key, void *data, void *fdata);
static Eina_Bool  _e_fm2_custom_file_hash_foreach_save(const Eina_Hash *hash, const void *key, void *data, void *fdata);
static void       _e_fm2_custom_file_info_load(void);
static void       _e_fm2_custom_file_info_save(void);
static void       _e_fm2_custom_file_info_free(void);
static void       _e_fm2_custom_file_cb_defer_save(void *data);

static E_Powersave_Deferred_Action *_e_fm2_flush_defer = NULL;
static Eet_File *_e_fm2_custom_file = NULL;
static Eet_Data_Descriptor *_e_fm2_custom_file_edd = NULL;
static Eet_Data_Descriptor *_e_fm2_custom_dir_edd = NULL;
static Eina_Hash *_e_fm2_custom_hash = NULL;
static int _e_fm2_custom_writes = 0;
static int _e_fm2_custom_init = 0;

/* FIXME: this uses a flat path key for custom file info. this is fine as
 * long as we only expect a limited number of custom info nodes. if we
 * start to see whole dire trees stored this way things will suck. we need
 * to use a tree struct to store this so deletes and renmes are sane.
 */

/* externally accessible functions */
EINTERN int
e_fm2_custom_file_init(void)
{
   Eet_Data_Descriptor_Class eddc;

   _e_fm2_custom_init++;
   if (_e_fm2_custom_init > 1) return _e_fm2_custom_init;

   if (!eet_eina_stream_data_descriptor_class_set(&eddc, sizeof (eddc), "e_fm2_custom_dir", sizeof (E_Fm2_Custom_Dir)))
     {
        _e_fm2_custom_init--;
        return 0;
     }

   _e_fm2_custom_hash = eina_hash_string_superfast_new(NULL);

   _e_fm2_custom_dir_edd = eet_data_descriptor_stream_new(&eddc);
#define DAT(y, z) EET_DATA_DESCRIPTOR_ADD_BASIC(_e_fm2_custom_dir_edd, E_Fm2_Custom_Dir, #y, y, z)
   DAT(pos.x, EET_T_DOUBLE);
   DAT(pos.y, EET_T_DOUBLE);
   DAT(prop.icon_size, EET_T_SHORT);
   DAT(prop.view_mode, EET_T_CHAR);
   DAT(prop.order_file, EET_T_UCHAR);
   DAT(prop.show_hidden_files, EET_T_UCHAR);
   DAT(prop.in_use, EET_T_UCHAR);

   DAT(prop.sort.no_case, EET_T_UCHAR);
   DAT(prop.sort.size, EET_T_UCHAR);
   DAT(prop.sort.extension, EET_T_UCHAR);
   DAT(prop.sort.mtime, EET_T_UCHAR);
   DAT(prop.sort.dirs.first, EET_T_UCHAR);
   DAT(prop.sort.dirs.last, EET_T_UCHAR);
#undef DAT
   eddc.size = sizeof (E_Fm2_Custom_File);
   eddc.name = "e_fm_custom_file";

   _e_fm2_custom_file_edd = eet_data_descriptor_stream_new(&eddc);
#define DAT(x, y, z) EET_DATA_DESCRIPTOR_ADD_BASIC(_e_fm2_custom_file_edd, E_Fm2_Custom_File, x, y, z)
   DAT("g.x", geom.x, EET_T_INT);
   DAT("g.y", geom.y, EET_T_INT);
   DAT("g.w", geom.w, EET_T_INT);
   DAT("g.h", geom.h, EET_T_INT);
   DAT("g.rw", geom.res_w, EET_T_INT);
   DAT("g.rh", geom.res_h, EET_T_INT);
   DAT("g.s", geom.scale, EET_T_DOUBLE);
   DAT("g.v", geom.valid, EET_T_UCHAR);

   DAT("i.t", icon.type, EET_T_INT);
   DAT("i.i", icon.icon, EET_T_STRING);
   DAT("i.v", icon.valid, EET_T_UCHAR);

   DAT("l", label, EET_T_STRING);

#undef DAT

   EET_DATA_DESCRIPTOR_ADD_SUB(_e_fm2_custom_file_edd, E_Fm2_Custom_File, "dir",
                               dir, _e_fm2_custom_dir_edd);

   return 1;
}

EINTERN void
e_fm2_custom_file_shutdown(void)
{
   _e_fm2_custom_init--;
   if (_e_fm2_custom_init != 0) return;

   _e_fm2_custom_file_info_save();
   _e_fm2_custom_file_info_free();
   if (_e_fm2_flush_defer) e_powersave_deferred_action_del(_e_fm2_flush_defer);
   _e_fm2_flush_defer = NULL;
   eet_data_descriptor_free(_e_fm2_custom_file_edd);
   _e_fm2_custom_file_edd = NULL;
   eet_data_descriptor_free(_e_fm2_custom_dir_edd);
   _e_fm2_custom_dir_edd = NULL;
}

E_API E_Fm2_Custom_File *
e_fm2_custom_file_get(const char *path)
{
   E_Fm2_Custom_File *cf;

   _e_fm2_custom_file_info_load();
   if (!_e_fm2_custom_file) return NULL;
   if (_e_fm2_flush_defer) e_fm2_custom_file_flush();
   cf = eina_hash_find(_e_fm2_custom_hash, path);
   return cf;
}

static void
_e_fm2_custom_dir_del(E_Fm2_Custom_Dir *dir)
{
   free(dir);
}

static void
_e_fm2_custom_file_del(E_Fm2_Custom_File *cf)
{
   if (!cf) return;

   eina_stringshare_del(cf->icon.icon);
   eina_stringshare_del(cf->label);
   _e_fm2_custom_dir_del(cf->dir);
   free(cf);
}

static E_Fm2_Custom_Dir *
_e_fm2_custom_dir_dup(const E_Fm2_Custom_Dir *dir)
{
   E_Fm2_Custom_Dir *copy;

   if (!dir) return NULL;

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

   memcpy(copy, dir, sizeof(*copy));
   return copy;
}

E_API E_Fm2_Custom_File *
e_fm2_custom_file_dup(const E_Fm2_Custom_File *cf)
{
   E_Fm2_Custom_File *copy;

   if (!cf) return NULL;

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

   memcpy(copy, cf, sizeof(*copy));
   copy->icon.icon = eina_stringshare_add(cf->icon.icon);
   copy->label = eina_stringshare_add(cf->label);
   copy->dir = _e_fm2_custom_dir_dup(cf->dir);
   return copy;
}

E_API void
e_fm2_custom_file_set(const char *path, const E_Fm2_Custom_File *cf)
{
   E_Fm2_Custom_File *cf1;
   _e_fm2_custom_file_info_load();
   if (!_e_fm2_custom_file) return;
   if (_e_fm2_flush_defer) e_fm2_custom_file_flush();

   cf1 = eina_hash_find(_e_fm2_custom_hash, path);
   if ((cf1 != cf) || ((cf1) && (cf) && (cf1->dir != cf->dir)))
     {
        E_Fm2_Custom_File *cf2 = e_fm2_custom_file_dup(cf);
        if (cf2)
          {
             if (cf1)
               {
                  eina_hash_modify(_e_fm2_custom_hash, path, cf2);
                  _e_fm2_custom_file_del(cf1);
               }
             else
               eina_hash_add(_e_fm2_custom_hash, path, cf2);
          }
     }
   _e_fm2_custom_writes = 1;
}

E_API void
e_fm2_custom_file_del(const char *path)
{
   Eina_List *list, *l;
   E_Fm2_Custom_File *cf2;
   const void *key;

   _e_fm2_custom_file_info_load();
   if (!_e_fm2_custom_file) return;
   if (_e_fm2_flush_defer) e_fm2_custom_file_flush();

   list = _e_fm2_custom_hash_key_base_list(_e_fm2_custom_hash, path);
   if (list)
     {
        EINA_LIST_FOREACH(list, l, key)
          {
             cf2 = eina_hash_find(_e_fm2_custom_hash, key);
             if (cf2)
               {
                  eina_hash_del(_e_fm2_custom_hash, key, cf2);
                  _e_fm2_custom_file_del(cf2);
               }
          }
        eina_list_free(list);
     }
   _e_fm2_custom_writes = 1;
}

E_API void
e_fm2_custom_file_rename(const char *path, const char *new_path)
{
   E_Fm2_Custom_File *cf, *cf2;
   Eina_List *list, *l;
   const void *key;

   _e_fm2_custom_file_info_load();
   if (!_e_fm2_custom_file) return;
   if (_e_fm2_flush_defer) e_fm2_custom_file_flush();
   cf2 = eina_hash_find(_e_fm2_custom_hash, path);
   if (cf2)
     {
        eina_hash_del(_e_fm2_custom_hash, path, cf2);
        cf = eina_hash_find(_e_fm2_custom_hash, new_path);
        if (cf)
          _e_fm2_custom_file_del(cf);
        eina_hash_add(_e_fm2_custom_hash, new_path, cf2);
     }
   list = _e_fm2_custom_hash_key_base_list(_e_fm2_custom_hash, path);
   if (list)
     {
        EINA_LIST_FOREACH(list, l, key)
          {
             cf2 = eina_hash_find(_e_fm2_custom_hash, key);
             if (cf2)
               {
                  char buf[PATH_MAX];

                  strcpy(buf, new_path);
                  strcat(buf, (char *)key + strlen(path));
                  eina_hash_del(_e_fm2_custom_hash, key, cf2);
                  cf = eina_hash_find(_e_fm2_custom_hash, buf);
                  if (cf)
                    _e_fm2_custom_file_del(cf);
                  eina_hash_add(_e_fm2_custom_hash, buf, cf2);
               }
          }
        eina_list_free(list);
     }
   _e_fm2_custom_writes = 1;
}

E_API void
e_fm2_custom_file_flush(void)
{
   if (!_e_fm2_custom_file) return;

   if (_e_fm2_flush_defer)
     e_powersave_deferred_action_del(_e_fm2_flush_defer);
   _e_fm2_flush_defer =
     e_powersave_deferred_action_add(_e_fm2_custom_file_cb_defer_save, NULL);
}

/**/

struct _E_Custom_List
{
   Eina_List  *l;
   const char *base;
   int         base_len;
};

static Eina_Bool
_e_fm2_custom_file_hash_foreach_list(const Eina_Hash *hash EINA_UNUSED, const void *key, void *data EINA_UNUSED, void *fdata)
{
   struct _E_Custom_List *cl;

   cl = fdata;
   if (!strncmp(cl->base, key, cl->base_len))
     cl->l = eina_list_append(cl->l, key);
   return 1;
}

static Eina_List *
_e_fm2_custom_hash_key_base_list(Eina_Hash *hash, const char *str)
{
   struct _E_Custom_List cl;

   cl.l = NULL;
   cl.base = str;
   cl.base_len = strlen(cl.base);
   eina_hash_foreach(hash, _e_fm2_custom_file_hash_foreach_list, &cl);
   return cl.l;
}

/*
   static Eina_Bool
   _e_fm2_custom_file_hash_foreach_sub_list(const Eina_Hash *hash, const void *key, void *data, void *fdata)
   {
   struct _E_Custom_List *cl;

   cl = fdata;
   if (!strncmp(cl->base, key, strlen(key)))
     cl->l = eina_list_append(cl->l, key);
   return 1;
   }
 */

/*
   static Eina_List *
   _e_fm2_custom_hash_key_sub_list(const Eina_Hash *hash, const void *str)
   {
   struct _E_Custom_List cl;

   cl.l = NULL;
   cl.base = str;
   eina_hash_foreach(hash,
                     _e_fm2_custom_file_hash_foreach_sub_list, &cl);
   return cl.l;
   }
 */

static Eina_Bool
_e_fm2_custom_file_hash_foreach(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED)
{
   _e_fm2_custom_file_del(data);
   return 1;
}

static Eina_Bool
_e_fm2_custom_file_hash_foreach_save(const Eina_Hash *hash EINA_UNUSED, const void *key, void *data, void *fdata)
{
   Eet_File *ef;
   E_Fm2_Custom_File *cf;

   ef = fdata;
   cf = data;
   eet_data_write(ef, _e_fm2_custom_file_edd, key, cf, 1);
   return 1;
}

static void
_e_fm2_custom_file_info_load(void)
{
   char buf[PATH_MAX];

   if (_e_fm2_custom_file) return;
   _e_fm2_custom_writes = 0;
   e_user_dir_concat_static(buf, "fileman/custom.cfg");
   _e_fm2_custom_file = eet_open(buf, EET_FILE_MODE_READ);
   if (!_e_fm2_custom_file)
     _e_fm2_custom_file = eet_open(buf, EET_FILE_MODE_WRITE);
   if (_e_fm2_custom_file)
     {
        E_Fm2_Custom_File *cf;
        char **list;
        int i, num;

        list = eet_list(_e_fm2_custom_file, "*", &num);
        if (list)
          {
             for (i = 0; i < num; i++)
               {
                  cf = eet_data_read(_e_fm2_custom_file,
                                     _e_fm2_custom_file_edd, list[i]);
                  if (cf)
                    eina_hash_add(_e_fm2_custom_hash, list[i], cf);
               }
             free(list);
          }
     }
}

static void
_e_fm2_custom_file_info_save(void)
{
   Eet_File *ef;
   char buf[PATH_MAX], buf2[PATH_MAX];
   size_t len;
   int ret;

   if (!_e_fm2_custom_file) return;
   if (!_e_fm2_custom_writes) return;

   len = e_user_dir_concat_static(buf, "fileman/custom.cfg.tmp");
   if (len >= sizeof(buf)) return;
   ef = eet_open(buf, EET_FILE_MODE_WRITE);
   if (!ef) return;
   eina_hash_foreach(_e_fm2_custom_hash,
                     _e_fm2_custom_file_hash_foreach_save, ef);
   eet_close(ef);

   memcpy(buf2, buf, len - (sizeof(".tmp") - 1));
   buf2[len - (sizeof(".tmp") - 1)] = '\0';
   eet_close(_e_fm2_custom_file);
   _e_fm2_custom_file = NULL;
   ret = rename(buf, buf2);
   if (ret < 0)
     {
        /* FIXME: do we want to trap individual errno
           and provide a short blurp to the user? */
        perror("rename");
     }
}

static void
_e_fm2_custom_file_info_free(void)
{
   _e_fm2_custom_writes = 0;
   if (_e_fm2_custom_file)
     {
        eet_close(_e_fm2_custom_file);
        _e_fm2_custom_file = NULL;
     }
   if (_e_fm2_custom_hash)
     {
        eina_hash_foreach(_e_fm2_custom_hash,
                          _e_fm2_custom_file_hash_foreach, NULL);
        eina_hash_free(_e_fm2_custom_hash);
        _e_fm2_custom_hash = eina_hash_string_superfast_new(NULL);
     }
}

static void
_e_fm2_custom_file_cb_defer_save(void *data EINA_UNUSED)
{
   _e_fm2_custom_file_info_save();
   _e_fm2_custom_file_info_free();
   _e_fm2_flush_defer = NULL;
}

