/* -*- mode:C; tab-width:4; -*- */
/* 
 *  Author: 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 <libecal/e-cal.h>
#include <libecal/e-cal-time-util.h>
#include <libical/ical.h>
#include <libedataserver/e-data-server-util.h>

#include <gtk/gtk.h>
#include <glib/gi18n.h>

#include "dates_navigator.h"

struct _DatesNavigatorModelPrivate
{
	ECalView * cview;
	guint      format;
};

#define DATES_NAVIGATOR_MODEL_GET_PRIVATE(obj) \
            (G_TYPE_INSTANCE_GET_PRIVATE ((obj), DATES_TYPE_NAVIGATOR_MODEL, \
             DatesNavigatorModelPrivate))

G_DEFINE_TYPE (DatesNavigatorModel, dates_navigator_model,
			   GTK_TYPE_TREE_MODEL_FILTER);

static void
dates_navigator_model_finalize (GObject *gobject)
{
	DatesNavigatorModelPrivate *priv = DATES_NAVIGATOR_MODEL (gobject)->priv;

	GtkWidget * store = GTK_WIDGET (gtk_tree_model_filter_get_model (
										GTK_TREE_MODEL_FILTER (gobject)));
	
	if (store)
		gtk_widget_unref (store);

	if (priv->cview)
		g_object_unref (priv->cview);
	
	G_OBJECT_CLASS (dates_navigator_model_parent_class)->finalize (gobject);
}

static GObject *
dates_navigator_model_constructor (GType type,
							 guint n_construct_params,
							 GObjectConstructParam *construct_params)
{
	GObject *gobject;
	GtkTreeModelFilter * filter;
  
	gobject =
		G_OBJECT_CLASS (dates_navigator_model_parent_class)->constructor (
			type,
			n_construct_params,
			construct_params);

	filter = GTK_TREE_MODEL_FILTER (gobject);

	g_assert (filter);
	g_assert (gtk_tree_model_filter_get_model (filter));
  
	return gobject;
}

static void
dates_navigator_model_class_init (DatesNavigatorModelClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
	gobject_class->finalize = dates_navigator_model_finalize;
	gobject_class->constructor = dates_navigator_model_constructor;

	g_type_class_add_private (klass, sizeof (DatesNavigatorModelPrivate));
}

static void
dates_navigator_model_init (DatesNavigatorModel *nav)
{
	DatesNavigatorModelPrivate *priv = DATES_NAVIGATOR_MODEL_GET_PRIVATE (nav);
	nav->priv = priv;
	
	priv->cview = NULL;
	priv->format = DN_DateAndTime;
}

GtkWidget *
dates_navigator_model_new ()
{
	GtkListStore * store = gtk_list_store_new (DN_n_columns,
											   G_TYPE_STRING,
											   G_TYPE_STRING,
											   G_TYPE_STRING,
											   G_TYPE_STRING);
	
	return g_object_new (DATES_TYPE_NAVIGATOR_MODEL,
						 "child-model", store,
						 NULL);
}

static void
event_time (icalcomponent * icalcomp, gchar * str, size_t len,
			DNTimeFormat format)
{
	struct icaltimetype istart, iend;
	struct tm tm_start, tm_end;
	
	istart = icalcomponent_get_dtstart (icalcomp);
	iend   = icalcomponent_get_dtend (icalcomp);

	tm_start = icaltimetype_to_tm (&istart);
	tm_end   = icaltimetype_to_tm (&iend);

	gint offset = 0;
	
	if (format == DN_TimeOnly)
	{
		offset = e_utf8_strftime (str, len, "%R", &tm_start);
		str += offset;

		*str++ = ' ';
		*str++ = '-';
		*str++ = ' ';
		
		len -= (offset + 3);
		g_assert (len > 0);
		
		e_utf8_strftime (str, len, "%R", &tm_end);
	}
	else
	{
		offset = e_utf8_strftime (str, len, "%x", &tm_start);
		str += offset;
		*str++ = ' ';
		*str++ = ' ';
		len -= (offset + 2);
		g_assert (len > 0);
		
		offset = e_utf8_strftime (str, len, "%R", &tm_start);
		str += offset;
		*str++ = '-';
		len -= (offset + 1);
		g_assert (len > 0);

		e_utf8_strftime (str, len, "%R", &tm_end);
	}
}

static gboolean
find_item (GtkListStore * store, const char * uri_uid, GtkTreeIter * iter)
{
	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL(store), iter))
	{
		gboolean done = FALSE;
		
		do
		{
			char * uid = NULL;
			gtk_tree_model_get (GTK_TREE_MODEL (store),
								iter, DN_Uid, &uid, -1);
			
			if (uid && !strcmp (uid, uri_uid))
				done = TRUE;
			
			g_free (uid);
			
			if (done)
				return TRUE;
		}
		while(gtk_tree_model_iter_next(GTK_TREE_MODEL(store),
									   iter));
	}

	return FALSE;
}

static void
ecal_objects_changed (ECalView * cview, GList *objects,
					  DatesNavigatorModel * nav)
{
	ECal *ecal = e_cal_view_get_client (cview);
	GtkListStore * store =
		GTK_LIST_STORE (gtk_tree_model_filter_get_model (
							GTK_TREE_MODEL_FILTER (nav)));

	for (; objects; objects = objects->next)
	{
		const char *uid = icalcomponent_get_uid (objects->data);
		gchar *uri_uid;
		GtkTreeIter iter;
		const gchar * summary;
		gchar * s = NULL;
		gchar time[100];
		gchar * folded = NULL;
		
		if (!uid)
			continue;
			
		uri_uid = g_strconcat (e_cal_get_uri (ecal), uid, NULL);

		if (!find_item (store, uri_uid, &iter))
			gtk_list_store_append (store, &iter);

		summary = icalcomponent_get_summary (objects->data);

		if (summary)
			folded = g_utf8_casefold (summary, -1);
		
		/* use only first 15 chars of the summary */
		if (summary && g_utf8_strlen (summary, -1) > 15)
		{
			s = g_strdup (summary);
			gchar * p = g_utf8_offset_to_pointer (s, 15);
			*p = 0;
			summary = s;
		}
		
		event_time (objects->data, (gchar*)&time, sizeof(time),
					nav->priv->format);
		
		gtk_list_store_set (store, &iter,
							DN_Name, summary,
							DN_Time, time,
							DN_Uid, uri_uid,
							DN_NameFolded, folded,
							-1);
		g_free (uri_uid);
		g_free (s);
		g_free (folded);
	}
}

