#include "e.h"

/* TODO List:
 * 
 * * if a application .eap file is added in a different location in a monitored app tree but has the same filename as an existing one somewhere else, the existing one gets a changed callback, not an dded callback for the new one
 * * track app execution state, visibility state etc. and call callbacks
 * * calls to execute an app or query its runing/starting state etc.
 */

/* local subsystem functions */
typedef struct _E_App_Change_Info E_App_Change_Info;
typedef struct _E_App_Callback    E_App_Callback;

struct _E_App_Change_Info
{
   E_App        *app;
   E_App_Change  change;
};

struct _E_App_Callback
{
   void (*func) (void *data, E_App *a, E_App_Change ch);
   void  *data;
   unsigned char delete_me : 1;
};

static void      _e_app_free               (E_App *a);
static void      _e_app_fields_fill        (E_App *a, char *path);
static void      _e_app_fields_empty       (E_App *a);
static Evas_List *_e_app_dir_file_list_get (E_App *a, char *path);
static E_App     *_e_app_subapp_path_find  (E_App *a, char *subpath);
static void      _e_app_monitor            (void);
static void      _e_app_change             (E_App *a, E_App_Change ch);
static int       _e_app_check              (void *data);
static Evas_Bool _e_app_check_each         (Evas_Hash *hash, const char *key, void *data, void *fdata);
static int       _e_apps_cb_exit           (void *data, int type, void *event);

/* local subsystem globals */
static Evas_Hash   *_e_apps = NULL;
static Evas_List   *_e_apps_list = NULL;
static Ecore_Timer *_e_apps_checker = NULL;
static int          _e_apps_callbacks_walking = 0;
static int          _e_apps_callbacks_delete_me = 0;
static Evas_List   *_e_apps_change_callbacks = NULL;
static Ecore_Event_Handler *_e_apps_exit_handler = NULL;
static Evas_List   *_e_apps_repositories = NULL;
static E_App       *_e_apps_all = NULL;

/* externally accessible functions */
int
e_app_init(void)
{
   char *home;
   char buf[4096];
   
   home = e_user_homedir_get();
   snprintf(buf, sizeof(buf), "%s/.e/e/applications/all", home);
   free(home);
   _e_apps_repositories = evas_list_append(_e_apps_repositories, strdup(buf));
   _e_apps_exit_handler = ecore_event_handler_add(ECORE_EVENT_EXE_EXIT, _e_apps_cb_exit, NULL);
   _e_apps_all = e_app_new(buf, 1);
   return 1;
}

int
e_app_shutdown(void)
{
   if (_e_apps_all)
     {
	e_object_unref(E_OBJECT(_e_apps_all));
	_e_apps_all = NULL;
     }
   while (_e_apps_repositories)
     {
	free(_e_apps_repositories->data);
	_e_apps_repositories = evas_list_remove_list(_e_apps_repositories, _e_apps_repositories);
     }
   if (_e_apps_exit_handler)
     {
	ecore_event_handler_del(_e_apps_exit_handler);
	_e_apps_exit_handler = NULL;
     }
   return 1;
}

E_App *
e_app_new(char *path, int scan_subdirs)
{
   E_App *a;

   a = evas_hash_find(_e_apps, path);
   if (a)
     {
	e_object_ref(E_OBJECT(a));
	return a;
     }
   a = E_OBJECT_ALLOC(E_App, _e_app_free);
   a->mod_time = e_file_mod_time(path);
   if (e_file_is_dir(path))
     {
	char buf[4096];
	
	a->path = strdup(path);
	snprintf(buf, sizeof(buf), "%s/.directory.eap", path);
	a->directory_mod_time = e_file_mod_time(buf);
	if (e_file_exists(buf))
	  _e_app_fields_fill(a, buf);
	else
	  a->name = strdup(e_file_get_file(path));
	if (scan_subdirs) e_app_subdir_scan(a, scan_subdirs);
     }
   else if (e_file_exists(path))
     {
	char *p;
	
	/* check if file ends in .eap */
	p = strrchr(path, '.');
	if (!p)
	  {
	     free(a);
	     return NULL;
	  }
	p++;
	if ((strcasecmp(p, "eap")))
	  {
	     free(a);
	     return NULL;
	  }
	/* record the path */
	a->path = strdup(path);
	
	/* get the field data */
	_e_app_fields_fill(a, path);
	
	/* no exe field.. not valid. drop it */
	if (!a->exe)
	  {
	     if (a->name) free(a->name);
	     if (a->generic) free(a->generic);
	     if (a->comment) free(a->comment);
	     if (a->exe) free(a->exe);
	     if (a->path) free(a->path);
	     if (a->win_name) free(a->win_name);
	     if (a->win_class) free(a->win_class);
	     free(a);
	     return NULL;
	  }
     }
   else
     {
	free(a);
	return NULL;
     }
   _e_apps = evas_hash_add(_e_apps, a->path, a);
   _e_apps_list = evas_list_prepend(_e_apps_list, a);
   _e_app_monitor();
   return a;
}

