/*
 * GNOME panel menu module.
 * (C) 1997, 1998, 1999, 2000 The Free Software Foundation
 * Copyright 2000 Eazel, Inc.
 *
 * Authors: Miguel de Icaza
 *          Federico Mena
 */

#include <config.h>
#include <ctype.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <errno.h>

#include <libgnome/libgnome.h>

#include <libgnomevfs/gnome-vfs-mime.h>
#include <libgnomevfs/gnome-vfs-uri.h>
#include <libgnomevfs/gnome-vfs-ops.h>
#include <libgnomevfs/gnome-vfs-directory.h>
#include <libgnomevfs/gnome-vfs-utils.h>

#include "menu-fentry.h"
#include "quick-desktop-reader.h"

#include "panel-util.h"
#include "panel-config-global.h"
#include "session.h"

#undef MENU_FENTRY_DEBUG

/* the minimum number of seconds between stats of files */
#define STAT_EVERY 3

static GSList *dir_list = NULL;

extern GlobalConfig global_config;

static GSList *
prepend_mfile (GSList *list,
	       const char *name,
	       gboolean verified,
	       time_t mtime,
	       gboolean is_dir)
{
	MFile *mfile = g_new0 (MFile, 1);

	mfile->name = g_strdup (name);
	mfile->verified = verified;
	mfile->mtime = mtime;
	mfile->is_dir = is_dir;

	return g_slist_prepend (list, mfile);
}

static MFile *
find_mfile (GSList *list, const char *string)
{
	GSList *li;

	g_return_val_if_fail (string != NULL, NULL);

	for (li = list; li != NULL; li = li->next) {
		MFile *mfile = li->data;

		if (mfile->name != NULL &&
		    strcmp (mfile->name, string) == 0)
			return mfile;
	}

	return NULL;
}


/*reads in the sort order and makes a list*/
static GSList *
get_presorted_from (const char *dir_uri)
{
	char *uri, *sort_order, *p;
	GSList *list = NULL;
	QuickDesktopItem *qitem;
	
	uri = g_build_path ("/", dir_uri, ".directory", NULL);
	qitem = quick_desktop_item_load_uri (uri,
					     NULL /* expected_type */,
					     FALSE /* run_tryexec */);

	g_free (uri);

	if (qitem == NULL)
		return NULL;

	/* steal sort_order, we're gonna mangle it */
	sort_order = qitem->sort_order;
	qitem->sort_order = NULL;

	quick_desktop_item_destroy (qitem);

	if (sort_order == NULL)
		return NULL;

	p = strtok (sort_order, ";");
	while (p != NULL) {
		list = prepend_mfile (list,
				      p /* name */,
				      FALSE /* verified */,
				      0 /* mtime */,
				      /* We don't know if it's
				       * a directory */
				      FALSE /* is_dir */);

		p = strtok (NULL, ";");
	}

	g_free (sort_order);

	return list;
}

static GSList *
read_directory (GSList *list, const char *menuuri)
{
	GnomeVFSDirectoryHandle *handle = NULL;

	if (gnome_vfs_directory_open (&handle, menuuri,
				      GNOME_VFS_FILE_INFO_DEFAULT)
	    == GNOME_VFS_OK) {
		GnomeVFSFileInfo *info = gnome_vfs_file_info_new ();
		while (gnome_vfs_directory_read_next (handle, info)
		       == GNOME_VFS_OK) {
			MFile *mfile;

			if (info->name[0] == '.') {
				gnome_vfs_file_info_clear (info);
				continue;
			}

			mfile = find_mfile (list, info->name);

			/* FIXME: what if there is no mtime in the info? */

			if (mfile == NULL) {
				list = prepend_mfile (list,
						      info->name,
						      TRUE /* verified */,
						      info->mtime,
						      (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY));
			/* if we've already found it in the presorted,
			 * just verify it */
			} else {
				mfile->verified = TRUE;
				mfile->mtime = info->mtime;
				mfile->is_dir = (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY);
			}

			gnome_vfs_file_info_clear (info);
		}

		gnome_vfs_file_info_unref (info);
		gnome_vfs_directory_close (handle);
	}

	return list;
}

void
free_mfile (MFile *mfile)
{
	if (mfile != NULL) {
		g_free (mfile->name);
		mfile->name = NULL;

		g_free (mfile);
	}
}

void
free_mfile_list (GSList *list)
{
	GSList *li;
	for (li = list; li != NULL; li = li->next) {
		free_mfile (li->data);
		li->data = NULL;
	}
	g_slist_free (list);
}

