/*
 *  Copyright (C) 2001 Matt Aubury, 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.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "bookmarks.h"
#include "bookmarks-iterator.h"
#include "eel-gconf-extensions.h"
#include "galeon-config.h"
#include "gul-general.h"
#include "gul-string.h"
#include "src/galeon-shell.h"

#include <stdlib.h>
#include <string.h>
#include <glib/gi18n.h>
#include <libgnomevfs/gnome-vfs-utils.h>

#include "nsCOMPtr.h"
#include "nsIIOService.h"
#include "nsIServiceManager.h"
#include "nsIStorageStream.h"
#include "nsIURI.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsIChannel.h"

#include "MyportalProtocolHandler.h"

/* set this to use a file in /tmp rather than generating on the fly  */
#define USE_INTERMEDIATE_FILE 0
#define MAX_LINK_NAME_LENGTH 40

/* output stream type */
typedef nsIOutputStream OutStream;

/* local function prototypes */
static void render_from_path (OutStream *stream, const char *path);
static void render_from_item (OutStream *stream, GbBookmark *b,
			      const char *prefix, gint depth);
static void render_folder (OutStream *stream, GbFolder *b,
			      const char *prefix, gint depth);
static void render_url (OutStream *stream, GbSite *b);
static void render_search_form (OutStream *stream, GbSmartSite *b);

/* hackish shorthand to render HTML into the browser */
#if USE_INTERMEDIATE_FILE
#define RENDER(str) \
        if (str) fwrite (str, strlen (str), 1, stream)
#else
static PRUint32 bytesWritten;
#define RENDER(str) \
        if (str) stream->Write (str, strlen(str), &bytesWritten)
#endif

static NS_DEFINE_CID(kSimpleURICID, NS_SIMPLEURI_CID);

/* Implementation file */
NS_IMPL_ISUPPORTS2 (GMyportalProtocolHandler, nsIProtocolHandler, nsIAboutModule)

GMyportalProtocolHandler::GMyportalProtocolHandler (void)
{
	NS_INIT_ISUPPORTS();
}

GMyportalProtocolHandler::~GMyportalProtocolHandler()
{
	/* destructor code */
}

/* readonly attribute string scheme; */
NS_IMETHODIMP GMyportalProtocolHandler::GetScheme(nsACString &aScheme)
{
	aScheme = NS_LITERAL_CSTRING("myportal");
	return NS_OK;
}

/* readonly attribute long defaultPort; */
NS_IMETHODIMP GMyportalProtocolHandler::GetDefaultPort(PRInt32 *aDefaultPort)
{
	nsresult rv = NS_OK;
	if (aDefaultPort)
		*aDefaultPort = -1;
	else
		rv = NS_ERROR_NULL_POINTER;
	return rv;
}

/* readonly attribute short protocolFlags; */
NS_IMETHODIMP GMyportalProtocolHandler::GetProtocolFlags(PRUint32 *aProtocolFlags)
{
	if (aProtocolFlags)
		*aProtocolFlags = nsIProtocolHandler::URI_STD;
	else
		return NS_ERROR_NULL_POINTER;
	return NS_OK;
} 

/* nsIURI newURI (in string aSpec, in nsIURI aBaseURI); */
NS_IMETHODIMP GMyportalProtocolHandler::NewURI(const nsACString &aSpec,
					       const char *aOriginCharset,
					       nsIURI *aBaseURI,
					       nsIURI **_retval)
{
	nsresult rv = NS_OK;
	nsCOMPtr <nsIURI> newUri;

	rv = nsComponentManager::CreateInstance(kSimpleURICID, NULL,
						NS_GET_IID(nsIURI),
						getter_AddRefs(newUri));

        if (NS_SUCCEEDED(rv)) 
        {
		newUri->SetSpec(aSpec);
		rv = newUri->QueryInterface(NS_GET_IID(nsIURI),
					    (void **) _retval);
        }
	return rv;
}

