#include "e.h"

/* local subsystem functions */
typedef struct _E_Theme_Result E_Theme_Result;

struct _E_Theme_Result
{
   const char *file;
   const char *cache;
   Eina_Hash  *quickfind;
};

static Eina_Bool _e_theme_mappings_free_cb(const Eina_Hash *hash, const void *key, void *data, void *fdata);
static Eina_Bool _e_theme_mappings_quickfind_free_cb(const Eina_Hash *hash, const void *key, void *data, void *fdata);
static void _e_theme_category_register(const char *category);
static Eina_List *_e_theme_collection_item_register(Eina_List *list, const char *name);
static Eina_List *_e_theme_collection_items_find(const char *base, const char *collname);


/* local subsystem globals */
static Eina_Hash *mappings = NULL;
static Eina_Hash *group_cache = NULL;

static Eina_List *categories = NULL;
static Eina_List *transitions = NULL;
static Eina_List *borders = NULL;
static Eina_List *shelfs = NULL;
static Eina_List *comps = NULL;
static E_Fm2_Mime_Handler *theme_hdl = NULL;

/* externally accessible functions */

EINTERN int
e_theme_init(void)
{
   E_Config_Theme *et;
   Eina_List *l = NULL;

   /* Register mime handler */
   theme_hdl = e_fm2_mime_handler_new(_("Set As Theme"), "preferences-desktop-theme",
				      e_theme_handler_set, NULL,
				      e_theme_handler_test, NULL);
   if (theme_hdl) e_fm2_mime_handler_glob_add(theme_hdl, "*.edj");

   /* this is a fallback that is ALWAYS there - if all fails things will */
   /* always fall back to the default theme. the rest after this are config */
   /* values users can set */
   e_theme_file_set("base", "default.edj");

   EINA_LIST_FOREACH(e_config->themes, l, et)
     {
	char buf[256];

	snprintf(buf, sizeof(buf), "base/%s", et->category);
	e_theme_file_set(buf, et->file);
     }

   /* Find transitions */
   transitions = _e_theme_collection_items_find("base/theme/transitions", "e/transitions");
   borders = _e_theme_collection_items_find("base/theme/borders", "e/widgets/border");
   shelfs = _e_theme_collection_items_find("base/theme/shelf", "e/shelf");
   comps = _e_theme_collection_items_find("base/theme/borders", "e/comp");
   if (!mappings) mappings = eina_hash_string_superfast_new(NULL);
   group_cache = eina_hash_string_superfast_new(NULL);

   return 1;
}

EINTERN int
e_theme_shutdown(void)
{
   const char *str;

   if (theme_hdl)
     {
	e_fm2_mime_handler_glob_del(theme_hdl, "*.edj");
	e_fm2_mime_handler_free(theme_hdl);
     }
   if (mappings)
     {
	eina_hash_foreach(mappings, _e_theme_mappings_free_cb, NULL);
	eina_hash_free(mappings);
	mappings = NULL;
     }
   if (group_cache)
     {
	eina_hash_free(group_cache);
	group_cache = NULL;
     }
   EINA_LIST_FREE(categories, str)
     eina_stringshare_del(str);
   EINA_LIST_FREE(transitions, str)
     eina_stringshare_del(str);
   EINA_LIST_FREE(borders, str)
     eina_stringshare_del(str);
   EINA_LIST_FREE(shelfs, str)
     eina_stringshare_del(str);
   EINA_LIST_FREE(comps, str)
     eina_stringshare_del(str);
   return 1;
}

EAPI int
e_theme_edje_object_set(Evas_Object *o, const char *category, const char *group)
{
   E_Theme_Result *res;
   char buf[256];
   char *p;

   /* find category -> edje mapping */
   _e_theme_category_register(category);
   res = eina_hash_find(mappings, category);
   if (res)
     {
	const char *str;

	/* if found check cached path */
	str = res->cache;
	if (!str)
	  {
	     /* no cached path */
	     str = res->file;
	     /* if its not an absolute path find it */
	     if (str[0] != '/')
	       str = e_path_find(path_themes, str);
	     /* save cached value */
	     if (str) res->cache = str;
	  }
	if (str)
	  {
	     void *tres;
	     int ok;

	     snprintf(buf, sizeof(buf), "%s/::/%s", str, group);
	     tres = eina_hash_find(group_cache, buf);
	     if (!tres)
	       {
		  ok = edje_object_file_set(o, str, group);
                  /* save in the group cache hash */
		  if (ok)
		    eina_hash_add(group_cache, buf, res);
		  else
		    eina_hash_add(group_cache, buf, (void *)1);
	       }
	     else if (tres == (void *)1)
	       ok = 0;
	     else
	       ok = 1;
	     if (ok)
	       {
		  if (tres)
		    edje_object_file_set(o, str, group);
		  return 1;
	       }
	  }
     }
   /* no mapping or set failed - fall back */
   eina_strlcpy(buf, category, sizeof(buf));
   /* shorten string up to and not including last / char */
   p = strrchr(buf, '/');
   if (p) *p = 0;
   /* no / anymore - we are already as far back as we can go */
   else return 0;
   /* try this category */
   return e_theme_edje_object_set(o, buf, group);
}

