/*
 *  Copyright (C) 2002, 2003 Philip Langdale
 *
 *  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.
 */

/* GSidebar: Only a single instance of this class should be present at 
 * any one time. Access to the singleton GlobalSidebar object will either
 * be from mozilla through GSidebarProxy instances when the user adds a
 * sidebar page from a website, from within galeon through a set of callbacks.
 * As part of the mozilla subsystem initialisation, mozilla_init_sidebar() is
 * called which attaches two callbacks to the session manager to respond to
 * window creation and closure. When a window is opened (including the first
 * one) the callback will ensure that GlobalSidebar is instanced and then
 * add a refcount for the open window and then initialise the galeon sidebar
 * by loading the existing pages into the sidebar's option menu and attaching
 * a callback to respond to page selections from said menu.
 * The window closure callback simply decrements the ref count. GlobalSidebar
 * will remain instanced as long as there are open windows or GSidebarProxy
 * instances holding the ref count above 0. In practice, the ref count will
 * almost always be >=1 because there will be a window open unless galeon
 * is running in server mode. This could be optimised by having galeon's
 * sidebar created on show rather than on parent window creation.
 * The central list of pages is stored as a STL map with nsCStrings for
 * the key (url) and data (title). The map offers a big advantage by
 * being leak free and easy to add and remove entries into/from. A GLib
 * hash_table has a smaller memory footprint with inefficient compilers
 * but is very difficult to work with if strings are used for keys as it
 * is impossible to free a string key without freeing the whole table. This
 * is not good for us because the user will be deleting pages during program
 * execution. nsCStrings are the chosen string type for two main reasons:
 * no memory management headache and they are much more utf8 aware than STL
 * strings.
 */

#include "MozillaPrivate.h"
#include "SideBar.h"

#include "galeon-config.h"
#include "src/galeon-shell.h"
#include "galeon-embed-shell.h"
#include "gul-general.h"
#include "src/session.h"

#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xmlmemory.h>

#include <nsIWindowWatcher.h>
#include <nsIServiceManagerUtils.h>
#include <nsCRT.h>

#define WINDOWWATCHER_CONTRACTID "@mozilla.org/embedcomp/window-watcher;1"

extern "C" void GSidebar_page_changed_cb (GaleonSidebar *sidebar, 
					  const char *page_id, 
					  GSidebar *gSidebar);

extern "C" void GSidebar_remove_request_cb (GaleonSidebar *sidebar, 
					    const char *page_id, 
					    GSidebar *gSidebar);

extern "C" void moz_session_new_window_cb (Session *session,
					   GaleonWindow *window,
					   gpointer data);

extern "C" void moz_session_close_window_cb (Session *session,
					     gpointer data);

static gint embed_mouse_down_cb  (GaleonEmbed *embed,
		      		  GaleonEmbedEvent *event,
		      		  gpointer data);

static gint embed_mouse_click_cb  (GaleonEmbed *embed,
				   GaleonEmbedEvent *event,
				   gpointer data);

static void embed_new_window_cb (GaleonEmbed *embed, GaleonEmbed **new_embed,
				 EmbedChromeMask chromemask, gpointer data);

GSidebar *GlobalSidebar(nsnull);

/* Implementation file */
NS_IMPL_ISUPPORTS1(GSidebar, nsISidebar)

GSidebar::GSidebar() : mRefCount(0)
{
	NS_INIT_ISUPPORTS();
	LoadSidebarPages();
}

GSidebar::~GSidebar()
{
	g_assert(mRefCount == 0);
	SaveSidebarPages();
	GlobalSidebar = nsnull;
}

//------------------------------------------------------------------------------
//nsISidebar Impl.
//------------------------------------------------------------------------------

#if MOZILLA_SNAPSHOT < 12
/* void setWindow (in nsIDOMWindowInternal aWindow); */
NS_IMETHODIMP GSidebar::SetWindow(nsIDOMWindowInternal *aWindow)
{
	nsresult rv;
	nsCOMPtr<nsIDOMWindow> DOMWindow =
		do_QueryInterface(aWindow, &rv);
	if(NS_FAILED(rv) || !DOMWindow) return NS_ERROR_FAILURE;

        nsCOMPtr<nsIWindowWatcher> wwatch
                (do_GetService(WINDOWWATCHER_CONTRACTID, &rv));
	if(NS_FAILED(rv) || !wwatch) return NS_ERROR_FAILURE;

	return wwatch->SetActiveWindow(DOMWindow);	
}
#endif