static void
ecal_objects_removed (ECalView *ecalview, GList *uids,
					  DatesNavigatorModel * nav)
{
	ECal *ecal = e_cal_view_get_client (ecalview);
	GtkTreeIter iter;
	GtkListStore * store =
		GTK_LIST_STORE (gtk_tree_model_filter_get_model (
							GTK_TREE_MODEL_FILTER (nav)));

	for (; uids; uids = uids->next)
	{
		gchar *uri_uid;
		
#ifdef HAVE_CID_TYPE
		ECalComponentId *id = uids->data;
		uri_uid = g_strconcat (e_cal_get_uri (ecal), id->uid, NULL);
#else
		uri_uid = g_strconcat (e_cal_get_uri (ecal), uids->data, NULL);
#endif
		if (find_item (store, uri_uid, &iter))
			gtk_list_store_remove (store, &iter);

		g_free (uri_uid);
	}
}

/*
 * Populate the navigator with events from given calendar matching query,
 * using the specified format
 */
void
dates_navigator_model_populate (DatesNavigatorModel * nav, ECal * ecal,
								const gchar * query, DNTimeFormat format)
{
	GError * error = NULL;
	GtkListStore * store =
		GTK_LIST_STORE (gtk_tree_model_filter_get_model (
							GTK_TREE_MODEL_FILTER (nav)));

	g_return_if_fail (store);
	
	if (nav->priv->cview)
	{
		g_object_unref (nav->priv->cview);
		nav->priv->cview = NULL;
	}

	gtk_list_store_clear  (store);

	e_cal_get_query (ecal, query, &nav->priv->cview, &error);
	
	if (error)
	{
		g_warning ("%s: %s (%s)", __FUNCTION__, error->message, query);
		g_error_free (error);
		return;
	}

	/* Attach signals and start view */
	g_signal_connect (nav->priv->cview, "objects-added",
					  G_CALLBACK (ecal_objects_changed), nav);
	g_signal_connect (nav->priv->cview, "objects-modified",
					  G_CALLBACK (ecal_objects_changed), nav);
	g_signal_connect (nav->priv->cview, "objects-removed",
					  G_CALLBACK (ecal_objects_removed), nav);

	nav->priv->format = format;
	
	e_cal_view_start (nav->priv->cview);
}


