/*
 * vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2
 */
#include <limits.h>

#include "Ecore_Desktop.h"
#include "ecore_desktop_private.h"

//#define DEBUG 1

static char        *_ecore_desktop_icon_find0(const char *icon,
					      const char *icon_size,
					      const char *icon_theme);

static int          _ecore_desktop_icon_theme_list_add(void *data,
						       const char *path);
static void         _ecore_desktop_icon_theme_destroy(Ecore_Desktop_Icon_Theme *
						      icon_theme);
static void        
_ecore_desktop_icon_theme_directory_destroy(Ecore_Desktop_Icon_Theme_Directory *
					    icon_theme_directory);

/* FIXME: We need a way for the client to disable searching for any of these that they don't support. */
static const char  *ext[] =
   { "", ".edj", ".png", ".svgz", ".svg", ".xpm", NULL };  /* "" is in case the icon already has an extension, search for that first. */
static int          init_count = 0;
static Ecore_Hash  *icon_theme_cache = NULL;

/**
 * @defgroup Ecore_Desktop_Icon_Group icon theme Functions
 *
 * Functions that deal with freedesktop.org icon themes.
 *
 * This conforms with the freedesktop.org XDG Icon Theme Specification version 0.11
 */

/**
 * Find the path to an icon.
 *
 * Using the search algorithm specified by freedesktop.org,
 * search for an icon in the currently installed set of icon themes.
 *
 * The returned string needs to be freed eventually.
 *
 * @param   icon The name of the required icon.
 * @param   icon_size The size of the required icon.
 * @param   icon_theme The theme of the required icon.
 * @return  The full path to an icon file, or NULL.
 * @ingroup Ecore_Desktop_Icon_Group
 */

EAPI char         *
ecore_desktop_icon_find(const char *icon, const char *icon_size,
			const char *icon_theme)
{
   char           *result = NULL, *icn;
   Ecore_List     *icons;
   int             in_cache = 0;
   double          begin;

   begin = ecore_time_get();
   if (icon)
     {
        /* Easy check first, was a full path supplied? */
        if ((icon[0] == '/') && (ecore_file_exists(icon)))
           result = strdup(icon);
	else
	  {
             icons = ecore_desktop_paths_to_list(icon);
             if (icons)
	       {

                  if (icon_size == NULL)
                     icon_size = "48x48";
                  if (icon_theme == NULL)
                     icon_theme = "hicolor";
                  ecore_list_goto_first(icons);
                  while ((icn = ecore_list_next(icons)))
                    {
	               char *ext;
#ifdef DEBUG
	               fprintf(stderr, "\tTrying To Find Icon %s\n", icn);
#endif
	               ext = strrchr(icn, '.');
	               /* Check for unsupported extension */
	               if ((ext) && (!strcmp(ext, ".ico")))
		          continue;

	               result = _ecore_desktop_icon_find0(icn, icon_size, icon_theme);
	               if (result)
	                  break;
                    }
                  ecore_list_destroy(icons);

	       } /* if (icons) */
	  } /* if ((icon[0] == '/') && (ecore_file_exists(icon))) ; else */
     } /* if (icon) */

   if (result)
     {
        if (in_cache)
          {
             instrumentation.icons_in_cache_time += ecore_time_get() - begin;
             instrumentation.icons_in_cache++;
	  }
	else
	  {
             instrumentation.icons_time += ecore_time_get() - begin;
             instrumentation.icons++;
	  }
     }
   else
     {
        instrumentation.icons_not_found_time += ecore_time_get() - begin;
        instrumentation.icons_not_found++;
     }

   return result;
}

/** Search for an icon the fdo way.
 *
 * This complies with the freedesktop.org Icon Theme Specification version 0.7
 *
 * @param   icon The icon to search for.
 * @param   icon_size The icon size to search for.
 * @param   icon_theme The icon theme to search in.
 * @return  The full path to the found icon.
 */
