/*
 * Copyright (C) 2012 - Juan Ferrer Toribio
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include "vn-date-chooser.h"
#include <db/db.h>

#define	GUI_FILE	_GUI_DIR"/date-chooser.glade"

#define DATE_SIZE   50

/**
 * SECTION:vn-date-chooser
 * @Short_description: an embedded popup date selector
 * @Title: VnDateChooser
 * @See_also: #VnField
 * @Image: date-chooser.png
 *
 * An embedded button and text field that pops-up a #GtkCalendar.
 */
G_DEFINE_TYPE (VnDateChooser, vn_date_chooser, VN_TYPE_FIELD);

/**
 * vn_date_chooser_new:
 *
 * Creates a new #VnDateChooser
 *
 * Return value: a #VnDateChooser
 **/
VnField * vn_date_chooser_new ()
{
	return VN_FIELD (g_object_new (VN_TYPE_DATE_CHOOSER, NULL));
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Private

static void vn_date_chooser_hide_popup (VnDateChooser * obj)
{
	if (obj->device)
	{
		gdk_device_ungrab (obj->device, GDK_CURRENT_TIME);
		gtk_device_grab_remove (obj->popup, obj->device);
	}

	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (obj->button), FALSE);
	gtk_widget_hide (obj->popup);
}

static void vn_date_chooser_changed (VnDateChooser * obj)
{
	GDateTime * datetime = obj->datetime;

	if (datetime)
	{
		gchar * str = g_date_time_format (datetime, obj->format);
		gtk_label_set_text (obj->label, str);
		g_free (str);
	}
	else
		gtk_label_set_text (obj->label, "");
}

static void vn_date_chooser_set_value (VnDateChooser * obj, const GValue * value)
{
	GDateTime * datetime = NULL;

	if (gvn_value_is_null (value)
	|| G_VALUE_TYPE (value) == G_TYPE_DATE_TIME)
		datetime = g_value_get_boxed (value);
	else
	{
		GValue new_value = G_VALUE_INIT;
		g_value_init (&new_value, G_TYPE_DATE_TIME);
		g_value_transform (value, &new_value);
		datetime = g_value_get_boxed (&new_value);
	}

	if (datetime)
	{
		if (gvn_value_is_null (datetime))
			obj->datetime = datetime;
		else
			obj->datetime = g_date_time_ref (datetime);
	}
	else if (obj->datetime)
	{
		g_date_time_unref (obj->datetime);
		obj->datetime = NULL;
	}

	vn_date_chooser_changed (obj);
}

static GDateTime * vn_date_chooser_get_datetime (VnDateChooser * obj)
{
	guint year, month, day, hour = 0, minute = 0;
	gdouble second = 0.0;

	if (obj->show_date)
	{
		gtk_calendar_get_date (obj->calendar, &year, &month, &day);
		month++;
	}

	if (obj->show_time)
	{
		hour = (guint) gtk_adjustment_get_value (obj->hour);
		minute = (guint) gtk_adjustment_get_value (obj->minute);
		second = gtk_adjustment_get_value (obj->second);
	}
	
	return g_date_time_new_local (year, month, day, hour, minute, second);
}

void vn_date_chooser_on_day_selected (GtkCalendar * calendar, VnDateChooser * obj)
{
	GValue value = G_VALUE_INIT;
	GDateTime * datetime = vn_date_chooser_get_datetime (obj);

	if (!obj->datetime
	|| (obj->datetime && g_date_time_compare (datetime, obj->datetime)))
	{
		obj->datetime = datetime;
		g_value_init (&value, G_TYPE_DATE_TIME);
		g_value_set_boxed (&value, obj->datetime);
	}
	else
	{
		g_value_init (&value, GVN_TYPE_NULL);

		if (obj->datetime)
			g_date_time_unref (obj->datetime);

		obj->datetime = NULL;
	}

	VN_FIELD_GET_CLASS (obj)->value_changed (VN_FIELD (obj), &value);
	g_value_unset (&value);

	vn_date_chooser_hide_popup (obj);
	vn_date_chooser_changed (obj);
}