/* void addPanel (in wstring aTitle, in string aContentURL, in string aCustomizeURL); */
NS_IMETHODIMP GSidebar::AddPanel(const PRUnichar *aTitle,
				 const char *aContentURL,
				 const char *aCustomizeURL)
{
	const nsCString utf8Url(aContentURL);
	if(mUrls.find(utf8Url) != mUrls.end()) return NS_OK;

	const nsACString &utf8Title = NS_ConvertUCS2toUTF8(aTitle);
	mUrls[utf8Url] = utf8Title;

	Session *session = galeon_shell_get_session(galeon_shell);
	if (!session) return NS_ERROR_FAILURE;

	const GList *windows = session_get_windows(session);
	if (!windows) return NS_OK;

	for(const GList *i = windows ; i ; i = i->next)
	{
		if (!IS_GALEON_WINDOW(i->data)) continue;
		AddPage(GALEON_WINDOW(i->data), utf8Title, utf8Url);
	}

	return NS_OK;
}

/* void addPersistentPanel (in wstring aTitle, in string aContentURL, in string aCustomizeURL); */
NS_IMETHODIMP GSidebar::AddPersistentPanel(const PRUnichar *aTitle,
					   const char *aContentURL,
					   const char *aCustomizeURL)
{
	g_print ("GSidebar::AddPersistentPanel called.\n");
	return NS_ERROR_NOT_IMPLEMENTED;
}

/* void addSearchEngine (in string engineURL, in string iconURL, in wstring suggestedTitle, in wstring suggestedCategory); */
NS_IMETHODIMP GSidebar::AddSearchEngine(const char *engineURL,
					const char *iconURL,
					const PRUnichar *suggestedTitle,
					const PRUnichar *suggestedCategory)
{
	g_print ("GSidebar::AddSearchEngine called.\n");
	return NS_ERROR_NOT_IMPLEMENTED;
}

//------------------------------------------------------------------------------
//GSidebar public methods Impl.
//------------------------------------------------------------------------------

void GSidebar::InitSidebar(GaleonWindow *aWindow)
{
	GaleonSidebar *sidebar = galeon_window_get_sidebar(aWindow);

	URLMap::const_iterator i;
	for(i = mUrls.begin() ; i != mUrls.end() ; i++)
	{
		AddPage(aWindow, (*i).second, (*i).first);
	}

	g_signal_connect (sidebar, "page_changed",
			  G_CALLBACK (GSidebar_page_changed_cb),
			  this);

	g_signal_connect (sidebar, "remove_requested",
			  G_CALLBACK (GSidebar_remove_request_cb),
			  this);
}

void GSidebar::ShowPage(GaleonSidebar *aSidebar, const nsACString &aUrl)
{
	if(mUrls.find(nsCString(aUrl)) == mUrls.end()) return;

	GaleonEmbedShell *shell = galeon_shell_get_embed_shell(galeon_shell);

	GaleonEmbed *sidebarEmbed = galeon_embed_new(G_OBJECT(shell));
	gtk_widget_show (GTK_WIDGET(sidebarEmbed));

	g_signal_connect (G_OBJECT (sidebarEmbed),
			  "ge_dom_mouse_down",
			  G_CALLBACK(embed_mouse_down_cb),
			  NULL);
	g_signal_connect (G_OBJECT (sidebarEmbed),
			  "ge_dom_mouse_click",
			  G_CALLBACK(embed_mouse_click_cb),
			  NULL);
	g_signal_connect (G_OBJECT (sidebarEmbed),
			  "ge_new_window",
			  G_CALLBACK(embed_new_window_cb),
			  NULL);

	galeon_sidebar_set_content(aSidebar, G_OBJECT(sidebarEmbed));
	galeon_embed_load_url(sidebarEmbed, PromiseFlatCString(aUrl).get());
}