const char *
_e_theme_edje_file_get(const char *category, const char *group, Eina_Bool fallback_icon)
{
   E_Theme_Result *res;
   char buf[4096];
   const char *q;
   char *p;

   /* find category -> edje mapping */
   _e_theme_category_register(category);
   res = eina_hash_find(mappings, category);

   if (e_config->icon_theme &&
       (!fallback_icon) &&
       (!strcmp(category, "base")) &&
       (!strncmp(group, "e/icons", 7)))
     return "";

   if (res)
     {
	const char *str;

	/* if found check cached path */
	str = res->cache;
	if (!str)
	  {
	     /* no cached path */
	     str = res->file;
	     /* if its not an absolute path find it */
	     if (str[0] != '/')
	       str = e_path_find(path_themes, str);
	     /* save cached value */
	     if (str) res->cache = str;
	  }
	if (str)
	  {
	     void *tres;
	     Eina_List *coll, *l;
	     int ok;

	     snprintf(buf, sizeof(buf), "%s/::/%s", str, group);
	     tres = eina_hash_find(group_cache, buf);
	     if (!tres)
	       {
		  /* if the group exists - return */
		  if (!res->quickfind)
		    {
		       const char *col;

		       res->quickfind = eina_hash_string_superfast_new(NULL);
		       /* create a quick find hash of all group entries */
		       coll = edje_file_collection_list(str);

		       EINA_LIST_FOREACH(coll, l, col)
			 {
			    q = eina_stringshare_add(col);
			    eina_hash_direct_add(res->quickfind, q, q);
			 }
		       if (coll) edje_file_collection_list_free(coll);
		    }
		  /* save in the group cache hash */
		  if (eina_hash_find(res->quickfind, group))
		    {
		       eina_hash_add(group_cache, buf, res);
		       ok = 1;
		    }
		  else
		    {
		       eina_hash_add(group_cache, buf, (void *)1);
		       ok = 0;
		    }
	       }
	     else if (tres == (void *)1) /* special pointer "1" == not there */
	       ok = 0;
	     else
	       ok = 1;
	     if (ok) return str;
	  }
     }
   /* no mapping or set failed - fall back */
   eina_strlcpy(buf, category, sizeof(buf));
   /* shorten string up to and not including last / char */
   p = strrchr(buf, '/');
   if (p) *p = 0;
   /* no / anymore - we are already as far back as we can go */
   else return "";
   /* try this category */
   return e_theme_edje_file_get(buf, group);
}

EAPI const char *
e_theme_edje_file_get(const char *category, const char *group)
{
   return _e_theme_edje_file_get(category, group, EINA_FALSE);
}

EAPI const char *
e_theme_edje_icon_fallback_file_get(const char *group)
{
   return _e_theme_edje_file_get("base", group, EINA_TRUE);
}

/*
 * this is used to set the theme for a CATEGORY of E17. "base" is always set
 * to the default theme - because if a selected theme wants "base/theme", but
 * does not provide theme elements, it can fall back to the default theme.
 * 
 * the idea is you can actually set a different theme for different parts of
 * the desktop... :)
 * 
 * other possible categories...
 *  e_theme_file_set("base/theme/about", "default.edj");
 *  e_theme_file_set("base/theme/borders", "default.edj");
 *  e_theme_file_set("base/theme/background", "default.edj");
 *  e_theme_file_set("base/theme/configure", "default.edj");
 *  e_theme_file_set("base/theme/dialog", "default.edj");
 *  e_theme_file_set("base/theme/menus", "default.edj");
 *  e_theme_file_set("base/theme/error", "default.edj");
 *  e_theme_file_set("base/theme/gadman", "default.edj");
 *  e_theme_file_set("base/theme/dnd", "default.edj");
 *  e_theme_file_set("base/theme/icons", "default.edj");
 *  e_theme_file_set("base/theme/pointer", "default.edj");
 *  e_theme_file_set("base/theme/transitions", "default.edj");
 *  e_theme_file_set("base/theme/widgets", "default.edj");
 *  e_theme_file_set("base/theme/winlist", "default.edj");
 *  e_theme_file_set("base/theme/modules", "default.edj");
 *  e_theme_file_set("base/theme/modules/pager", "default.edj");
 *  e_theme_file_set("base/theme/modules/ibar", "default.edj");
 *  e_theme_file_set("base/theme/modules/ibox", "default.edj");
 *  e_theme_file_set("base/theme/modules/clock", "default.edj");
 *  e_theme_file_set("base/theme/modules/battery", "default.edj");
 *  e_theme_file_set("base/theme/modules/cpufreq", "default.edj");
 *  e_theme_file_set("base/theme/modules/start", "default.edj");
 *  e_theme_file_set("base/theme/modules/temperature", "default.edj");
 */

