#ifdef HAVE_CONFIG_H
# include "elementary_config.h"
#endif

#include <Elementary.h>

#include "elm_priv.h"
#include "elm_icon_eo.h"

#include "efl_ui_theme.eo.h"

static Elm_Theme *theme_default = NULL;

static Eina_List *themes = NULL;

static Eina_File *
_elm_theme_find_try(Elm_Theme *th, Elm_Theme_File *etf, const char *group, Eina_Bool force)
{
   if (edje_mmap_group_exists(etf->handle, group))
     {
        if (etf->match_theme && (!force)) // overlay or extension
          {
             Elm_Theme_File *base_etf;
             Eina_Bool found = EINA_FALSE;

             EINA_INLIST_FOREACH(th->themes, base_etf)
               {
                  if (base_etf->base_theme != etf->match_theme) continue;
                  found = EINA_TRUE;
                  break;
               }
             if (!found) return NULL;
          }
        eina_hash_add(th->cache, group, eina_file_dup(etf->handle));
        return etf->handle;
     }
   return NULL;
}

static inline void
_elm_theme_item_finalize(Eina_Inlist **files,
                         const char *item,
                         Eina_File *f,
                         Eina_Bool prepend,
                         Eina_Bool istheme)
{
   Elm_Theme_File *etf;
   char *name;
   /* Theme version history:
    * <110: legacy, had no version tag
    *  110: first supported version
    *  119: switched windows to always use border
    *       win group has no menu, no blocker
    *       border group has all required swallows (conformant, bg, win)
    *       data: "elm_bg_version" matches "version" ("119")
    */
   if (!f) return;
   if (istheme)
     {
        char *version;
        int v;

        if (!(version = edje_mmap_data_get(f, "version")))
          {
             eina_file_close(f); // close matching open (finalize expected to consume eina file) OK
             return;
          }
        v = atoi(version);
        if (v < 110) // bump this version number when we need to
          {
             WRN("Selected theme is too old (version = %d, needs >= 110)", v);
          }
        free(version);
     }
   etf = calloc(1, sizeof(Elm_Theme_File));
   EINA_SAFETY_ON_NULL_RETURN(etf);
   etf->item = eina_stringshare_add(item);
   etf->handle = f; // now own/consume the file handle
   if (istheme)
     {
        name = edje_mmap_data_get(f, "efl_theme_base");
        etf->base_theme = eina_stringshare_add(name);
     }
   else
     {
        name = edje_mmap_data_get(f, "efl_theme_match");
        etf->match_theme = eina_stringshare_add(name);
     }
   free(name);
   if (prepend)
     {
        *files = eina_inlist_prepend(*files, EINA_INLIST_GET(etf));
     }
   else
     {
        *files = eina_inlist_append(*files, EINA_INLIST_GET(etf));
     }
}

static void
_elm_theme_file_item_add(Eina_Inlist **files, const char *item, Eina_Bool prepend, Eina_Bool istheme)
{
   Eina_Strbuf *buf = NULL;
   Eina_File *f = NULL;
   const char *home;

   home = eina_environment_home_get();
   buf = eina_strbuf_new();

   if ((item[0] == '/') ||
       ((item[0] == '.') && (item[1] == '/')) ||
       ((item[0] == '.') && (item[1] == '.') && (item[2] == '/')) ||
       ((isalpha(item[0])) && (item[1] == ':')))
     {
        f = eina_file_open(item, EINA_FALSE);
        if (!f) goto on_error;
     }
   else if (((item[0] == '~') && (item[1] == '/')))
     {
        eina_strbuf_append_printf(buf, "%s/%s", home, item + 2);

        f = eina_file_open(eina_strbuf_string_get(buf), EINA_FALSE);
        if (!f) goto on_error;
     }
   else
     {
        eina_strbuf_append_printf(buf,
                                  "%s/"ELEMENTARY_BASE_DIR"/themes/%s.edj",
                                  home, item);
        f = eina_file_open(eina_strbuf_string_get(buf), EINA_FALSE);
        _elm_theme_item_finalize(files, item, f, prepend, istheme);

        eina_strbuf_reset(buf);

        eina_strbuf_append_printf(buf,
                                  "%s/themes/%s.edj",
                                  _elm_data_dir, item);
        f = eina_file_open(eina_strbuf_string_get(buf), EINA_FALSE);
        /* Finalize will be done by the common one */
     }

   _elm_theme_item_finalize(files, item, f, prepend, istheme);

 on_error:
   if (buf) eina_strbuf_free(buf);
}