/*************************************************************************/
struct _DatesNavigatorViewPrivate
{
	DatesNavigatorModel * navigator;
};

#define DATES_NAVIGATOR_VIEW_GET_PRIVATE(obj) \
             (G_TYPE_INSTANCE_GET_PRIVATE ((obj), DATES_TYPE_NAVIGATOR_VIEW, \
             DatesNavigatorViewPrivate))

G_DEFINE_TYPE (DatesNavigatorView, dates_navigator_view, GTK_TYPE_TREE_VIEW);

static void
dates_navigator_view_finalize (GObject *gobject)
{
	DatesNavigatorViewPrivate *priv = DATES_NAVIGATOR_VIEW (gobject)->priv;

	if (priv->navigator)
		g_object_unref (priv->navigator);

	G_OBJECT_CLASS (dates_navigator_view_parent_class)->finalize (gobject);
}

static void
dates_navigator_view_class_init (DatesNavigatorViewClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
	gobject_class->finalize = dates_navigator_view_finalize;

	g_type_class_add_private (klass, sizeof (DatesNavigatorViewPrivate));
}

static void
dates_navigator_view_init (DatesNavigatorView *view)
{
	g_debug ("Initialising Navigator View");
	DatesNavigatorViewPrivate *priv = DATES_NAVIGATOR_VIEW_GET_PRIVATE (view);
	view->priv = priv;

	priv->navigator = DATES_NAVIGATOR_MODEL (dates_navigator_model_new ());
	g_assert (priv->navigator);
  
	gtk_tree_view_set_model (GTK_TREE_VIEW (view),
							 GTK_TREE_MODEL (priv->navigator));

    GtkCellRenderer* ren = gtk_cell_renderer_text_new ();
    GtkTreeViewColumn* col =
		gtk_tree_view_column_new_with_attributes(_("What"), ren, "text",
												 DN_Name, NULL);
	gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
	
	ren = gtk_cell_renderer_text_new ();
    col = gtk_tree_view_column_new_with_attributes(_("When"), ren, "text",
												   DN_Time, NULL);
	gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
}

GtkWidget *
dates_navigator_view_new ()
{
	return g_object_new (DATES_TYPE_NAVIGATOR_VIEW, NULL);
}

void
dates_navigator_view_set_visible_func (DatesNavigatorView * view,
									   GtkTreeModelFilterVisibleFunc func,
									   gpointer data,
									   GtkDestroyNotify destroy)
{
	if (!view->priv->navigator)
		return;

	GtkTreeModelFilter *filter = GTK_TREE_MODEL_FILTER (view->priv->navigator);

	gtk_tree_model_filter_set_visible_func (filter, func, data, destroy);
}

void
dates_navigator_view_refilter (DatesNavigatorView * view)
{
	if (!view->priv->navigator)
		return;

	GtkTreeModelFilter *filter = GTK_TREE_MODEL_FILTER (view->priv->navigator);
	gtk_tree_model_filter_refilter (filter);
}

void
dates_navigator_view_populate (DatesNavigatorView * view, ECal * ecal,
							   const gchar * query, DNTimeFormat format)
{
	if (!view->priv->navigator)
		return;

	dates_navigator_model_populate(view->priv->navigator, ecal, query, format);
}


/*************************************************************************/
struct _DatesNavigatorPrivate
{
	DatesNavigatorView * view;
	GtkEntry           * entry;
	GtkWidget          * search_bar;
	gboolean             search_mode;
};

#define DATES_NAVIGATOR_GET_PRIVATE(obj) \
             (G_TYPE_INSTANCE_GET_PRIVATE ((obj), DATES_TYPE_NAVIGATOR, \
             DatesNavigatorPrivate))

G_DEFINE_TYPE (DatesNavigator, dates_navigator, GTK_TYPE_VBOX);

static void
dates_navigator_class_init (DatesNavigatorClass *klass)
{
	g_type_class_add_private (klass, sizeof (DatesNavigatorPrivate));
}

static void
entry_changed_cb (GtkEntry *entry, gpointer data)
{
	DatesNavigator * nav = data;
	dates_navigator_refilter (nav);
}