GSList *
get_mfiles_from_menudir (const char *menuuri, gboolean *sorted)
{
	GSList *list = NULL;

	if (sorted != NULL)
		*sorted = FALSE;

	list = get_presorted_from (menuuri);
	if (list != NULL && sorted != NULL)
		*sorted = TRUE;
	list = read_directory (list, menuuri);

	return g_slist_reverse (list);
}

static void
fr_free (FileRec *fr, gboolean free_fr)
{
	if (fr == NULL)
		return;

	g_free (fr->name);
	fr->name = NULL;
	g_free (fr->fullname);
	fr->fullname = NULL;
	g_free (fr->name_collate_key);
	fr->name_collate_key = NULL;
	g_free (fr->comment);
	fr->comment = NULL;
	g_free (fr->icon);
	fr->icon = NULL;
	g_free (fr->tryexec_path);
	fr->tryexec_path = NULL;

	if (fr->parent != NULL &&
	    free_fr)
		fr->parent->recs = g_slist_remove (fr->parent->recs, fr);
	fr->parent = NULL;

	if (fr->type == FILE_REC_DIR) {
		DirRec *dr = (DirRec *)fr;
		GSList *li;

		for (li = dr->mfl; li != NULL; li = li->next) {
			MenuFinfo *mf = li->data;
			li->data = NULL;
			mf->fr = NULL;
		}
		g_slist_free (dr->mfl);
		dr->mfl = NULL;

		for (li = dr->recs; li != NULL; li = li->next) {
			FileRec *ffr = li->data;
			li->data = NULL;
			ffr->parent = NULL;
			fr_free (ffr, TRUE);
		}
		g_slist_free (dr->recs);
		dr->recs = NULL;

		if (dr->tryexecs != NULL) {
			panel_g_slist_deep_free (dr->tryexecs);
			dr->tryexecs = NULL;
		}
		dir_list = g_slist_remove (dir_list, fr);
	}

	if (free_fr) {
		g_free (fr);
	} else  {
		int type = fr->type;
		if (fr->type == FILE_REC_DIR)
			memset (fr, 0, sizeof(DirRec));
		else
			memset (fr, 0, sizeof(FileRec));
		/* we must reset the type so that we don't crash
		 * if we call fr_free on this again */
		fr->type = type;
	}
}

static void
fr_fill_dir (FileRec *fr, int sublevels)
{
	GSList *flist;
	DirRec *dr = (DirRec *)fr;
	time_t curtime = time (NULL);
	gboolean sorted = FALSE;
	
	g_return_if_fail (dr->recs == NULL);
	g_return_if_fail (fr != NULL);
	g_return_if_fail (fr->name != NULL);

	flist = get_mfiles_from_menudir (fr->name, &sorted);
	while (flist != NULL) {
		MFile *mfile = flist->data;
		char *name;
		GSList *tmp = flist;
		flist = flist->next;
		g_slist_free_1 (tmp);

		if ( ! mfile->verified) {
			free_mfile (mfile);
			continue;
		}

#ifdef MENU_FENTRY_DEBUG
		g_print ("fr->name = \"%s\" , mfile->name = \"%s\"\n",
			 fr->name, mfile->name);
#endif

		name = g_build_path ("/", fr->name, mfile->name, NULL);

		if (mfile->is_dir) {
			FileRec *ffr = fr_read_dir (NULL, name, mfile->mtime,
						     sublevels - 1);
			if (ffr != NULL) {
				dr->recs = g_slist_prepend (dr->recs, ffr);
			}
		} else {
			QuickDesktopItem *qitem;
			char *tryexec_path;
			if ( ! is_ext2 (mfile->name, ".desktop", ".kdelnk")) {
				g_free (name);
				free_mfile (mfile);
#ifdef MENU_FENTRY_DEBUG
				g_print ("BAD EXTENSION\n");
#endif
				continue;
			}

			tryexec_path = NULL;

			qitem = quick_desktop_item_load_uri (name /* uri */,
							     NULL /* expected_type */,
							     FALSE /* run_tryexec */);
			if (qitem != NULL &&
			    qitem->tryexec != NULL) {
				tryexec_path = g_find_program_in_path (qitem->tryexec);
				if (tryexec_path == NULL) {
					dr->tryexecs = g_slist_prepend (dr->tryexecs,
									g_strdup (qitem->tryexec));
					quick_desktop_item_destroy (qitem);
					qitem = NULL;
				}
			}

			if (qitem != NULL) {
				FileRec *ffr = g_new0 (FileRec, 1);
				if (qitem->type != NULL &&
				    g_ascii_strcasecmp (qitem->type, "separator") == 0)
					ffr->type = FILE_REC_SEP;
				else
					ffr->type = FILE_REC_FILE;
				ffr->name = name;
				name = NULL;
				ffr->mtime = mfile->mtime;
				ffr->last_stat = curtime;
				ffr->parent = dr;
				ffr->icon = qitem->icon;
				qitem->icon = NULL;
				ffr->fullname = qitem->name;
				qitem->name = NULL;
				ffr->comment = qitem->comment;
				qitem->comment = NULL;
				ffr->tryexec_path = tryexec_path;

				quick_desktop_item_destroy (qitem);

				dr->recs = g_slist_prepend (dr->recs, ffr);
			} 
#ifdef MENU_FENTRY_DEBUG
			else {
				g_print ("NO QITEM\n");
			}
#endif
		}
		g_free (name);
		free_mfile (mfile);
	}
	if (sorted)
		dr->recs = g_slist_reverse (dr->recs);
	else
		dr->recs = g_slist_sort (dr->recs, (GCompareFunc)fr_compare);
}

