/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 2009 Intel Corporation
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * 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 Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 * Author: Travis Reitter (travis.reitter@collabora.co.uk)
 */

#include <stdlib.h>
#include <glib.h>
#include <gio/gio.h>
#include <libecal/e-cal.h>

#include "ecal-test-utils.h"

void
test_print (const gchar *format,
	    ...)
{
	va_list args;
	const gchar *debug_string;
	static gboolean debug_set = FALSE;
	static gboolean debug = FALSE;

	if (!debug_set) {
		debug_string = g_getenv ("EDS_TEST_DEBUG");
		if (debug_string) {
			debug = (g_ascii_strtoll (debug_string, NULL, 10) >= 1);
		}
		debug_set = TRUE;
	}

	if (debug) {
		va_start (args, format);
		vprintf (format, args);
		va_end (args);
	}
}

ECal*
ecal_test_utils_cal_new_from_uri (const gchar     *uri,
				  ECalSourceType  type)
{
        ECal *cal;

        test_print ("loading calendar '%s'\n", uri);
        cal = e_cal_new_from_uri (uri, type);
        if (!cal)
                g_error ("failed to create calendar: `%s'", uri);

        return cal;
}

ECal*
ecal_test_utils_cal_new_temp (gchar           **uri,
                              ECalSourceType   type)
{
        ECal *cal;
        gchar *file_template;
        gchar *uri_result;

        file_template = g_build_filename (g_get_tmp_dir (),
                        "ecal-test-XXXXXX/", NULL);
        g_mkstemp (file_template);

        uri_result = g_strconcat ("local:", file_template, NULL);
        if (!uri_result) {
                g_error ("failed to convert %s to a 'local:' URI", file_template);
        }
        g_free (file_template);

	cal = ecal_test_utils_cal_new_from_uri (uri_result, type);

        if (uri)
                *uri = g_strdup (uri_result);

        g_free (uri_result);

        return cal;
}

void
ecal_test_utils_cal_open (ECal     *cal,
                          gboolean  only_if_exists)
{
        GError *error = NULL;

        if (!e_cal_open (cal, only_if_exists, &error)) {
                const gchar *uri;

                uri = e_cal_get_uri (cal);

                g_warning ("failed to open calendar: `%s': %s", uri,
                                error->message);
                exit (1);
        }
}

static void
open_ex_cb (ECal            *cal,
	 const GError *error,
	 ECalTestClosure *closure)
{
	if (FALSE) {
	} else if (error && error->code == E_CALENDAR_STATUS_BUSY) {
		test_print ("calendar server is busy; waiting...");
		return;
	} else if (error) {
                g_warning ("failed to asynchronously remove the calendar: "
                                "status %d (%s)", error->code, error->message);
                exit (1);
        }

	closure->cal = cal;

        test_print ("successfully asynchronously removed the temporary "
                        "calendar\n");
        if (closure)
                (*closure->cb) (closure);

	g_signal_handlers_disconnect_by_func (cal, open_ex_cb, closure);
	g_free (closure);
}

void
ecal_test_utils_cal_async_open (ECal        *cal,
				gboolean     only_if_exists,
                                GSourceFunc  callback,
                                gpointer     user_data)
{
        ECalTestClosure *closure;

        closure = g_new0 (ECalTestClosure, 1);
        closure->cb = callback;
        closure->user_data = user_data;

	g_signal_connect (G_OBJECT (cal), "cal_opened_ex", G_CALLBACK (open_ex_cb), closure);
	e_cal_open_async (cal, only_if_exists);
}

void
ecal_test_utils_cal_remove (ECal *cal)
{
        GError *error = NULL;

        if (!e_cal_remove (cal, &error)) {
                g_warning ("failed to remove calendar; %s\n", error->message);
                exit (1);
        }
        test_print ("successfully removed the temporary calendar\n");

        g_object_unref (cal);
}