static void
_elm_theme_file_item_del(Eina_Inlist **files, const char *str)
{
   Eina_Inlist *l;
   Elm_Theme_File *item;

   str = eina_stringshare_add(str);

   EINA_INLIST_FOREACH_SAFE(*files, l, item)
     {
        if (item->item != str) continue;
        eina_file_close(item->handle); // close matching open (file consumed in finalize by putting it in etf) OK
        eina_stringshare_del(item->item);
        *files = eina_inlist_remove(*files, EINA_INLIST_GET(item));
        free(item);
     }

   eina_stringshare_del(str);
}

static void
_elm_theme_file_mmap_del(Eina_Inlist **files, const Eina_File *file)
{
   Eina_Inlist *l;
   Elm_Theme_File *item;

   EINA_INLIST_FOREACH_SAFE(*files, l, item)
     {
        if (item->handle != file) continue;
        eina_file_close(item->handle); // close matching open (file consumed in finalize by putting it in etf) OK
        eina_stringshare_del(item->item);
        *files = eina_inlist_remove(*files, EINA_INLIST_GET(item));
        free(item);
     }
}

static void
_elm_theme_file_clean(Eina_Inlist **files)
{
   while (*files)
     {
        Elm_Theme_File *etf = EINA_INLIST_CONTAINER_GET(*files, Elm_Theme_File);

        eina_stringshare_del(etf->item);
        eina_file_close(etf->handle); // close matching open (file consumed in finalize by putting it in etf) OK
        eina_stringshare_del(etf->match_theme);
        *files = eina_inlist_remove(*files, *files);
        free(etf);
     }
}

static void
_elm_theme_clear(Elm_Theme *th)
{
   Efl_Ui_Theme_Data *td;

   _elm_theme_file_clean(&th->themes);
   _elm_theme_file_clean(&th->overlay);
   _elm_theme_file_clean(&th->extension);

   ELM_SAFE_FREE(th->overlay_items, eina_list_free);
   ELM_SAFE_FREE(th->theme_items, eina_list_free);
   ELM_SAFE_FREE(th->extension_items, eina_list_free);

   ELM_SAFE_FREE(th->cache, eina_hash_free);
   ELM_SAFE_FREE(th->cache_data, eina_hash_free);
   ELM_SAFE_FREE(th->cache_style_load_failed, eina_hash_free);
   ELM_SAFE_FREE(th->theme, eina_stringshare_del);
   if (th->ref_theme)
     {
        th->ref_theme->referrers =
           eina_list_remove(th->ref_theme->referrers, th);
        elm_theme_free(th->ref_theme);
        th->ref_theme = NULL;
     }

   td = efl_data_scope_get(th->eo_theme, EFL_UI_THEME_CLASS);
   td->th = NULL;
   th->eo_theme = NULL;
}

static Eina_File *
_elm_theme_group_file_find_internal(Elm_Theme *th, const char *group, Eina_Bool force)
{
   Elm_Theme_File *etf;
   Eina_File *file = eina_hash_find(th->cache, group);

   if (file) return file;

   EINA_INLIST_FOREACH(th->overlay, etf)
     {
        file = _elm_theme_find_try(th, etf, group, force);
        if (file) return file;
     }
   EINA_INLIST_FOREACH(th->themes, etf)
     {
        file = _elm_theme_find_try(th, etf, group, force);
        if (file) return file;
     }
   EINA_INLIST_FOREACH(th->extension, etf)
     {
        file = _elm_theme_find_try(th, etf, group, force);
        if (file) return file;
     }
   if (th->ref_theme) return _elm_theme_group_file_find_internal(th->ref_theme, group, force);
   return NULL;
}

Eina_File *
_elm_theme_group_file_find(Elm_Theme *th, const char *group)
{
   Eina_File *file = _elm_theme_group_file_find_internal(th, group, EINA_FALSE);
   if (file) return file;
   file = _elm_theme_group_file_find_internal(th, group, EINA_TRUE);
   return file;
}

static const char *
_elm_theme_find_data_try(Elm_Theme *th, const Eina_File *f, const char *key)
{
   char *data;
   const char *t;

   data = edje_mmap_data_get(f, key);
   t = eina_stringshare_add(data);
   free(data);
   if (t)
     {
        eina_hash_add(th->cache_data, key, t);
        return t;
     }
   return NULL;
}

static const char *
_elm_theme_data_find(Elm_Theme *th, const char *key)
{
   Elm_Theme_File *etf;

   const char *data = eina_hash_find(th->cache_data, key);
   if (data) return data;

   EINA_INLIST_FOREACH(th->overlay, etf)
     {
        data = _elm_theme_find_data_try(th, etf->handle, key);
        if (data) return data;
     }
   EINA_INLIST_FOREACH(th->themes, etf)
     {
        data = _elm_theme_find_data_try(th, etf->handle, key);
        if (data) return data;
     }
   EINA_INLIST_FOREACH(th->extension, etf)
     {
        data = _elm_theme_find_data_try(th, etf->handle, key);
        if (data) return data;
     }
   if (th->ref_theme) return _elm_theme_data_find(th->ref_theme, key);
   return NULL;
}