FileRec *
fr_read_dir (DirRec *dr, const char *muri, time_t mtime, int sublevels)
{
	char *furi;
	FileRec *fr;
	time_t curtime = time (NULL);
	GnomeVFSFileInfo *info;
	
	g_return_val_if_fail (muri != NULL, NULL);

#ifdef MENU_FENTRY_DEBUG
	g_print ("fr_read_dir (..., \"%s\", %ld, %d)\n",
		 muri, (long)mtime, sublevels);
#endif

	info = gnome_vfs_file_info_new ();

	/*this will zero all fields*/
	if (dr == NULL) {
		dr = g_new0 (DirRec, 1);
		dr->force_reread = FALSE;
		/* this must be set otherwise we may messup on
		   fr_free */
		dr->frec.type = FILE_REC_DIR;
	}
	fr = (FileRec *)dr;

	if (fr->last_stat < curtime-STAT_EVERY) {
		if (mtime <= 0) {
			if (gnome_vfs_get_file_info
			    (muri, info, GNOME_VFS_FILE_INFO_DEFAULT)
			    != GNOME_VFS_OK) {
				gnome_vfs_file_info_unref (info);
				fr_free (fr, TRUE);
				return NULL;
			}

			/* FIXME: what if there is no mtime in the info? */
			fr->mtime = info->mtime;
		} else {
			fr->mtime = mtime;
		}

		fr->last_stat = curtime;
	}

	fr->type = FILE_REC_DIR;
	g_free (fr->name);
	fr->name = g_strdup (muri);

	gnome_vfs_file_info_clear (info);

	/* FIXME: we've already read this when reading the sort_order,
	 * so damnit we should just use that copy, oh well */
	furi = g_build_path ("/", muri, ".directory", NULL);
	if (dr->ditemlast_stat >= curtime-STAT_EVERY ||
	    gnome_vfs_get_file_info (furi, info,
				     GNOME_VFS_FILE_INFO_DEFAULT)
	    == GNOME_VFS_OK) {
		QuickDesktopItem *qitem;
		qitem = quick_desktop_item_load_uri (furi /* uri */,
						     NULL /* expected_type */,
						     TRUE /* run_tryexec */);
		if (qitem != NULL) {
			g_free (fr->icon);
			fr->icon = g_strdup (qitem->icon);

			g_free (fr->fullname);
			fr->fullname = g_strdup (qitem->name);

			g_free (fr->name_collate_key);
			fr->name_collate_key = NULL;

			g_free (fr->comment);
			fr->comment = g_strdup (qitem->comment);

			quick_desktop_item_destroy (qitem);
		} else {
			g_free (fr->icon);
			fr->icon = NULL;

			g_free (fr->fullname);
			fr->fullname = NULL;

			g_free (fr->name_collate_key);
			fr->name_collate_key = NULL;

			g_free (fr->comment);
			fr->comment = NULL;
		}
		/*if we statted*/
		if (info->mtime != 0)
			dr->ditemlast_stat = curtime;
		dr->ditemmtime = info->mtime;
	}
	g_free (furi);
	
	/* add if missing from list of directories */
	if (g_slist_find (dir_list, fr) == NULL)
		dir_list = g_slist_prepend (dir_list, fr);
	
	/*if this is a fake structure, so we don't actually look into
	  the directory*/
	if (sublevels > 0)
		fr_fill_dir (fr, sublevels);

	gnome_vfs_file_info_unref (info);

	return fr;
}


