/* -*- 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 <libosso.h>
#include <hildon-widgets/hildon-window.h>
#include <hildon-widgets/hildon-program.h>
#include <hildon-widgets/hildon-time-editor.h>
#include <hildon-widgets/hildon-date-editor.h>

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

/* functions for hildon hibernation */

/* the osso state data size has to be fixed; this defines the space
 * we need for storing the time stamp (UTC string is at most 20 bytes)
 */
#define ICALTIME_MAX_SIZE 21 

/* Wrapper around the data we save by libosso API
 *
 * For now just a timestamp, but will need to save scroll later
 */
typedef struct 
{
	char t[ICALTIME_MAX_SIZE];
} StateData;

/* saves state data via libosso API
 *
 * The saved data survives application exit until next reboot
 */
gboolean
dates_save_state (DatesData *d)
{
	const icaltimetype * t;
	const char         * t_sz;
	osso_state_t         osd;
	StateData            sd;

#ifdef DEBUG
	if (d->debug & DATES_DEBUG_HIBERNATE)
		g_debug ("entered %s", G_STRFUNC);
#endif
	
	g_return_val_if_fail (d && d->view && d->osso_context, FALSE);

	t = dates_view_get_date (d->view);
	g_return_val_if_fail (t, FALSE);
	
	t_sz = icaltime_as_ical_string (*t);
	g_return_val_if_fail (t_sz, FALSE);

	memset (&sd, 0, sizeof (sd));
	strncpy (sd.t, t_sz, sizeof (sd.t));
	
	osd.state_size = sizeof(StateData);
	osd.state_data = (gpointer)&sd;

	if (osso_state_write (d->osso_context, &osd) == OSSO_OK)
	{
#ifdef DEBUG
		if (d->debug & DATES_DEBUG_HIBERNATE)
			g_debug ("success [%s]", sd.t);
#endif
		return TRUE;
	}

#ifdef DEBUG
	if (d->debug & DATES_DEBUG_HIBERNATE)
		g_debug ("saving state failed");
#endif
	return FALSE;
}

/* Load our state data from the libosso storage
 * 
 * We have a problem -- the system uses a dbus message to let us know that we
 * are comming out of hibernation, rather than starting afresh, but
 * unfortunately, it does not work (I think that message is dispatched *before*
 * we had a chance to register our callback, so we never see it. The problem is
 * cause by the fact that the state data is persistent, so we cannot load it
 * on every startup, but only when weaking up from hibernation.
 *
 * We work around this problem by replacing the state data with a single byte
 * placeholder everytime we get topped; we then use the size of the save data to
 * differentiate between real data to use for restore, and phony one to ignore.
 *
 * This hack will not work if the application is closed in some other way than
 * being background killed while in the background, but this should not be
 * happening in Maemo 2, as there is no way to close the up from the TN, but
 * might become issue in the future (hopefully, the will fix the API by then).
 */
gboolean
dates_restore_state (DatesData *d)
{
	icaltimetype t;
	osso_state_t osd;
	StateData    sd;
	char         c = 0;
	
#ifdef DEBUG
	if (d->debug & DATES_DEBUG_HIBERNATE)
		g_debug ("entered %s", G_STRFUNC);
#endif
	
	g_return_val_if_fail (d && d->view && d->osso_context, FALSE);

	osd.state_size = sizeof(c);
	osd.state_data = (gpointer)& c;
	
	if (osso_state_read (d->osso_context, & osd) == OSSO_OK) {
#ifdef DEBUG
		if (d->debug & DATES_DEBUG_HIBERNATE)
			g_debug ("state date contains place-holder only");
#endif
		d->restored = TRUE;
		return FALSE;
	}
	
	osd.state_size = sizeof(StateData);
	osd.state_data = (gpointer)& sd;
	
	if (osso_state_read (d->osso_context, & osd) != OSSO_OK) {
#ifdef DEBUG
		if (d->debug & DATES_DEBUG_HIBERNATE)
			g_debug ("Could not read saved state");
#endif
		return FALSE;
	}

#ifdef DEBUG
	if (d->debug & DATES_DEBUG_HIBERNATE)
		g_debug ("Retrieved saved date [%s]", sd.t);
#endif
	
	/* I hope this does not leak anything */
	t = icaltime_from_string (sd.t);
	dates_view_set_date (d->view, &t);

	d->restored = TRUE;
	
	return TRUE;
}