Eina_Error
_elm_theme_object_set(Evas_Object *parent, Evas_Object *o, const char *clas, const char *group, const char *style)
{
   Elm_Theme *th = NULL;

   if (parent) th = elm_widget_theme_get(parent);

   return _elm_theme_set(th, o, clas, group, style, elm_widget_is_legacy(parent));
}

/* only issued by elm_icon.c */
Eina_Bool
_elm_theme_object_icon_set(Evas_Object *o,
                           const char *group,
                           const char *style)
{
   Elm_Theme *th = elm_widget_theme_get(o);

   return _elm_theme_icon_set(th, o, group, style);
}

Eina_Error
_elm_theme_set(Elm_Theme *th, Evas_Object *o, const char *clas, const char *group, const char *style, Eina_Bool is_legacy)
{
   Eina_File *file;
   char buf2[1024];
   const char *group_sep = "/";
   const char *style_sep = ":";

   if ((!clas) || !o) return EFL_UI_THEME_APPLY_ERROR_GENERIC;
   if (!th) th = theme_default;
   if (!th) return EFL_UI_THEME_APPLY_ERROR_GENERIC;

   if (eina_streq(style, "default")) style = NULL;

   if (is_legacy)
     snprintf(buf2, sizeof(buf2), "elm/%s/%s/%s", clas, (group) ? group : "base", (style) ? style : "default");
   else
     snprintf(buf2, sizeof(buf2), "efl/%s%s%s%s%s", clas,
            ((group) ? group_sep : "\0"), ((group) ? group : "\0"),
            ((style) ? style_sep : "\0"), ((style) ? style : "\0"));
   if (!eina_hash_find(th->cache_style_load_failed, buf2))
     {
        file = _elm_theme_group_file_find(th, buf2);
        if (file)
          {
             if (edje_object_mmap_set(o, file, buf2)) return EFL_UI_THEME_APPLY_ERROR_NONE;
             else
               {
                  ERR("could not set theme group '%s' from file '%s': %s",
                      buf2,
                      eina_file_filename_get(file),
                      edje_load_error_str(edje_object_load_error_get(o)));
               }
          }
        //style not found, add to the not found list
        eina_hash_add(th->cache_style_load_failed, buf2, (void *)1);
     }

   if (!style)
     return EFL_UI_THEME_APPLY_ERROR_GENERIC;

   // Use the elementary default style.
   if (_elm_theme_set(th, o, clas, group, NULL, is_legacy) == EFL_UI_THEME_APPLY_ERROR_NONE)
     return EFL_UI_THEME_APPLY_ERROR_DEFAULT;
   return EFL_UI_THEME_APPLY_ERROR_GENERIC;
}

Eina_Bool
_elm_theme_icon_set(Elm_Theme *th,
                    Evas_Object *o,
                    const char *group,
                    const char *style)
{
   Eina_File *file;
   char buf2[1024];
   int w, h;

   if (efl_isa((o), ELM_ICON_CLASS) && elm_icon_standard_get(o) &&
       strcmp(elm_config_icon_theme_get(), ELM_CONFIG_ICON_THEME_ELEMENTARY))
     {
        elm_icon_standard_set(o, elm_icon_standard_get(o));
        return EINA_TRUE;
     }

   if (!th) th = theme_default;
   if (!th) return EINA_FALSE;

   snprintf(buf2, sizeof(buf2), "elm/icon/%s/%s", group, style);
   file = _elm_theme_group_file_find(th, buf2);
   if (file)
     {
        elm_image_mmap_set(o, file, buf2);
        elm_image_object_size_get(o, &w, &h);
        if (w > 0) return EINA_TRUE;
     }
   snprintf(buf2, sizeof(buf2), "elm/icon/%s/default", group);
   file = _elm_theme_group_file_find(th, buf2);

   if (!file) return EINA_FALSE;

   elm_image_mmap_set(o, file, buf2);
   elm_image_object_size_get(o, &w, &h);

   return w > 0;
}