gchar *
ecal_test_utils_cal_get_alarm_email_address (ECal *cal)
{
        GError *error = NULL;
	gchar *address = NULL;

        if (!e_cal_get_alarm_email_address (cal, &address, &error)) {
                g_warning ("failed to get alarm email address; %s\n", error->message);
                exit (1);
        }
        test_print ("successfully got the alarm email address\n");

	return address;
}

gchar *
ecal_test_utils_cal_get_cal_address (ECal *cal)
{
        GError *error = NULL;
	gchar *address = NULL;

        if (!e_cal_get_cal_address (cal, &address, &error)) {
                g_warning ("failed to get calendar address; %s\n", error->message);
                exit (1);
        }
        test_print ("successfully got the calendar address\n");

	return address;
}

gchar *
ecal_test_utils_cal_get_ldap_attribute (ECal *cal)
{
        GError *error = NULL;
	gchar *attr = NULL;

        if (!e_cal_get_ldap_attribute (cal, &attr, &error)) {
                g_warning ("failed to get ldap attribute; %s\n", error->message);
                exit (1);
        }
        test_print ("successfully got the ldap attribute\n");

	return attr;
}

static const gchar *
b2s (gboolean value)
{
        return value ? "true" : "false";
}

void
ecal_test_utils_cal_get_capabilities (ECal *cal)
{
        test_print ("calendar capabilities:\n");
        test_print ("        One alarm only:                  %s\n"
                 "        Organizers must attend meetings: %s\n"
                 "        Organizers must accept meetings: %s\n"
                 "        Master object for recurrences:   %s\n"
                 "        Can save schedules:              %s\n"
                 "        No alarm repeat:                 %s\n"
                 "        No audio alarms:                 %s\n"
                 "        No display alarms:               %s\n"
                 "        No email alarms:                 %s\n"
                 "        No procedure alarms:             %s\n"
                 "        No task assignment:              %s\n"
                 "        No 'this and future':            %s\n"
                 "        No 'this and prior':             %s\n"
                 "        No transparency:                 %s\n"
                 "        Organizer not email address:     %s\n"
                 "        Remove alarms:                   %s\n"
                 "        Create messages:                 %s\n"
                 "        No conv. to assigned task:       %s\n"
                 "        No conv. to recurring:           %s\n"
                 "        No general options:              %s\n"
                 "        Requires send options:           %s\n"
                 "        Delegate supported:              %s\n"
                 "        No organizer required:           %s\n"
                 "        Delegate to many:                %s\n"
                 "        Has unaccepted meeting:          %s\n"
                 ,
		 b2s (e_cal_get_one_alarm_only (cal)),
		 b2s (e_cal_get_organizer_must_attend (cal)),
		 b2s (e_cal_get_organizer_must_accept (cal)),
		 b2s (e_cal_get_recurrences_no_master (cal)),
		 b2s (e_cal_get_save_schedules (cal)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_ALARM_REPEAT)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_AUDIO_ALARMS)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_DISPLAY_ALARMS)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_PROCEDURE_ALARMS)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_TASK_ASSIGNMENT)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_THISANDFUTURE)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_THISANDPRIOR)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_TRANSPARENCY)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_ORGANIZER_NOT_EMAIL_ADDRESS)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_REMOVE_ALARMS)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_CREATE_MESSAGES)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_CONV_TO_ASSIGN_TASK)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_CONV_TO_RECUR)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_GEN_OPTIONS)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_REQ_SEND_OPTIONS)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_DELEGATE_SUPPORTED)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_NO_ORGANIZER)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_DELEGATE_TO_MANY)),
		 b2s (e_cal_get_static_capability (cal,
				 CAL_STATIC_CAPABILITY_HAS_UNACCEPTED_MEETING))
		 );
}

