/*
 *  Copyright (C) 2000 Marco Pesenti Gritti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "galeon.h"
#include "bookmarks.h"
#include "session.h"
#include "embed.h"
#include "misc.h"
#include "mime.h"
#include "window.h"
#include "prefs.h"
#include "history.h"
#include "mozilla.h"
#include "eel-gconf-extensions.h"

#include <gtkmozembed.h>
#include <string.h>
#include <libgnome/gnome-i18n.h>
#include <libgnome/gnome-config.h>
#include <libgnomeui/gnome-uidefs.h>
#include <libgnomeui/gnome-dialog-util.h>
#include <libgnomevfs/gnome-vfs.h>

/*
 *  Session code 
 *
 *  Copyright (C) 2000 Matthew Aubury
 *
 *  This code implements the idea of "sessions", meaning the state of
 *  all the open browsers with respect to the URLs they're pointed at.
 *
 *  This should be useful in two ways:
 *    -- You need to quit X but have got lots of windows open
 *       with stuff you want to read, but you don't want to
 *       bookmark it all, just come back and see it later.
 *    -- By autosaving sessions we should offer a degree of 
 *       automatic crash recovery. This should mitigate some
 *       of the Mozilla/Galeon instability problems.
 *
 *  NOTE: this code won't do the right thing with frames yet. This is
 *  an ongoing problem with frames (they can't easily be referenced by
 *  URL) which is shared with bookmarks.
 *
 *  This code is quite derived from the history code,
 *  kudos to whoever did that...
 *
 *  At the moment we only do crash recovery saves, as I haven't yet
 *  gotten around to doing the dialogs for generic session saving.
 *
 *  -- MattA
 */

/*
 *  Hmm, I think we should really be using the gnome-client /
 *  gnome-session stuff to handle this properly. -- MattA 16/4/2001
 */

/* local function prototypes */
static xmlDocPtr session_load (const gchar *filename);
static void session_resume (xmlDocPtr doc, gboolean override_behaviour);
static void session_close_all_windows (void);
static gint session_server_timeout_cb (gpointer data);

/* local variables */
static GtkWidget *dialog = NULL;

/** The global list of all GaleonWindow structures */
GList *all_windows = NULL;
gint window_count = 0;

/* session quit timeout id */
gint quit_timeout_id = 0;

/**
 * session_autosave: save to the "crash recovery" session file
 */
void
session_autosave (void)
{
	gchar *filename;
	gint autosave;

	/* check we're configured to autosave */
	autosave = eel_gconf_get_integer(CONF_CRASH_RECOVERY);
	if (autosave == FALSE)
		return;

	/* make the session filename string */
	filename = g_strconcat (g_get_home_dir (),
				"/.galeon/session_crashed.xml", NULL);

	/* save it out */
	session_save_to (filename);

	/* free allocated string */
	g_free (filename);
}

/**
 * session_autoresume: check to see if there's a crashed session and load it
 */
gint
session_autoresume (void)
{
	gchar *filename;
	xmlDocPtr session;
	AutoResume autoresume;
	gint crashed, saved, choice;

	/* see if there's a crashed or saved session to resume */
	crashed = eel_gconf_get_integer (CONF_CRASH_CRASHED);
	saved = eel_gconf_get_integer (CONF_STATE_SESSION_SAVED);
	eel_gconf_set_integer (CONF_STATE_SESSION_SAVED, 0);

	if (crashed == FALSE && saved == FALSE)
	{
		return FALSE;
	}

	/* see if we're configured to autoresume */
	autoresume = eel_gconf_get_integer(CONF_CRASH_RECOVERY);
	if (autoresume == RESUME_NEVER && !saved)
	{
		return FALSE;
	}

	/* see if there's a session file to resume from */
	if (crashed)
	{
		filename = g_strconcat (g_get_home_dir (),
					"/.galeon/session_crashed.xml", NULL);
	}
	else
	{
		filename = g_strconcat (g_get_home_dir (),
					"/.galeon/session_saved.xml", NULL);
	}
	if (access (filename, F_OK) != 0)
	{
		g_free(filename);
		return FALSE;
	}
	
	/* load it */
	session = session_load (filename);
	g_free (filename);
	if (session == NULL)
	{
		return FALSE;
	}

	/* abort if no windows in session */
	if (session->root->childs == NULL)
	{
		xmlFreeDoc (session);
		return FALSE;
	}

	/* resume saved session */
	if (saved)
	{
		/* resume and free */
		session_resume (session, TRUE);
		xmlFreeDoc (session);
		return TRUE;
	}

	/* do the appropriate thing */
	switch (autoresume)
	{
	case RESUME_NEVER:
		/* never reached: this is here to stop a warning */
		g_assert_not_reached ();
		return FALSE;

	case RESUME_ASK:
		/* build question dialog */
		dialog = gnome_question_dialog_modal
			(_("Galeon appears to have crashed or "
			   "been killed last time it was run\n\n"
			   "Would you like to recover the state "
			   "of your last browsing session?"), NULL, NULL);

		/* run it */
		choice = gnome_dialog_run_and_close (GNOME_DIALOG (dialog));

		/* check response */
		switch (choice)
		{
		case -1: /* dialog was killed */
		case 1: /* opted not to recover */
			xmlFreeDoc (session);
			return FALSE;
			
		case 0: /* do recovery */
			break;
		}
		/* deliberate fall through! */

	case RESUME_ALWAYS:
		/* resume and free */
		session_resume (session, FALSE);
		xmlFreeDoc (session);
		return TRUE;
	}

	/* never reached */
	g_assert_not_reached ();
	return FALSE;
}