FileRec *
fr_replace (FileRec *fr)
{
	char *name = fr->name;
	DirRec *par = fr->parent;
	
	g_assert (fr->type == FILE_REC_DIR);

	/* null these so they don't get freed */
	fr->parent = NULL;
	fr->name = NULL;

	/* don't free the actual structure */
	fr_free (fr, FALSE);

	/* sanity */
	fr->type = FILE_REC_DIR;

	fr = fr_read_dir ((DirRec *)fr, name, 0, 1);
	if (fr != NULL)
		fr->parent = par;
	g_free (name);

	return fr;
}


FileRec *
fr_check_and_reread (FileRec *fr)
{
	DirRec *dr = (DirRec *)fr;
	FileRec *ret = fr;
	time_t curtime;
	GnomeVFSFileInfo *info;

	g_return_val_if_fail (fr != NULL, fr);
	g_return_val_if_fail (fr->type == FILE_REC_DIR, fr);

	info = gnome_vfs_file_info_new ();

	if (dr->recs == NULL) {
		fr_fill_dir (fr, 1);
	} else {
		gboolean reread = FALSE;
		gboolean any_change = FALSE;
		GSList *li;

		if ( ! global_config.menu_check) {
			gnome_vfs_file_info_unref (info);
			return ret;
		}

		if (dr->force_reread)
			reread = TRUE;
		dr->force_reread = FALSE;

		/* recheck tryexecs */
		for (li = dr->tryexecs; ! reread && li != NULL; li = li->next) {
			char *tryexec = li->data;
			char *p = g_find_program_in_path  (tryexec);

			if (p != NULL) {
				reread = TRUE;
				g_free (p);
			}
		}

		curtime = time (NULL);

		if ( ! reread &&
		    fr->last_stat < curtime - STAT_EVERY) {
			if (gnome_vfs_get_file_info
			    (fr->name, info, GNOME_VFS_FILE_INFO_DEFAULT)
			    != GNOME_VFS_OK) {
				gnome_vfs_file_info_unref (info);
				fr_free (fr, TRUE);
				return NULL;
			}

			if (info->mtime != fr->mtime)
				reread = TRUE;
		}

		for (li = dr->recs;
		     ! reread && li != NULL;
		     li = li->next) {
			FileRec *ffr = li->data;
			DirRec *ddr;
			GnomeVFSResult result;
			char *p;
			struct stat s;

			switch(ffr->type) {
			case FILE_REC_DIR:
				ddr = (DirRec *)ffr;
				p = g_build_path ("/",
						  ffr->name,
						  ".directory",
						  NULL);
				if (ddr->ditemlast_stat >= curtime-STAT_EVERY) {
					g_free (p);
					break;
				}
				dr->ditemlast_stat = curtime;
				gnome_vfs_file_info_clear (info);
				if (gnome_vfs_get_file_info
				    (p, info, GNOME_VFS_FILE_INFO_DEFAULT)
				    != GNOME_VFS_OK) {
					gnome_vfs_file_info_clear (info);
					/* perhaps the directory is gone */
					if (gnome_vfs_get_file_info
					    (ffr->name, info,
					     GNOME_VFS_FILE_INFO_DEFAULT)
					    != GNOME_VFS_OK) {
						g_free (p);
						reread = TRUE;
						break;
					}
					/* if not, we're just now missing a
					 * desktop file */
					if (dr->ditemmtime > 0) {
						g_free (ffr->icon);
						ffr->icon = NULL;

						g_free (ffr->fullname);
						ffr->fullname = NULL;

						g_free (fr->name_collate_key);
						fr->name_collate_key = NULL;

						g_free (ffr->comment);
						ffr->comment = NULL;

						ddr->ditemmtime = 0;
						any_change = TRUE;
					}
					dr->ditemlast_stat = 0;
					g_free (p);
					break;
				}
				if (ddr->ditemmtime != info->mtime) {
					QuickDesktopItem *qitem;
					qitem = quick_desktop_item_load_file (p /* file */,
									      NULL /* expected_type */,
									      TRUE /* run_tryexec */);
					if (qitem != NULL) {
						g_free (ffr->icon);
						ffr->icon = g_strdup (qitem->icon);

						g_free (ffr->fullname);
						ffr->fullname = g_strdup (qitem->name);

						g_free (fr->name_collate_key);
						fr->name_collate_key = NULL;

						g_free (ffr->comment);
						ffr->comment = g_strdup (qitem->comment);

						quick_desktop_item_destroy (qitem);
					} else {
						g_free (ffr->icon);
						ffr->icon = NULL;

						g_free (ffr->fullname);
						ffr->fullname = NULL;

						g_free (fr->name_collate_key);
						fr->name_collate_key = NULL;

						g_free (ffr->comment);
						ffr->comment = NULL;
					}
					ddr->ditemmtime = info->mtime;
					any_change = TRUE;
				}
				g_free(p);
				break;
			case FILE_REC_FILE:
				if (ffr->last_stat >= curtime-STAT_EVERY)
					break;
				gnome_vfs_file_info_clear (info);
				if (gnome_vfs_get_file_info
				    (ffr->name, info, GNOME_VFS_FILE_INFO_DEFAULT)
				    != GNOME_VFS_OK) {
					reread = TRUE;
					break;
				}
				ffr->last_stat = curtime;
				if (ffr->mtime != info->mtime) {
					QuickDesktopItem *qitem;
					qitem = quick_desktop_item_load_file (ffr->name /* file */,
									      NULL /* expected_type */,
									      TRUE /* run_tryexec */);
					if (qitem != NULL) {
						g_free (ffr->icon);
						ffr->icon = g_strdup (qitem->icon);

						g_free (ffr->fullname);
						ffr->fullname = g_strdup (qitem->name);

						g_free (fr->name_collate_key);
						fr->name_collate_key = NULL;

						g_free (ffr->comment);
						ffr->comment = g_strdup (qitem->comment);

						quick_desktop_item_destroy (qitem);
					} else {
						reread = TRUE;
						break;
					}
					ffr->mtime = s.st_mtime;
					any_change = TRUE;
				}
				if (ffr->tryexec_path != NULL &&
				    ! g_file_test (ffr->tryexec_path,
						   G_FILE_TEST_EXISTS)) {
					reread = TRUE;
				}
				break;
			case FILE_REC_EXTRA:
				if (ffr->last_stat >= curtime-STAT_EVERY)
					break;
				gnome_vfs_file_info_clear (info);
				result = gnome_vfs_get_file_info
				    (ffr->name,
				     info,
				     GNOME_VFS_FILE_INFO_DEFAULT);
				if((result != GNOME_VFS_OK && ffr->mtime > 0) ||
				   (result == GNOME_VFS_OK && ffr->mtime != info->mtime))
					reread = TRUE;
				ffr->last_stat = curtime;
				break;
			}
		}

		if (reread) {
			ret = fr_replace (fr);
		} else if (any_change) {
			GSList *li;
			for (li = dr->mfl; li != NULL; li = li->next) {
				MenuFinfo *mf = li->data;
				li->data = NULL;
				mf->fr = NULL;
			}
			g_slist_free (dr->mfl);
			dr->mfl = NULL;
		}
	}

	gnome_vfs_file_info_unref (info);

	return ret;
}

