/* 
 *  Copyright (C) 2001 Ross Burton
 *
 *  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 "misc.h"
#include "bookmarks.h"

#include <string.h>
#include <gnome-xml/tree.h>
#include <libgnome/gnome-util.h>

/* Export Prototypes */
static void xbel_export_xbel (xmlDocPtr doc, BookmarkItem *b);
static void xbel_export_notes (xmlNodePtr xmlNode, BookmarkItem *b);
static void xbel_export_galeon_data (xmlNodePtr xmlNode, BookmarkItem *b);
static void xbel_export_site (xmlNodePtr xmlNode, BookmarkItem *b);
static void xbel_export_folder (xmlNodePtr xmlNode, BookmarkItem *b);
static void xbel_export_node (xmlNodePtr xmlNode, BookmarkItem *b);

/* Import Prototypes */
static BookmarkItem *xbel_import_node (xmlNodePtr node);
static void xbel_import_populate_bookmark (xmlNodePtr node,
					   BookmarkItem *item);

/* Utility Prototypes */
static xmlNodePtr getChild (xmlNodePtr parent, const char *name);
static gchar *getChildValue (xmlNodePtr parent, const char *name);

#define XBEL_GALEON_OWNER "http://galeon.sourceforge.net/"

/**
 * Write the bookmark tree as a XBEL file.  Takes a gchar* as a
 * filename, and a BookmarkItem as the root of the bookmark tree.
 */
gboolean
xbel_export_bookmarks (const gchar *filename, BookmarkItem *root)
{
        xmlDocPtr doc;

        /* Sanity checks */
        g_return_val_if_fail (root->type == BM_FOLDER, -1);
        g_return_val_if_fail (filename != NULL, -1);

        /* Create a new XML document TODO: add the DTD*/
        doc = xmlNewDoc ("1.0");
        /* Add the root XBEL node */
        xbel_export_xbel (doc, root);
        /* Try and save this document */
        if (xmlSaveFile (filename, doc) == -1) {
                xmlFreeDoc (doc);
                return FALSE;
        }
        xmlFreeDoc (doc);
        return 0;
}

/**
 * Treat a BM_FOLDER item specially and use it as the root of the XBEL tree
 */
static void
xbel_export_xbel (xmlDocPtr doc, BookmarkItem *b)
{
        GList *list;
        gchar *strippedname;
        xmlNodePtr newNode;
        g_return_if_fail (doc != NULL);

        newNode = xmlNewDocNode (doc, NULL, "xbel", NULL);
        doc->root = newNode;
        xmlSetProp(newNode, "version", "1.0");
        /* In Galeon the root of the tree is a BM_FOLDER. However,
	   XBEL complicates the situation by have a special root element,
	   XBEL. This is identical to FOLDER apart from the name of the
	   tag... Thus this hack.  */
        xbel_export_galeon_data (newNode, b);
        xbel_export_notes (newNode, b);
        strippedname = strip_uline_accel (b->name);
        xmlNewChild (newNode, NULL, "title", strippedname);
        g_free(strippedname);
        for (list = b->list; list != NULL; list = list->next)
                xbel_export_node (newNode, list->data);
}

/**
 * Add an item of unknown type to the tree. This delegates the
 * responsibility of adding nodes to the tree to other functions.
 */
static void
xbel_export_node(xmlNodePtr xmlNode, BookmarkItem *b)
{
        g_return_if_fail (xmlNode != NULL);
        switch (b->type) {
        case BM_SITE:
                xbel_export_site (xmlNode, b);
                break;
        case BM_FOLDER:
                xbel_export_folder (xmlNode, b);
                break;
        case BM_SEPARATOR:
                xmlNewChild (xmlNode, NULL, "separator", NULL);
                break;
        case BM_AUTOBOOKMARKS:
                g_warning ("Found autobookmarks - what do I do here?");
                break;
        default:
                g_warning 
			("Detected a bookmark item type that I can't export!");
                break;
        }
}

/**
 * Export the data which Galeon supports but XBEL doesn't by putting
 * it in metadata elements. This is done by create a metadata element
 * with an owner of "http://galeon.sourceforge.net" and adding child
 * nodes which describe features which XBEL doesn't support.
 */
