/* sunone-config-listener.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>
 *
 */

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

#include <string.h>
#include <glib/gi18n.h>
#include <e-util/e-dialog-utils.h>
#include "sunone-account.h"
#include "sunone-i18n.h"
#include "sunone-config-listener.h"
#include "sunone-util.h"
#include "sunone-marshal.h"
#include "sunone-component.h"

struct _SunOneConfigListenerPrivate {
	GConfClient *gconf;
	guint idle_id;
};

enum {
	SUNONE_ACCOUNT_CREATED,
	SUNONE_ACCOUNT_REMOVED,
	SUNONE_ACCOUNT_CHANGED,
	LAST_SIGNAL
};

static guint signals [LAST_SIGNAL] = { 0 };

#define PARENT_TYPE E_TYPE_ACCOUNT_LIST
static GObjectClass *parent_class = NULL;

static void dispose (GObject *object);
static void finalize (GObject *object);

static void add_account(EAccountList *account_list, EAccount *account);
static void change_account(EAccountList *account_list, EAccount *account);
static void remove_account(EAccountList *account_list, EAccount *account);

static ESourceList *source_list_cal = NULL, *source_list_task = NULL;

static void 
class_init (GObjectClass *object_class)
{
	EAccountListClass *e_account_list_class = E_ACCOUNT_LIST_CLASS (object_class);
	
	parent_class =  g_type_class_ref (PARENT_TYPE);
	
	/* virtual method override */
	object_class->dispose = dispose;
	object_class->finalize = finalize;

	e_account_list_class->account_added = add_account;
	e_account_list_class->account_changed = change_account;
	e_account_list_class->account_removed = remove_account;

	/* signal */
	signals[SUNONE_ACCOUNT_CREATED] =
		g_signal_new ("sunone_account_created",
				G_OBJECT_CLASS_TYPE (object_class),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (SunOneConfigListenerClass, sunone_account_created),
				NULL, NULL,
				sunone_marshal_NONE__POINTER,
				G_TYPE_NONE, 1,
				G_TYPE_POINTER);
	signals[SUNONE_ACCOUNT_REMOVED] =
		g_signal_new ("sunone_account_removed",
				G_OBJECT_CLASS_TYPE (object_class),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (SunOneConfigListenerClass, sunone_account_removed),
				NULL, NULL,
				sunone_marshal_NONE__POINTER,
				G_TYPE_NONE, 1,
				G_TYPE_POINTER);
	signals[SUNONE_ACCOUNT_CHANGED] =
		g_signal_new ("sunone_account_changed",
				G_OBJECT_CLASS_TYPE (object_class),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (SunOneConfigListenerClass, sunone_account_changed),
				NULL, NULL,
				sunone_marshal_NONE__POINTER,
				G_TYPE_NONE, 1,
				G_TYPE_POINTER);
}

static void 
init (GObject *object)
{
	char *conf_key_cals = "/apps/evolution/calendar/sources";
	char *conf_key_tasks = "/apps/evolution/tasks/sources";
	SunOneConfigListener *config_listener = SUNONE_CONFIG_LISTENER (object);

	config_listener->priv = g_new0 (SunOneConfigListenerPrivate, 1);

	if (!source_list_cal)
		source_list_cal = e_source_list_new_for_gconf_default (conf_key_cals);
	if (!source_list_task)
		source_list_task = e_source_list_new_for_gconf_default (conf_key_tasks);

}