/**
 * session_save_to: record the session state to the give location
 */
void
session_save_to (const gchar *filename)
{
	GList *w;
	xmlNodePtr root_node;
	xmlNodePtr window_node;
	xmlNodePtr embed_node;
	xmlDocPtr doc;
	gint x = 0, y = 0, width = 0, height = 0;
	gchar buffer[32];
	gint i, num_embeds;

	/* version doesn't really make sense, but... */
        doc = xmlNewDoc ("2.0");

	/* create and set the root node for the session */
        root_node = xmlNewDocNode (doc, NULL, "session", NULL);
        xmlDocSetRootElement (doc, root_node);

	/* iterate through all the windows */
	for (w = all_windows; w != NULL; w = g_list_next (w))
	{
		/* get this item */
		GaleonWindow *window = (GaleonWindow *)(w->data);
		if (window->magic != GALEON_WINDOW_MAGIC)
		{
			g_warning ("not a valid galeon window");
			continue;
		}

		/* skip if for some reason it doesn't contain any embeds */
		if (window->embed_list == NULL)
		{
			g_warning ("window with no GaleonEmbeds");
			continue;
		}

		/* make a new XML node */
		window_node = xmlNewDocNode (doc, NULL, "window", NULL);

		/* get window geometry */
		gdk_window_get_size (GTK_WIDGET (window->WMain)->window,
				     &width, &height);
		gdk_window_get_root_origin (GTK_WIDGET (window->WMain)->window,
					    &x, &y);

		/* set window properties */
		snprintf(buffer, 32, "%d", x);
		xmlSetProp (window_node, "x", buffer);
		snprintf(buffer, 32, "%d", y);
		xmlSetProp (window_node, "y", buffer);
		snprintf(buffer, 32, "%d", width);
		xmlSetProp (window_node, "width", buffer);
		snprintf(buffer, 32, "%d", height);
		xmlSetProp (window_node, "height", buffer);

		/* iterate through all the embeds */
		num_embeds = g_list_length (window->embed_list);
		for (i = 0; i < num_embeds; i++)
		{
			GtkWidget *mozembed;
			GaleonEmbed *embed;

			mozembed = gtk_notebook_get_nth_page (
				GTK_NOTEBOOK (window->notebook), i);
			embed = (GaleonEmbed *) gtk_object_get_data (
				GTK_OBJECT (mozembed), "GaleonEmbed");

			/* skip if embed is not valid */
			if (!embed || embed->magic != GALEON_EMBED_MAGIC)
			{
				g_warning ("Invalid GaleonEmbed");
				continue;
			}

			/* skip if it's a XUL dialog */
			if (embed->is_chrome)
			{
				continue;
			}

			/* skip if the wrapper is NULL, can't do anything
			 * with such a crippled embed */
			if (embed->wrapper == NULL)
			{
				g_warning ("GaleonEmbed with no GaleonWrapper");
				continue;
			}

			if (embed->site_location != NULL &&
			    strlen (embed->site_location) > 0)
			{
			/* make a new XML node */
			embed_node = xmlNewDocNode (doc, NULL, "embed", NULL);

			/* store url and title in the node */
			xmlSetProp (embed_node, "url", embed->site_location);
			xmlSetProp (embed_node, "title", 
				    embed->site_title_utf8);

			/* insert node into the tree */
			xmlAddChild (window_node, embed_node);
			}
		}

		/* add window into tree */
		if (g_list_length (window->embed_list) != 0)
		{
			xmlAddChild (root_node, window_node);
		}
	}

	/* save it all out to disk */
        xmlSaveFile (filename, doc);
	xmlFreeDoc (doc);
}

/**
 * session_load: load a session into a new XML document
 */
static xmlDocPtr
session_load (const gchar *filename)
{
	xmlDocPtr doc;

	/* read the session file */
        doc = xmlParseFile (filename);
        if (doc == NULL) 
	{
		g_warning("unable to parse session file `%s'", filename);
		/* NOTE: fall through to return NULL */
	}

	return doc;
}