static void
xbel_export_galeon_data(xmlNodePtr xmlNode, BookmarkItem *b) {
        xmlNodePtr infoNode, newNode;
        g_return_if_fail(xmlNode != NULL);
        if (strlen (b->nick) > 0 || strlen(b->pixmap_file) > 0 
	    || b->create_toolbar || b->create_context_menu) {
                infoNode = xmlNewChild(xmlNode, NULL, "info", NULL);
                infoNode = xmlNewChild(infoNode, NULL, "metadata", NULL);
                xmlSetProp(infoNode, "owner", XBEL_GALEON_OWNER);
                /* export the nick name */
                if (strlen (b->nick) > 0) {
                        newNode = xmlNewChild
				(infoNode, NULL, "nick", b->nick);
                }
                /* pixmap path */
		/* TODO: move this into the icon attribute somehow */
                if (strlen (b->pixmap_file) > 0) {
                        newNode = xmlNewChild (infoNode, NULL, "pixmap",
                                               b->pixmap_file);
                }
                /* create a toolbar */
                if (b->create_toolbar) {
			newNode = xmlNewChild 
				(infoNode, NULL, "create_toolbar", 
				 b->create_toolbar ? "yes" : "no");
                }
                /* create a context menu */
                if (b->create_context_menu) {
			newNode = xmlNewChild 
				(infoNode, NULL, "create_context", 
				 b->create_context_menu ? "yes" : "no");
                }
        }
}

static void
xbel_export_notes (xmlNodePtr xmlNode, BookmarkItem *b) {
        if (b->notes && (strlen (b->notes) > 0)) {
                xmlNewChild(xmlNode, NULL, "desc", b->notes);
        }
}


/**
 * Add a bookmark to the tree
 */
static void
xbel_export_site (xmlNodePtr xmlNode, BookmarkItem *b)
{
        xmlNodePtr newNode;
        gchar *strippedname;
        g_return_if_fail(xmlNode != NULL);
        newNode = xmlNewChild (xmlNode, NULL, "bookmark", NULL);
        xmlSetProp (newNode, "href", b->url);
        strippedname = strip_uline_accel (b->name);
        xmlNewChild (newNode, NULL, "title", strippedname);
        g_free (strippedname);
        xbel_export_notes (newNode, b);
        xbel_export_galeon_data (newNode, b);
        /* TODO */
	/* TODO: aliases */
}

/**
 * Add a folder to the tree
 */
static void
xbel_export_folder (xmlNodePtr xmlNode, BookmarkItem *b)
{
        GList *list;
        gchar *strippedname;
        xmlNodePtr newNode;
        g_return_if_fail (xmlNode != NULL);

        newNode = xmlNewChild (xmlNode, NULL, "folder", NULL);
        xmlSetProp (newNode, "folded", b->expanded ? "no" : "yes");
        strippedname = strip_uline_accel (b->name);
        xmlNewChild (newNode, NULL, "title", strippedname);
        g_free (strippedname);
        xbel_export_notes (newNode, b);
        xbel_export_galeon_data (newNode, b);
	/* TODO: aliases. At the moment, this just looks if this node is an
	   alias and does not export any children (to avoid infinite recursion)
	*/
        /* now iterate over the children of this folder */
	if (!b->alias_of)
		for (list = b->list; list != NULL; list = list->next)
			xbel_export_node(newNode, list->data);
}


/**
 * LibXML Utility Function: Iterate through all children of parent returning a
 * pointer to the first child which is called name.
 */
static xmlNodePtr
getChild (xmlNodePtr parent, const char *name)
{
        xmlNodePtr child;
        g_return_val_if_fail(parent != NULL, NULL);
        g_return_val_if_fail(strlen(name) > 0, NULL);
        for (child = parent->childs ; child != NULL ; child = child->next)
		if (!strcmp (child->name, name))
                        return child;
        return NULL;
}


/**
 * LibXML Utility Function: Get the text value of the first child of parent 
 * with the name name.
 */
static gchar *
getChildValue (xmlNodePtr parent, const char *name)
{
        xmlNodePtr child = getChild (parent, name);
        if (child) {
		gchar *v = xmlNodeGetContent (child);
		gchar *ret = NULL;
		if (v) ret = g_strdup (v);
		xmlFree (v);
		return ret;
	} else return NULL;
}

/**
 * Take a node and populate a BookmarkItem with information from the
 * node, such as metadata, etc. 
 */
