/* cal-backend-wcap-tasks.c
 *
 * Copyright (C) 2002-2004 Sun Microsystems, Inc
 *
 * AUTHORS
 *     Jack Jia <jack.jia@sun.com>
 *     Harry Lu <harry.lu@sun.com>
 *     Alfred Peng <alfred.peng@sun.com>
 *     Jedy Wang <jedy.wang@sun.com>
 *     Rodrigo Moya <rodrigo@ximian.com>
 *     JP Rosevear <jpr@ximian.com>
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include "lib/sunone-util.h"
#include "cal-backend-wcap-tasks.h"

static CalBackendWCAPClass *parent_class = NULL;

#define d(x)

G_DEFINE_TYPE (CalBackendWCAPTasks, cal_backend_wcap_tasks,  CAL_BACKEND_WCAP_TYPE)

static void
process_tasks_component_to_server (CalBackendWCAP *wcap, ECalComponent *comp, gboolean *relativeAlarm, gboolean *allday)
{
	ECalComponentDateTime dt,dtstart;
	icalcomponent *icalcomp;
	icalproperty *prop;
	icaltimezone *zone = NULL, *old_zone = NULL;
	char *location = NULL;

	/* Every task has a start time */
	e_cal_component_get_dtstart (comp, &dt);
	if (!dt.value) {
		dt.value = g_new0 (struct icaltimetype, 1);
		*dt.value = icaltime_from_timet_with_zone (time (NULL), FALSE, cal_backend_wcap_get_default_zone (wcap));
		dt.tzid = g_strdup (icaltimezone_get_tzid (cal_backend_wcap_get_default_zone (wcap)));
		e_cal_component_set_dtstart (comp, &dt);
	}
	e_cal_component_free_datetime (&dt);

	/* Ok, Sunone can't handle other timezones, so, we make a
	 * guess as to the timezone intended, if we fail, we convert
	 * it to the default server side timezone for this calendar */
	e_cal_component_get_dtstart (comp, &dtstart);
	if (dtstart.value && dtstart.tzid) {
		zone = cal_backend_wcap_get_timezone_from_tzid (wcap, dtstart.tzid, TRUE);
		if (!zone) {
			zone = icaltimezone_get_builtin_timezone_from_tzid (dtstart.tzid);
			if (zone) {
				icaltimezone *local_zone;
				
				location = icaltimezone_get_location (zone);
				local_zone = cal_backend_wcap_get_timezone_from_tzid (wcap, location, TRUE);
				if (!local_zone)
					local_zone = cal_backend_wcap_get_server_default_zone (wcap);
				icaltimezone_convert_time (dtstart.value, zone, local_zone);
				g_free ((char *)dtstart.tzid);
				dtstart.tzid = g_strdup (icaltimezone_get_tzid (local_zone));
				e_cal_component_set_dtstart (comp, &dtstart);
				zone = local_zone;
			}
		}
	}
	e_cal_component_free_datetime (&dtstart);

	if (zone)
		location = icaltimezone_get_tzid (zone);

	/* Convert other Date-Time values to the same zone as the start time */
	if (zone) {
		e_cal_component_get_dtend (comp, &dt);
		if (dt.value && dt.tzid) {
			old_zone = e_cal_backend_internal_get_timezone (E_CAL_BACKEND (wcap), dt.tzid);
			icaltimezone_convert_time (dt.value, old_zone, zone);
			g_free ((char *)dt.tzid);
			dt.tzid = g_strdup (icaltimezone_get_tzid (zone));
			e_cal_component_set_dtend (comp, &dt);
		}
		e_cal_component_free_datetime (&dt);
	}
	
	if (zone) {
		e_cal_component_get_due (comp, &dt);
		if (dt.value && dt.tzid) {
			old_zone = e_cal_backend_internal_get_timezone (E_CAL_BACKEND (wcap), dt.tzid);
			icaltimezone_convert_time (dt.value, old_zone, zone);
			g_free ((char *)dt.tzid);
			dt.tzid = g_strdup (icaltimezone_get_tzid (zone));
			e_cal_component_set_due (comp, &dt);
		}
		e_cal_component_free_datetime (&dt);
	}

	e_cal_component_abort_sequence (comp);

	/* Set x properties */
	icalcomp = e_cal_component_get_icalcomponent (comp);
	prop = icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY);
	while (prop) {
		const char *x_name, *x_val;

		x_name = icalproperty_get_x_name (prop);
		x_val = icalproperty_get_x (prop);

		if (!strcmp (x_name, "X-NSCP-DTSTART-TZID") && location) {
			icalproperty_set_x (prop, location);
			location = NULL;
		}
		
		prop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY);
	}
	
	if (location) {
		prop = icalproperty_new_x (location);
		icalproperty_set_x_name (prop, "X-NSCP-DTSTART-TZID");
		icalcomponent_add_property (icalcomp, prop);
	}
	
	e_cal_component_abort_sequence (comp);
}