EAPI void
e_theme_file_set(const char *category, const char *file)
{
   E_Theme_Result *res;

   if (group_cache)
     {
	eina_hash_free(group_cache);
	group_cache = NULL;
     }
   _e_theme_category_register(category);
   res = eina_hash_find(mappings, category);
   if (res)
     {
        eina_hash_del(mappings, category, res);
	if (res->file)
	  {
	     e_filereg_deregister(res->file);
	     eina_stringshare_del(res->file);
	  }
	if (res->cache) eina_stringshare_del(res->cache);
	E_FREE(res);
     }
   res = E_NEW(E_Theme_Result, 1);
   res->file = eina_stringshare_add(file);
   e_filereg_register(res->file);
   if (!mappings)
      mappings = eina_hash_string_superfast_new(NULL);
   eina_hash_add(mappings, category, res);
}

EAPI int
e_theme_config_set(const char *category, const char *file)
{
   E_Config_Theme *ect;
   Eina_List *next;

   /* Don't accept unused categories */
#if 0
   if (!e_theme_category_find(category)) return 0;
#endif

   /* search for the category */
   EINA_LIST_FOREACH(e_config->themes, next, ect)
     {
	if (!strcmp(ect->category, category))
	  {
	     if (ect->file) eina_stringshare_del(ect->file);
	     ect->file = eina_stringshare_add(file);
	     return 1;
	  }
     }

   /* the text class doesnt exist */
   ect = E_NEW(E_Config_Theme, 1);
   ect->category = eina_stringshare_add(category);
   ect->file = eina_stringshare_add(file);
   
   e_config->themes = eina_list_append(e_config->themes, ect);
   return 1;
}

/*
 * returns a pointer to the data, return null if nothing if found.
 */
EAPI E_Config_Theme *
e_theme_config_get(const char *category)
{
   E_Config_Theme *ect = NULL;
   Eina_List *next;

   /* search for the category */
   EINA_LIST_FOREACH(e_config->themes, next, ect)
     {
	if (!strcmp(ect->category, category))
	  return ect;
     }
   return NULL;
}

EAPI int
e_theme_config_remove(const char *category)
{
   E_Config_Theme *ect;
   Eina_List *next;

   /* search for the category */
   EINA_LIST_FOREACH(e_config->themes, next, ect)
     {
	if (!strcmp(ect->category, category))
	  {
	     e_config->themes = eina_list_remove_list(e_config->themes, next);
	     if (ect->category) eina_stringshare_del(ect->category);
	     if (ect->file) eina_stringshare_del(ect->file);
	     free(ect);
	     return 1;
	  }
    }
   return 1;
}

EAPI Eina_List *
e_theme_config_list(void)
{
   return e_config->themes;
}

EAPI int
e_theme_category_find(const char *category)
{
   if (eina_list_search_sorted(categories, EINA_COMPARE_CB(strcmp), category))
     return 1;
   return 0;
}

EAPI Eina_List *
e_theme_category_list(void)
{
   return categories;
}

EAPI int
e_theme_transition_find(const char *transition)
{
   if (eina_list_search_sorted(transitions, EINA_COMPARE_CB(strcmp), transition))
     return 1;
   return 0;
}

EAPI Eina_List *
e_theme_transition_list(void)
{
   return transitions;
}

EAPI int
e_theme_border_find(const char *border)
{
   if (eina_list_search_sorted(borders, EINA_COMPARE_CB(strcmp), border))
     return 1;
   return 0;
}

EAPI Eina_List *
e_theme_border_list(void)
{
   return borders;
}

EAPI int
e_theme_shelf_find(const char *shelf)
{
   if (eina_list_search_sorted(shelfs, EINA_COMPARE_CB(strcmp), shelf))
     return 1;
   return 0;
}

EAPI Eina_List *
e_theme_shelf_list(void)
{
   return shelfs;
}

EAPI int
e_theme_comp_find(const char *comp)
{
   if (eina_list_search_sorted(comps, EINA_COMPARE_CB(strcmp), comp))
     return 1;
   return 0;
}

EAPI Eina_List *
e_theme_comp_list(void)
{
   return comps;
}