void
_elm_theme_parse(Elm_Theme *th, const char *theme)
{
   Eina_List *names = NULL;
   const char *p, *pe;

   if (!th) th = theme_default;
   if (!th) return;

   if (theme)
     {
        Eina_Strbuf *buf;

        buf = eina_strbuf_new();

        p = theme;
        pe = p;
        for (;;)
          {
             if ((pe[0] == '\\') && (pe[1] == ':'))
               {
                  eina_strbuf_append_char(buf, ':');
                  pe += 2;
               }
#ifdef HAVE_ELEMENTARY_WIN32
             else if (isalpha(pe[0]) && (pe[1] == ':') &&
                      ((pe[2] == '/') || (pe[2] == '\\')))
               {
                  // Correct processing file path on  Windows OS "<disk>:/" or "<disk>:\"
                  eina_strbuf_append_char(buf, *pe);
                  pe++;
                  eina_strbuf_append_char(buf, *pe);
                  pe++;
               }
#endif
             else if ((*pe == ':') || (!*pe))
               { // p -> pe == 'name:'
                  if (pe > p)
                    {
                       const char *nn;

                       nn = eina_stringshare_add(eina_strbuf_string_get(buf));
                       if (nn) names = eina_list_append(names, nn);
                       eina_strbuf_reset(buf);
                    }
                  if (!*pe) break;
                  p = pe + 1;
                  pe = p;
               }
             else
               {
                  eina_strbuf_append_char(buf, *pe);
                  pe++;
               }
          }

        eina_strbuf_free(buf);
     }
   p = eina_list_data_get(eina_list_last(names));
   if ((!p) || (strcmp(p, "default")))
     {
        p = eina_stringshare_add("default");
        if (p) names = eina_list_append(names, p);
     }
   if (th->cache) eina_hash_free(th->cache);
   th->cache = eina_hash_string_superfast_new(EINA_FREE_CB(eina_file_close));
   if (th->cache_data) eina_hash_free(th->cache_data);
   th->cache_data = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del));
   if (th->cache_style_load_failed) eina_hash_free(th->cache_style_load_failed);
   th->cache_style_load_failed = eina_hash_string_superfast_new(NULL);
   _elm_theme_file_clean(&th->themes);
   th->theme_items = eina_list_free(th->theme_items);

   EINA_LIST_FREE(names, p)
     _elm_theme_file_item_add(&th->themes, p, EINA_FALSE, EINA_TRUE);
   elm_theme_get(th);
}

void
_elm_theme_init(void)
{
   Eo *theme_default_obj;
   Efl_Ui_Theme_Data *td;

   if (theme_default) return;

   theme_default_obj = efl_add(EFL_UI_THEME_CLASS, efl_main_loop_get());
   td = efl_data_scope_get(theme_default_obj, EFL_UI_THEME_CLASS);
   theme_default = td->th;
}

void
_elm_theme_shutdown(void)
{
   Elm_Theme *th;

   while (themes)
     {
        th = eina_list_data_get(themes);

        /* In theme object destructor, theme is deallocated and the theme is
         * removed from themes list in _elm_theme_free_internal().
         * Consequently, themes list is updated.
         */
        efl_del(th->eo_theme);
     }
}

EAPI Elm_Theme *
elm_theme_new(void)
{
   Eo *obj = efl_add(EFL_UI_THEME_CLASS, efl_main_loop_get());
   Efl_Ui_Theme_Data *td = efl_data_scope_get(obj, EFL_UI_THEME_CLASS);
   return td->th;
}

EAPI void
elm_theme_free(Elm_Theme *th)
{
   EINA_SAFETY_ON_NULL_RETURN(th);

   /* Destructs theme object and theme is deallocated in
    * _elm_theme_free_internal() in theme object desctructor.
    */
   if (efl_ref_count(th->eo_theme) > 1)
     efl_unref(th->eo_theme);
   else
     efl_del(th->eo_theme);
}

static void
elm_theme_files_copy(Eina_Inlist **dst, Eina_Inlist **src)
{
   Elm_Theme_File *etf, *cpy;

   EINA_INLIST_FOREACH(*src, etf)
     {
        cpy = malloc(sizeof(Elm_Theme_File));
        EINA_SAFETY_ON_NULL_RETURN(cpy);
        cpy->item = eina_stringshare_ref(etf->item);
        cpy->handle = eina_file_dup(etf->handle);
        *dst = eina_inlist_append(*dst, EINA_INLIST_GET(cpy));
     }
}