static ECalBackendSyncStatus
cal_backend_wcap_tasks_update_objects (ECalBackendSync *backend, EDataCal *cal,
				 const char *calobj, CalObjModType mod, char **old_object)
{
	ECalBackendSyncStatus result = GNOME_Evolution_Calendar_Success;
	CalBackendWCAP *wcap = CAL_BACKEND_WCAP (backend);
	ECalComponent *comp;
	icalcomponent *toplevel_comp, *subcomp;
	icalcomponent_kind kind;
	gboolean hasImport = FALSE;
	gboolean relativeAlarm = FALSE;

	g_return_val_if_fail (IS_CAL_BACKEND_WCAP (wcap), GNOME_Evolution_Calendar_OtherError);

	if (!cal_backend_wcap_is_online (wcap))
		return GNOME_Evolution_Calendar_RepositoryOffline;
	if (!cal_backend_wcap_get_connection (wcap))
		return cal_backend_wcap_get_open_error (wcap);

	toplevel_comp = icalparser_parse_string (calobj);
	if (!toplevel_comp)
		return GNOME_Evolution_Calendar_InvalidObject;
	
	kind = icalcomponent_isa (toplevel_comp);
	if (kind == ICAL_VTODO_COMPONENT) {
		icalcomponent *icalcomp;
	
		/* Create a temporary toplevel component and put
		   VTODO in it, to simplify the code below. */
		icalcomp = toplevel_comp;
		toplevel_comp = e_cal_util_new_top_level ();
		icalcomponent_add_component (toplevel_comp, icalcomp);
	} else if (kind != ICAL_VCALENDAR_COMPONENT) {
		/* We don't support this type of component */
		icalcomponent_free (toplevel_comp);
		return GNOME_Evolution_Calendar_InvalidObject;
	}

	subcomp = icalcomponent_get_first_component (toplevel_comp, ICAL_ANY_COMPONENT);
	comp = e_cal_component_new ();
	while (subcomp) {
		/* We ignore anything except VTODO and VJOURNAL
		   components. */
		icalcomponent_kind child_kind = icalcomponent_isa (subcomp);
		if (child_kind == ICAL_VTODO_COMPONENT) {
			icalcomponent *return_icalcomp, *return_subcomp;
			const char *uid;
			gboolean allday = FALSE, expand = FALSE;
			guint error_code;			
			SunOneMethod method;
			
			e_cal_component_set_icalcomponent (comp, subcomp);

			/* Fix up the component for the server */
			process_tasks_component_to_server (wcap, comp, &relativeAlarm, &allday);

			if (cal_backend_wcap_need_import (wcap, comp)) {
				result = cal_backend_wcap_handle_import (wcap, comp, mod, toplevel_comp, subcomp, child_kind);
				subcomp = icalcomponent_get_next_component (toplevel_comp, ICAL_ANY_COMPONENT);
				hasImport = TRUE;
				continue;
			}
			/* Get the uid for verification */
			e_cal_component_get_uid (comp, &uid);
			
			/* Guess the method */
			method = cal_backend_wcap_guess_method (wcap, cal, comp);
			error_code = sunone_connection_storetodos (cal_backend_wcap_get_connection (wcap),
								   cal_backend_wcap_get_calid (wcap), subcomp, 
								   method, cal_backend_wcap_to_sunone_mod (mod, comp), 
								   expand, allday, &return_icalcomp,
								   cal_backend_wcap_get_account_email (wcap));
			cal_backend_wcap_verify_exists_uid (wcap, uid);

			e_cal_component_set_icalcomponent (comp, NULL);

			if (!SUNONE_ERROR_IS_SUCCESSFUL (error_code)) {
				result = cal_backend_wcap_result_from_error (error_code);
				subcomp = icalcomponent_get_next_component (toplevel_comp,
									    ICAL_ANY_COMPONENT);
				continue;
			}

			return_subcomp = icalcomponent_get_first_component (return_icalcomp,
									    child_kind);
			while (return_subcomp) {
				cal_backend_wcap_add_component (wcap, return_subcomp, TRUE, old_object, NULL);
				return_subcomp = icalcomponent_get_next_component (return_icalcomp,
										   child_kind);
			}

			icalcomponent_free (return_icalcomp);
			
		}
		subcomp = icalcomponent_get_next_component (toplevel_comp,
							    ICAL_ANY_COMPONENT);
	}

	g_object_unref (G_OBJECT (comp));
	icalcomponent_free (toplevel_comp);

	if (hasImport || relativeAlarm)
		cal_backend_wcap_poll_cb (wcap);
	else
		cal_backend_wcap_write_cache (wcap);

	return result;
}