void GSidebar::RemovePage(const nsACString &aUrl)
{
	const nsCString cUrl(aUrl);
	if (mUrls.find(cUrl) == mUrls.end()) return;

	Session *session = galeon_shell_get_session(galeon_shell);
	if (!session) return;

	const GList *windows = session_get_windows(session);
	if (!windows) return;

	for(const GList *i = windows ; i ; i = i->next)
	{
		if (!IS_GALEON_WINDOW(i->data)) continue;
		GaleonSidebar *sidebar =
			galeon_window_get_sidebar(GALEON_WINDOW(i->data));
		galeon_sidebar_remove_page (sidebar, cUrl.get());
	}

	mUrls.erase(cUrl);
}

//------------------------------------------------------------------------------
//GSidebar private methods Impl.
//------------------------------------------------------------------------------

void GSidebar::AddPage(GaleonWindow *aParent,
		       const nsACString &aTitle,
		       const nsACString &aURL) const
{
	GaleonSidebar *sidebar = galeon_window_get_sidebar(aParent);
	galeon_sidebar_add_page(sidebar,
				PromiseFlatCString(aTitle).get(),
				PromiseFlatCString(aURL).get(),
				TRUE);
}

void GSidebar::LoadSidebarPages(void)
{
	gchar *filename = g_build_filename(g_get_home_dir(),
					   GALEON_DIR"/sidebars.xml",
					   NULL);

	/* check the file exists */
	if (!(g_file_test (filename, G_FILE_TEST_EXISTS)))
	{
		g_free (filename);
		return;
	}

	/* load the file */
	xmlDocPtr doc = xmlParseFile (filename);
	if (doc == NULL) 
	{
		g_warning ("Unable to parse `%s', no sidebars loaded.",
			   filename);
		g_free (filename);
		return;
	}
	g_free (filename);

	/* iterate over sidebar pages in document */
        for (xmlNodePtr page = doc->children->children;
             page != NULL; page = page->next)
	{
		xmlChar *desc = xmlGetProp (page,
				   reinterpret_cast<const xmlChar*>("description"));
		xmlChar *url = xmlGetProp (page,
				  reinterpret_cast<const xmlChar*>("url"));
		if(!desc || !url) continue;

		const nsCString utf8Title(reinterpret_cast<const char *>(desc));
		const nsCString utf8Url(reinterpret_cast<const char *>(url));
		mUrls[utf8Url] = utf8Title;

		if (desc) xmlFree (desc);
		if (url) xmlFree (url);
	}

	xmlFreeDoc (doc);
}