EAPI void
elm_theme_copy(Elm_Theme *th, Elm_Theme *thdst)
{
   Eo *thdst_obj;
   Efl_Ui_Theme_Data *thdst_td;

   if (!th) th = theme_default;
   if (!th) return;
   if (!thdst) thdst = theme_default;
   if (!thdst) return;

   //Clear the given theme and restore the relationship with theme object.
   thdst_obj = thdst->eo_theme;
   _elm_theme_clear(thdst);
   thdst->eo_theme = thdst_obj;
   thdst_td = efl_data_scope_get(thdst_obj, EFL_UI_THEME_CLASS);
   thdst_td->th = thdst;

   if (th->ref_theme)
     {
        thdst->ref_theme = th->ref_theme;
        thdst->ref_theme->referrers =
           eina_list_append(thdst->ref_theme->referrers, thdst);
        efl_ref(thdst->ref_theme->eo_theme);
     }
   elm_theme_files_copy(&thdst->overlay, &th->overlay);
   elm_theme_files_copy(&thdst->themes, &th->themes);
   elm_theme_files_copy(&thdst->extension, &th->extension);

   if (th->theme) thdst->theme = eina_stringshare_add(th->theme);
   elm_theme_flush(thdst);
}

EAPI void
elm_theme_ref_set(Elm_Theme *th, Elm_Theme *thref)
{
   Eo *th_obj;
   Efl_Ui_Theme_Data *th_td;

   if (!th) th = theme_default;
   if (!th) return;
   if (!thref) thref = theme_default;
   if (!thref) return;
   if (th->ref_theme == thref) return;

   //Clear the given theme and restore the relationship with theme object.
   th_obj = th->eo_theme;
   _elm_theme_clear(th);
   th->eo_theme = th_obj;
   th_td = efl_data_scope_get(th_obj, EFL_UI_THEME_CLASS);
   th_td->th = th;

   if (thref)
     {
        thref->referrers = eina_list_append(thref->referrers, th);
        efl_ref(thref->eo_theme);
     }
   th->ref_theme = thref;
   elm_theme_flush(th);
}

EAPI Elm_Theme *
elm_theme_ref_get(const Elm_Theme *th)
{
   if (!th) th = theme_default;
   if (!th) return NULL;
   return th->ref_theme;
}

EAPI Elm_Theme *
elm_theme_default_get(void)
{
   return theme_default;
}

EAPI void
elm_theme_overlay_add(Elm_Theme *th, const char *item)
{
   if (!th) th = theme_default;
   if (!th) return;
   efl_ui_theme_overlay_add(th->eo_theme, item);
}

EAPI void
elm_theme_overlay_del(Elm_Theme *th, const char *item)
{
   if (!th) th = theme_default;
   if (!th) return;
   efl_ui_theme_overlay_del(th->eo_theme, item);
}

EAPI void
elm_theme_overlay_mmap_add(Elm_Theme *th, const Eina_File *f)
{
   Eina_File *file = eina_file_dup(f);

   if (!th) th = theme_default;
   if (!th)
     {
        eina_file_close(file); // close matching open (finalize expected to consume eina file) OK
        return;
     }
   th->overlay_items = eina_list_free(th->overlay_items);
   _elm_theme_item_finalize(&th->overlay, eina_file_filename_get(file), file, EINA_TRUE, EINA_FALSE);
   elm_theme_flush(th);
}

EAPI void
elm_theme_overlay_mmap_del(Elm_Theme *th, const Eina_File *f)
{
   if (!f) return;
   if (!th) th = theme_default;
   if (!th) return;
   th->overlay_items = eina_list_free(th->overlay_items);
   _elm_theme_file_mmap_del(&th->overlay, f);
   elm_theme_flush(th);
}

EAPI const Eina_List *
elm_theme_overlay_list_get(const Elm_Theme *th)
{
   if (!th) th = theme_default;
   if (!th) return NULL;
   if (!th->overlay_items)
     {
        Elm_Theme_File *etf;

        EINA_INLIST_FOREACH(th->overlay, etf)
          ((Elm_Theme*)th)->overlay_items = eina_list_append(th->overlay_items, etf->item);
     }
   return th->overlay_items;
}

EAPI void
elm_theme_extension_add(Elm_Theme *th, const char *item)
{
   if (!th) th = theme_default;
   if (!th) return;
   efl_ui_theme_extension_add(th->eo_theme, item);
}

EAPI void
elm_theme_extension_del(Elm_Theme *th, const char *item)
{
   if (!th) th = theme_default;
   if (!th) return;
   efl_ui_theme_extension_del(th->eo_theme, item);
}

EAPI void
elm_theme_extension_mmap_add(Elm_Theme *th, const Eina_File *f)
{
   Eina_File *file;

   if (!f) return;
   if (!th) th = theme_default;
   if (!th) return;
   file = eina_file_dup(f);
   th->extension_items = eina_list_free(th->extension_items);
   _elm_theme_item_finalize(&th->extension, eina_file_filename_get(file), file, EINA_FALSE, EINA_FALSE);
   elm_theme_flush(th);
}