void
e_app_subdir_scan(E_App *a, int scan_subdirs)
{
   Evas_List *files;
   char buf[4096];

   E_OBJECT_CHECK(a);
   if (a->exe) return;
   if (a->scanned)
     {
	Evas_List *l;
	
	if (!scan_subdirs) return;
	for (l = a->subapps; l; l = l->next)
	  e_app_subdir_scan(l->data, scan_subdirs);
	return;
     }
   a->scanned = 1;
   files = _e_app_dir_file_list_get(a, a->path);
   while (files)
     {
	E_App *a2;
	char *s;
	
	s = files->data;
	a2 = NULL;
	if (s[0] != '.')
	  {
	     Evas_List *pl;
			
	     snprintf(buf, sizeof(buf), "%s/%s", a->path, s);
	     if (e_file_exists(buf)) a2 = e_app_new(buf, scan_subdirs);
	     pl = _e_apps_repositories;
	     while ((!a2) && (pl))
	       {
		  snprintf(buf, sizeof(buf), "%s/%s", (char *)pl->data, s);
		  a2 = e_app_new(buf, scan_subdirs);
		  pl = pl->next;
	       }
	     if (a2)
	       {
		  a->subapps = evas_list_append(a->subapps, a2);
		  a2->parent = a;
	       }
	     free(s);
	  }
	files = evas_list_remove_list(files, files);
     }
}

int
e_app_exec(E_App *a)
{
   Ecore_Exe *exe;
   
   E_OBJECT_CHECK_RETURN(a, 0);
   if (!a->exe) return 0;
   exe = ecore_exe_run(a->exe, a);
   if (!exe) return 0;
   a->instances = evas_list_append(a->instances, exe);
   if (a->startup_notify) a->starting = 1;
   _e_app_change(a, E_APP_EXEC);
   return 1;
}

int
e_app_starting_get(E_App *a)
{
   E_OBJECT_CHECK_RETURN(a, 0);
   return a->starting;
}

int
e_app_running_get(E_App *a)
{
   E_OBJECT_CHECK_RETURN(a, 0);
   if (a->instances) return 1;
   return 0;
}

void
e_app_change_callback_add(void (*func) (void *data, E_App *a, E_App_Change ch), void *data)
{
   E_App_Callback *cb;
   
   cb = calloc(1, sizeof(E_App_Callback));
   cb->func = func;
   cb->data = data;
   _e_apps_change_callbacks = evas_list_append(_e_apps_change_callbacks, cb);
   _e_app_monitor();
}

void
e_app_change_callback_del(void (*func) (void *data, E_App *a, E_App_Change ch), void *data)
{
   Evas_List *l;
   
   for (l = _e_apps_change_callbacks; l; l = l->next)
     {
	E_App_Callback *cb;
	
	cb = l->data;
	if ((cb->func == func) && (cb->data == data))
	  {
	     if (_e_apps_callbacks_walking)
	       {
		  cb->delete_me = 1;
		  _e_apps_callbacks_delete_me = 1;
	       }
	     else
	       {
		  _e_apps_change_callbacks = evas_list_remove_list(_e_apps_change_callbacks, l);
		  free(cb);
		  _e_app_monitor();
	       }
	     return;
	  }
     }
}

E_App *
e_app_window_name_class_find(char *name, char *class)
{
   Evas_List *l;
   
   for (l = _e_apps_list; l; l = l->next)
     {
	E_App *a;
	
	a = l->data;
	if ((a->win_name) || (a->win_class))
	  {
	     int ok = 0;
	     
//	     printf("%s.%s == %s.%s\n", name, class, a->win_name, a->win_class);
	     if ((!a->win_name) ||
		 ((a->win_name) && (!strcmp(a->win_name, name))))
	       ok++;
	     if ((!a->win_class) ||
		 ((a->win_class) && (!strcmp(a->win_class, class))))
	       ok++;
	     if (ok >= 2)
	       {
		  _e_apps_list = evas_list_remove_list(_e_apps_list, l);
		  _e_apps_list = evas_list_prepend(_e_apps_list, a);
		  return a;
	       }
	  }
     }
   return NULL;
}

