/* -*- mode:C; c-file-style:"linux"; tab-width:8; -*- */
/* 
 *  Dates - An electronic calendar optimised for embedded devices.
 *
 *  Principal author	: Chris Lord <chris@o-hand.com>
 *  Maemo port		: Tomas Frydrych <tf@o-hand.com>
 *
 *  Copyright (c) 2005 - 2006 OpenedHand Ltd - http://o-hand.com
 *
 *  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.
 *
 */

#include <string.h>
#include <math.h>
#include <libecal/e-cal-time-util.h>
#include <libical/icaltime.h>
#include <gconf/gconf-client.h>

#include "dates_types.h"
#include "dates_platform.h"
#include "dates_callbacks.h"

#include "gconf-bridge.h"

static GtkWidget *
create_dates_view ()
{
	GtkWidget *widget;
	
	widget = dates_view_new ();
	/* TODO: This is temporary until the widget sizes itself properly */
	gtk_widget_set_size_request (widget, 240, 240);
	gtk_widget_show (widget);
	
	return widget;
}

static void
dates_load_calendars (DatesData *d)
{
	ESourceList *source_list;
	GSList *group_list;
	gboolean remote_present = FALSE;
	GError *error = NULL;

	/* Load calendars */
	source_list = e_source_list_new_for_gconf_default (CALENDAR_GCONF_SOURCES);

	if (!source_list) {
		g_error ("Error loading ESource list");
	}

	group_list = e_source_list_peek_groups (source_list);

	/* 
	 * If the group list is completely empty then we are in a totally
	 * fresh state. Create the local group and a source for it.
	 */
	if (group_list == NULL)
	{
		ESourceGroup *local_group;
		ESource *system_source;
		ECal *system_ecal;
		const gchar *uri = NULL;
		gchar *base_uri = NULL;
		gchar *path = NULL;
		gchar *dirname = NULL;

		/* If no groups then we must be on a first run */

		/* First we must create a calendar so that we can get it's
		 * uri so we can know the base uri for group.
		 */
		
		system_ecal = e_cal_new_system_calendar ();

		if (!e_cal_open (system_ecal, FALSE, &error))
		{
			g_error ("Error creating system calendar: %s", error->message);
		}

		/* Extract the URI from the newly created calendar */
		uri = e_cal_get_uri (system_ecal);

		if (uri == NULL)
			g_error ("Error getting the uri for the new system calendar");

		/* Create a default "On This Computer" Group. */
		path = g_filename_from_uri (uri, NULL, NULL);
		dirname = g_path_get_dirname (path);

		base_uri = g_strdup_printf ("file://%s", dirname);

		local_group = e_source_group_new ("On This Computer", base_uri);

		g_free (path);
		g_free (dirname);
		g_free (base_uri);

		/* Now create a default source for it */
		system_source = e_source_new ("Personal", "system");

		/* Default Evolution colour */
		e_source_set_color (system_source, 0xBECEDD);

		/* Set the group for the source and put it in the group */
		e_source_set_group (system_source, local_group);
		e_source_group_add_source (local_group, system_source, 0);

		/* Put the group for local things into the list */
		if (!e_source_list_add_group (source_list, local_group, 0))
			g_warning ("Error adding group");

		/* The system calendar client is no longer needed */
		g_object_unref (system_ecal);
		d->first_load = TRUE;
	}

	/* Check the available groups for a "On The Web" one */
	for (GSList *group_list_it = group_list; group_list_it != NULL;
			group_list_it = g_slist_next (group_list_it))
	{
		ESourceGroup *group;

		group = (ESourceGroup *)group_list_it->data;

		if (strncmp(e_source_group_peek_base_uri (group), "webcal://", 9) == 0)
		{
			remote_present = TRUE;
			break;
		}
	}

	/* If not present then create it */
	if (!remote_present)
	{
		ESourceGroup *remote_group;
		
		remote_group = e_source_group_new ("On The Web", "webcal://");

		if (!e_source_list_add_group (source_list, remote_group, 0))
			g_warning ("Error adding group");
	}

	/* Make sure that the sources point back to their group */
	for (GSList *group_list_it = group_list; group_list_it != NULL;
			group_list_it = g_slist_next (group_list_it))
	{
		ESourceGroup *group;
		GSList *source_list;

		group = (ESourceGroup *)group_list_it->data;

		source_list = e_source_group_peek_sources (group);

		for (GSList *source_list_it = source_list; source_list_it != NULL;
				source_list_it = g_slist_next (source_list_it))
		{
			ESource *source;
			source = (ESource *)source_list_it->data;

			/* Set the source's group */
			e_source_set_group (source, group);
		}
	}

	/* 
	 * Ensure that the local group has a base_uri starting with file://
	 * (previous versions had it starting with file:/ which is incorrect)
	 */
	for (GSList *group_list_it = group_list; group_list_it != NULL;
			group_list_it = g_slist_next (group_list_it))
	{
		ESourceGroup *group;
		const gchar *base_uri;;

		group = (ESourceGroup *)group_list_it->data;

		base_uri = e_source_group_peek_base_uri (group);

		if (strncmp(base_uri, "file:/", 6) == 0 && 
				strncmp (base_uri, "file://", 7) != 0)
		{
			gchar *new_uri = NULL;
			gchar *path = g_filename_from_uri (base_uri, NULL, NULL);
			GSList *source_list = NULL;

			new_uri = g_strdup_printf ("file://%s", path);
			e_source_group_set_base_uri (group, new_uri);

			/* 
			 * Also ensure that the sources contained within this
			 * group have an appropriate uri setup. Removing the
			 * absolute uri in favour of a relative one.
			 */
			source_list = e_source_group_peek_sources (group);

			for (GSList *source_list_it = source_list; source_list_it != NULL;
					source_list_it = g_slist_next (source_list_it))
			{
				ESource *source = (ESource *)source_list_it->data;

				if (g_str_equal (e_source_peek_relative_uri (source), ""))
				{
					const gchar *uri = e_source_peek_absolute_uri (source);
					gchar *path = g_filename_from_uri (uri, NULL, NULL);
					gchar *base_name = g_path_get_basename (path);

					e_source_set_absolute_uri (source, NULL);
					e_source_set_relative_uri (source, base_name);

					g_free (base_name);
					g_free (path);
				}
			}

			g_free (path);
			g_free (new_uri);
		}
	}

	/* Sync the list with version in gconf */
	if (!e_source_list_sync (source_list, &error))
	{
		g_warning ("Error syncing ESourceList: %s",
				   error->message);
		g_error_free (error);
	}

	dates_autoselect_calendars (d, source_list);

	g_signal_connect (G_OBJECT (source_list), "changed",
		G_CALLBACK (dates_sources_changed_cb), d);
	dates_update_calendars (source_list, d);

	d->source_list = source_list;
}