void vn_date_chooser_on_ok_clicked (GtkButton * button, VnDateChooser * obj)
{
	GDateTime * datetime = vn_date_chooser_get_datetime (obj);

	if (datetime)
	{
		GValue value = G_VALUE_INIT;

		if (obj->datetime)
			g_date_time_unref (obj->datetime);

		obj->datetime = datetime;
		g_value_init (&value, G_TYPE_DATE_TIME);
		g_value_set_boxed (&value, datetime);
		VN_FIELD_GET_CLASS (obj)->value_changed (VN_FIELD (obj), &value);
		g_value_unset (&value);
	}

	vn_date_chooser_hide_popup (obj);
	vn_date_chooser_changed (obj);
}

void vn_date_chooser_on_clear_clicked (GtkButton * button, VnDateChooser * obj)
{
	GValue value = G_VALUE_INIT;
	g_value_init (&value, GVN_TYPE_NULL);

	if (obj->datetime)
		g_date_time_unref (obj->datetime);

	obj->datetime = NULL;
	VN_FIELD_GET_CLASS (obj)->value_changed (VN_FIELD (obj), &value);
	g_value_unset (&value);
	vn_date_chooser_hide_popup (obj);
	vn_date_chooser_changed (obj);

	gtk_adjustment_set_value (obj->hour, 0);
	gtk_adjustment_set_value (obj->minute, 0);
	gtk_adjustment_set_value (obj->second, 0);
}

void vn_date_chooser_on_now_clicked (GtkButton * button, VnDateChooser * obj)
{
	GValue value = G_VALUE_INIT;

	if (obj->datetime)
		g_date_time_unref (obj->datetime);

	obj->datetime = g_date_time_new_now_local ();
	g_value_init (&value, G_TYPE_DATE_TIME);
	g_value_set_boxed (&value, obj->datetime);
	VN_FIELD_GET_CLASS (obj)->value_changed (VN_FIELD (obj), &value);
	g_value_unset (&value);
	vn_date_chooser_hide_popup (obj);
	vn_date_chooser_changed (obj);
}

static gboolean vn_date_chooser_on_button_press (GtkWidget * widget,
	GdkEventButton * event, VnDateChooser * obj)
{
	gint x, y;
	GtkAllocation allocation;

	gdk_window_get_origin (gtk_widget_get_window (obj->popup), &x, &y);
	gtk_widget_get_allocation (obj->popup, &allocation);

	if (!( event->x_root >= x && event->x_root <= x + allocation.width
		&& event->y_root >= y && event->y_root <= y + allocation.height))
	{
		vn_date_chooser_hide_popup (obj);
		return TRUE;
	}

	return FALSE;
}

static gboolean vn_date_chooser_on_key_press (GtkWidget * widget,
	GdkEventKey * event, VnDateChooser * obj)
{
	if (event->keyval == GDK_KEY_Escape)
	{
		vn_date_chooser_hide_popup (obj);
		return TRUE;
	}
	
	return FALSE;
}