static gboolean
is_row_visible_cb (GtkTreeModel * model, GtkTreeIter * iter, DatesNavigator *n)
{
	if (!n->priv->search_mode)
		return TRUE;
	
	gboolean retval = FALSE;
	
	if (!n || !n->priv->entry)
		return TRUE;
	
	gchar * what_lc = NULL;
	const gchar * etext = gtk_entry_get_text (n->priv->entry);
	gchar * etext_lc = NULL;
	
	if (!etext || !*etext)
		return TRUE;

	gtk_tree_model_get (model, iter, DN_NameFolded, &what_lc, -1);

	if (!what_lc)
		return FALSE;

	etext_lc = g_utf8_casefold (etext, -1);
	
	if (etext_lc && strstr (what_lc, etext_lc))
		retval = TRUE;
	
	g_free (what_lc);
	g_free (etext_lc);
	
	return retval;
}

void
close_button_clicked_cb (GtkToolButton *toolbutton, gpointer data)
{
	DatesNavigator * n = data;

	dates_navigator_show_search_bar (n, FALSE);
}

static void
dates_navigator_init (DatesNavigator *nav)
{
	g_debug ("Initialising Navigator");
	DatesNavigatorPrivate *priv = DATES_NAVIGATOR_GET_PRIVATE (nav);
	nav->priv = priv;

	priv->view = DATES_NAVIGATOR_VIEW (dates_navigator_view_new ());
	gtk_widget_show (GTK_WIDGET (priv->view));

	priv->entry = GTK_ENTRY (gtk_entry_new ());
	gtk_widget_show (GTK_WIDGET (priv->entry));

	priv->search_bar = gtk_hbox_new (FALSE, 0);
	gtk_widget_show (priv->search_bar);

	gtk_box_pack_start (GTK_BOX (priv->search_bar), GTK_WIDGET (priv->entry),
						TRUE, TRUE, 0);
	
	GtkToolItem * close_button = gtk_tool_button_new_from_stock ("gtk-close");
	gtk_widget_show (GTK_WIDGET (close_button));
	gtk_box_pack_start (GTK_BOX (priv->search_bar), GTK_WIDGET (close_button),
						FALSE, FALSE, 0);
	
	gtk_box_pack_start (GTK_BOX (nav), priv->search_bar,
						FALSE, FALSE, 0);
	gtk_box_pack_start (GTK_BOX (nav), GTK_WIDGET (priv->view),
						FALSE, FALSE, 0);

	dates_navigator_view_set_visible_func (priv->view,
										   (GtkTreeModelFilterVisibleFunc)
										   is_row_visible_cb, nav, NULL);

	g_signal_connect ((gpointer) priv->entry, "changed",
					  G_CALLBACK (entry_changed_cb), nav);
	g_signal_connect ((gpointer) close_button, "clicked",
					  G_CALLBACK (close_button_clicked_cb), nav);
}

GtkWidget *
dates_navigator_new ()
{
	return g_object_new (DATES_TYPE_NAVIGATOR, NULL);
}

void
dates_navigator_refilter (DatesNavigator * nav)
{
	if (!nav->priv->view)
		return;

	dates_navigator_view_refilter (nav->priv->view);	
}

void
dates_navigator_populate (DatesNavigator * nav, ECal * ecal,
						  const gchar * query, DNTimeFormat format)
{
	if (!nav->priv->view)
		return;

	dates_navigator_view_populate (nav->priv->view, ecal, query, format);
}

void
dates_navigator_set_search_mode (DatesNavigator * nav, gboolean m)
{
	g_return_if_fail (nav);

	if (nav->priv->search_mode == m)
		return;

	nav->priv->search_mode = m;

	dates_navigator_view_refilter (nav->priv->view);	
}

gboolean
dates_navigator_get_search_mode (DatesNavigator * nav)
{
	g_return_val_if_fail (nav, FALSE);

	return nav->priv->search_mode;
}

void
dates_navigator_show_search_bar (DatesNavigator * nav, gboolean show)
{
	g_return_if_fail (nav);
	
	if (show)
		gtk_widget_show (GTK_WIDGET (nav->priv->search_bar));
	else
		gtk_widget_hide (GTK_WIDGET (nav->priv->search_bar));
}