static char  *
_ecore_desktop_icon_find0(const char *icon, const char *icon_size,
			  const char *icon_theme)
{
   /*  NOTES ON OPTIMIZATIONS
    *
    * The spec has this to say -
    *
    * "The algorithm as described in this document works by always looking up 
    * filenames in directories (a stat in unix terminology). A good 
    * implementation is expected to read the directories once, and do all 
    * lookups in memory using that information.
    *
    * "This caching can make it impossible for users to add icons without having 
    * to restart applications. In order to handle this, any implementation that 
    * does caching is required to look at the mtime of the toplevel icon 
    * directories when doing a cache lookup, unless it already did so less than 
    * 5 seconds ago. This means that any icon editor or theme installation 
    * program need only to change the mtime of the the toplevel directory where 
    * it changed the theme to make sure that the new icons will eventually get 
    * used."
    *
    * The phrase "toplevel icon directories" is ambigous, but I guess they mean 
    * the directory where the index.theme file lives.
    *
    * On the other hand, OS caching (at least in linux) seems to do a reasonable 
    * job here.
    *
    * We also precalculate and cache all the information extracted from 
    * the .theme files.
    */

   Ecore_Desktop_Icon_Theme *theme;
   char                path[PATH_MAX];
   char               *found = NULL;
   int                 wanted_size;
   int                 minimal_size = INT_MAX;
   int                 i;
   char               *closest = NULL;
   Ecore_Desktop_Icon_Theme_Directory *directory;

   if ((icon == NULL) || (icon[0] == '\0'))
      return NULL;

#ifdef DEBUG
   fprintf(stderr, "\tTrying To Find Icon %s (%s) in theme %s\n", icon,
	   icon_size, icon_theme);
#endif

   /* Get the theme description file. */
   theme = ecore_desktop_icon_theme_get(icon_theme, NULL);
#ifdef DEBUG
   printf("SEARCHING FOR %s\n", icn);
#endif

   if (!theme) return NULL;
   if (!theme->Directories) goto done;

   wanted_size = atoi(icon_size);
   /* Loop through the themes directories. */

   ecore_list_goto_first(theme->Directories);
   while ((directory = ecore_list_next(theme->Directories)) != NULL)
     {
	if (directory->size)
	  {
	     int                 match = 0;
	     int                 result_size = 0;

	     /* Does this theme directory match the required icon size? */
	     switch (directory->type[0])
	       {
		case 'F':	/* Fixed. */
		   match = (wanted_size == directory->size);
		   result_size = abs(directory->size - wanted_size);
		   break;
		case 'S':	/* Scaled. */
		   match = ((directory->minimum <= wanted_size) &&
			    (wanted_size <= directory->maximum));
		   if (wanted_size < directory->minimum)
		     result_size = directory->minimum - wanted_size;
		   if (wanted_size > directory->maximum)
		     result_size = wanted_size - directory->maximum;
		   break;
		default:	/* Threshold. */
		   match = (((directory->size - directory->threshold) <= wanted_size) &&
			     (wanted_size <= (directory->size + directory->threshold)));
		   if (wanted_size < (directory->size - directory->threshold))
		     result_size = directory->minimum - wanted_size;
		   if (wanted_size > (directory->size + directory->threshold))
		     result_size = wanted_size - directory->maximum;
		   break;
	       }

	     /* Look for icon with all extensions. */
	     for (i = 0; ext[i] != NULL; i++)
	       {
		  /* Check if there will be an extension. */
		  if ((ext[i][0] == '\0') && (strrchr(icon, '.') == NULL))
		    continue;
		  snprintf(path, PATH_MAX, "%s/%s/%s%s",
			   icon_theme, directory->path, icon, ext[i]);
#ifdef DEBUG
		  printf("FDO icon = %s\n", path);
#endif
		  found = ecore_desktop_paths_file_find(ecore_desktop_paths_icons, path,
							0, NULL, NULL);
		  if (found)
		    {
		       if (ecore_file_is_dir(found))
			 {
			    free(found);
			    found = NULL;
			 }
		       else if (match)	/* If there is a match in sizes, return the icon. */
			 goto done;
		       else if (result_size < minimal_size)	/* While we are here, figure out our next fallback strategy. */
			 {
			    minimal_size = result_size;
			    if (closest) free(closest);
			    closest = found;
			    found = NULL;
			 }
		       else
			 {
			    free(found);
			    found = NULL;
			 }
		    }
	       }
	  }
     }		/* while ((directory = ecore_list_next(directory_paths)) != NULL) */

   if (!found)
     {
	/* Fall back strategy #1, look for closest size in this theme. */
	found = closest;
	if (found)
	  {
	    closest = NULL;
	    goto done;
	  }

	/* Fall back strategy #2, Try again with the parent themes. */
	if ((theme->Inherits) && (strcmp(icon_theme, "hicolor") != 0))
	  {
	     char *inherits;

	     ecore_list_goto_first(theme->Inherits);
	     while ((inherits = ecore_list_next(theme->Inherits)) != NULL)
	       {
		  found = _ecore_desktop_icon_find0(icon, icon_size, inherits);
		  if (found) goto done;
	       }
	  }

	/* Fall back strategy #3, Try the default hicolor theme. */
	if ((!(theme->Inherits)) && (strcmp(icon_theme, "hicolor") != 0))
	  {
	     found = _ecore_desktop_icon_find0(icon, icon_size, "hicolor");
	     if (found) goto done;
	  }

	/* Fall back strategy #4, Just search in the base of the icon directories. */
	for (i = 0; ext[i] != NULL; i++)
	  {
	     /* Check if there will be an extension. */
	     if ((ext[i][0] == '\0') && (strrchr(icon, '.') == NULL))
	       continue;
	     snprintf(path, PATH_MAX, "%s%s", icon, ext[i]);
#ifdef DEBUG
	     printf("FDO icon = %s\n", path);
#endif
	     found = ecore_desktop_paths_file_find(ecore_desktop_paths_icons,
						   path, 0, NULL, NULL);
	     if (found)
	       {
		  if (ecore_file_is_dir(found))
		    {
		       free(found);
		       found = NULL;
		    }
		  else
		    goto done;
	       }
	  }
     }

done:
   if (closest) free(closest);
   ecore_desktop_icon_theme_destroy(theme);

   return found;
}

