/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 2002 CodeFactory AB
 * Copyright (C) 2002 Mikael Hallendal <micke@imendio.com>
 * Copyright (C) 2004-2006 Imendio AB
 *
 * 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 of the
 * License, 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 <config.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <gtk/gtkmain.h>
#include <gdk/gdkx.h>
#include <gconf/gconf-client.h>

#define WNCK_I_KNOW_THIS_IS_UNSTABLE
#include <libwnck/libwnck.h>

#include "dh-gecko-utils.h"
#include "dh-window.h"
#include "dh-link.h"
#include "dh-parser.h"
#include "dh-preferences.h"
#include "dh-base.h"

#define d(x) 

struct _DhBasePriv {
	GSList      *windows;
	GNode       *book_tree;
	GList       *keywords;
	GHashTable  *books;
	GConfClient *gconf_client;
};

static void base_init                (DhBase      *base);
static void base_class_init          (DhBaseClass *klass);
static void base_window_finalized_cb (DhBase      *base,
				      DhWindow    *window);
static void base_init_books          (DhBase      *base);
static void base_add_books           (DhBase      *base,
				      const gchar *directory);


static GObjectClass *parent_class;
static DhBase       *base_instance;

GType
dh_base_get_type (void)
{
	static GType type = 0;

	if (!type) {
		static const GTypeInfo info = {
			sizeof (DhBaseClass),
			NULL,
			NULL,
			(GClassInitFunc) base_class_init,
			NULL,
			NULL,
			sizeof (DhBase),
			0,
			(GInstanceInitFunc) base_init,
		};

		type = g_type_register_static (G_TYPE_OBJECT, "DhBase",
					       &info, 0);
	}

	return type;
}

static void
base_init (DhBase *base)
{
        DhBasePriv *priv;
	int         n_screens, i;

        priv = g_new0 (DhBasePriv, 1);
        base->priv = priv;

	priv->windows   = NULL;
	priv->book_tree = g_node_new (NULL);
	priv->keywords  = NULL;
	priv->books     = g_hash_table_new_full (g_str_hash, g_str_equal,
						 g_free, g_free);

	/* For some reason, libwnck doesn't seem to update its list of
	 * workspaces etc if we don't do this.
	 */
	n_screens = gdk_display_get_n_screens (gdk_display_get_default ());
	for (i = 0; i < n_screens; i++) {
		WnckScreen *screen;

		screen = wnck_screen_get (i);
	}

	priv->gconf_client = gconf_client_get_default ();
	gconf_client_add_dir (priv->gconf_client,
			      GCONF_PATH,
			      GCONF_CLIENT_PRELOAD_ONELEVEL,
			      NULL);
}

static void
dh_base_finalize (GObject *object)
{
        DhBasePriv *priv;

	priv = DH_BASE (object)->priv;

	g_object_unref (priv->gconf_client);
	
	dh_gecko_utils_shutdown ();

	parent_class->finalize (object);
}

static void
base_class_init (DhBaseClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	parent_class = g_type_class_peek_parent (klass);

	object_class->finalize = dh_base_finalize;
}

static void
base_window_finalized_cb (DhBase *base, DhWindow *window)
{
	DhBasePriv *priv;

	priv = base->priv;

	priv->windows = g_slist_remove (priv->windows, window);

	if (g_slist_length (priv->windows) == 0) {
		gtk_main_quit ();
	}
}

static gint
book_sort_func (gconstpointer a,
		gconstpointer b)
{
	DhLink      *link_a, *link_b;
	const gchar *name_a, *name_b;

	link_a = ((GNode *) a)->data;
	link_b = ((GNode *) b)->data;

	name_a = link_a->name;
	if (!name_a) {
		name_a = "";
	}

	name_b = link_b->name;
	if (!name_b) {
		name_b = "";
	}

	if (g_ascii_strncasecmp (name_a, "the ", 4) == 0) {
		name_a += 4;
	}
	if (g_ascii_strncasecmp (name_b, "the ", 4) == 0) {
		name_b += 4;
	}

	return g_utf8_collate (name_a, name_b);
}

static void
base_sort_books (DhBase *base)
{
	DhBasePriv *priv;
	GNode      *n;
	DhLink     *link;
	GList      *list = NULL, *l;

	priv = base->priv;

	if (base->priv->book_tree) {
		n = base->priv->book_tree->children;

		while (n) {
			list = g_list_prepend (list, n);
			n = n->next;
		}

		list = g_list_sort (list, book_sort_func);
	}

	for (l = list; l; l = l->next) {
		n = l->data;
		link = n->data;
		g_node_unlink (n);
	}

	for (l = list; l; l = l->next) {
		n = l->data;

		g_node_append (base->priv->book_tree, n);
	}

	g_list_free (list);
}

static void
base_add_books_in_data_dir (DhBase *base, const gchar *data_dir)
{
	gchar *dir;
	
	dir = g_build_filename (data_dir, "gtk-doc", "html", NULL);
	base_add_books (base, dir);
	g_free (dir);

	dir = g_build_filename (data_dir, "devhelp", "books", NULL);
	base_add_books (base, dir);
	g_free (dir);
}