/* this is a hack working around the broken hibernation in hildon
 *
 * Basically, this function replaces any previously saved state data with data
 * of size sizeof(char) -- we then use the size of the saved data to
 * differentiate between real saved state and a mere placeholder
 */
gboolean
dates_clear_state (DatesData *d)
{
	osso_state_t         osd;
	char                 t = 0;

#ifdef DEBUG
	if (d->debug & DATES_DEBUG_HIBERNATE)
		g_debug ("entered %s", G_STRFUNC);
#endif
	
	g_return_val_if_fail (d && d->view && d->osso_context, FALSE);

	osd.state_size = sizeof(t);
	osd.state_data = (gpointer)&t;

	if (osso_state_write (d->osso_context, &osd) == OSSO_OK)
	{
#ifdef DEBUG
		if (d->debug & DATES_DEBUG_HIBERNATE)
			g_debug ("success");
#endif
		return TRUE;
	}
	
#ifdef DEBUG
	if (d->debug & DATES_DEBUG_HIBERNATE)
		g_debug ("clearing state failed");
#endif
	return FALSE;
}

static void
dates_is_topmost_notify (GObject *self,
			 GParamSpec *property_param,
			 DatesData * d)
{
	HildonProgram *program = HILDON_PROGRAM (self);
	if (hildon_program_get_is_topmost (program)){
#ifdef DEBUG
		if (d->debug & DATES_DEBUG_HIBERNATE)
			g_debug ("dates_is_topmost_notify: TRUE");
#endif

		/* if this is not the initial topping of the application,
		 * clear the state data
		 */
		if (d->restored) {
			dates_clear_state(d);
		}
		
		hildon_program_set_can_hibernate (program, FALSE);
	} else {
#ifdef DEBUG
		if (d->debug & DATES_DEBUG_HIBERNATE)
			g_debug ("dates_is_topmost_notify: FALSE");
#endif
		dates_save_state(d);
		hildon_program_set_can_hibernate (program, TRUE);
	}
}

static gboolean
dates_restore_timeout_cb (gpointer p)
{
	DatesData * d = (DatesData*)p;

#ifdef DEBUG
	if (d->debug & DATES_DEBUG_HIBERNATE)
		g_debug ("Entered restore timeout CB");
#endif

	if (d && d->restored) {
#ifdef DEBUG
		if (d->debug & DATES_DEBUG_HIBERNATE)
			g_debug ("State already restored");
#endif
		return FALSE;
	}
	
	if (!d || !d->init_done) {
#ifdef DEBUG
		if (d->debug & DATES_DEBUG_HIBERNATE)
			g_debug ("Initialisation not completed, yet ...");
#endif
		return TRUE;
	}

#ifdef DEBUG
	if (d->debug & DATES_DEBUG_HIBERNATE)
		g_debug ("restoring state");
#endif
	dates_restore_state (d);
	return FALSE;
}


static gint
dates_osso_rpc_event_cb (const gchar     *interface,
			 const gchar     *method,
			 GArray          *arguments,
			 gpointer         p,
			 osso_rpc_t      *retval)
{
	DatesData * d = (DatesData*) d;
	
#ifdef DEBUG
	if (d->debug & DATES_DEBUG_HIBERNATE)
		g_debug("osso_rpc_event_cb() interface [%s], method [%s]",
			interface, method);
#endif
	
	g_return_val_if_fail (d, OSSO_ERROR);

	
	/* the restored message can arrive *before* the app finished loading --
	 * if that happens we cannot retrive the state, so we install a
	 * callback to be called in 300ms intervals until the init is finished
	 */
	   
	if(!strcmp(method, "restored"))
	{
		if(d->init_done)
		{
#ifdef DEBUG
			if (d->debug & DATES_DEBUG_HIBERNATE)
				g_debug ("restoring state");
#endif
			dates_restore_state (d);
		}
		else
			g_timeout_add (300, dates_restore_timeout_cb, d);
	}

	return OSSO_OK;
}