/**
 * session_resume: open browsers to resume the session state
 */
static void
session_resume (xmlDocPtr doc, gboolean override_behaviour)
{
	xmlNodePtr window_node;
	xmlNodePtr embed_node;
	gint behaviour;
	gchar *url;
	gchar *title;
	BookmarkItem *cat = NULL; 
	BookmarksEditorControls *controls;
	gint x = 0, y = 0, width = 0, height = 0;
	GdkWindow *gdk_window;
	GaleonWindow *window = NULL;
	GaleonEmbed *embed;
	gint embed_count = 0;

	/* so we don't open saved sessions into temporary bookmarks) */
	if (override_behaviour)
	{
		behaviour = 0;
	}
	else
	{
		/* decide whether to put into bookmarks or windows */
		behaviour = eel_gconf_get_integer 
			(CONF_CRASH_BEHAVIOUR);
	}
	
	if (behaviour == 1)
	{
		cat = add_bookmark_default (BM_FOLDER, 
					    _("URLs opened before crashing"),
	                                    NULL, NULL);
	}

	/* iterate over session windows */
        for (window_node = doc->root->childs; window_node != NULL;
		window_node = window_node->next)
	{
		if (window_node->childs == NULL)
		{
			g_warning ("window with no GaleonEmbeds");
			continue;
		}

		if (behaviour != 1)
		{
			/* create a default window */
			window = window_create (GTK_MOZ_EMBED_FLAG_ALLCHROME);

			/* get the size of the window */
			x      = xmlGetIntProp (window_node, "x");
			y      = xmlGetIntProp (window_node, "y");
			width  = xmlGetIntProp (window_node, "width");
			height = xmlGetIntProp (window_node, "height");
			
			/* set the size and position of the window */
			gtk_window_set_default_size  
				(GTK_WINDOW (window->WMain), width, height);
			gtk_widget_set_uposition (GTK_WIDGET (window->WMain),
						  x, y);
			window->set_size = TRUE;
		}
		
		/* iterate over session embeds */
		for (embed_node = window_node->childs; embed_node != NULL;
		     embed_node = embed_node->next)
		{
			/* get the properties */
			url = xmlGetProp (embed_node, "url");
			title = xmlGetProp (embed_node, "title");
			
			if (behaviour == 1)
			{
				/* add the bookmark */
				add_bookmark_default (BM_SITE, title, 
						      url, cat);
			}
			else
			{
				/* open a browser pointing to this URL */
				embed = embed_create_in_window (window);
				embed_set_visibility (embed, TRUE);
				embed_load_url (embed, url);
				embed_count++;
			}

			/* free allocated strings */
			if (url != NULL) xmlFree (url);
			if (title != NULL) xmlFree (title);
		}

		if (behaviour != 1)
		{
			gdk_window = GTK_WIDGET (window->WMain)->window;
			gdk_window_move (gdk_window, x, y);
		}
	}
 
	if (embed_count == 0)
	{
		g_warning ("having to create default window");
		embed_create_default (NULL, FALSE);
	}

	/* Add an empty window bookmark to the temp bookmarks window, just
	   in case all the saved bookmarks are crashers */
	if (behaviour == 1)
	{
		controls = bookmarks_editor_show_dock (all_windows->data);
		bookmarks_editor_select_bookmark (controls, cat);
	}
}

void 
session_add_window (GaleonWindow *window)
{
	/* add it to the list of windows */
	all_windows = g_list_prepend (all_windows, window);

	/* Increment window count */
	window_count++;
}

void 
session_remove_window (GaleonWindow *window)
{
	/* remove window from list */
	all_windows = g_list_remove (all_windows, window);

	/* Decrement window count */
	window_count--;

	/* quit if this was the last window to go and
	 * we're not in server mode */
	if (g_list_length (all_windows) == 0 && !galeon_server_mode
	    && window_count ==0)
	{
		galeon_exit ();
	}
}

void
session_save (void)
{
	gchar *filename;
	
	/* make the session filename string */
	filename = g_strconcat (g_get_home_dir (), 
				"/.galeon/session_saved.xml", NULL);

	/* save it out */
	session_save_to (filename);
	
	/* set config marker */
	eel_gconf_set_integer (CONF_STATE_SESSION_SAVED, 1);
	
	g_free (filename);
}

void 
session_quit (gboolean auto_save)
{
	/* save the session, if it was requested or if the user has the
	 * "always save" option enabled */
	if (auto_save ||
	    eel_gconf_get_boolean (CONF_GENERAL_ALWAYS_SAVE_SESSION)) {
		session_save ();
	}

	if (all_windows != NULL)
	{
		/* close all windows, this should quit */
		session_close_all_windows ();
	}
}