static void
base_init_books (DhBase *base)
{
	const gchar * const * system_dirs;

	base_add_books_in_data_dir (base, g_get_user_data_dir ());

	system_dirs = g_get_system_data_dirs ();
	while (*system_dirs) {
		base_add_books_in_data_dir (base, *system_dirs);
		system_dirs++;
	}

	base_sort_books (base);
}

static gchar *
base_get_book_path (DhBase      *base,
		    const gchar *base_path,
		    const gchar *name,
		    const gchar *suffix)
{
	gchar *tmp;
	gchar *book_path;
	
	tmp = g_build_filename (base_path, name, name, NULL);
	book_path = g_strconcat (tmp, ".", suffix, NULL);
	g_free (tmp);

	if (!g_file_test (book_path, G_FILE_TEST_EXISTS)) {
		g_free (book_path);
		return NULL;
	}

	return book_path;
}	

static void
base_add_books (DhBase *base, const gchar *path)
{
	DhBasePriv  *priv;
	GDir        *dir;
	const gchar *name;

	priv = base->priv;

	d(g_print ("Adding books from %s\n", path));

	dir = g_dir_open (path, 0, NULL);
	if (!dir) {
		return;
	}

	while ((name = g_dir_read_name (dir)) != NULL) {
		gchar  *book_path;
		GError *error = NULL;

		if (g_hash_table_lookup (priv->books, name)) {
			continue;
		}

		book_path = base_get_book_path (base, path, name, "devhelp2");
		if (!book_path) {
			book_path = base_get_book_path (base, path, name, "devhelp2.gz");
		}
		if (!book_path) {
			book_path = base_get_book_path (base, path, name, "devhelp");
		}
		if (!book_path) {
			book_path = base_get_book_path (base, path, name, "devhelp.gz");
		}
		
		if (!book_path) {
			continue;
		}

		if (!dh_parser_read_file  (book_path,
					   priv->book_tree,
					   &priv->keywords,
					   &error)) {
			g_warning ("Failed to read '%s': %s",
				   book_path, error->message);
			g_clear_error (&error);
		} else {
			g_hash_table_insert (priv->books,
					     g_strdup (name),
					     book_path);
			d(g_print ("Found book: '%s'\n", book_path));
		}

		g_free (book_path);
	}

	g_dir_close (dir);
}

DhBase *
dh_base_get (void)
{
	if (!base_instance) {
		dh_gecko_utils_init ();
		
		base_instance = g_object_new (DH_TYPE_BASE, NULL);		

		base_init_books (base_instance);
		dh_preferences_init ();
	}
	
	return base_instance;
}

DhBase *
dh_base_new (void)
{
	if (base_instance) {
		g_error ("You can only have one DhBase instance.");
	}

	return dh_base_get ();
}

GtkWidget *
dh_base_new_window (DhBase *base)
{
	DhBasePriv *priv;
	GtkWidget  *window;

        g_return_val_if_fail (DH_IS_BASE (base), NULL);

	priv = base->priv;

        window = dh_window_new (base);

	priv->windows = g_slist_prepend (priv->windows, window);

	g_object_weak_ref (G_OBJECT (window),
			   (GWeakNotify) base_window_finalized_cb,
			   base);

	return window;
}

GNode *
dh_base_get_book_tree (DhBase *base)
{
	g_return_val_if_fail (DH_IS_BASE (base), NULL);

	return base->priv->book_tree;
}

GList *
dh_base_get_keywords (DhBase *base)
{
	g_return_val_if_fail (DH_IS_BASE (base), NULL);

	return base->priv->keywords;
}

GSList *
dh_base_get_windows (DhBase *base)
{
	DhBasePriv *priv;

	g_return_val_if_fail (DH_IS_BASE (base), NULL);

	priv = base->priv;

	return priv->windows;
}

GtkWidget *
dh_base_get_window_on_current_workspace (DhBase *base)
{
	DhBasePriv    *priv;
	WnckWorkspace *workspace;
	WnckScreen    *screen;
	GtkWidget     *window;
	GList         *windows, *w;
	GSList        *l;
	gulong         xid;
	pid_t          pid;

	g_return_val_if_fail (DH_IS_BASE (base), NULL);

	priv = base->priv;

	if (!priv->windows) {
		return NULL;
	}

	screen = wnck_screen_get (0);
	if (!screen) {
		return NULL;
	}

	workspace = wnck_screen_get_active_workspace (screen);
	if (!workspace) {
		return NULL;
	}

	xid = 0;
	pid = getpid ();

	/* Use _stacked so we can use the one on top. */
	windows = wnck_screen_get_windows_stacked (screen);
	windows = g_list_last (windows);

	for (w = windows; w; w = w->prev) {
		if (wnck_window_is_on_workspace (w->data, workspace) &&
		    wnck_window_get_pid (w->data) == pid) {
			xid = wnck_window_get_xid (w->data);
			break;
		}
	}

	if (!xid) {
		return NULL;
	}

	/* Return the first matching window we have. */
	for (l = priv->windows; l; l = l->next) {
		window = l->data;

		if (GDK_WINDOW_XID (window->window) == xid) {
			return window;
		}
	}

	return NULL;
}

GConfClient *
dh_base_get_gconf_client (DhBase *base)
{
	DhBasePriv *priv;

	g_return_val_if_fail (DH_IS_BASE (base), NULL);
	
	priv = base->priv;
	
	return priv->gconf_client;
}


syntax highlighted by Code2HTML, v. 0.9.1