Ecore_Hash         *
ecore_desktop_icon_theme_list(void)
{
   static int          loaded = 0;
   if (!loaded)
     {
	char *tmp;
	tmp = ecore_desktop_paths_file_find(ecore_desktop_paths_icons, "index.theme", 2,
					    _ecore_desktop_icon_theme_list_add, NULL);
	loaded = 1;
	free(tmp);
     }
   return icon_theme_cache;
}

static int
_ecore_desktop_icon_theme_list_add(void *data, const char *path)
{
   char                icn[PATH_MAX];

   snprintf(icn, PATH_MAX, "%sindex.theme", path);
   if (ecore_desktop_icon_theme_get(icn, NULL))
      return 1;			/* Should stop it from recursing this directory, but let it continue searching the next. */
   return 0;
}

/**
 * Setup what ever needs to be setup to support ecore_desktop_icon.
 *
 * There are internal structures that are needed for ecore_desktop_icon
 * functions to operate, this sets them up.
 *
 * @ingroup Ecore_Desktop_Icon_Group
 */
EAPI int
ecore_desktop_icon_init()
{
   if (++init_count != 1)
      return init_count;

   if (!icon_theme_cache)
     {
	icon_theme_cache = ecore_hash_new(ecore_str_hash, ecore_str_compare);
	if (icon_theme_cache)
	  {
	     ecore_hash_set_free_key(icon_theme_cache, free);
	     ecore_hash_set_free_value(icon_theme_cache,
				       ECORE_FREE_CB(_ecore_desktop_icon_theme_destroy));
	  }
     }

   return init_count;
}

