/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * GData Client
 * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk>
 *
 * GData Client is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * GData Client 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * SECTION:gdata-gd-reminder
 * @short_description: GData reminder element
 * @stability: Stable
 * @include: gdata/gd/gdata-gd-reminder.h
 *
 * #GDataGDReminder represents a "reminder" element from the
 * <ulink type="http" url="http://code.google.com/apis/gdata/docs/2.0/elements.html#gdReminder">GData specification</ulink>.
 *
 * Since: 0.4.0
 */

#include <glib.h>
#include <libxml/parser.h>

#include "gdata-gd-reminder.h"
#include "gdata-parsable.h"
#include "gdata-parser.h"
#include "gdata-types.h"
#include "gdata-comparable.h"

static void gdata_gd_reminder_comparable_init (GDataComparableIface *iface);
static void gdata_gd_reminder_finalize (GObject *object);
static void gdata_gd_reminder_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
static void gdata_gd_reminder_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
static gboolean pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error);
static void pre_get_xml (GDataParsable *parsable, GString *xml_string);
static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);

struct _GDataGDReminderPrivate {
	gchar *method;
	gint64 absolute_time;
	gint relative_time;
};

enum {
	PROP_METHOD = 1,
	PROP_ABSOLUTE_TIME,
	PROP_IS_ABSOLUTE_TIME,
	PROP_RELATIVE_TIME
};

G_DEFINE_TYPE_WITH_CODE (GDataGDReminder, gdata_gd_reminder, GDATA_TYPE_PARSABLE,
                         G_IMPLEMENT_INTERFACE (GDATA_TYPE_COMPARABLE, gdata_gd_reminder_comparable_init))