EAPI void
e_theme_handler_set(Evas_Object *obj __UNUSED__, const char *path, void *data __UNUSED__)
{
   E_Action *a;
   char buf[PATH_MAX];
   int copy = 1;

   if (!path) return;

   /* if not in system dir or user dir, copy to user dir */
   e_prefix_data_concat_static(buf, "data/themes");
   if (!strncmp(buf, path, strlen(buf)))
      copy = 0;
   if (copy)
     {
        e_user_dir_concat_static(buf, "themes");
        if (!strncmp(buf, path, strlen(buf)))
           copy = 0;
     }
   if (copy)
     {
        const char *file;
        char *name;

        file = ecore_file_file_get(path);
        name = ecore_file_strip_ext(file);

        e_user_dir_snprintf(buf, sizeof(buf), "themes/%s-%f.edj", name, ecore_time_unix_get());
        free(name);

        if (!ecore_file_exists(buf))
          {
             ecore_file_cp(path, buf);
             e_theme_config_set("theme", buf);
          }
        else
           e_theme_config_set("theme", path);
     }
   else
      e_theme_config_set("theme", path);

   e_config_save_queue();
   a = e_action_find("restart");
   if ((a) && (a->func.go)) a->func.go(NULL, NULL);
}

EAPI int
e_theme_handler_test(Evas_Object *obj __UNUSED__, const char *path, void *data __UNUSED__)
{
   if (!path) return 0;
   if (!edje_file_group_exists(path, "e/widgets/border/default/border"))
     return 0;
   return 1;
}

/* local subsystem functions */
static Eina_Bool
_e_theme_mappings_free_cb(const Eina_Hash *hash __UNUSED__, const void *key __UNUSED__, void *data, void *fdata __UNUSED__)
{
   E_Theme_Result *res;

   res = data;
   if (res->file) eina_stringshare_del(res->file);
   if (res->cache) eina_stringshare_del(res->cache);
   if (res->quickfind)
     {
	eina_hash_foreach(res->quickfind, _e_theme_mappings_quickfind_free_cb, NULL);
	eina_hash_free(res->quickfind);
     }
   free(res);
   return EINA_TRUE;
}

static Eina_Bool
_e_theme_mappings_quickfind_free_cb(const Eina_Hash *hash __UNUSED__, const void *key, void *data __UNUSED__, void *fdata __UNUSED__)
{
   eina_stringshare_del(key);
   return EINA_TRUE;
}

static void
_e_theme_category_register(const char *category)
{
   Eina_List *l;
   int ret;

   if (!categories)
     categories = eina_list_append(categories, eina_stringshare_add(category));

   l = eina_list_search_sorted_near_list(categories, EINA_COMPARE_CB(strcmp),
         category, &ret);

   if (!ret) return;

   if (ret < 0)
     categories = eina_list_append_relative_list(categories, eina_stringshare_add(category), l);
   else
     categories = eina_list_prepend_relative_list(categories, eina_stringshare_add(category), l);
}

static Eina_List *
_e_theme_collection_item_register(Eina_List *list, const char *name)
{
   const char *item;
   Eina_List *l;

   EINA_LIST_FOREACH(list, l, item)
     {
	if (!strcmp(name, item)) return list;
     }
   list = eina_list_append(list, eina_stringshare_add(name));
   return list;
}

static Eina_List *
_e_theme_collection_items_find(const char *base, const char *collname)
{
   Eina_List *list = NULL;
   E_Theme_Result *res;
   char *category, *p, *p2;
   int collname_len;

   collname_len = strlen(collname);
   category = alloca(strlen(base) + 1);
   strcpy(category, base);
   do
     {
	res = eina_hash_find(mappings, category);
	if (res)
	  {
	     const char *str;

	     /* if found check cached path */
	     str = res->cache;
	     if (!str)
	       {
		  /* no cached path */
		  str = res->file;
		  /* if its not an absolute path find it */
		  if (str[0] != '/') str = e_path_find(path_themes, str);
		  /* save cached value */
		  if (str) res->cache = str;
	       }
	     if (str)
	       {
		  Eina_List *coll, *l;
		  
		  coll = edje_file_collection_list(str);
		  if (coll)
		    {
		       const char *c;

		       EINA_LIST_FOREACH(coll, l, c)
			 {
			    if (!strncmp(c, collname, collname_len))
			      {
				 char *trans;

				 trans = strdup(c);
				 p = trans + collname_len + 1;
				 if (*p)
				   {
				      p2 = strchr(p, '/');
				      if (p2) *p2 = 0;
				      list = _e_theme_collection_item_register(list, p);
				   }
				 free(trans);
			      }
			 }
		       edje_file_collection_list_free(coll);
		    }
	       }
	  }
	p = strrchr(category, '/');
	if (p) *p = 0;
     }
   while (p);

   list = eina_list_sort(list, 0, EINA_COMPARE_CB(strcmp));
   return list;
}