void GSidebar::SaveSidebarPages(void)
{
	gchar *filename = g_build_filename (g_get_home_dir(),
					    GALEON_DIR"/sidebars.xml",
					    NULL);

	/* version doesn't really make sense, but... */
        xmlDocPtr doc = xmlNewDoc (reinterpret_cast<const xmlChar*>("1.0"));

	/* create and set the root node for the session */
        xmlNodePtr root_node = xmlNewDocNode (doc, NULL,
        				      reinterpret_cast<const xmlChar*>("sidebars"),
        				      NULL);
        xmlDocSetRootElement (doc, root_node);
	/* iterate through all the sidebars */
	URLMap::const_iterator i;
	for (i = mUrls.begin() ; i != mUrls.end() ; i++)
	{
		/* make a new XML node */
		xmlNodePtr page_node = xmlNewDocNode (doc, NULL,
						      reinterpret_cast<const xmlChar*>("page"),
						      NULL);

		/* fill out fields */
		xmlSetProp (page_node,
			    reinterpret_cast<const xmlChar*>("description"),
			    reinterpret_cast<const xmlChar*>((*i).second.get()));
		xmlSetProp (page_node,
			    reinterpret_cast<const xmlChar*>("url"),
			    reinterpret_cast<const xmlChar*>((*i).first.get()));

		xmlAddChild (root_node, page_node);
	}

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

//------------------------------------------------------------------------------
// Embed callbacks
//------------------------------------------------------------------------------

static gint
embed_mouse_down_cb  (GaleonEmbed *embed,
		      GaleonEmbedEvent *event,
		      gpointer data)
{
	GaleonPopup *popup;
	GaleonWindow *window;
	guint button;
	EmbedEventContext context;

	event->context |= EMBED_CONTEXT_SIDEBAR; 

	galeon_embed_event_get_context (event, &context); 
	
	g_assert (IS_GALEON_EMBED_EVENT(event));
	
	galeon_embed_event_get_mouse_button (event, &button);
	
	window = GALEON_WINDOW(gtk_widget_get_toplevel (GTK_WIDGET(embed)));
	g_return_val_if_fail (window != NULL, FALSE);

	if (button == 2)
	{
		popup = GALEON_POPUP (galeon_window_get_popup_factory (window));
		galeon_popup_set_event (popup, event);
		galeon_popup_show (popup, embed);
	}
	else if (button == 1 && 
		 (context & EMBED_CONTEXT_LINK))
	{
		const GValue *value;
	
		galeon_embed_event_get_property (event, "link", &value);
		galeon_shell_new_tab (galeon_shell, window, NULL, 
				      g_value_get_string (value),
				      (GaleonNewTabFlags)0);
	}
	
	return FALSE;
}

static gint
embed_mouse_click_cb  (GaleonEmbed *embed,
		       GaleonEmbedEvent *event,
		       gpointer data)
{
	event->context |= EMBED_CONTEXT_SIDEBAR; 

	EmbedEventContext context;
	galeon_embed_event_get_context (event, &context);
	g_assert (IS_GALEON_EMBED_EVENT(event));

	guint button, modifier;
	galeon_embed_event_get_mouse_button (event, &button);
	galeon_embed_event_get_modifier(event, &modifier);

	const GValue *targetValue;
	galeon_embed_event_get_property (event, "link_target", &targetValue);

	if (button == 0 && (context & EMBED_CONTEXT_LINK) && (modifier == 0) &&
	    (nsCRT::strcmp(g_value_get_string(targetValue), "_content") == 0))
	{
		const GValue *linkValue;
		galeon_embed_event_get_property (event, "link", &linkValue);

		GaleonWindow *window = 
			GALEON_WINDOW(gtk_widget_get_toplevel (GTK_WIDGET(embed)));
		g_return_val_if_fail (window != NULL, FALSE);

		galeon_window_load_url(window, g_value_get_string(linkValue));

		return TRUE;
	}
	return FALSE;
}

static void 
embed_new_window_cb (GaleonEmbed *embed, GaleonEmbed **new_embed,
		     EmbedChromeMask chromemask, gpointer data)
{
	GaleonEmbedShell *shell = galeon_shell_get_embed_shell (galeon_shell);

	/* Use the shell to generate the new window */
	g_signal_emit_by_name (shell, "new_window_orphan", new_embed, chromemask);
}

//------------------------------------------------------------------------------
// GSidebar callbacks
//------------------------------------------------------------------------------

void GSidebar_page_changed_cb (GaleonSidebar *sidebar, 
			       const char *page_id, 
			       GSidebar *gSidebar)
{
	gSidebar->ShowPage(sidebar, nsDependentCString(page_id));
}

void GSidebar_remove_request_cb (GaleonSidebar *sidebar, 
			         const char *page_id, 
			         GSidebar *gSidebar)
{
	gSidebar->RemovePage(nsDependentCString(page_id));
}

//------------------------------------------------------------------------------
// Helper functions
//------------------------------------------------------------------------------

void mozilla_init_sidebar(void)
{
	Session *session = galeon_shell_get_session(galeon_shell);

	g_signal_connect (session, "new_window",
			  G_CALLBACK (moz_session_new_window_cb),
			  NULL);

	g_signal_connect (session, "close_window",
			  G_CALLBACK (moz_session_close_window_cb),
			  NULL);
}

void moz_session_new_window_cb (Session *session,
				GaleonWindow *window,
				gpointer data)
{
	if(!GlobalSidebar)
	{
		GlobalSidebar = new GSidebar();
	}

	GlobalSidebar->IncRefCount();
	GlobalSidebar->InitSidebar(window);
}

void moz_session_close_window_cb (Session *session,
				  gpointer data)
{
	GlobalSidebar->DecRefCount();
}