static ECalBackendSyncStatus
cal_backend_wcap_tasks_create_object (ECalBackendSync *backend, EDataCal *cal, char **calobj, char **uid)
{
	ECalBackendSyncStatus result = GNOME_Evolution_Calendar_Success;
	CalObjModType mod = CALOBJ_MOD_ALL;

	g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_InvalidObject);

	d(printf("cal_backend_wcap_tasks_create_object(%p, %s)\n", backend, *calobj));

	result = cal_backend_wcap_tasks_update_objects (backend, cal, *calobj, mod, NULL);
/*
	*calobj = NULL;
*/

	return result;
}

static ECalBackendSyncStatus
cal_backend_wcap_tasks_modify_object (ECalBackendSync *backend, EDataCal *cal, const char *calobj,
			CalObjModType mod, char **old_object, char **new_object)
{
	ECalBackendSyncStatus result = GNOME_Evolution_Calendar_Success;

	g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_InvalidObject);

	d(printf("cal_backend_wcap_tasks_modify_object(%p, %s)\n", backend, calobj));

	result = cal_backend_wcap_tasks_update_objects (backend, cal, calobj, mod, old_object);

	return result;
}

static ECalBackendSyncStatus
cal_backend_wcap_tasks_remove_object (ECalBackendSync *backend, EDataCal *cal, const char *uid,
			const char *rid, CalObjModType mod, char **old_object, char **object)
{
	CalBackendWCAP *wcap = CAL_BACKEND_WCAP (backend);
	ECalComponent *comp;
	icalcomponent *icalcomp;
	ECalBackendSyncStatus status;
	guint error_code;
	char *calobj;
	char *ridstr;
	
	d(printf("cal_backend_wcap_tasks_remove_object(%p, %s, %s)\n", backend, uid, rid));

	if (!cal_backend_wcap_is_online (wcap))
		return GNOME_Evolution_Calendar_RepositoryOffline;
	if (!cal_backend_wcap_get_connection (wcap))
		return cal_backend_wcap_get_open_error (wcap);

	status = E_CAL_BACKEND_SYNC_CLASS (parent_class)->get_object_sync (backend, cal, uid, rid, &calobj);
	if (status != GNOME_Evolution_Calendar_Success)
		return status;

	icalcomp = icalcomponent_new_from_string (calobj);
	if (!icalcomp) {
		g_free (calobj);
		return GNOME_Evolution_Calendar_InvalidObject;
	}
	comp = e_cal_component_new ();
	e_cal_component_set_icalcomponent (comp, icalcomp);

	e_cal_component_get_uid (comp, &uid);
	ridstr = cal_backend_wcap_get_rid_string (comp);
	
	error_code = sunone_connection_deletetodos_by_id (
			cal_backend_wcap_get_connection (wcap),
			cal_backend_wcap_get_calid (wcap),
			uid, ridstr, 
			cal_backend_wcap_to_sunone_mod (mod, comp));
	g_free (ridstr);


	if (!SUNONE_ERROR_IS_SUCCESSFUL (error_code)) {
		g_object_unref (G_OBJECT (comp));
		g_free (calobj);
		return cal_backend_wcap_result_from_error (error_code);
	}

	cal_backend_wcap_remove_component (wcap, uid, mod, old_object);
	g_object_unref (G_OBJECT (comp));
	g_free (calobj);

	cal_backend_wcap_write_cache (wcap);

	return GNOME_Evolution_Calendar_Success;
}