static NS_IMETHODIMP
OpenMyportalStylesheet (nsIURI *aURI, nsIChannel **aChannel)
{
	nsresult rv;

	gchar *cssPath = gul_general_user_file ("myportal.css", FALSE);
	g_return_val_if_fail (cssPath != NULL, NS_ERROR_FAILURE);

	// open the path as file since it could be relative path (thanks to
	// gul_general_user_file) and simply prepending 'file://' would make 
	// an invalid URI
	nsCOMPtr<nsILocalFile> cssFile;
	rv = NS_NewLocalFile(NS_ConvertUTF8toUCS2(cssPath), PR_TRUE,
			     getter_AddRefs(cssFile));
	g_free (cssPath);

	nsCOMPtr<nsIURI> cssURI;
	rv = NS_NewFileURI(getter_AddRefs(cssURI), cssFile);
	if (NS_FAILED(rv)) return rv;

	nsCOMPtr<nsIInputStream> stream;
	rv = NS_OpenURI(getter_AddRefs(stream), cssURI);
	if (NS_FAILED(rv)) return rv;

	rv = NS_NewInputStreamChannel(aChannel, aURI,
				      stream, NS_LITERAL_CSTRING("text/css"),
				      NS_LITERAL_CSTRING("utf-8"));
	return rv;
}

/* nsIChannel newChannel (in nsIURI aURI); */
NS_IMETHODIMP GMyportalProtocolHandler::NewChannel(nsIURI *aURI,
					      nsIChannel **_retval)
{
	nsresult rv;
	PRBool isabout;
	rv =  aURI->SchemeIs ("about", &isabout);
	if (NS_FAILED(rv)) return rv;
	if (isabout)
		rv = CreateMyportalPage (NS_LITERAL_CSTRING("/"), aURI, _retval);
	else
	{
		nsCAutoString path;
		rv = aURI->GetPath (path);
		if (NS_FAILED(rv)) return rv;
		if (path.Equals("myportal.css"))
			rv = OpenMyportalStylesheet (aURI, _retval);
		else
			rv = CreateMyportalPage (path, aURI, _retval);
	}
	return rv;
}

/* boolean allowPort (in long port, in string scheme); */
NS_IMETHODIMP GMyportalProtocolHandler::AllowPort(PRInt32 port, const char *scheme,
					     PRBool *_retval)
{
	*_retval = PR_FALSE;
	return NS_OK;
}

NS_METHOD GMyportalProtocolHandler::CreateMyportalPage (const nsACString &path, nsIURI *aURI, nsIChannel **aChannel)
{
	nsresult rv;

	/* check bookmarks are loaded */
	//	g_return_val_if_fail (bookmarks_root != NULL,NS_ERROR_FAILURE);
	
	/* open the rendering stream */
#if USE_INTERMEDIATE_FILE
	gchar *filename = g_strconcat (g_get_home_dir (),
				       "/.galeon/myportal.html",
				       NULL);
	gchar *myportalURI = g_strconcat ("file://",filename, NULL);
	FILE *stream = fopen (filename, "w");
	if (stream == NULL)
	{
		g_warning ("unable to create `%s'", filename);
		g_free (filename);
		return NS_ERROR_FAILURE;
	}
#else
	nsCOMPtr<nsIStorageStream> sStream;
	nsCOMPtr<nsIOutputStream> stream;

	rv = NS_NewStorageStream(16384, (PRUint32)-1, getter_AddRefs(sStream));
	if (NS_FAILED(rv)) return rv;

	rv = sStream->GetOutputStream(0, getter_AddRefs(stream));
	if (NS_FAILED(rv)) return rv;

#endif

	/* render the complete portal */
	gchar* myportal_utfstr = _("My Portal");
	RENDER ("<html><head>\n");
	RENDER ("<link rel=\"stylesheet\" href=\"myportal:myportal.css\" type=\"text/css\">\n");
	RENDER ("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=utf-8\">\n");
	RENDER ("<title>");
	RENDER (myportal_utfstr);
	RENDER ("</title>");
	RENDER("</head>\n");
	RENDER ("<body>\n");
	/* RENDER ("<a href=\"" GALEON_HOMEPAGE_URL "\">"
	       "<img class=\"logo\" src=\"file:" SHARE_DIR
	       "/logo.png\" alt=\"\"></a>"); */
	RENDER ("<div class=\"top_bar\"><table cellpadding=\"2\" cellspacing=\"0\" width=\"100%\" border=0><tr><td><img src=\"file://" SHARE_DIR "/galeon.png\">myportal:</td>\n");
	RENDER ("<td valign=\"top\" align=\"right\" class=\"slogan\"></td></tr></table></div>\n");
	RENDER ("<div class=\"top2\"><a href=\"http://galeon.sourceforge.net\"><img class=\"logo\" src=\"file://" SHARE_DIR "/logo.png\" alt=\"\"></a></div>\n");
	RENDER ("<p><br></p> \n");
	RENDER("<!-- BOOKMARKS -->\n\n");
	render_from_path (stream, PromiseFlatCString(path).get());
	RENDER("\n\n<!-- END -->");
	RENDER ("<div class=\"bottom_bar\"><center><b>Galeon</b> : <i>the web, the only web</i></center></div>");
	RENDER ("</body></html>\n");
//	g_free(myportal_utfstr);

	/* finish the rendering */
#if USE_INTERMEDIATE_FILE
	fclose (stream);
	g_free (filename);

	static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID);
	nsCOMPtr<nsIIOService> ioService = do_GetService (kIOServiceCID, &rv);
	if (NS_FAILED(rv) || !ioService) return rv;
	rv = ioService->NewChannel(myportalURI, nsnull, aChannel);

	g_free (myportalURI);