static GtkWidget *
load_tb_item (GtkWidget * toolbar,
	      const char * name, const char * fallback,
	      GCallback cb, gpointer data)
{
	GtkToolItem * tb;
	GtkWidget *icon;

	icon = gtk_image_new_from_icon_name (
		name, GTK_ICON_SIZE_SMALL_TOOLBAR);

	if (!icon) {
#ifdef DEBUG
		g_debug ("could not load theme icon [%s] -- using stock [%s]",
			 name, fallback);
#endif
		tb = gtk_tool_button_new_from_stock (fallback);
	} else
		tb = gtk_tool_button_new (icon, NULL);

	gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tb, -1);
	
	g_signal_connect (G_OBJECT (tb), "clicked",
			  cb, data);
	
	return GTK_WIDGET (tb);
}

#define DATES_PLATFORM_create_toolbar
static GtkWidget *
create_toolbar (DatesData * d)
{
	/* Create new GTK toolbar */
	GtkWidget * toolbar = gtk_toolbar_new ();

	/* Set toolbar properties */
	gtk_toolbar_set_orientation( GTK_TOOLBAR(toolbar),
				     GTK_ORIENTATION_HORIZONTAL);
	gtk_toolbar_set_style( GTK_TOOLBAR(toolbar),
			       GTK_TOOLBAR_BOTH_HORIZ);

	/* Create toolitems using defined items from stock */
	d->TBNew = load_tb_item (toolbar,
		"qgn_list_messagin_editor", GTK_STOCK_NEW,
		G_CALLBACK(dates_new_cb), d);
	d->TBEdit = load_tb_item (toolbar, "qgn_list_gene_fldr_opn",
							  GTK_STOCK_EDIT,
		G_CALLBACK(dates_edit_cb), d);
	d->TBDelete = load_tb_item (toolbar, "qgn_list_gene_invalid",
								GTK_STOCK_DELETE,
								G_CALLBACK(dates_delete_cb), d);
		
        gtk_toolbar_insert (GTK_TOOLBAR(toolbar),
		GTK_TOOL_ITEM (GTK_SEPARATOR_TOOL_ITEM (
			gtk_separator_tool_item_new ())), -1);
		
	d->TBBack = load_tb_item (toolbar, "qgn_list_gene_back",
							  GTK_STOCK_GO_BACK,
							  G_CALLBACK(dates_back_cb), d);
	d->TBToday = load_tb_item (toolbar, "qgn_toolb_browser_home",
							   GTK_STOCK_HOME,
							   G_CALLBACK(dates_today_cb), d);
	d->TBForward = load_tb_item (toolbar, "qgn_list_gene_forward",
								 GTK_STOCK_GO_FORWARD,
								 G_CALLBACK(dates_forward_cb), d);
		
        gtk_toolbar_insert (GTK_TOOLBAR(toolbar),
		GTK_TOOL_ITEM (GTK_SEPARATOR_TOOL_ITEM (
			gtk_separator_tool_item_new ())), -1);
			
	d->TBZoomOut = load_tb_item (toolbar, "qgn_toolb_gene_zoomout",
								 GTK_STOCK_ZOOM_OUT,
								 G_CALLBACK(dates_zoom_out_cb), d);
	d->TBZoomIn = load_tb_item (toolbar, "qgn_toolb_gene_zoomin",
								GTK_STOCK_ZOOM_IN,
								G_CALLBACK(dates_zoom_in_cb), d);

	gtk_widget_show_all (GTK_WIDGET (toolbar));
	gtk_widget_hide (d->TBEdit);
	gtk_widget_hide (d->TBDelete);

	return toolbar;
}