EAPI void
elm_theme_extension_mmap_del(Elm_Theme *th, const Eina_File *f)
{
   if (!f) return;
   if (!th) th = theme_default;
   if (!th) return;
   th->extension_items = eina_list_free(th->extension_items);
   _elm_theme_file_mmap_del(&th->extension, f);
   elm_theme_flush(th);
}

EAPI const Eina_List *
elm_theme_extension_list_get(const Elm_Theme *th)
{
   if (!th) th = theme_default;
   if (!th) return NULL;
   if (!th->extension_items)
     {
        Elm_Theme_File *etf;

        EINA_INLIST_FOREACH(th->extension, etf)
          ((Elm_Theme*)th)->extension_items = eina_list_append(th->extension_items, etf->item);
     }
   return th->extension_items;
}

EAPI void
elm_theme_set(Elm_Theme *th, const char *theme)
{
   if (!th) th = theme_default;
   if (!th) return;
   _elm_theme_parse(th, theme);
   ELM_SAFE_FREE(th->theme, eina_stringshare_del);
   elm_theme_flush(th);
   if (th == theme_default)
     eina_stringshare_replace(&_elm_config->theme, theme);
}

EAPI const char *
elm_theme_get(Elm_Theme *th)
{
   if (!th) th = theme_default;
   if (!th) return NULL;
   if (!th->theme)
     {
        Eina_Strbuf *buf;
        Elm_Theme_File *etf;
        const char *f;

        buf = eina_strbuf_new();
        EINA_INLIST_FOREACH(th->themes, etf)
          {
             f = etf->item;
             while (*f)
               {
                  if (*f == ':')
                    eina_strbuf_append_char(buf, '\\');
                  eina_strbuf_append_char(buf, *f);

                  f++;
               }
             if (EINA_INLIST_GET(etf)->next)
               eina_strbuf_append_char(buf, ':');
          }
        th->theme = eina_stringshare_add(eina_strbuf_string_get(buf));
        eina_strbuf_free(buf);
     }
   return th->theme;
}

EAPI const Eina_List *
elm_theme_list_get(const Elm_Theme *th)
{
   if (!th) th = theme_default;
   if (!th) return NULL;
   if (!th->theme_items)
     {
        Elm_Theme_File *etf;

        EINA_INLIST_FOREACH(th->themes, etf)
          ((Elm_Theme*)th)->theme_items = eina_list_append(th->theme_items, etf->item);
     }
   return th->theme_items;
}

EAPI char *
elm_theme_list_item_path_get(const char *f, Eina_Bool *in_search_path)
{
   static const char *home = NULL;
   char buf[PATH_MAX];

   if (!f)
     {
        if (in_search_path) *in_search_path = EINA_FALSE;
        return NULL;
     }

   if (!home)
     {
        home = eina_environment_home_get();
        if (!home) home = "";
     }

   if ((f[0] == '/') || ((f[0] == '.') && (f[1] == '/')) ||
       ((f[0] == '.') && (f[1] == '.') && (f[2] == '/')) ||
       ((isalpha(f[0])) && (f[1] == ':')))
     {
        if (in_search_path) *in_search_path = EINA_FALSE;
        return strdup(f);
     }
   else if (((f[0] == '~') && (f[1] == '/')))
     {
        if (in_search_path) *in_search_path = EINA_FALSE;
        snprintf(buf, sizeof(buf), "%s/%s", home, f + 2);
        return strdup(buf);
     }
   snprintf(buf, sizeof(buf), "%s/"ELEMENTARY_BASE_DIR"/themes/%s.edj", home, f);
   if (ecore_file_exists(buf))
     {
        if (in_search_path) *in_search_path = EINA_TRUE;
        return strdup(buf);
     }

   snprintf(buf, sizeof(buf), "%s/themes/%s.edj", _elm_data_dir, f);
   if (ecore_file_exists(buf))
     {
        if (in_search_path) *in_search_path = EINA_TRUE;
        return strdup(buf);
     }

   if (in_search_path) *in_search_path = EINA_FALSE;
   return NULL;
}

EAPI void
elm_theme_flush(Elm_Theme *th)
{
   if (!th) th = theme_default;
   if (!th) return;
   if (th->cache) eina_hash_free(th->cache);
   th->cache = eina_hash_string_superfast_new(EINA_FREE_CB(eina_file_close));
   if (th->cache_data) eina_hash_free(th->cache_data);
   th->cache_data = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del));
   if (th->cache_style_load_failed) eina_hash_free(th->cache_style_load_failed);
   th->cache_style_load_failed = eina_hash_string_superfast_new(NULL);
   _elm_win_rescale(th, EINA_TRUE);
   _elm_ews_wm_rescale(th, EINA_TRUE);
   if (th->referrers)
     {
        Eina_List *l;
        Elm_Theme *th2;

        EINA_LIST_FOREACH(th->referrers, l, th2) elm_theme_flush(th2);
     }
}