#else
	nsCOMPtr<nsIInputStream> iStream;
	rv = sStream->NewInputStream(0, getter_AddRefs(iStream));
	if (NS_FAILED(rv)) return rv;

	rv = NS_NewInputStreamChannel(aChannel, aURI,
				      iStream, NS_LITERAL_CSTRING("text/html"),
				      NS_LITERAL_CSTRING("utf-8"));
	if (NS_FAILED(rv)) return rv;
#endif
	return rv;
}

////////////////////////////////////////////////////////////////////////////////
/*static gint 
bookmark_name_compare (const GbBookmark *a, const gchar *name)
{
	if (a->name == NULL)
		return -1;
	gchar *stripped = gul_string_strip_uline_accel (a->name);
	int rv = strcmp(stripped, name);
	g_free (stripped);
	return rv;
}
*/

static void
render_from_path (OutStream *stream, const gchar *path)
{
	GbBookmark *root = 
		GB_BOOKMARK(galeon_shell_get_bookmark_set(galeon_shell)->root);
	GString *clippedpath = g_string_new (path);
	GString *prefix = g_string_new ("myportal://");
	gboolean blockquote_started = FALSE;
	gchar *anch = strchr (path, '#');
	if (anch)
		g_string_truncate (clippedpath, anch-path);
	gchar **paths = g_strsplit (clippedpath->str, "/", 0);
/*
	for (int i=0; paths[i] != NULL; ++i)
	{
		if (!*paths[i]) //ignore empty components
			continue;
		gchar *name = gul_string_unescape_hexed_string (paths[i]);
//		GList *match = g_list_find_custom (root->list, name,
//					(GCompareFunc)bookmark_name_compare);
		GList *match = NULL;
		g_free (name);
		if (match == NULL)
		{
			gchar *buf = g_strdup_printf(_("%s not found\n"), 
						      clippedpath->str);
			RENDER (buf);
			g_free (buf);
			return;
		}

		if (!blockquote_started)
		{
			RENDER ("<blockquote>\n");
			RENDER ("<strong>");
			blockquote_started = TRUE;
		}
		else
			RENDER ("/");
		gchar *strippedname =
			gul_string_strip_uline_accel (root->name);
		RENDER ("<a href=\"");
		RENDER (prefix->str);
		RENDER ("\">");
		RENDER (strippedname);
		g_free (strippedname);
		RENDER ("</a>");
		
		g_string_sprintfa (prefix, "%s/", paths[i]);
		root = (GbBookmark*)match->data;
	}
*/
	g_strfreev (paths);
	if (blockquote_started)
		RENDER ("</strong>\n");
	render_from_item (stream, root, prefix->str, 0);
	g_string_free (prefix, TRUE);
	g_string_free (clippedpath, TRUE);
	if (blockquote_started)
		RENDER ("</blockquote>\n");
}

/**
 * render_from_item: render a single bookmark item
 */