void
ecal_test_utils_cal_assert_objects_equal_shallow (icalcomponent *a,
						  icalcomponent *b)
{
	const gchar *uid_a, *uid_b;

	if (!icalcomponent_is_valid (a) && !icalcomponent_is_valid (b)) {
		g_warning ("both components invalid");
		return;
	}

	if (!icalcomponent_is_valid (a) || !icalcomponent_is_valid (b)) {
		g_error ("exactly one of the components being compared is invalid");
	}

	uid_a = icalcomponent_get_uid (a);
	uid_b = icalcomponent_get_uid (b);
        if (g_strcmp0 (uid_a, uid_b)) {
                g_error ("icomponents not equal:\n"
			 "        uid A: '%s'\n"
			 "        uid b: '%s'\n",
			 uid_a, uid_b);
        }
}

void
ecal_test_utils_cal_assert_e_cal_components_equal (ECalComponent *a,
						   ECalComponent *b)
{
	icalcomponent *ical_a, *ical_b;
	ECalComponentTransparency transp_a, transp_b;

	ical_a = e_cal_component_get_icalcomponent (a);
	ical_b = e_cal_component_get_icalcomponent (b);
        ecal_test_utils_cal_assert_objects_equal_shallow (ical_a, ical_b);

        /* Dumping icalcomp into a string is not useful as the retrieved object
         * has some generated information like timestamps. We compare
         * member values we set during creation*/
        g_assert (e_cal_component_event_dates_match (a, b));

	e_cal_component_get_transparency (a, &transp_a);
	e_cal_component_get_transparency (b, &transp_b);
	g_assert (transp_a == transp_b);
}

icalcomponent*
ecal_test_utils_cal_get_object (ECal       *cal,
				const gchar *uid)
{
        GError *error = NULL;
	icalcomponent *component = NULL;

        if (!e_cal_get_object (cal, uid, NULL, &component, &error)) {
                g_warning ("failed to get icalcomponent object '%s'; %s\n", uid, error->message);
                exit (1);
        }
        if (!icalcomponent_is_valid (component)) {
                g_warning ("retrieved icalcomponent is invalid\n");
                exit (1);
        }
        test_print ("successfully got the icalcomponent object '%s'\n", uid);

	return component;
}

void
ecal_test_utils_cal_modify_object (ECal          *cal,
				   icalcomponent *component,
				   CalObjModType  mod_type)
{
        GError *error = NULL;

        if (!icalcomponent_is_valid (component)) {
                g_warning (G_STRLOC ": icalcomponent argument is invalid\n");
                exit (1);
        }
        if (!e_cal_modify_object (cal, component, mod_type, &error)) {
                g_warning ("failed to modify icalcomponent object; %s\n", error->message);
                exit (1);
        }
        test_print ("successfully modified the icalcomponent object\n");
}

void
ecal_test_utils_cal_remove_object (ECal       *cal,
				   const gchar *uid)
{
        GError *error = NULL;

        if (!e_cal_remove_object (cal, uid, &error)) {
                g_warning ("failed to remove icalcomponent object '%s'; %s\n", uid, error->message);
                exit (1);
        }
        test_print ("successfully remoed the icalcomponent object '%s'\n", uid);
}

icalcomponent*
ecal_test_utils_cal_get_default_object (ECal *cal)
{
        GError *error = NULL;
	icalcomponent *component = NULL;

        if (!e_cal_get_default_object (cal, &component, &error)) {
                g_warning ("failed to get default icalcomponent object; %s\n", error->message);
                exit (1);
        }
        if (!icalcomponent_is_valid (component)) {
                g_warning ("default icalcomponent is invalid\n");
                exit (1);
        }
        test_print ("successfully got the default icalcomponent object\n");

	return component;
}

GList*
ecal_test_utils_cal_get_object_list (ECal       *cal,
				     const gchar *query)
{
        GError *error = NULL;
	GList *objects = NULL;

        if (!e_cal_get_object_list (cal, query, &objects, &error)) {
                g_warning ("failed to get list of icalcomponent objects for query '%s'; %s\n", query, error->message);
                exit (1);
        }
        test_print ("successfully got list of icalcomponent objects for the query '%s'\n", query);

	return objects;
}