static void vn_date_chooser_on_toggled (GtkToggleButton * button, VnDateChooser * obj)
{
	if (gtk_toggle_button_get_active (button))
	{
		gint x, y;
		GdkWindow * window;
		GdkScreen * screen;
		GtkRequisition req;
		GdkRectangle monitor;
		GtkAllocation allocation;
		GtkWidget * widget = GTK_WIDGET (button);

		// Set the date on the calendar

		if (obj->datetime)
		{
			if (obj->show_date)
			{
				gtk_calendar_select_month (obj->calendar
					 ,g_date_time_get_month (obj->datetime) - 1
					 ,g_date_time_get_year (obj->datetime)
				);
				gtk_calendar_select_day (obj->calendar,
					g_date_time_get_day_of_month (obj->datetime));
			}

			if (obj->show_time)
			{
				gtk_adjustment_set_value (obj->hour,
					g_date_time_get_hour (obj->datetime));
				gtk_adjustment_set_value (obj->minute,
					g_date_time_get_minute (obj->datetime));
				gtk_adjustment_set_value (obj->second,
					g_date_time_get_second (obj->datetime));
				gtk_widget_set_no_show_all (GTK_WIDGET (obj->time), obj->show_time);
			}
		}
		else
			gtk_calendar_select_day (obj->calendar, 0);

		// Set visibility

		if (!obj->show_date)
			gtk_widget_hide (GTK_WIDGET (obj->calendar));
		if (!obj->show_time)
			gtk_widget_hide (GTK_WIDGET (obj->time));

		gtk_widget_set_no_show_all (GTK_WIDGET (obj->calendar), !obj->show_date);
		gtk_widget_set_no_show_all (GTK_WIDGET (obj->time), !obj->show_time);

		// Setting the position of the popup

		window = gtk_widget_get_window (widget);

		gtk_widget_get_allocation (widget, &allocation);

		gdk_window_get_origin (window, &x, &y);
		x += allocation.x;
		y += allocation.y;

  		screen = gtk_widget_get_screen (widget);
  		gdk_screen_get_monitor_geometry (screen,
  			gdk_screen_get_monitor_at_point (screen, x, y), &monitor);

		gtk_widget_show (GTK_WIDGET (obj->calendar));
  		gtk_widget_get_preferred_size (obj->popup, &req, NULL);

  		if (y - monitor.y > monitor.height)
  			y = monitor.y + monitor.height - req.height;
  		else if ((y - monitor.y) + req.height + allocation.height > monitor.height)
  			y -= req.height;
  		else
  			y += allocation.height;

  		if ((x + allocation.width) - monitor.x > monitor.width)
  			x = monitor.x + monitor.width - req.width; 	
  		else if ((x - monitor.x) + req.width > monitor.width)
  			x -= req.width - allocation.width;

		gtk_window_set_screen (GTK_WINDOW (obj->popup), screen);
		gtk_window_move (GTK_WINDOW (obj->popup), x, y);
		gtk_widget_show_all (obj->popup);

		// Grabbing the focus on the popup window

		obj->device = gtk_get_current_event_device ();

		if (obj->device && gdk_device_get_source (obj->device) == GDK_SOURCE_KEYBOARD)
			obj->device = gdk_device_get_associated_device (obj->device);

		if (!obj->device)
		{
			GList * devices;
			GdkDisplay * display;
			GdkDeviceManager * device_manager;

			display = gtk_widget_get_display (GTK_WIDGET (obj->popup));
			device_manager = gdk_display_get_device_manager (display);

			devices = gdk_device_manager_list_devices (device_manager,
				GDK_DEVICE_TYPE_MASTER);
			obj->device = devices->data;
			g_list_free (devices);
		}

		gtk_device_grab_add (obj->popup, obj->device, TRUE);
		gdk_device_grab (obj->device
			,gtk_widget_get_window (obj->popup)
			,GDK_OWNERSHIP_WINDOW, TRUE
			,GDK_BUTTON_PRESS_MASK, NULL
			,GDK_CURRENT_TIME
		);
	}
	else
		vn_date_chooser_hide_popup (obj);
}

gboolean vn_date_chooser_on_spin_output (GtkSpinButton * spin, VnDateChooser * obj)
{
	gchar * format;
	GtkAdjustment * adj = gtk_spin_button_get_adjustment (spin);
	format = g_strdup_printf ("%02d", (guint) gtk_adjustment_get_value (adj));
	gtk_entry_set_text (GTK_ENTRY (spin), format);
	g_free (format);
	return TRUE;
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Properties

enum
{
	 PROP_FORMAT = 1
	,PROP_SHOW_TIME
	,PROP_SHOW_DATE
};

static void vn_date_chooser_set_property (VnDateChooser * obj, guint id,
	const GValue * value, GParamSpec * pspec)
{
	switch (id)
	{
		case PROP_FORMAT:
			g_free (obj->format);
			obj->format = g_value_dup_string (value);
			break;
		case PROP_SHOW_TIME:
			obj->show_time = g_value_get_boolean (value);
			break;
		case PROP_SHOW_DATE:
			obj->show_date = g_value_get_boolean (value);
			break;
		default:		
			G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec);
	}
}