#define DATES_PLATFORM_dates_platform_init
void
dates_platform_init (DatesData * d)
{
	d->init_done = FALSE;
	d->restored  = FALSE;

#ifdef DEBUG
	if (d->debug & DATES_DEBUG_HILDON)
		g_debug ("Creating HildonProgram");
#endif
	g_set_application_name (_("Dates"));
	d->program = hildon_program_new ();

#ifdef DEBUG
	if (d->debug & DATES_DEBUG_HILDON)
		g_debug ("Initialising HildonProgram");
#endif
	d->osso_context = osso_initialize ("dates", "0.1", TRUE, NULL);

	if (!d->osso_context)
	{
		g_critical("Could not initialize libosso\n");
		exit (-1);
	}

	if (osso_rpc_set_default_cb_f(d->osso_context,
								  dates_osso_rpc_event_cb, d) != OSSO_OK)
	{
		g_warning ("Could not register rpc callback");
	}
}

#define DATES_PLATFORM_create_time_dialog
static GtkWidget *
create_time_dialog (DatesData * d)
{
	GtkWidget *dialog_vbox2;
	GtkWidget *vbox1;
	GtkWidget *time_separator_label;
	GtkWidget *hour_button;
	GtkWidget *hour_up_button;
	GtkWidget *hour_up;
	GtkWidget *hour_down_button;
	GtkWidget *hour_down;
	GtkWidget *lminute_up_button;
	GtkWidget *lminute_up;
	GtkWidget *lminute_down_button;
	GtkWidget *lminute_down;
	GtkWidget *rminute_up_button;
	GtkWidget *rminute_up;
	GtkWidget *rminute_down_button;
	GtkWidget *rminute_down;
	GtkWidget *dialog_action_area2;
	GtkWidget *time_closebutton;

	d->time_dialog = gtk_dialog_new ();
	gtk_window_set_title (GTK_WINDOW (d->time_dialog), _("Time"));
	gtk_window_set_modal (GTK_WINDOW (d->time_dialog), TRUE);
	gtk_window_set_resizable (GTK_WINDOW (d->time_dialog), FALSE);
	gtk_window_set_icon_name (GTK_WINDOW (d->time_dialog), "dates");
	gtk_window_set_skip_taskbar_hint (GTK_WINDOW (d->time_dialog), TRUE);
	gtk_window_set_skip_pager_hint (GTK_WINDOW (d->time_dialog), TRUE);
	gtk_window_set_type_hint (GTK_WINDOW (d->time_dialog),
							  GDK_WINDOW_TYPE_HINT_DIALOG);
	gtk_dialog_set_has_separator (GTK_DIALOG (d->time_dialog), FALSE);

	dialog_vbox2 = GTK_DIALOG (d->time_dialog)->vbox;
	gtk_widget_show (dialog_vbox2);

	vbox1 = gtk_vbox_new (FALSE, 12);
	gtk_widget_show (vbox1);
	gtk_box_pack_start (GTK_BOX (dialog_vbox2), vbox1, TRUE, TRUE, 0);
	gtk_container_set_border_width (GTK_CONTAINER (vbox1), 6);

	d->time_date_editor = hildon_date_editor_new ();
	gtk_widget_show (d->time_date_editor);
	gtk_box_pack_start (GTK_BOX (vbox1), d->time_date_editor, TRUE, TRUE, 0);

	d->time_time_editor = hildon_time_editor_new ();
	gtk_widget_show (d->time_time_editor);
	gtk_box_pack_start (GTK_BOX (vbox1), d->time_time_editor, TRUE, TRUE, 0);

	d->time_forever_checkbutton =
		gtk_check_button_new_with_mnemonic (_("Forever"));
	
	gtk_widget_show (d->time_forever_checkbutton);
	gtk_box_pack_start (GTK_BOX (vbox1), d->time_forever_checkbutton,
						FALSE, FALSE, 0);

	dialog_action_area2 = GTK_DIALOG (d->time_dialog)->action_area;
	gtk_widget_show (dialog_action_area2);
	gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area2),
							   GTK_BUTTONBOX_END);

	time_closebutton = gtk_button_new_from_stock ("gtk-close");
	gtk_widget_show (time_closebutton);
	gtk_dialog_add_action_widget (GTK_DIALOG (d->time_dialog),
								  time_closebutton, GTK_RESPONSE_CLOSE);
	GTK_WIDGET_SET_FLAGS (time_closebutton, GTK_CAN_DEFAULT);


	gtk_widget_grab_focus (time_closebutton);
	gtk_widget_grab_default (time_closebutton);
  
	return d->time_dialog;
}