GList*
ecal_test_utils_cal_get_objects_for_uid (ECal       *cal,
					 const gchar *uid)
{
        GError *error = NULL;
	GList *objects = NULL;

        if (!e_cal_get_objects_for_uid (cal, uid, &objects, &error)) {
                g_warning ("failed to get icalcomponent objects for UID '%s'; %s\n", uid, error->message);
                exit (1);
        }
        test_print ("successfully got objects for the icalcomponent with UID '%s'\n", uid);

	return objects;
}

gchar *
ecal_test_utils_cal_create_object (ECal          *cal,
				   icalcomponent *component)
{
        GError *error = NULL;
	gchar *uid = NULL;
	gchar *ical_string = NULL;

        if (!icalcomponent_is_valid (component)) {
                g_warning ("supplied icalcomponent is invalid\n");
                exit (1);
        }

        if (!e_cal_create_object (cal, component, &uid, &error)) {
                g_warning ("failed to get create an icalcomponent object; %s\n", error->message);
                exit (1);
        }

	ical_string = icalcomponent_as_ical_string (component);
        test_print ("successfully created icalcomponent object '%s'\n%s\n", uid,
			ical_string);
	g_free (ical_string);

	return uid;
}

static void
cal_set_mode_cb (ECal            *cal,
		 ECalendarStatus  status,
		 CalMode          mode,
		 ECalTestClosure *closure)
{
	if (FALSE) {
	} else if (status == E_CALENDAR_STATUS_BUSY) {
		test_print ("calendar server is busy; waiting...");
		return;
	} else if (status != E_CALENDAR_STATUS_OK) {
                g_warning ("failed to asynchronously remove the calendar: "
                                "status %d", status);
                exit (1);
        }

	closure->mode = mode;

        test_print ("successfully set the calendar mode to %d\n", mode);
        if (closure)
                (*closure->cb) (closure);

	g_signal_handlers_disconnect_by_func (cal, cal_set_mode_cb, closure);
	g_free (closure);
}

void
ecal_test_utils_cal_set_mode (ECal        *cal,
			      CalMode      mode,
			      GSourceFunc  callback,
			      gpointer     user_data)
{
        ECalTestClosure *closure;

        closure = g_new0 (ECalTestClosure, 1);
        closure->cb = callback;
        closure->user_data = user_data;

	g_signal_connect (G_OBJECT (cal), "cal_set_mode", G_CALLBACK (cal_set_mode_cb), closure);
	e_cal_set_mode (cal, mode);
}

void
ecal_test_utils_create_component (ECal           *cal,
				  const gchar     *dtstart,
				  const gchar     *dtstart_tzid,
				  const gchar     *dtend,
				  const gchar     *dtend_tzid,
				  const gchar     *summary,
				  ECalComponent **comp_out,
				  gchar          **uid_out)
{
        ECalComponent *comp;
        icalcomponent *icalcomp;
        struct icaltimetype tt;
        ECalComponentText text;
        ECalComponentDateTime dt;
        gchar *uid;

        comp = e_cal_component_new ();
        /* set fields */
        e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
        text.value = summary;
        text.altrep = NULL;
        e_cal_component_set_summary (comp, &text);
        tt = icaltime_from_string (dtstart);
        dt.value = &tt;
        dt.tzid = dtstart_tzid;
        e_cal_component_set_dtstart (comp, &dt);
        tt = icaltime_from_string (dtend);
        dt.value = &tt;
        dt.tzid = dtend_tzid;
        e_cal_component_set_dtend (comp, &dt);
        e_cal_component_set_transparency (comp, E_CAL_COMPONENT_TRANSP_OPAQUE);

        e_cal_component_commit_sequence (comp);
        icalcomp = e_cal_component_get_icalcomponent (comp);

        uid = ecal_test_utils_cal_create_object (cal, icalcomp);
        e_cal_component_commit_sequence (comp);

        *comp_out = comp;
        *uid_out = uid;
}