EAPI void
elm_theme_full_flush(void)
{
   Eina_List *l;
   Elm_Theme *th;

   EINA_LIST_FOREACH(themes, l, th)
     {
        elm_theme_flush(th);
     }
   elm_theme_flush(theme_default);
}

EAPI Eina_List *
elm_theme_name_available_list_new(void)
{
   Eina_List *list = NULL;
   Eina_List *dir, *l;
   char buf[PATH_MAX], *file, *s, *th;
   static const char *home = NULL;

   if (!home)
     {
        home = eina_environment_home_get();
        if (!home) home = "";
     }

   snprintf(buf, sizeof(buf), "%s/"ELEMENTARY_BASE_DIR"/themes", home);
   dir = ecore_file_ls(buf);
   EINA_LIST_FREE(dir, file)
     {
        snprintf(buf, sizeof(buf), "%s/"ELEMENTARY_BASE_DIR"/themes/%s", home, file);
        if ((!ecore_file_is_dir(buf)) && (ecore_file_size(buf) > 0))
          {
             if (eina_str_has_extension(file, ".edj"))
               {
                  th = strdup(file);
                  s = strrchr(th, '.');
                  *s = 0;
                  list = eina_list_append(list, th);
               }
          }
        free(file);
     }

   snprintf(buf, sizeof(buf), "%s/themes", _elm_data_dir);
   dir = ecore_file_ls(buf);
   EINA_LIST_FREE(dir, file)
     {
        snprintf(buf, sizeof(buf), "%s/themes/%s", _elm_data_dir, file);
        if ((!ecore_file_is_dir(buf)) && (ecore_file_size(buf) > 0))
          {
             if (eina_str_has_extension(file, ".edj"))
               {
                  int dupp;

                  th = strdup(file);
                  s = strrchr(th, '.');
                  *s = 0;
                  dupp = 0;
                  EINA_LIST_FOREACH(list, l, s)
                    {
                       if (!strcmp(s, th))
                         {
                            dupp = 1;
                            break;
                         }
                    }
                  if (dupp) free(th);
                  else list = eina_list_append(list, th);
               }
          }
        free(file);
     }
   list = eina_list_sort(list, 0, EINA_COMPARE_CB(strcasecmp));
   return list;
}

EAPI void
elm_theme_name_available_list_free(Eina_List *list)
{
   char *s;
   EINA_LIST_FREE(list, s) free(s);
}

EAPI void
elm_object_theme_set(Evas_Object *obj, Elm_Theme *th)
{
   EINA_SAFETY_ON_NULL_RETURN(obj);
   elm_widget_theme_set(obj, th);
}

EAPI Elm_Theme *
elm_object_theme_get(const Evas_Object *obj)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
   return elm_widget_theme_get(obj);
}

EAPI const char *
elm_theme_data_get(Elm_Theme *th, const char *key)
{
   if (!th) th = theme_default;
   if (!th) return NULL;
   return _elm_theme_data_find(th, key);
}

EAPI const char *
elm_theme_group_path_find(Elm_Theme *th, const char *group)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(group, NULL);
   Eina_File *th_file = NULL;
   if (!th) th = theme_default;
   if (!th) return NULL;

   th_file = _elm_theme_group_file_find(th, group);
   if (th_file)
     return eina_file_filename_get(th_file);
   return NULL;
}

static Eina_List *
_elm_theme_file_group_base_list(Eina_List *list,
                                Eina_Inlist *handles,
                                const char *base, int len)
{
   Eina_Stringshare *c, *c2;
   Eina_List *coll;
   Eina_List *in, *ll;
   Elm_Theme_File *etf;

   EINA_INLIST_FOREACH(handles, etf)
     {
        coll = edje_mmap_collection_list(etf->handle);
        EINA_LIST_FOREACH(coll, ll, c)
          {
             // if base == start of collection str
             if (!strncmp(c, base, len))
               {
                  // check if already in list
                  EINA_LIST_FOREACH(list, in, c2)
                    if (c == c2)
                      break;

                  // if not already in list append shared str to list
                  if (!in)
                    list = eina_list_append(list, eina_stringshare_ref(c));
               }
          }
        edje_mmap_collection_list_free(coll);
     }

   return list;
}