/**
 * Tear down what ever needs to be torn down to support ecore_desktop_ycon.
 *
 * There are internal structures that are needed for ecore_desktop_icon
 * functions to operate, this tears them down.
 *
 * @ingroup Ecore_Desktop_Icon_Group
 */
EAPI int
ecore_desktop_icon_shutdown()
{
   if (--init_count != 0)
      return init_count;

   if (icon_theme_cache)
     {
	ecore_hash_destroy(icon_theme_cache);
	icon_theme_cache = NULL;
     }

   return init_count;
}

/**
 * Get the contents of an index.theme file.
 *
 * Everything that is in the index.theme file is returned in the
 * data member of the Ecore_Desktop_Icon_Theme structure, it's an Ecore_Hash 
 * as returned by ecore_desktop_ini_get().  Some of the data in the
 * index.theme file is decoded into specific members of the returned 
 * structure.
 *
 * Use ecore_desktop_icon_theme_destroy() to free this structure.
 * 
 * @param   icon_theme Name of the icon theme, or full path to the index.theme file.
 * @param   lang Language to use, or NULL for default.
 * @return  An Ecore_Desktop_Icon_Theme containing the files contents.
 * @ingroup Ecore_Desktop_Icon_Group
 */
Ecore_Desktop_Icon_Theme *
ecore_desktop_icon_theme_get(const char *icon_theme, const char *lang)
{
   Ecore_Desktop_Icon_Theme *result = NULL;
   char *theme_path = NULL, *dir = NULL;
   char               *value;
   Ecore_List         *Directories;
   char               *directory;

   if (icon_theme[0] == '/')
      {
         theme_path = strdup(icon_theme);
	 dir = ecore_file_get_dir(theme_path);
	 if (dir)
	    icon_theme = (char *)ecore_file_get_file(dir);
#ifdef DEBUG
	 printf("LOADING THEME %s  -   %s\n", icon_theme, theme_path);
#endif
      }

   result = ecore_hash_get(icon_theme_cache, icon_theme);
   if (result) goto done;
   if (!dir)
     {
	char icn[PATH_MAX];

	snprintf(icn, PATH_MAX, "%s/index.theme", icon_theme);
#ifdef DEBUG
	printf("SEARCHING FOR %s\n", icn);
#endif
	theme_path = ecore_desktop_paths_file_find(ecore_desktop_paths_icons, icn,
						   2, NULL, NULL);
     }
   if (!theme_path) goto error;
   result = calloc(1, sizeof(Ecore_Desktop_Icon_Theme));
   if (!result) goto error;
   result->data = ecore_desktop_ini_get(theme_path);
   if (!result->data) goto error;
   result->group = ecore_hash_get(result->data, "Icon Theme");
   if (!result->group) goto error;

   /* According to the spec, name and comment are required, but we can fake those easily enough. */
   value = ecore_hash_get(result->group, "Name");
   if (!value) value = (char *) icon_theme;
   result->name = strdup(value);
   value = ecore_hash_get(result->group, "Comment");
   if (!value) value = "No comment provided.";
   result->comment = strdup(value);
   value = ecore_hash_get(result->group, "Inherits");
   if (value)
     {
	result->inherits = strdup(value);
	if (result->inherits)
	  result->Inherits = ecore_desktop_paths_to_list(result->inherits);
     }
   value = ecore_hash_get(result->group, "Example");
   if (!value) value = "exec";
   result->example = strdup(value);
   value = ecore_hash_get(result->group, "Directories");
   /* FIXME: Directories is also required, don't feel like faking it for now. */
   if (!value) goto error;
   result->directories = strdup(value);
   Directories = ecore_desktop_paths_to_list(result->directories);
   if (!Directories) goto error;
   result->Directories = ecore_list_new();
   if (!result->Directories) goto error;
   ecore_list_set_free_cb(result->Directories,
			  ECORE_FREE_CB(_ecore_desktop_icon_theme_directory_destroy));
   ecore_list_goto_first(Directories);
   while ((directory = ecore_list_next(Directories)) != NULL)
     {
	Ecore_Hash         *sub_group;
	Ecore_Desktop_Icon_Theme_Directory *dir;

	/* Get the details for this theme directory. */
	sub_group = ecore_hash_get(result->data, directory);
	dir = calloc(1, sizeof (Ecore_Desktop_Icon_Theme_Directory));
	if ((dir) && (sub_group))
	  {
	     char *size, *minsize, *maxsize, *threshold;

	     dir->path = strdup(directory);
	     value = ecore_hash_get(sub_group, "Type");
	     if (!value)
	       value = "Threshold";
	     dir->type = strdup(value);
	     size = ecore_hash_get(sub_group, "Size");
	     minsize = ecore_hash_get(sub_group, "MinSize");
	     maxsize = ecore_hash_get(sub_group, "MaxSize");
	     threshold = ecore_hash_get(sub_group, "Threshold");
	     if (size)
	       {
		  if (!minsize)
		    minsize = size;
		  if (!maxsize)
		    maxsize = size;
		  if (!threshold)
		    threshold = "2";
		  dir->minimum = atoi(minsize);
		  dir->maximum = atoi(maxsize);
		  dir->threshold = atoi(threshold);

		  dir->size = atoi(size);
		  ecore_list_append(result->Directories, dir);
	       }
	     else
	       _ecore_desktop_icon_theme_directory_destroy(dir);
	  }
	else if (dir)
	  _ecore_desktop_icon_theme_directory_destroy(dir);
     }
   ecore_list_destroy(Directories);

   /* This passes the basic validation tests, mark it as real and cache it. */
   result->path = strdup(theme_path);
   ecore_hash_set(icon_theme_cache, strdup(icon_theme), result);
   ecore_hash_destroy(result->data);
   result->data = NULL;
   result->group = NULL;

done:
   if (dir)        free(dir);
   if (theme_path) free(theme_path);

   return result;

error:
   if (dir)        free(dir);
   if (theme_path) free(theme_path);
   if (result)
     {
	if (result->data) ecore_hash_destroy(result->data);
	_ecore_desktop_icon_theme_destroy(result);
     }
   return NULL;
}