/* Thanks to GnuCash to find out how to do this */
static void
dates_autoconnect (const gchar *handler_name, GObject *object,
			const gchar *signal_name, const gchar *signal_data,
			GObject *connect_object, gboolean after,
			gpointer user_data)
{
	static GModule *symbols = NULL;
	GCallback func;
	GCallback *pfunc = &func;
	
	if (!symbols) {
		symbols = g_module_open(NULL, 0);
	}
	
	if (!g_module_symbol (symbols, handler_name, (gpointer *)pfunc)) {
		g_warning ("Handler '%s' not found.", handler_name);
		return;
	}

	if (connect_object) {
		if (after)
			g_signal_connect_object (object, signal_name,
				func, connect_object, G_CONNECT_AFTER);
		else
			g_signal_connect_object (object, signal_name,
				func, connect_object, 0);
	} else {
		if (after)
			g_signal_connect_after(object, signal_name,
				func, user_data);
		else
			g_signal_connect(object, signal_name, func, user_data);
	}
}

static gboolean
dates_selected_calendars_filter_func (GtkTreeModel *model,
				      GtkTreePath *path,
				      GtkTreeIter *iter,
				      gpointer data)
{
	GSList **list;
	gboolean selected;
	ECal *ecal;
	gchar *uid;

	gtk_tree_model_get (model, iter,
			COL_SELECTED, &selected,
			COL_CALPTR, &ecal, -1);
	uid = (gchar *)e_source_peek_uid (e_cal_get_source (ecal));
	list = (GSList **)data;
	if (selected) *list = g_slist_prepend (*list, uid);

	return FALSE; /* foreach goes on */
}

static gboolean
dates_sel_or_read_only_filter_func (GtkTreeModel *model,
				    GtkTreeIter *iter,
				    gpointer user_data)
{
	gboolean not_read_only, selected;

	gtk_tree_model_get (model, iter,
			COL_SELECTED, &selected,
			COL_NOTREADONLY, &not_read_only, -1);
	
	if ((!not_read_only) || (!selected))
		return FALSE;
	else
		return TRUE;
}

static void
dates_toggle_renderer_toggled_cb (GtkCellRendererToggle *renderer,
				  gchar *path_str,
				  gpointer userdata)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	gboolean toggle;
	GSList *selected;

	model = GTK_TREE_MODEL (userdata);
	gtk_tree_model_get_iter_from_string (model, &iter, path_str);
	gtk_tree_model_get (model, &iter,
			    COL_SELECTED, &toggle, -1);
	gtk_list_store_set (GTK_LIST_STORE (model), &iter,
			    COL_SELECTED, !toggle, -1);

	selected = NULL;
	gtk_tree_model_foreach (model, dates_selected_calendars_filter_func,
				&selected);

	/* Changing the GConf selected calendars string will cause the
	 * calendars to be updated (we're listening to this GConf value).
	 */
	gconf_client_set_list (gconf_client_get_default (),
		CALENDAR_GCONF_SELECTED, GCONF_VALUE_STRING,
		selected, NULL);
}