static ECalBackendSyncStatus
cal_backend_wcap_tasks_receive_objects (ECalBackendSync *backend, EDataCal *cal, const char *calobj)
{
	ECalBackendSyncStatus result = GNOME_Evolution_Calendar_Success;
	CalBackendWCAP *wcap = CAL_BACKEND_WCAP (backend);
	icalcomponent *toplevel_comp;
	icalcomponent_kind kind;
	guint error_code;

	g_return_val_if_fail (calobj != NULL, GNOME_Evolution_Calendar_InvalidObject);

	d(printf("cal_backend_wcap_tasks_receive_objects(%p, %p, %s)\n", backend, cal, calobj));

	g_return_val_if_fail (IS_CAL_BACKEND_WCAP (wcap), GNOME_Evolution_Calendar_OtherError);

	if (!cal_backend_wcap_is_online (wcap))
		return GNOME_Evolution_Calendar_RepositoryOffline;
	if (!cal_backend_wcap_get_connection (wcap))
		return cal_backend_wcap_get_open_error (wcap);

	toplevel_comp = icalparser_parse_string (calobj);
	if (!toplevel_comp)
		return GNOME_Evolution_Calendar_InvalidObject;

	kind = icalcomponent_isa (toplevel_comp);
	if (kind == ICAL_VTODO_COMPONENT) {
		icalcomponent *icalcomp;

		/* Create a temporary toplevel component and put
			VTODO in it, to simplify the code below. */
		icalcomp = toplevel_comp;
		toplevel_comp = e_cal_util_new_top_level ();
		icalcomponent_add_component (toplevel_comp, icalcomp);
	} else if (kind != ICAL_VCALENDAR_COMPONENT) {
		/* We don't support this type of component */
		icalcomponent_free (toplevel_comp);
		return GNOME_Evolution_Calendar_InvalidObject;
	}

	error_code = sunone_connection_import (cal_backend_wcap_get_connection (wcap), cal_backend_wcap_get_calid (wcap), toplevel_comp);
	if (!SUNONE_ERROR_IS_SUCCESSFUL (error_code))
		result = cal_backend_wcap_result_from_error (error_code);
	cal_backend_wcap_poll_cb (wcap);

	icalcomponent_free (toplevel_comp);

	return result;
}

static void
e_cal_backend_wcap_tasks_dispose(GObject *object)
{
	if (G_OBJECT_CLASS (parent_class)->dispose)
		(*G_OBJECT_CLASS (parent_class)->dispose) (object);
}

static void
e_cal_backend_wcap_tasks_finalize(GObject *object)
{
	if (G_OBJECT_CLASS (parent_class)->finalize)
		(*G_OBJECT_CLASS (parent_class)->finalize) (object);
}

static void
cal_backend_wcap_tasks_class_init (CalBackendWCAPTasksClass *klass)
{
	GObjectClass *object_class;
	ECalBackendClass *backend_class;
	ECalBackendSyncClass *sync_class;

	object_class = G_OBJECT_CLASS (klass);
	backend_class = E_CAL_BACKEND_CLASS (klass);
	sync_class = E_CAL_BACKEND_SYNC_CLASS (klass);

	parent_class = g_type_class_peek_parent (klass);

	sync_class->create_object_sync = cal_backend_wcap_tasks_create_object;
	sync_class->modify_object_sync = cal_backend_wcap_tasks_modify_object;
	sync_class->remove_object_sync = cal_backend_wcap_tasks_remove_object;
	sync_class->receive_objects_sync = cal_backend_wcap_tasks_receive_objects;

	object_class->dispose = e_cal_backend_wcap_tasks_dispose;
	object_class->finalize = e_cal_backend_wcap_tasks_finalize;
}

static void
cal_backend_wcap_tasks_init (CalBackendWCAPTasks *klass)
{
}