static void
session_close_all_windows (void)
{
	GList *copy;

	/* close all windows: this in turn will close all embeds */
	/* at the closing of the last window galeon_exit will be called */
	copy = g_list_copy (all_windows);
	g_list_foreach (copy, (GFunc)window_close, NULL);
	g_list_free (copy);

	/* wait for pending destruction events */
	while (gtk_events_pending ())
	{
		gtk_main_iteration ();
	}

	/* check we've been successful in shutting everything down */
	g_assert (g_list_length (all_windows) == 0);
}

/**
 * Restore a session from a file
 * Closes all current windows and opens the ones saved in the file
 */
void
session_load_from (const gchar *filename)
{
	xmlDocPtr session = session_load (filename);
	if (session != NULL) 
	{
		GList *copy;
		gboolean old_server_mode;
		/* Switch to serveer mode to avoid exiting from galeon
		 * when all windows are closed. This is a bit hacky... 
		 */
		old_server_mode = galeon_server_mode;
		galeon_server_mode = TRUE;
		/* close all windows */
		copy = g_list_copy (all_windows);
		g_list_foreach (copy, (GFunc) window_close, NULL);
		g_list_free (copy);
		/* reopen windows... */
		session_resume (session, TRUE);
		xmlFreeDoc (session);
		/* restore server mode */
		galeon_server_mode = old_server_mode;
	} 
	else
	{
		gchar *s = g_strdup_printf 
			(_("Couldn't load session from %s"), filename);
		GtkWidget *d = gnome_error_dialog (s);
		g_free (s);
		gnome_dialog_run_and_close (GNOME_DIALOG (d));
		gtk_widget_destroy (d);
	}
}

/**
 * gets the list of recently used sessions
 */
GList *
session_history_get (void)
{
	GList *ret = NULL;
	gint i, count;

	count = eel_gconf_get_integer (CONF_HISTORY_SESSION_COUNT);
	for (i = count; i > 0; i--) 
	{
		gchar *key = g_strdup_printf (CONF_HISTORY_SESSION_PATH"/File%d", i);
		gchar *file = eel_gconf_get_string (key);
		if (file != NULL)
			ret = g_list_prepend (ret, file);
		g_free (key);
	}
	return ret;
}

/**
 * modifies the list of recently used sessions, addid the filename at the top
 */
void
session_history_add (const gchar *filename)
{
	gint max, count;
	GList *li;
	GList *current_list = session_history_get ();
	/* remove the file if already present */
	for (li = current_list; li != NULL; li = li->next) 
	{
		if (!strcmp (li->data, filename))
		{
			g_free (li->data);
			current_list = g_list_remove_link (current_list, li);
			break;
		}		
	}
	/* prepend the new item */
	current_list = g_list_prepend (current_list, g_strdup (filename));
	/* save the new list */
	max = eel_gconf_get_integer (CONF_HISTORY_SESSION_MAXITEMS);
	count = 0;
	for (li = current_list; li != NULL; li = li->next) 
	{
		gchar *key = g_strdup_printf (CONF_HISTORY_SESSION_PATH"/File%d", 
					      ++count);
		gchar *file = li->data;
		eel_gconf_set_string (key, file);
		g_free (file);
		g_free (key);
		if (count == max) break;
	}
	eel_gconf_set_integer (CONF_HISTORY_SESSION_COUNT, count);

	g_list_free (current_list);
	/* update the GUI */
	window_session_history_update_all ();
}

void
session_history_load_index (gint index)
{
	gint i = 1;
	GList *l = session_history_get ();
	GList *li;
	for (li = l; li != NULL; li = li->next)
	{
		if (i == index) 
		{
			gchar *fname = li->data;
			session_load_from (fname);
			/* put the item at the top*/
			session_history_add (fname);
			break;
		}
		i++;
	}
	for (li = l; li != NULL; li = li->next)
	{
		g_free (li->data);
	}
	g_list_free (l);
}

void
session_server_start_timeout ()
{
	if (quit_timeout_id > 0)
		session_server_stop_timeout ();
	if (all_embeds != NULL)
		return;
	if (galeon_server_timeout == 0)
		return;
	
	if (galeon_server_timeout < 10)
		/* I don't wnat strange races when closing an embed */
		galeon_server_timeout = 10;
	
	quit_timeout_id = gtk_timeout_add
		(galeon_server_timeout * 1000,
		 session_server_timeout_cb,
		 NULL);
}

void
session_server_stop_timeout ()
{
	if (quit_timeout_id == 0)
		return;
	
	gtk_timeout_remove (quit_timeout_id);
	quit_timeout_id = 0;
}

static gint
session_server_timeout_cb (gpointer data)
{
	if (all_embeds != NULL)
		return FALSE;
	
	galeon_server_mode = FALSE;
	
	session_quit (FALSE);
	galeon_exit ();
	
	/* not really important what we return here */
	return FALSE;
}