static void
connect_callbacks_view (DatesData * d)
{
	g_signal_connect (G_OBJECT (d->view), "date_changed",
			  G_CALLBACK (dates_date_changed_cb), d);
	g_signal_connect (G_OBJECT (d->view), "event_selected",
			  G_CALLBACK (dates_event_selected_cb), d);
	g_signal_connect (G_OBJECT (d->view), "event_moved",
			  G_CALLBACK (dates_event_moved_cb), d);
	g_signal_connect (G_OBJECT (d->view), "event_sized",
			  G_CALLBACK (dates_event_sized_cb), d);
	g_signal_connect (G_OBJECT (d->view), "event_activated",
			  G_CALLBACK (dates_edit_cb), d);
	g_signal_connect (G_OBJECT (d->view), "button_press_event",
			  G_CALLBACK (dates_button_press_cb), d);
	g_signal_connect (G_OBJECT (d->view), "key_press_event",
			  G_CALLBACK (dates_cal_key_press_cb), d);
	g_signal_connect (G_OBJECT (d->view), "ical_drop",
			  G_CALLBACK (dates_ical_drop_cb), d);
}

int
main (int argc, char **argv)
{
	GtkWidget *widget;
	DatesData data;
	GtkTreeModel *filter;
	GConfClient *client;
	GConfBridge *bridge;
	GOptionContext *context;
	static gint plug = 0;
#ifdef DEBUG
	const gchar *debug;
#endif
	
	static GOptionEntry entries[] = {
		{ "plug", 'p', 0, G_OPTION_ARG_INT, &plug,
			"Socket ID of an XEmbed socket to plug into", NULL },
		{ NULL }
	};

	memset (&data, 0, sizeof (DatesData));
	
	/* Initialise the i18n support code */
	bindtextdomain (GETTEXT_PACKAGE, DATES_LOCALE_DIR);;
	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
	textdomain (GETTEXT_PACKAGE);

	context = g_option_context_new (" - A light-weight, zooming calendar");
	g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
	g_option_context_add_group (context, gtk_get_option_group (TRUE));
	g_option_context_parse (context, &argc, &argv, NULL);

	/* NOTE: No need to call gtk_init after the g_option_context_parse */
	/* gtk_init (&argc, &argv); */

#ifdef DEBUG
	debug = g_getenv ("DATES_DEBUG");
	if (debug)
		data.debug = g_parse_debug_string (debug,
			dates_debug_keys, G_N_ELEMENTS (dates_debug_keys));
#endif

	
	/* Set critical errors to close application */
	/*g_log_set_always_fatal (G_LOG_LEVEL_CRITICAL);*/

	data.view = DATES_VIEW (create_dates_view ());
	g_assert(data.view);

	connect_callbacks_view (&data);

	gtk_window_set_default_icon_name ("dates");
	dates_platform_init (&data);
	dates_platform_create_ui (&data);
	
	client = gconf_client_get_default ();
	if (!client)
		g_error ("Failed to retrieve default gconf client");
	data.zoom    = gconf_client_get_int (client, DATES_GCONF_ZOOM, NULL);
	data.comp    = NULL;
	data.widgets = NULL;
	data.waiting = NONE;

	if (data.zoom == 0) {
		data.zoom = 3;
		/* OMOKO dates has no zoom button */
		/* gtk_widget_set_sensitive (data.TBZoomIn, FALSE);*/
	} else if (data.zoom == 3) {
		/* OMOKO dates has no zoom button */
		/* gtk_widget_set_sensitive (data.TBZoomIn, FALSE); */

	} else if (data.zoom == 16) {
		/* OMOKO dates has no zoom button */
		/* gtk_widget_set_sensitive (data.TBZoomOut, FALSE); */
	}

	gtk_widget_set_sensitive (data.TBDelete, FALSE);
	
        /* Perhaps in a future version? */
        /* dates_view_set_use_list (data.view, TRUE);*/

	dates_zoom_change (data.zoom, data.view);

	/* Load calendars */
	data.cal_list_store = gtk_list_store_new (COL_LAST, G_TYPE_STRING,
		G_TYPE_POINTER, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);

	/* TODO: UI for multiple calendars */
	{
		GtkTreeView *treeview;
		GtkCellRenderer *toggle_renderer, *label_renderer;
		GtkTreeViewColumn *toggle_column, *label_column;

		treeview = GTK_TREE_VIEW (data.cal_tree_view);
		gtk_tree_view_set_model (treeview,
			GTK_TREE_MODEL (data.cal_list_store));

		toggle_renderer = gtk_cell_renderer_toggle_new ();
		g_signal_connect (G_OBJECT (toggle_renderer), "toggled",
			G_CALLBACK (dates_toggle_renderer_toggled_cb),
			data.cal_list_store);
		toggle_column = gtk_tree_view_column_new_with_attributes (
			_("Selected"), toggle_renderer,
			"active", COL_SELECTED, NULL);
		gtk_tree_view_append_column (treeview, toggle_column);

		label_renderer = gtk_cell_renderer_text_new ();
		label_column = gtk_tree_view_column_new_with_attributes (
			_("Calendar"), label_renderer,
			"text", COL_CALNAME, NULL);
		gtk_tree_view_append_column (treeview, label_column);
	}


	data.first_load = FALSE;
	dates_load_calendars (&data);

	{
		GtkTreeSelection *selection;
		GtkTreeIter iter;

		/* Select the first item in the list */
		selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (data.cal_tree_view));
		gtk_tree_model_get_iter_first (GTK_TREE_MODEL (data.cal_list_store), &iter);
		gtk_tree_selection_select_iter (selection, &iter);
	}

	/* Set transient parent for dialogs */
	if (GTK_IS_WINDOW (data.calendars_dialog))
		gtk_window_set_transient_for (
			GTK_WINDOW (data.calendars_dialog),
			GTK_WINDOW (data.main_window));
	if (GTK_IS_WINDOW (data.details_dialog))
		gtk_window_set_transient_for (
			GTK_WINDOW (data.details_dialog),
			GTK_WINDOW (data.main_window));
	if (GTK_IS_WINDOW (data.time_dialog) &&
	    GTK_IS_WINDOW (data.details_dialog))
		gtk_window_set_transient_for (
			GTK_WINDOW (data.time_dialog),
			GTK_WINDOW (data.details_dialog));
	if (GTK_IS_WINDOW (data.exceptions_dialog) &&
	    GTK_IS_WINDOW (data.details_dialog))
		gtk_window_set_transient_for (
			GTK_WINDOW (data.exceptions_dialog),
			GTK_WINDOW (data.details_dialog));

	/* Create tree model filter to filter out read-only calendars */
	filter = gtk_tree_model_filter_new (
		GTK_TREE_MODEL (data.cal_list_store), NULL);
	gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
		dates_sel_or_read_only_filter_func, NULL, NULL);
	
	/* Set tree model for details dialog calendar selector */
	widget = data.details_calendar_combobox;
	gtk_combo_box_set_model (GTK_COMBO_BOX (widget), filter);
	
	dates_date_changed_cb (data.view, &data);

	/* Bind widgets to gconf properties */
	bridge = gconf_bridge_get ();
	gconf_bridge_bind_window (bridge, DATES_GCONF_WINDOW, GTK_WINDOW (
		data.main_window), TRUE, TRUE);