void
ecal_test_utils_cal_component_set_icalcomponent (ECalComponent *e_component,
						 icalcomponent *component)
{
        if (!e_cal_component_set_icalcomponent (e_component, component)) {
                g_error ("Could not set icalcomponent\n");
        }
}

icaltimezone*
ecal_test_utils_cal_get_timezone (ECal       *cal,
				  const gchar *tzid)
{
        GError *error = NULL;
	icaltimezone *zone = NULL;

        if (!e_cal_get_timezone (cal, tzid, &zone, &error)) {
                g_warning ("failed to get icaltimezone* for ID '%s'; %s\n", tzid, error->message);
                exit (1);
        }
        test_print ("successfully got icaltimezone* for ID '%s'\n", tzid);

	return zone;
}

void
ecal_test_utils_cal_add_timezone (ECal         *cal,
				  icaltimezone *zone)
{
        GError *error = NULL;
	const gchar *name;

	name = icaltimezone_get_display_name (zone);

        if (!e_cal_add_timezone (cal, zone, &error)) {
                g_warning ("failed to add icaltimezone '%s'; %s\n", name, error->message);
                exit (1);
        }
        test_print ("successfully added icaltimezone '%s'\n", name);
}

void
ecal_test_utils_cal_set_default_timezone (ECal         *cal,
					  icaltimezone *zone)
{
        GError *error = NULL;
	const gchar *name;

	name = icaltimezone_get_display_name (zone);

        if (!e_cal_set_default_timezone (cal, zone, &error)) {
                g_warning ("failed to set default icaltimezone '%s'; %s\n", name, error->message);
                exit (1);
        }
        test_print ("successfully set default icaltimezone '%s'\n", name);
}

GList*
ecal_test_utils_cal_get_free_busy (ECal   *cal,
				   GList  *users,
				   time_t  start,
				   time_t  end)
{
	GList *free_busy = NULL;
	GList *l = NULL;
	GError *error = NULL;

	if (!e_cal_get_free_busy (cal, users, start, end, &free_busy, &error)) {
		g_error ("Test free/busy : Could not retrieve free busy information :  %s\n", error->message);
	}
	if (free_busy) {
		test_print ("Printing free/busy information\n");

		for (l = free_busy; l; l = l->next) {
			gchar *comp_string;
			ECalComponent *comp = E_CAL_COMPONENT (l->data);

			comp_string = e_cal_component_get_as_string (comp);
			test_print ("%s\n", comp_string);
			g_object_unref (comp);
			g_free (comp_string);
		}
	} else {
		g_error ("got empty free/busy information");
	}

	return free_busy;
}

void
ecal_test_utils_cal_send_objects (ECal           *cal,
				  icalcomponent  *component,
				  GList         **users,
				  icalcomponent **component_final)
{
	GList *l = NULL;
	GError *error = NULL;

	if (!e_cal_send_objects (cal, component, users, component_final, &error)) {
		g_error ("sending objects: %s\n", error->message);
	}

	test_print ("successfully sent the objects to the following users:\n");
	if (g_list_length (*users) <= 0) {
		test_print ("        (none)\n");
		return;
	}
	for (l = *users; l; l = l->next) {
		test_print ("        %s\n", (const gchar *) l->data);
	}
}

void
ecal_test_utils_cal_receive_objects (ECal          *cal,
				     icalcomponent *component)
{
	GError *error = NULL;

	if (!e_cal_receive_objects (cal, component, &error)) {
		g_error ("receiving objects: %s\n", error->message);
	}

	test_print ("successfully received the objects\n");
}

ECalView*
ecal_test_utils_get_query (ECal       *cal,
			   const gchar *sexp)
{
	GError *error = NULL;
	ECalView *query = NULL;

	if (!e_cal_get_query (cal, sexp, &query, &error)) {
		g_error (G_STRLOC ": Unable to obtain calendar view: %s\n",
				error->message);
	}
	test_print ("successfully retrieved calendar view for query '%s'", sexp);

	return query;
}