EAPI Eina_List *
elm_theme_group_base_list(Elm_Theme *th, const char *base)
{
   Eina_List *list;
   int len;

   EINA_SAFETY_ON_NULL_RETURN_VAL(base, NULL);
   if (!th) th = theme_default;
   if (!th) return NULL;

   // XXX: look results up in a hash for speed
   len = strlen(base);
   // go through all possible theme files and find collections that match
   list = _elm_theme_file_group_base_list(NULL, th->overlay,
                                          base, len);
   list = _elm_theme_file_group_base_list(list, th->themes,
                                          base, len);
   list = _elm_theme_file_group_base_list(list, th->extension,
                                          base, len);

   // sort the list nicely at the end
   list = eina_list_sort(list, 0, EINA_COMPARE_CB(strcmp));
   // XXX: store results in hash for fast lookup...
   return list;
}

EAPI const char *
elm_theme_system_dir_get(void)
{
   static char *path = NULL;
   char buf[PATH_MAX];

   if (path) return path;

   snprintf(buf, sizeof(buf), "%s/themes", _elm_data_dir);
   path = strdup(buf);

   return path;
}

EAPI const char *
elm_theme_user_dir_get(void)
{
   static char *path = NULL;
   char buf[PATH_MAX];
   const char *home;

   if (path) return path;

   home = eina_environment_home_get();
   snprintf(buf, sizeof(buf), "%s/"ELEMENTARY_BASE_DIR"/themes", home);
   path = strdup(buf);

   return path;
}

/* Allocates memory for theme and appends the theme to themes list. */
static Elm_Theme *
_elm_theme_new_internal(Eo *obj)
{
   Elm_Theme *th = calloc(1, sizeof(Elm_Theme));
   if (!th) return NULL;
   _elm_theme_file_item_add(&th->themes, "default", EINA_FALSE, EINA_TRUE);
   themes = eina_list_append(themes, th);
   th->eo_theme = obj;
   return th;
}

EOLIAN static Eo *
_efl_ui_theme_efl_object_constructor(Eo *obj, Efl_Ui_Theme_Data *pd)
{
   obj = efl_constructor(efl_super(obj, EFL_UI_THEME_CLASS));

   pd->th = _elm_theme_new_internal(obj);

   return obj;
}

/* Removes the given theme from themes list and deallocates the given theme. */
static void
_elm_theme_free_internal(Elm_Theme *th)
{
   _elm_theme_clear(th);
   themes = eina_list_remove(themes, th);
   free(th);
}

EOLIAN static void
_efl_ui_theme_efl_object_destructor(Eo *obj, Efl_Ui_Theme_Data *pd)
{
   Eina_Bool is_theme_default = EINA_FALSE;

   if (pd->th == theme_default)
     is_theme_default = EINA_TRUE;

   _elm_theme_free_internal(pd->th);

   if (is_theme_default) theme_default = NULL;

   efl_destructor(efl_super(obj, EFL_UI_THEME_CLASS));
}

EOLIAN static Efl_Ui_Theme *
_efl_ui_theme_default_get(void)
{
   if (theme_default)
     return theme_default->eo_theme;

   return NULL;
}

EOLIAN static void
_efl_ui_theme_extension_add(Eo *obj EINA_UNUSED, Efl_Ui_Theme_Data *pd, const char *item)
{
   if (!item) return;
   pd->th->extension_items = eina_list_free(pd->th->extension_items);
   _elm_theme_file_item_add(&pd->th->extension, item, EINA_FALSE, EINA_FALSE);
   elm_theme_flush(pd->th);
}

EOLIAN static void
_efl_ui_theme_extension_del(Eo *obj EINA_UNUSED, Efl_Ui_Theme_Data *pd, const char *item)
{
   if (!item) return;
   pd->th->extension_items = eina_list_free(pd->th->extension_items);
   _elm_theme_file_item_del(&pd->th->extension, item);
   elm_theme_flush(pd->th);
}

EOLIAN static void
_efl_ui_theme_overlay_add(Eo *obj EINA_UNUSED, Efl_Ui_Theme_Data *pd, const char *item)
{
   if (!item) return;
   pd->th->overlay_items = eina_list_free(pd->th->overlay_items);
   _elm_theme_file_item_add(&pd->th->overlay, item, EINA_TRUE, EINA_FALSE);
   elm_theme_flush(pd->th);
}

EOLIAN static void
_efl_ui_theme_overlay_del(Eo *obj EINA_UNUSED, Efl_Ui_Theme_Data *pd, const char *item)
{
   if (!item) return;
   pd->th->overlay_items = eina_list_free(pd->th->overlay_items);
   _elm_theme_file_item_del(&pd->th->overlay, item);
   elm_theme_flush(pd->th);
}

#include "efl_ui_theme.eo.c"