void
xbel_import_populate_bookmark(xmlNodePtr node, BookmarkItem *item)
{
        gchar *title = NULL, *url = NULL, *nick = NULL, *pixmap = NULL,
                *toolbar = NULL, *context = NULL, *notes = NULL, 
		*folded = NULL;
	
        /* The title may have been set already */
        if (item->name != NULL)
		title = getChildValue (node, "title");
	if (item->url)
		url = xmlGetRawProp (node, "href");
	/* this is probably wrong, but it is harmless because the setting in
	 * the metadata will take precedence, if present */
	pixmap = xmlGetRawProp (node, "icon"); 
	
        notes = getChildValue (node, "desc");
        folded = xmlGetProp (node, "folded");
        /* Now extract the Galeon-specific data */
        node = getChild (node, "info");
        if (node) {
                /* Iterate over the metadata children */
                for (node = node->childs ; node != NULL ; node = node->next) {
			if (!strcmp (node->name, "metadata")) {
                                gchar *owner = xmlGetProp(node, "owner");
                                if (!strcmp (owner, XBEL_GALEON_OWNER)) {
					/* This is a Galeon metadata element,
                                         * extract the data from it */
                                        nick = getChildValue (node, "nick");
					if (pixmap) xmlFree (pixmap);
                                        pixmap = getChildValue
						(node, "pixmap");
                                        toolbar = getChildValue
						(node, "create_toolbar");
                                        context = getChildValue
						(node, "create_context");
                                }
                        }
                }
        }
	
        /* Now set the attributes of the bookmark */
        if (title) item->name = g_strdup (title);
        if (url) item->url = g_strdup (url);
        if (nick) item->nick = g_strdup (nick);
        if (pixmap) item->pixmap_file = g_strdup (pixmap);
        if (toolbar) 
		item->create_toolbar = 
			(!strcmp (toolbar, "yes")) ? TRUE : FALSE;
        if (context) 
		item->create_context_menu = 
			(!strcmp (context, "yes")) ? TRUE : FALSE;
        if (notes) item->notes = g_strdup (notes);
        if (folded) 
		item->expanded = (!strcmp (folded, "yes")) ? FALSE : TRUE;
	
        /* Now free the memory we don't need any more */
	if (title) xmlFree (title);
	if (url) xmlFree (url);
	if (nick) xmlFree (nick);
	if (pixmap) xmlFree (pixmap);
        if (toolbar) xmlFree (toolbar);
        if (context) xmlFree (context);
	if (notes) xmlFree (notes);
        if (folded) xmlFree (folded);
}

/**
 * Start the import of a XBEL document, with a given filename and root
 * of the bookmark tree
 */
BookmarkItem *xbel_import_bookmarks (const gchar *filename) {
        xmlDocPtr doc;
	
        if (!(g_file_exists (filename))) {
                /* no bookmarks to open */
                g_error ("File %s does not exist", filename);
                return NULL;
        }
	
        doc = xmlParseFile (filename);
        if (doc) {
		BookmarkItem *ret = xbel_import_node (doc->root);
                xmlFreeDoc (doc);
		return ret;
        }
        else
        {
                g_error ("unable to parse bookmarks file: %s", filename);
                return NULL;
        }
}

/**
 * Recursive XBEL node import.
 */
static BookmarkItem *
xbel_import_node (xmlNodePtr node)
{ 
	BookmarkItem *item = NULL;
	xmlNodePtr node2;
        g_return_val_if_fail (node != NULL, NULL);
	
        if (!strcmp (node->name, "xbel"))
        {
		item = bookmarks_new_bookmark
			(BM_FOLDER, TRUE, "XBEL Bookmarks", NULL,
			 NULL, NULL, NULL);
                // TODO: extract date added and set it
		for (node2 = node->childs; node2 != NULL;
		     node2 = node2->next) {
			BookmarkItem *b = xbel_import_node (node2);
			if (b) { 
				item->list = g_list_append (item->list, b);
				b->parent = item;
			}
		}
        } else if (!strcmp (node->name, "folder")) {
		gchar *title = getChildValue (node, "title");
		item = bookmarks_new_bookmark (BM_FOLDER, TRUE, title, NULL,
					       NULL, NULL, NULL);
		g_free (title);
                // Now apply the attributes from the XBEL
                xbel_import_populate_bookmark (node, item);
                for (node2 = node->childs; node2 != NULL;
		     node2 = node2->next) {
			BookmarkItem *b = xbel_import_node (node2);
			if (b) {
				item->list = g_list_append 
				       (item->list, b);
				b->parent = item;
			}
		}
	} else if (!strcmp (node->name, "bookmark")) {
		// Here, node is a <bookmark> element and item points
                // to the relevant parent folder in the bookmark tree.
                gchar *title = getChildValue (node, "title");
                gchar *url = xmlGetRawProp (node, "href");
		item = bookmarks_new_bookmark (BM_SITE, TRUE, title, url,
					       NULL, NULL, NULL);
                g_free (title); 
		xmlFree (url);
		xbel_import_populate_bookmark (node, item);
        } else if (!strcmp (node->name, "alias")) {
		// FIXME
                g_message ("TODO: alias import");
        } else if (!strcmp (node->name, "separator")) {
		item  = bookmarks_new_bookmark (BM_SEPARATOR, TRUE, NULL, NULL,
						NULL, NULL, NULL);
	} else {
		// Unknown 
        }
	return item;
}