/* local subsystem functions */
static void
_e_app_free(E_App *a)
{
   while (a->instances)
     {
	Ecore_Exe *exe;
	
	exe = a->instances->data;
	ecore_exe_free(exe);
	a->instances = evas_list_remove_list(a->instances, a->instances);
     }
   while (a->subapps)
     {
	E_App *a2;
	
	a2 = a->subapps->data;
	a->subapps = evas_list_remove(a->subapps, a2);
	a2->parent = NULL;
	e_object_unref(E_OBJECT(a2));
     }
   if (a->parent)
     a->parent->subapps = evas_list_remove(a->parent->subapps, a);
   _e_apps = evas_hash_del(_e_apps, a->path, a);
   _e_apps_list = evas_list_remove(_e_apps_list, a);
   if (a->name) free(a->name);
   if (a->generic) free(a->generic);
   if (a->comment) free(a->comment);
   if (a->exe) free(a->exe);
   if (a->path) free(a->path);
   if (a->win_name) free(a->win_name);
   if (a->win_class) free(a->win_class);
   free(a);
   _e_app_monitor();
}

static void
_e_app_fields_fill(E_App *a, char *path)
{
   Eet_File *ef;
   char buf[4096];
   char *str, *v;
   char *lang;
   int size;
   
   /* get our current language */
   lang = getenv("LANG");
   /* if its "C" its the default - so drop it */
   if ((lang) && (!strcmp(lang, "C")))
     lang = NULL;
   ef = eet_open(a->path, EET_FILE_MODE_READ);
   if (!ef) return;

   if (lang)
     {
	snprintf(buf, sizeof(buf), "app/info/name[%s]", lang);
	v = eet_read(ef, buf, &size);
	if (!v) v = eet_read(ef, "app/info/name", &size);
     }
   else
     v = eet_read(ef, "app/info/name", &size);

   if (v)
     {
	str = malloc(size + 1);
	memcpy(str, v, size);
	str[size] = 0;
	a->name = str;
	free(v);
     }

   if (lang)
     {
	snprintf(buf, sizeof(buf), "app/info/generic[%s]", lang);
	v = eet_read(ef, buf, &size);
	if (!v) v = eet_read(ef, "app/info/generic", &size);
     }
   else
     v = eet_read(ef, "app/info/generic", &size);

   if (v)
     {
	str = malloc(size + 1);
	memcpy(str, v, size);
	str[size] = 0;
	a->generic = str;
	free(v);
     }

   if (lang)
     {
	snprintf(buf, sizeof(buf), "app/info/comment[%s]", lang);
	v = eet_read(ef, buf, &size);
	if (!v) v = eet_read(ef, "app/info/comment", &size);
     }
   else
     v = eet_read(ef, "app/info/comment", &size);

   if (v)
     {
	str = malloc(size + 1);
	memcpy(str, v, size);
	str[size] = 0;
	a->comment = str;
	free(v);
     }

   v = eet_read(ef, "app/info/exe", &size);
   if (v)
     {
	str = malloc(size + 1);
	memcpy(str, v, size);
	str[size] = 0;
	a->exe = str;
	free(v);
     }
   v = eet_read(ef, "app/window/name", &size);
   if (v)
     {
	str = malloc(size + 1);
	memcpy(str, v, size);
	str[size] = 0;
	a->win_name = str;
	free(v);
     }
   v = eet_read(ef, "app/window/class", &size);
   if (v)
     {
	str = malloc(size + 1);
	memcpy(str, v, size);
	str[size] = 0;
	a->win_class = str;
	free(v);
     }
   v = eet_read(ef, "app/info/startup_notify", &size);
   if (v)
     {
	a->startup_notify = *v;
	free(v);
     }
   v = eet_read(ef, "app/info/wait_exit", &size);
   if (v)
     {
	a->wait_exit = *v;
	free(v);
     }
   eet_close(ef);
}

static void
_e_app_fields_empty(E_App *a)
{
   if (a->name)
     {
	free(a->name);
	a->name = NULL;
     }
   if (a->generic)
     {
	free(a->generic);
	a->generic = NULL;
     }
   if (a->comment)
     {
	free(a->comment);
	a->comment = NULL;
     }
   if (a->exe)
     {
	free(a->exe);
	a->exe = NULL;
     }
   if (a->win_name)
     {
	free(a->win_name);
	a->win_name = NULL;
     }
   if (a->win_class)
     {
	free(a->win_class);
	a->win_class = NULL;
     }
}