static void
render_from_item (OutStream *stream, GbBookmark *b, const gchar *prefix, 
		  gint depth)
{
	gchar *strippedname;
	gchar *escapedname, *newprefix, *id;
	/* could be the case if there are no bookmarks */
	if (b == NULL)
		return;

	/* otherwise do the appropriate thing */
	if (GB_IS_FOLDER(b)) {
		RENDER ("<blockquote>");
		strippedname = gul_string_strip_uline_accel (b->name);
		RENDER ("<a class=\"heading\" ");
		id = g_strdup_printf ("%lu", (gulong) b);
		if (!GB_IS_ALIAS_PLACEHOLDER(b))
		{
			RENDER ("name=\"");
			RENDER (id);
			RENDER ("\" ");
		}
		RENDER ("href=\"");
		if (depth == 0)
		{
			newprefix = g_strdup (prefix);
		}
		else
		{
			escapedname = gnome_vfs_escape_string (strippedname);
			newprefix = g_strconcat (prefix, escapedname, "/", NULL);
			g_free(escapedname);
		}
		RENDER (newprefix);
		RENDER ("\">");
		RENDER ("<strong>");
		RENDER (strippedname);
		RENDER ("</strong>");
		g_free (strippedname);
		RENDER ("</a>");
		if (GB_IS_ALIAS_PLACEHOLDER(b))
		{
		/* we point to the root portal since 1) the real bm might not
		 * be in this same level we are viewing, and 2) relative
		 * anchors don't seem to work with myportal://  ;) */
			RENDER (" -> <a href=\"myportal://#");
			RENDER (id);
			RENDER ("\">#");
			RENDER (id);
			RENDER ("</a>");
		}
		g_free (id);
		RENDER ("<br>");
		if (!GB_IS_ALIAS_PLACEHOLDER(b) || depth==0)
			render_folder (stream, GB_FOLDER(b), 
				       newprefix, depth+1);
		g_free (newprefix);
		RENDER ("</blockquote>\n");
	} else if (GB_IS_SMART_SITE(b)) {
		render_search_form (stream, GB_SMART_SITE(b));
	} else if (GB_IS_SITE(b)) {
		render_url (stream, GB_SITE(b));
	} else if (GB_IS_SEPARATOR(b)) {		
		RENDER ("<br>\n");
	}
}

/**
 * render_from_list: render a list of bookmark items
 */
static void
render_folder (OutStream *stream, GbFolder *b, const gchar *prefix, gint depth)
{
	GbIterator *i = gb_iterator_new_folder(b, FALSE);
	GbBookmark *bi;
	while ((bi = gb_iterator_next(i))) {
		render_from_item(stream, bi, prefix, depth);
	}
	g_object_unref (i);
}


/**
 * render_url: render a bookmark url
 */
static void
render_url (OutStream *stream, GbSite *b)
{
	gchar *url;

	RENDER ("<a href=\"");

	/* If url has no preceding "http://" or "ftp://" presume "http://" */
	/* FIXME cant we just test for : ? */
	if (!strstr (b->url, "://") && 
	    !strstr (b->url, "myportal:") &&
	    !strstr (b->url, "javascript:") &&
	    !strstr (b->url, "ghelp:") &&
	    !strstr (b->url, "info:") &&
	    !strstr (b->url, "man:") &&
	    !strstr (b->url, "toc:"))
	{
		RENDER ("http://");
	}

	/* escape quotes, so we don't accidentally break out of the URI */
	url = gul_string_strdup_replace (b->url, "\"", "&quot;");
	RENDER (url);
	if (url) g_free (url);

	RENDER ("\">");
	if (gb_bookmark_get_image(GB_BOOKMARK(b)) == NULL)
	{
		gchar *name, *name2;

		/* use name if we have one, and otherwise, url.  shorten
		 * either one */
		if (GB_BOOKMARK(b)->name && GB_BOOKMARK(b)->name[0] != '\0')
		{
			gchar *stripped_name = gul_string_strip_uline_accel (
						 GB_BOOKMARK(b)->name);
			name = gul_string_shorten (stripped_name,
						 MAX_LINK_NAME_LENGTH);
			if (stripped_name) g_free (stripped_name);
		}
		else
		{
			name = gul_string_shorten (b->url,
						 MAX_LINK_NAME_LENGTH);
		}

		/* escape some more characters */
		name2 = gul_string_strdup_replace (name, "<", "&lt;");
		if (name) g_free (name);
		name = gul_string_strdup_replace (name2, ">", "&gt;");
		if (name2) g_free (name2);

		RENDER (name);
		if (name) g_free (name);
	}
	else
	{
		RENDER ("<img src=\"file://");
		RENDER (GB_BOOKMARK(b)->pixmap_file);
		RENDER ("\">");
	}
	RENDER ("</a>\n");
}