/*	gconf_bridge_bind_property (bridge, DATES_GCONF_PREFIX,
		G_OBJECT (data.view), "week_start");
	gconf_bridge_bind_property (bridge, DATES_GCONF_PREFIX,
		G_OBJECT (data.view), "use_24h");*/

	/* Calendar selection signals */
	gconf_client_add_dir (client, CALENDAR_GCONF_PREFIX,
		GCONF_CLIENT_PRELOAD_NONE, NULL);
	gconf_client_notify_add (client, CALENDAR_GCONF_SELECTED,
		dates_gconf_selected_cb, &data, NULL, NULL);
	
	widget = data.main_window;

	dates_platform_pre_show (&data);
	
	if (plug > 0) {
		GtkWidget *plug_widget;
		GtkWidget *contents;
		
#ifdef DEBUG
		if (data.debug & DATES_DEBUG_XEMBED)
			g_debug ("Plugging into socket %d", plug);
#endif
		plug_widget = gtk_plug_new (plug);
		contents = g_object_ref (gtk_bin_get_child (GTK_BIN (widget)));
		gtk_container_remove (GTK_CONTAINER (widget), contents);
		gtk_container_add (GTK_CONTAINER (plug_widget), contents);
		g_object_unref (contents);
		g_signal_connect (G_OBJECT (plug_widget), "destroy",
				  G_CALLBACK (gtk_main_quit), NULL);
		gtk_widget_hide (data.main_menu);
		gtk_widget_show (data.header_eventbox);
		gtk_widget_show (plug_widget);
	} else {
		gtk_widget_show (data.main_window);
	}

	gtk_main ();

	/* clean up */
	g_option_context_free (context);
	
	return 0;
}