/**
 * Free whatever resources are used by an Ecore_Desktop_Icon_Theme.
 *
 * There are internal resources used by each Ecore_Desktop_Icon_Theme
 * This releases those resources.
 *
 * @param  icon_theme  An Ecore_Desktop_Icon_Theme.
 * @ingroup Ecore_Desktop_Icon_Group
 */
void
ecore_desktop_icon_theme_destroy(Ecore_Desktop_Icon_Theme * icon_theme)
{
   /* This is just a dummy, because these structures are cached. */
   /* Later versions of the cache may reference count, then this will be useful. */
}

static void
_ecore_desktop_icon_theme_destroy(Ecore_Desktop_Icon_Theme * icon_theme)
{
   if (icon_theme->path)
      free(icon_theme->path);
   if (icon_theme->name)
      free(icon_theme->name);
   if (icon_theme->comment)
      free(icon_theme->comment);
   if (icon_theme->example)
      free(icon_theme->example);
   if (icon_theme->inherits)
      free(icon_theme->inherits);
   if (icon_theme->directories)
      free(icon_theme->directories);
   if (icon_theme->Directories)
      ecore_list_destroy(icon_theme->Directories);
   if (icon_theme->Inherits)
      ecore_list_destroy(icon_theme->Inherits);
   free(icon_theme);
}

static void
_ecore_desktop_icon_theme_directory_destroy(Ecore_Desktop_Icon_Theme_Directory *
					    icon_theme_directory)
{
   if (icon_theme_directory->path)
      free(icon_theme_directory->path);
   if (icon_theme_directory->type)
      free(icon_theme_directory->type);
   free(icon_theme_directory);
}