static Evas_List *
_e_app_dir_file_list_get(E_App *a, char *path)
{
   Evas_List *files, *files2 = NULL, *l;
   FILE *f;
   char buf[4096];
   
   snprintf(buf, sizeof(buf), "%s/.order", path);
   a->order_mod_time = e_file_mod_time(buf);
   files = e_file_ls(path);
   f = fopen(buf, "rb");
   if (f)
     {
	while (fgets(buf, sizeof(buf), f))
	  {
	     int len;
	     
	     len = strlen(buf);
	     if (len > 0)
	       {
		  if (buf[len - 1] == '\n')
		    {
		       buf[len - 1] = 0;
		       len--;
		    }
		  if (len > 0)
		    {
		       for (l = files; l; l = l->next)
			 {
			    if (!strcmp(buf, l->data))
			      {
				 free(l->data);
				 files = evas_list_remove_list(files, l);
				 break;
			      }
			 }
		       files2 = evas_list_append(files2, strdup(buf));
		    }
	       }
	  }
	fclose(f);
     }
   else
     {
	files2 = files;
	files = NULL;
     }
   while (files)
     {
	char *s;
	
	s = files->data;
	if (s[0] != '.')
	  files2 = evas_list_append(files2, s);
	else
	  free(s);
	files = evas_list_remove_list(files, files);
     }
   files = files2;
   return files;
}

static E_App *
_e_app_subapp_path_find(E_App *a, char *subpath)
{
   Evas_List *l;
   
   for (l = a->subapps; l; l = l->next)
     {
	E_App *a2;
	
	a2 = l->data;
	if (!strcmp(a2->path, subpath)) return a2;
     }
   return NULL;
}

static void
_e_app_monitor(void)
{
   if ((_e_apps) && (_e_apps_change_callbacks))
     {
	if (!_e_apps_checker)
	  _e_apps_checker = ecore_timer_add(1.0, _e_app_check, NULL);
     }
   else
     {
	if (_e_apps_checker)
	  {
	     ecore_timer_del(_e_apps_checker);
	     _e_apps_checker = NULL;
	  }
     }
}

static void
_e_app_change(E_App *a, E_App_Change ch)
{
   Evas_List *l;
   
   _e_apps_callbacks_walking = 1;
   for (l = _e_apps_change_callbacks; l; l = l->next)
     {
	E_App_Callback *cb;
	
	cb = l->data;
	if (!cb->delete_me)
	  {
	     cb->func(cb->data, a, ch);
	  }
     }
   _e_apps_callbacks_walking = 0;
   if (_e_apps_callbacks_delete_me)
     {
	for (l = _e_apps_change_callbacks; l;)
	  {
	     E_App_Callback *cb;
	     Evas_List *pl;
	     
	     cb = l->data;
	     pl = l;
	     l = l->next;
	     if (cb->delete_me)
	       {
		  _e_apps_change_callbacks = evas_list_remove_list(_e_apps_change_callbacks, pl);
		  free(cb);
	       }
	  }
	_e_apps_callbacks_delete_me = 0;
	_e_app_monitor();
     }
}

static int
_e_app_check(void *data)
{
   Evas_List *changes = NULL;
   
   evas_hash_foreach(_e_apps, _e_app_check_each, &changes);

   while (changes)
     {
	E_App_Change_Info *ch;
	
	ch = changes->data;
	changes = evas_list_remove_list(changes, changes);
	_e_app_change(ch->app, ch->change);
	e_object_unref(E_OBJECT(ch->app));
	free(ch);
     }
   if (_e_apps_checker) return 1;
   return 0;
}