static void
gdata_gd_reminder_class_init (GDataGDReminderClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);

	g_type_class_add_private (klass, sizeof (GDataGDReminderPrivate));

	gobject_class->get_property = gdata_gd_reminder_get_property;
	gobject_class->set_property = gdata_gd_reminder_set_property;
	gobject_class->finalize = gdata_gd_reminder_finalize;

	parsable_class->pre_parse_xml = pre_parse_xml;
	parsable_class->pre_get_xml = pre_get_xml;
	parsable_class->get_namespaces = get_namespaces;
	parsable_class->element_name = "reminder";
	parsable_class->element_namespace = "gd";

	/**
	 * GDataGDReminder:method:
	 *
	 * The notification method the reminder should use. For example: %GDATA_GD_REMINDER_ALERT or %GDATA_GD_REMINDER_EMAIL.
	 *
	 * For more information, see the
	 * <ulink type="http" url="http://code.google.com/apis/gdata/docs/2.0/elements.html#gdReminder">GData specification</ulink>.
	 *
	 * Since: 0.4.0
	 */
	g_object_class_install_property (gobject_class, PROP_METHOD,
	                                 g_param_spec_string ("method",
	                                                      "Method", "The notification method the reminder should use.",
	                                                      NULL,
	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	/**
	 * GDataGDReminder:absolute-time:
	 *
	 * Absolute time at which the reminder should be issued.
	 *
	 * For more information, see the
	 * <ulink type="http" url="http://code.google.com/apis/gdata/docs/2.0/elements.html#gdReminder">GData specification</ulink>.
	 *
	 * Since: 0.4.0
	 */
	g_object_class_install_property (gobject_class, PROP_ABSOLUTE_TIME,
	                                 g_param_spec_int64 ("absolute-time",
	                                                     "Absolute time", "Absolute time at which the reminder should be issued.",
	                                                     -1, G_MAXINT64, -1,
	                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	/**
	 * GDataGDReminder:is-absolute-time:
	 *
	 * Whether the reminder is specified as an absolute or relative time.
	 *
	 * For more information, see the
	 * <ulink type="http" url="http://code.google.com/apis/gdata/docs/2.0/elements.html#gdReminder">GData specification</ulink>.
	 *
	 * Since: 0.4.0
	 */
	g_object_class_install_property (gobject_class, PROP_IS_ABSOLUTE_TIME,
	                                 g_param_spec_boolean ("is-absolute-time",
	                                                       "Absolute time?", "Whether the reminder is specified as an absolute or relative time.",
	                                                       FALSE,
	                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

	/**
	 * GDataGDReminder:relative-time:
	 *
	 * Time at which the reminder should be issued, in minutes relative to the start time of the corresponding event.
	 *
	 * For more information, see the
	 * <ulink type="http" url="http://code.google.com/apis/gdata/docs/2.0/elements.html#gdReminder">GData specification</ulink>.
	 *
	 * Since: 0.4.0
	 */
	g_object_class_install_property (gobject_class, PROP_RELATIVE_TIME,
	                                 g_param_spec_int ("relative-time",
	                                                   "Relative time", "Time at which the reminder should be issued, in minutes.",
	                                                   -1, G_MAXINT, -1,
	                                                   G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

static gint
compare_with (GDataComparable *self, GDataComparable *other)
{
	gint method_cmp, time_cmp;
	GDataGDReminder *a = (GDataGDReminder*) self, *b = (GDataGDReminder*) other;

	if (gdata_gd_reminder_is_absolute_time (a) != gdata_gd_reminder_is_absolute_time (b))
		return 1;

	method_cmp = g_strcmp0 (a->priv->method, b->priv->method);
	if (gdata_gd_reminder_is_absolute_time (a) == TRUE) {
		time_cmp = a->priv->absolute_time - b->priv->absolute_time;
	} else {
		time_cmp = a->priv->relative_time - b->priv->relative_time;
	}

	if (method_cmp == 0)
		return time_cmp;
	else
		return method_cmp;
}

static void
gdata_gd_reminder_comparable_init (GDataComparableIface *iface)
{
	iface->compare_with = compare_with;
}

static void
gdata_gd_reminder_init (GDataGDReminder *self)
{
	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_GD_REMINDER, GDataGDReminderPrivate);
	self->priv->absolute_time = -1;
}

static void
gdata_gd_reminder_finalize (GObject *object)
{
	GDataGDReminderPrivate *priv = GDATA_GD_REMINDER (object)->priv;

	g_free (priv->method);

	/* Chain up to the parent class */
	G_OBJECT_CLASS (gdata_gd_reminder_parent_class)->finalize (object);
}

static void
gdata_gd_reminder_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
{
	GDataGDReminderPrivate *priv = GDATA_GD_REMINDER (object)->priv;

	switch (property_id) {
		case PROP_METHOD:
			g_value_set_string (value, priv->method);
			break;
		case PROP_ABSOLUTE_TIME:
			g_value_set_int64 (value, priv->absolute_time);
			break;
		case PROP_IS_ABSOLUTE_TIME:
			g_value_set_boolean (value, gdata_gd_reminder_is_absolute_time (GDATA_GD_REMINDER (object)));
			break;
		case PROP_RELATIVE_TIME:
			g_value_set_int (value, priv->relative_time);
			break;
		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

static void
gdata_gd_reminder_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
{
	GDataGDReminder *self = GDATA_GD_REMINDER (object);

	switch (property_id) {
		case PROP_METHOD:
			gdata_gd_reminder_set_method (self, g_value_get_string (value));
			break;
		case PROP_ABSOLUTE_TIME:
			gdata_gd_reminder_set_absolute_time (self, g_value_get_int64 (value));
			break;
		case PROP_RELATIVE_TIME:
			gdata_gd_reminder_set_relative_time (self, g_value_get_int (value));
			break;
		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

static gboolean
pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error)
{
	GDataGDReminderPrivate *priv = GDATA_GD_REMINDER (parsable)->priv;
	xmlChar *absolute_time, *relative_time;
	gint64 absolute_time_int64;
	gint relative_time_int = -1;
	gboolean is_absolute_time = FALSE;

	/* Absolute time */
	absolute_time = xmlGetProp (root_node, (xmlChar*) "absoluteTime");
	if (absolute_time != NULL) {
		is_absolute_time = TRUE;
		if (gdata_parser_int64_from_iso8601 ((gchar*) absolute_time, &absolute_time_int64) == FALSE) {
			/* Error */
			gdata_parser_error_not_iso8601_format (root_node, (gchar*) absolute_time, error);
			xmlFree (absolute_time);
			return FALSE;
		}
		xmlFree (absolute_time);
	}

	/* Relative time */
	relative_time = xmlGetProp (root_node, (xmlChar*) "days");
	if (relative_time != NULL) {
		relative_time_int = g_ascii_strtoll ((gchar*) relative_time, NULL, 10) * 60 * 24;
	} else {
		relative_time = xmlGetProp (root_node, (xmlChar*) "hours");
		if (relative_time != NULL) {
			relative_time_int = g_ascii_strtoll ((gchar*) relative_time, NULL, 10) * 60;
		} else {
			relative_time = xmlGetProp (root_node, (xmlChar*) "minutes");
			if (relative_time != NULL)
				relative_time_int = g_ascii_strtoll ((gchar*) relative_time, NULL, 10);
		}
	}
	xmlFree (relative_time);

	if (is_absolute_time == TRUE) {
		priv->absolute_time = absolute_time_int64;
		priv->relative_time = -1;
	} else {
		priv->absolute_time = -1;
		priv->relative_time = relative_time_int;
	}

	priv->method = (gchar*) xmlGetProp (root_node, (xmlChar*) "method");

	return TRUE;
}

static void
pre_get_xml (GDataParsable *parsable, GString *xml_string)
{
	GDataGDReminderPrivate *priv = GDATA_GD_REMINDER (parsable)->priv;

	if (priv->relative_time == -1) {
		gchar *absolute_time = gdata_parser_int64_to_iso8601 (priv->absolute_time);
		g_string_append_printf (xml_string, " absoluteTime='%s'", absolute_time);
		g_free (absolute_time);
	} else {
		g_string_append_printf (xml_string, " minutes='%i'", priv->relative_time);
	}

	if (priv->method != NULL)
		gdata_parser_string_append_escaped (xml_string, " method='", priv->method, "'");
}

static void
get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
{
	g_hash_table_insert (namespaces, (gchar*) "gd", (gchar*) "http://schemas.google.com/g/2005");
}

/**
 * gdata_gd_reminder_new:
 * @method: (allow-none): the notification method the reminder should use, or %NULL
 * @absolute_time: the absolute time for the reminder, or <code class="literal">-1</code>
 * @relative_time: the relative time for the reminder, in minutes, or <code class="literal">-1</code>
 *
 * Creates a new #GDataGDReminder. More information is available in the <ulink type="http"
 * url="http://code.google.com/apis/gdata/docs/2.0/elements.html#gdReminder">GData specification</ulink>.
 *
 * Return value: a new #GDataGDReminder, or %NULL; unref with g_object_unref()
 *
 * Since: 0.2.0
 */
GDataGDReminder *
gdata_gd_reminder_new (const gchar *method, gint64 absolute_time, gint relative_time)
{
	g_return_val_if_fail (absolute_time == -1 || relative_time == -1, NULL);
	g_return_val_if_fail (absolute_time >= -1, NULL);
	g_return_val_if_fail (relative_time >= -1, NULL);
	return g_object_new (GDATA_TYPE_GD_REMINDER, "absolute-time", absolute_time, "relative-time", relative_time, "method", method, NULL);
}

/**
 * gdata_gd_reminder_get_method:
 * @self: a #GDataGDReminder
 *
 * Gets the #GDataGDReminder:method property.
 *
 * Return value: the method, or %NULL
 *
 * Since: 0.4.0
 */
const gchar *
gdata_gd_reminder_get_method (GDataGDReminder *self)
{
	g_return_val_if_fail (GDATA_IS_GD_REMINDER (self), NULL);
	return self->priv->method;
}

/**
 * gdata_gd_reminder_set_method:
 * @self: a #GDataGDReminder
 * @method: (allow-none): the new method, or %NULL
 *
 * Sets the #GDataGDReminder:method property to @method.
 *
 * Set @method to %NULL to unset the property.
 *
 * Since: 0.4.0
 */
void
gdata_gd_reminder_set_method (GDataGDReminder *self, const gchar *method)
{
	g_return_if_fail (GDATA_IS_GD_REMINDER (self));

	g_free (self->priv->method);
	self->priv->method = g_strdup (method);
	g_object_notify (G_OBJECT (self), "method");
}

/**
 * gdata_gd_reminder_get_absolute_time:
 * @self: a #GDataGDReminder
 *
 * Gets the #GDataGDReminder:absolute-time property. If the property is unset, <code class="literal">-1</code> will be returned.
 *
 * Return value: the UNIX timestamp of the absolute time for the reminder, or <code class="literal">-1</code>
 *
 * Since: 0.4.0
 */
gint64
gdata_gd_reminder_get_absolute_time (GDataGDReminder *self)
{
	g_return_val_if_fail (GDATA_IS_GD_REMINDER (self), -1);
	return self->priv->absolute_time;
}

/**
 * gdata_gd_reminder_set_absolute_time:
 * @self: a #GDataGDReminder
 * @absolute_time: the new absolute time, or <code class="literal">-1</code>
 *
 * Sets the #GDataGDReminder:absolute-time property to @absolute_time.
 *
 * Set @absolute_time to <code class="literal">-1</code> to unset the property.
 *
 * Since: 0.4.0
 */
void
gdata_gd_reminder_set_absolute_time (GDataGDReminder *self, gint64 absolute_time)
{
	g_return_if_fail (GDATA_IS_GD_REMINDER (self));
	g_return_if_fail (absolute_time >= -1);

	self->priv->absolute_time = absolute_time;
	g_object_notify (G_OBJECT (self), "absolute-time");
}

/**
 * gdata_gd_reminder_is_absolute_time:
 * @self: a #GDataGDReminder
 *
 * Returns whether the reminder is specified as an absolute time, or as a number of minutes after
 * the corresponding event's start time.
 *
 * Return value: %TRUE if the reminder is absolute, %FALSE otherwise
 *
 * Since: 0.4.0
 */
gboolean
gdata_gd_reminder_is_absolute_time (GDataGDReminder *self)
{
	g_return_val_if_fail (GDATA_IS_GD_REMINDER (self), FALSE);
	return (self->priv->relative_time == -1) ? TRUE : FALSE;
}

/**
 * gdata_gd_reminder_get_relative_time:
 * @self: a #GDataGDReminder
 *
 * Gets the #GDataGDReminder:relative-time property.
 *
 * Return value: the relative time, or <code class="literal">-1</code>
 *
 * Since: 0.4.0
 */
gint
gdata_gd_reminder_get_relative_time (GDataGDReminder *self)
{
	g_return_val_if_fail (GDATA_IS_GD_REMINDER (self), -1);
	return self->priv->relative_time;
}

/**
 * gdata_gd_reminder_set_relative_time:
 * @self: a #GDataGDReminder
 * @relative_time: the new relative time, or <code class="literal">-1</code>
 *
 * Sets the #GDataGDReminder:relative-time property to @relative_time.
 *
 * Set @relative_time to <code class="literal">-1</code> to unset the property.
 *
 * Since: 0.4.0
 */
void
gdata_gd_reminder_set_relative_time (GDataGDReminder *self, gint relative_time)
{
	g_return_if_fail (GDATA_IS_GD_REMINDER (self));
	g_return_if_fail (relative_time >= -1);

	self->priv->relative_time = relative_time;
	g_object_notify (G_OBJECT (self), "method");
}