#define DATES_PLATFORM_create_main_window
static GtkWidget *
create_main_window (DatesData * d, GtkWidget * toolbar,
					GtkWidget * menu, GtkAccelGroup * accel_group)
{
	GtkWidget *main_vbox;
	GtkWidget *image;
	GtkWidget *scrolled_window;

	gtk_widget_show (menu);
	hildon_program_set_common_menu(d->program, GTK_MENU(menu));
	hildon_program_set_common_toolbar (d->program, GTK_TOOLBAR (toolbar));
	
	d->main_window = hildon_window_new ();
	gtk_window_set_title (GTK_WINDOW (d->main_window), _("Dates"));

	main_vbox = gtk_vbox_new (FALSE, 0);
	gtk_widget_show (main_vbox);
	gtk_container_add (GTK_CONTAINER (d->main_window), main_vbox);

	d->header_eventbox = gtk_event_box_new ();
	gtk_box_pack_start (GTK_BOX (main_vbox), d->header_eventbox,
						FALSE, FALSE, 0);

	d->header_label = gtk_label_new (_("<big><b>Dates</b></big>"));
	gtk_widget_show (d->header_label);
	gtk_container_add (GTK_CONTAINER (d->header_eventbox), d->header_label);
	gtk_label_set_use_markup (GTK_LABEL (d->header_label), TRUE);

	gtk_widget_show (GTK_WIDGET (d->view));

	dates_view_set_use_dragbox (d->view, FALSE);

	scrolled_window = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
						GTK_POLICY_NEVER,
						GTK_POLICY_AUTOMATIC);

	gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (d->view));

	gtk_widget_show (scrolled_window);

	gtk_box_pack_start (GTK_BOX (main_vbox), scrolled_window,
						TRUE, TRUE, 0);

	
	GTK_WIDGET_SET_FLAGS (d->view, GTK_CAN_FOCUS);
	GTK_WIDGET_UNSET_FLAGS (d->view, GTK_CAN_DEFAULT);

	gtk_widget_grab_focus (GTK_WIDGET (d->view));
	gtk_window_add_accel_group (GTK_WINDOW (d->main_window), accel_group);

	/* Set nice colours for full-screen date header */
	gtk_widget_set_state (d->header_eventbox, GTK_STATE_SELECTED);
	gtk_widget_set_state (d->header_label, GTK_STATE_SELECTED);
	
	g_signal_connect (G_OBJECT (d->main_window), "window_state_event",
					  G_CALLBACK (dates_window_state_cb), d);
	g_signal_connect (G_OBJECT (d->main_window), "key_press_event",
					  G_CALLBACK (dates_key_press_cb), d);

	return d->main_window;
}

static void
dates_gtk_create_ui (DatesData * d);

void
dates_platform_create_ui (DatesData * d)
{
	dates_gtk_create_ui (d);

	hildon_program_add_window (d->program, HILDON_WINDOW (d->main_window));
	
	g_object_set(G_OBJECT(gtk_widget_get_settings(d->main_window)),
			      "gtk-button-images",
			      FALSE, NULL );
	g_object_set(G_OBJECT(gtk_widget_get_settings(d->main_window)),
			      "gtk-menu-images",
			      FALSE, NULL );

	/* HildonTime/DateEditor are broken, show_all them */
	gtk_widget_show_all (d->time_date_editor);
	gtk_widget_show_all (d->time_time_editor);
}

#define DATES_PLATFORM_dates_platform_pre_show
void dates_platform_pre_show (DatesData *d)
{
	gtk_window_set_default_icon_name ("dates");

	g_signal_connect (G_OBJECT (d->program), "notify::is-topmost",
					  G_CALLBACK (dates_is_topmost_notify), d);

	d->init_done = TRUE;

	if (!d->restored)
		dates_restore_state (d);	
}

#define dates_platform_create_ui dates_gtk_create_ui

#include "dates_gtk.c"

#undef  dates_platform_create_ui