static Evas_Bool
_e_app_check_each(Evas_Hash *hash, const char *key, void *data, void *fdata)
{
   Evas_List **changes;
   E_App *a;
   E_App_Change_Info *ch;
   
   changes = fdata;
   a = data;
   if (a->exe)
     {
	time_t mod_time;
	
	mod_time = e_file_mod_time(a->path);
	if (mod_time != a->mod_time)
	  {
	     a->mod_time = mod_time;
	     if (e_file_exists(a->path))
	       {
		  _e_app_fields_empty(a);
		  _e_app_fields_fill(a, a->path);
		  if (!a->exe)
		    {
		       ch = calloc(1, sizeof(E_App_Change_Info));
		       ch->app = a;
		       ch->change = E_APP_DEL;
		       e_object_ref(E_OBJECT(ch->app));
		       *changes = evas_list_append(*changes, ch);
		    }
		  else
		    {
		       ch = calloc(1, sizeof(E_App_Change_Info));
		       ch->app = a;
		       ch->change = E_APP_CHANGE;
		       e_object_ref(E_OBJECT(ch->app));
		       *changes = evas_list_append(*changes, ch);
		    }
	       }
	     else
	       {
		  ch = calloc(1, sizeof(E_App_Change_Info));
		  ch->app = a;
		  ch->change = E_APP_DEL;
		  e_object_ref(E_OBJECT(ch->app));
		  *changes = evas_list_append(*changes, ch);
	       }
	  }
     }
   else
     {
	time_t mod_time, order_mod_time, directory_mod_time;
	char buf[4096];
	
	mod_time = e_file_mod_time(a->path);
	snprintf(buf, sizeof(buf), "%s/.order", a->path);
	order_mod_time = e_file_mod_time(buf);
	snprintf(buf, sizeof(buf), "%s/.directory.eap", a->path);
	directory_mod_time = e_file_mod_time(buf);
	if ((mod_time != a->mod_time) ||
	    (order_mod_time != a->order_mod_time) ||
	    (directory_mod_time != a->directory_mod_time))
	  {
	     a->mod_time = mod_time;
	     if (!e_file_is_dir(a->path))
	       {
		  ch = calloc(1, sizeof(E_App_Change_Info));
		  ch->app = a;
		  ch->change = E_APP_DEL;
		  e_object_ref(E_OBJECT(ch->app));
		  *changes = evas_list_append(*changes, ch);
	       }
	     else
	       {
		  if (order_mod_time != a->order_mod_time)
		    {
		       ch = calloc(1, sizeof(E_App_Change_Info));
		       ch->app = a;
		       ch->change = E_APP_ORDER;
		       e_object_ref(E_OBJECT(ch->app));
		       *changes = evas_list_append(*changes, ch);
		    }
		  if (directory_mod_time != a->directory_mod_time)
		    {
		       snprintf(buf, sizeof(buf), "%s/.directory.eap", a->path);
		       _e_app_fields_empty(a);
		       _e_app_fields_fill(a, buf);
		       ch = calloc(1, sizeof(E_App_Change_Info));
		       ch->app = a;
		       ch->change = E_APP_CHANGE;
		       e_object_ref(E_OBJECT(ch->app));
		       *changes = evas_list_append(*changes, ch);
		    }
		  a->order_mod_time = order_mod_time;
		  a->directory_mod_time = directory_mod_time;
		  if (a->scanned)
		    {
		       Evas_List *l, *files;
		       
		       files = _e_app_dir_file_list_get(a, a->path);
		       for (l = files; l; l = l->next)
			 {
			    E_App *a2;
			    char *s;
			    
			    s = l->data;
			    snprintf(buf, sizeof(buf), "%s/%s", a->path, s);
			    if (!_e_app_subapp_path_find(a, buf))
			      {
				 a2 = e_app_new(buf, 0);
				 if (a2)
				   {
				      a2->parent = a;
				      a->subapps = evas_list_append(a->subapps, a2);
				      ch = calloc(1, sizeof(E_App_Change_Info));
				      ch->app = a2;
				      ch->change = E_APP_ADD;
				      e_object_ref(E_OBJECT(ch->app));
				      *changes = evas_list_append(*changes, ch);
				   }
			      }
			 }
		       for (l = files; l; l = l->next)
			 {
			    E_App *a2;
			    char *s;
			    
			    s = l->data;
			    snprintf(buf, sizeof(buf), "%s/%s", a->path, s);
			    a2 = _e_app_subapp_path_find(a, buf);
			    if (a2)
			      {
				 a->subapps = evas_list_remove(a->subapps, a2);
				 a->subapps = evas_list_append(a->subapps, a2);
			      }
			 }
		       while (files)
			 {
			    free(files->data);
			    files = evas_list_remove_list(files, files);
			 }
		    }
	       }
	  }
     }
   return 1;
}

static int
_e_apps_cb_exit(void *data, int type, void *event)
{
   Ecore_Event_Exe_Exit *ev;
   E_App *a;
   
   ev = event;
   if (ev->exe)
     {
	a = ecore_exe_data_get(ev->exe);
	if (a)
	  {
	     a->instances = evas_list_remove(a->instances, ev->exe);
	     _e_app_change(a, E_APP_EXIT);
	  }
     }
   return 1;
}