static void 
dispose (GObject *object)
{
	SunOneConfigListener *config_listener = SUNONE_CONFIG_LISTENER (object);
	
	if (config_listener->priv->idle_id) {
		g_source_remove (config_listener->priv->idle_id);
		config_listener->priv->idle_id = 0;
	}

	if (config_listener->priv->gconf) {
		g_object_unref (config_listener->priv->gconf);
		config_listener->priv->gconf = NULL;
	}

	G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void 
finalize (GObject *object)
{
	SunOneConfigListener *config_listener = SUNONE_CONFIG_LISTENER (object);

	g_free (config_listener->priv);

	if (source_list_cal) {
		g_object_unref (source_list_cal);
		source_list_cal = NULL;
	}
	if (source_list_task) {
		g_object_unref (source_list_task);
		source_list_task = NULL;
	}

	G_OBJECT_CLASS (parent_class)->finalize (object);

}

static gboolean
is_active_sunone_account (EAccount *account)
{
	if (!account->enabled)
		return FALSE;
	if (!account->source || !account->source->url)
		return FALSE;
	return (strncmp (account->source->url, "wcap://", 7) == 0);
}

static void
add_esource (SunOneAccount *account, const char *folder_name,
		const char *physical_uri, ESourceList **source_list)
{
	ESource *source;
	ESourceGroup *source_group;
	char *relative_uri;
	int ret;

	g_return_if_fail (strlen(physical_uri) > 7);

	if (strncmp (physical_uri, "wcap://", 7))
		return;

	relative_uri = g_strdup (physical_uri + strlen ("wcap://"));

	if ((source_group = e_source_list_peek_group_by_name (*source_list,	sunone_account_get_name (account))) == NULL) {
		source_group = e_source_group_new (sunone_account_get_name (account), "wcap://");
		if (!e_source_list_add_group (*source_list, source_group, -1)) {
			g_object_unref (source_group);
			g_free (relative_uri);
			return;
		}
		source = e_source_new (folder_name, relative_uri);
		ret = e_source_group_add_source (source_group, source, -1);

		g_object_unref (source);
		g_object_unref (source_group);
	} else {
		if ((source = e_source_group_peek_source_by_name (source_group, folder_name)) == NULL) {
			source = e_source_new (folder_name, relative_uri);
			ret = e_source_group_add_source (source_group, source, -1);
			
			g_object_unref (source);
		} else {
			/*If exists, update relative_uri only*/
			e_source_set_relative_uri (source, relative_uri);
		}
	}

	g_free (relative_uri);
}

static void
remove_esource (SunOneAccount *account, const char *physical_uri, ESourceList **source_list, gboolean is_account)
{
	ESourceGroup *group;
	ESource *source;
	GSList *groups;
	GSList *sources;
	char *relative_uri = NULL;
	const char *source_uid;

	relative_uri = g_strdup (physical_uri + strlen ("wcap://"));

	for (groups = e_source_list_peek_groups (*source_list); 
			groups != NULL; groups = g_slist_next (groups)) {
		group = E_SOURCE_GROUP (groups->data);

		if (strcmp (e_source_group_peek_name (group), sunone_account_get_name (account)) == 0
				&&
			strcmp (e_source_group_peek_base_uri (group), "wcap://") == 0) {
			if (is_account) {
				e_source_list_remove_group (*source_list, group);
				break;
			}

			sources = e_source_group_peek_sources (group);
			for ( ; sources != NULL; sources = g_slist_next (sources)) {
				source = E_SOURCE (sources->data);
				if (strcmp (e_source_peek_relative_uri (source), relative_uri) == 0) {
					source_uid = e_source_peek_uid (source);
					e_source_group_remove_source_by_uid (group, source_uid);
					break;
				}
			}
		}
	}

	if (relative_uri)
		g_free (relative_uri);
}

static void
rename_esource (SunOneAccount *account, const char *folder_name, const char *physical_uri, ESourceList **source_list)
{
	ESourceGroup *group;
	ESource *source;
	GSList *groups;
	GSList *sources;
	gboolean found_group;
	char *relative_uri = NULL;
	const char *source_uid;

	relative_uri = g_strdup (physical_uri + strlen ("wcap://"));
	groups = e_source_list_peek_groups (*source_list);
	found_group = FALSE;

	for ( ; groups != NULL && !found_group; groups = g_slist_next (groups)) {
		group = E_SOURCE_GROUP (groups->data);

		if (strcmp (e_source_group_peek_name (group), sunone_account_get_name (account)) == 0
				&&
			strcmp (e_source_group_peek_base_uri (group), "wcap://") == 0) {
			sources = e_source_group_peek_sources (group);

			for ( ; sources != NULL; sources = g_slist_next (sources)) {
				source = E_SOURCE (sources->data);
				if (strcmp (e_source_peek_relative_uri (source), relative_uri) == 0) {
						source_uid = e_source_peek_uid (source);
						e_source_set_name (source, folder_name);
					found_group = TRUE;
					break;
				}
			}
		}
	}

	if (relative_uri)
		g_free (relative_uri);
}

void
sunone_folder_tree_sync (void)
{
	e_source_list_sync (source_list_cal, NULL);
	e_source_list_sync (source_list_task, NULL);
}

void
add_source (SunOneAccount *account, const char *calid, const char *display_name, gboolean personal)
{
	char *physical_uri;

	if (personal) {
		physical_uri = sunone_account_get_physical_uri (account, calid, "calendar");
		add_esource (account, _("Personal Calendar"), physical_uri, &source_list_cal);
		g_free (physical_uri);

		physical_uri = sunone_account_get_physical_uri (account, calid, "tasks");
		add_esource (account, _("Personal Task"), physical_uri, &source_list_task);
		g_free (physical_uri);
	} else {
		physical_uri = sunone_account_get_physical_uri (account, calid, "calendar");
		add_esource (account, display_name, physical_uri, &source_list_cal);
		g_free (physical_uri);

		physical_uri = sunone_account_get_physical_uri (account, calid, "tasks");
		add_esource (account, display_name, physical_uri, &source_list_task);
		g_free (physical_uri);
	}
}

static void
remove_unused_sources(SunOneAccount *account, SunOneConnectionPreferences *preference)
{
	ESourceGroup *group;
	const char *source_name;
	const char *pref_name;
	GSList *sources;

	g_return_if_fail ((account != NULL) && (preference != NULL));

	group = e_source_list_peek_group_by_name (source_list_cal,
						sunone_account_get_name (account));
	if (group) {
		for (sources = e_source_group_peek_sources (group); sources != NULL; sources = sources->next) {
			ESource *source = E_SOURCE (sources->data);
			GList *prefs;

			if (!source)
				continue;

			source_name = e_source_peek_name (source);
			for (prefs = preference->subscriptions; prefs != NULL; prefs = prefs->next) {
				SunOneCalendarSubscription *sub = (SunOneCalendarSubscription *) prefs->data;
				if (!sub)
					continue;

				pref_name = sub->display_name && *(sub->display_name) ? sub->display_name : sub->calid;
				if (!strcmp (source_name, pref_name))
					break;
			}
			if (!prefs)
				/*could not find the subscription, remove the unused esource*/
				e_source_group_remove_source_by_uid (group, e_source_peek_uid (source));
		}
	}

	group = e_source_list_peek_group_by_name (source_list_task,
						sunone_account_get_name (account));
	if (group) {
		for (sources = e_source_group_peek_sources (group); sources != NULL; sources = sources->next) {
			ESource *source = E_SOURCE (sources->data);
			GList *prefs;

			if (!source)
				continue;

			source_name = e_source_peek_name (source);
			for (prefs = preference->subscriptions; prefs != NULL; prefs = prefs->next) {
				SunOneCalendarSubscription *sub = (SunOneCalendarSubscription *) prefs->data;
				if (!sub)
					continue;

				pref_name = sub->display_name && *(sub->display_name) ? sub->display_name : sub->calid;
				if (!strcmp (source_name, pref_name))
					break;
			}
			if (!prefs)
				/*could not find the subscription, remove the unused esource*/
				e_source_group_remove_source_by_uid (group, e_source_peek_uid (source));
		}
	}

}

void
add_sources (SunOneAccount *account, SunOneConnectionPreferences *prefs)
{
	SunOneCalendarSubscription *sub;
	char *calid;
	char *display_name;
	char *physical_uri;
	GList *l;

	g_return_if_fail (account != NULL);

	remove_unused_sources (account, prefs);
	for (l = prefs->subscriptions; l != NULL; l = l->next) {
		sub = (SunOneCalendarSubscription *) l->data;
		if (!sub)
			continue;

		calid = sub->calid;
		display_name = sub->display_name && *(sub->display_name) ? sub->display_name : sub->calid;
		physical_uri = sunone_account_get_physical_uri (account, calid, "calendar");
		add_esource (account, display_name, physical_uri, &source_list_cal);
		g_free (physical_uri);

		physical_uri = sunone_account_get_physical_uri (account, calid, "tasks");
		add_esource (account, display_name, physical_uri, &source_list_task);
		g_free (physical_uri);
	}
}

void
remove_source (SunOneAccount *account, const char *calid, gboolean is_account)
{
	char *physical_uri;

	physical_uri = sunone_account_get_physical_uri (account, calid, "calendar");
	remove_esource (account, physical_uri, &source_list_cal, is_account);
	g_free (physical_uri);

	physical_uri = sunone_account_get_physical_uri (account, calid, "tasks");
	remove_esource (account, physical_uri, &source_list_task, is_account);
	g_free (physical_uri);
}

void
rename_source (SunOneAccount *account, const char *calid, const char *display_name, gboolean is_account)
{
	char *physical_uri;

	physical_uri = sunone_account_get_physical_uri (account, calid, "calendar");
	rename_esource (account, display_name, physical_uri, &source_list_cal);
	g_free (physical_uri);

	physical_uri = sunone_account_get_physical_uri (account, calid, "tasks");
	rename_esource (account, display_name, physical_uri, &source_list_task);
	g_free (physical_uri);
}


static void
add_account (EAccountList *account_list, EAccount *account)
{
	SunOneConfigListener *config_listener = SUNONE_CONFIG_LISTENER (account_list);
	SunOneAccount *sunone_account;

	if (!is_active_sunone_account (account))
		return;

	/* New account! Yippee! */
	sunone_account = sunone_account_new (account_list, account);
	if (!sunone_account) {
		g_warning ("Could not parse sunone uri '%s'",
			   account->source->url);
		return;
	}

	g_signal_emit (config_listener, signals[SUNONE_ACCOUNT_CREATED], 0,
		       sunone_account);

	g_object_unref (sunone_account);
}

static void
change_account (EAccountList *account_list, EAccount *account)
{
	SunOneAccount *old_account;

	old_account =  sunone_component_get_account_from_uid (global_sunone_component, account->uid);
	/* change account is not allowed when connecting */
	if (old_account && sunone_account_is_connecting (old_account)) {
		g_object_unref (old_account);
		return;
	}

	if (old_account && !is_active_sunone_account (account)) {
		/* If account is removed or settings is changed,
		 * remove the old sunone account.
		 */
		g_object_unref (old_account);
		remove_account (account_list, account);
	} else if (!old_account && is_active_sunone_account (account)) {
		/* Converted from a non-sunone account or 
		 * settings was changed, create a new one.
		 */
		add_account (account_list, account);
	} else if (is_active_sunone_account (account)){
		SunOneAccount *sunone_account;

		g_object_unref (old_account);
		sunone_account = sunone_account_new (account_list, account);
		if (sunone_account) {
			g_signal_emit (SUNONE_CONFIG_LISTENER (account_list), signals[SUNONE_ACCOUNT_CHANGED], 0,
					   sunone_account);

			g_object_unref (sunone_account);

			e_notice (NULL, GTK_MESSAGE_WARNING,
				  _("Some changes to Sun JESCS account configuration will "
					"take\nplace after you quit and restart Evolution."));
		}

	}

	return;

}

static void
remove_account (EAccountList *account_list, EAccount *account)
{
	SunOneConfigListener *config_listener =	SUNONE_CONFIG_LISTENER (account_list);
	SunOneAccount *sunone_account;

	sunone_account =  sunone_component_get_account_from_uid (global_sunone_component, account->uid);
	/* It might not be a sunone account */
	if (!sunone_account)
		return;

	/* remove account is not allowed when connecting */
	if (sunone_account_is_connecting (sunone_account)) {
		e_notice (NULL, GTK_MESSAGE_WARNING,
			_("The account is connecting now. It will be deleted after you restart Evolution."));
		g_object_unref (sunone_account);
		return;
	}

	g_signal_emit (config_listener,
		       signals[SUNONE_ACCOUNT_REMOVED], 0,
		       sunone_account);

	g_object_unref (sunone_account);

}

GType
sunone_config_listener_get_type (void)
{
	static GType sunone_config_listener_type  = 0;

	if (!sunone_config_listener_type) {
		static GTypeInfo info = {
                        sizeof (SunOneConfigListenerClass),
                        (GBaseInitFunc) NULL,
                        (GBaseFinalizeFunc) NULL,
                        (GClassInitFunc) class_init,
                        NULL, NULL,
                        sizeof (SunOneConfigListener),
                        0,
                        (GInstanceInitFunc) init
                };
		sunone_config_listener_type = g_type_register_static (PARENT_TYPE, "SunOneConfigListener", &info, 0);
	}

	return sunone_config_listener_type;
}

gboolean
sunone_config_listener_construct_account (SunOneConfigListener *config_listener)
{
	e_account_list_construct (E_ACCOUNT_LIST (config_listener),
				  config_listener->priv->gconf);
	return FALSE;
}

SunOneConfigListener*
sunone_config_listener_new ()
{
	SunOneConfigListener *config_listener;
       
	config_listener = g_object_new (SUNONE_TYPE_CONFIG_LISTENER, NULL);
	config_listener->priv->gconf = gconf_client_get_default ();

	return config_listener;

}