FileRec *
fr_get_dir (const char *mdir)
{
	GSList *li;
	g_return_val_if_fail (mdir!=NULL, NULL);

	for(li = dir_list; li != NULL; li = li->next) {
		FileRec *fr = li->data;
		g_assert(fr != NULL);
		g_assert(fr->name != NULL);
		if (strcmp (fr->name, mdir) == 0)
			return fr_check_and_reread (fr);
	}
	return fr_read_dir (NULL, mdir, 0, 1);
}

void
fr_force_reread (void)
{
	GSList *li;
	for(li = dir_list; li != NULL; li = li->next) {
		DirRec *dr = li->data;
		g_assert (dr != NULL);

		dr->force_reread = TRUE;
	}
}

int
fr_compare (FileRec *fra, FileRec *frb)
{
	if (fra->type == FILE_REC_DIR && frb->type != FILE_REC_DIR) {
		return -1;
	} else if (fra->type != FILE_REC_DIR && frb->type == FILE_REC_DIR) {
		return 1;
	}

	if (fra->name_collate_key == NULL) {
		fra->name_collate_key = g_utf8_collate_key (sure_string (fra->fullname), -1);
	}
	if (frb->name_collate_key == NULL) {
		frb->name_collate_key = g_utf8_collate_key (sure_string (frb->fullname), -1);
	}
        return strcmp (fra->name_collate_key, frb->name_collate_key);
}