static void vn_date_chooser_get_property (VnDateChooser * obj, guint id,
	GValue * value, GParamSpec * pspec)
{	
	switch (id)
	{
		case PROP_FORMAT:
			g_value_set_string (value, obj->format);
			break;
		case PROP_SHOW_TIME:
			g_value_set_boolean (value, obj->show_time);
			break;
		case PROP_SHOW_DATE:
			g_value_set_boolean (value, obj->show_date);
			break;
		default:		
			G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec);
	}
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Class

static void vn_date_chooser_init (VnDateChooser * obj)
{
	GtkBuilder * builder;

	obj->show_time = FALSE;
	obj->show_date = TRUE;
	obj->format = NULL;
	obj->popup = NULL;
	obj->device = NULL;
	obj->datetime = NULL;

	obj->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8);
	gtk_container_add (GTK_CONTAINER (obj), GTK_WIDGET (obj->box));
	VN_FIELD (obj)->field = obj->box;

	obj->button = gtk_toggle_button_new ();
	gtk_widget_set_tooltip_text (GTK_WIDGET (obj->button), _("Change date"));
	g_signal_connect (obj->button, "toggled",
		G_CALLBACK (vn_date_chooser_on_toggled), obj);
	gtk_box_pack_start (GTK_BOX (obj->box),
		GTK_WIDGET (obj->button), TRUE, TRUE, 0);
	g_object_set (obj->button, "relief", GTK_RELIEF_HALF, NULL);

	obj->label = GTK_LABEL (gtk_label_new (NULL));
	gtk_misc_set_alignment (GTK_MISC (obj->label), 0, 0.5);
	gtk_container_add (GTK_CONTAINER (obj->button), GTK_WIDGET (obj->label));

	obj->popup = gtk_window_new (GTK_WINDOW_POPUP);
	g_object_connect (obj->popup
		,"signal::button-press-event", vn_date_chooser_on_button_press, obj
		,"signal::key-press-event", vn_date_chooser_on_key_press, obj
		,NULL
	);

	builder = gtk_builder_new_from_file (GUI_FILE);
	gtk_builder_connect_signals (builder, obj);

	obj->calendar = GTK_CALENDAR (gtk_builder_get_object (builder, "calendar"));
	obj->hour = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "hour"));
	obj->minute = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "minute"));
	obj->second = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "second"));
	obj->time = GTK_WIDGET (gtk_builder_get_object (builder, "time"));

	gtk_container_add (GTK_CONTAINER (obj->popup),
		GTK_WIDGET (gtk_builder_get_object (builder, "box")));
}

static void vn_date_chooser_finalize (VnDateChooser * obj)
{
	if (obj->datetime)
		g_date_time_unref (obj->datetime);

	g_free (obj->format);
	gtk_widget_destroy (obj->popup);
	G_OBJECT_CLASS (vn_date_chooser_parent_class)->finalize (G_OBJECT (obj));
}

static void vn_date_chooser_class_init (VnDateChooserClass * klass)
{
	GObjectClass * k = G_OBJECT_CLASS (klass); 
	k->finalize = (GObjectFinalizeFunc) vn_date_chooser_finalize;
	k->set_property = (GObjectSetPropertyFunc) vn_date_chooser_set_property;
	k->get_property = (GObjectGetPropertyFunc) vn_date_chooser_get_property;
	VN_FIELD_CLASS (klass)->set_value = (VnFieldSetValueFunc) vn_date_chooser_set_value;

	g_object_class_install_property (k, PROP_FORMAT,
		g_param_spec_string ("format"
			,_("Format")
			,_("The date format string describing the order of the elements.")
			,C_("Default date format string", "%a, %d %b %Y")
			,G_PARAM_CONSTRUCT | G_PARAM_READWRITE
	));

	g_object_class_install_property (k, PROP_SHOW_TIME,
		g_param_spec_boolean ("show-time"
			,_("Show time")
			,_("Whether to show the hour, minute and second fields to set"
				" the time of the day in the popup.")
			,FALSE
			,G_PARAM_READWRITE
	));

	g_object_class_install_property (k, PROP_SHOW_DATE,
		g_param_spec_boolean ("show-date"
			,_("Show date")
			,_("Whether to show the calendar to set the date in the popup.")
			,TRUE
			,G_PARAM_READWRITE
	));
}