/* copied from bookmarks/bookmarks.c so that we don't need to link the whole
 * lib */
static gchar *
gb_smart_site_get_smarturl_only (GbSmartSite *b)
{
	const gchar *openbrace;
	const gchar *closebrace;
	const gchar *c;
	
	openbrace = strchr (b->smarturl, '{');
	if (!openbrace) return g_strdup (b->smarturl);
	for (c = b->smarturl; c < openbrace; ++c)
	{
		if (!strchr (" \t\n", *c)) return g_strdup (b->smarturl);
	}

	closebrace = strchr (openbrace + 1, '}');
	if (!closebrace) return g_strdup (b->smarturl);

	return g_strdup (closebrace + 1);
}

/**
 * render_search_form: render a search form based on a smart bookmark
 * FIXME: tidy
 */
static void 
render_search_form (OutStream *stream, GbSmartSite *b)
{
	gchar *smarturl;
	gchar *path;    /* static part of URL */
	gchar **query;  /* query string, broken into parameters */
	int num_inputs = 0, pathlen;
	gchar *tmp;
	int i;

	smarturl = gb_smart_site_get_smarturl_only (b);

	/* parse URL to separate static parts from parts needing user input */
	tmp = strchr (smarturl, '?');
	if (!tmp || ((int)strlen (smarturl)) < (tmp - smarturl + 2))
	{
                /* no query in URL, can't render it */
		g_free (smarturl);
		return; 
	}

	pathlen = tmp - smarturl;
	path = g_strndup (smarturl, pathlen);
	tmp = gb_smart_site_get_encoding (b);
	RENDER ("<form action=\"");
	if (!strstr (smarturl, "://"))
	{
		RENDER ("http://");
	}
	RENDER (path);
	RENDER ("\" method=\"get\" accept-charset=\"");
	RENDER (tmp);
	RENDER ("\">");
	g_free (tmp);
	g_free (path);


	/* count occurrences of %s in smarturl */
	tmp = smarturl;
	while ((tmp = strstr (tmp+1, "%s")))
	       ++num_inputs;

	query = g_strsplit (smarturl + pathlen + 1, "&", -1);
	g_free (smarturl);

	for (i = 0; query[i]; ++i) {
		gchar **param, *smtpos;
		if (query[i][0] == '\0')
			continue;
		if (strcmp (query[i], "%s") == 0)
		{
			/* anonymous field */
			RENDER ("<input type=\"text\" name=\"\" size=\"");
			RENDER (num_inputs>1?"10\">":"30\">");
			/* skip the remaining logic */
			continue;
		}
		param = g_strsplit (query[i], "=", 2);
		if (param[0] && param[1] && (smtpos = strstr (param[1], "%s")))
		{
			/* fill in the field with the text minus the %s */
			gchar *val=g_strdup_printf ("%.*s%s", smtpos-param[1],
					param[1],param[1]+(smtpos-param[1]+2));
			/* add a form input */
			RENDER (num_inputs>1?param[0]:"");
			RENDER ("<input type=\"text\" name=\"");
			RENDER (param[0]);
			RENDER ("\" value=\"");
			RENDER (val);
			RENDER ("\" size=\"");
			RENDER (num_inputs>1?"10\">":"30\">");
			g_free (val);
		}
		else
		{
			/* add the static parts of URL as hidden form inputs */
			RENDER ("<input type=\"hidden\" name=\"");
			RENDER (param[0]);
			RENDER ("\" value=\"");
			if (param[1])
				RENDER (param[1]);
			RENDER ("\">");
		}
		g_strfreev (param);
	}
	g_strfreev (query);
	
	if (gb_bookmark_get_image(GB_BOOKMARK(b)) == NULL)
	{
		gchar *strippedname = 
		     gul_string_strip_uline_accel (GB_BOOKMARK(b)->name);
		RENDER ("<input type=\"submit\" value=\"");
		RENDER (strippedname);
		g_free (strippedname);
	}
	else
	{
		RENDER ("<input type=\"image\" src=\"file://");
			RENDER (GB_BOOKMARK(b)->pixmap_file);
	}
	RENDER ("\"></form>\n");
}

#undef RENDER
