/* sml_data_sync.c
 *
 * Copyright (C) 2008-2009 Michael Bell <michael.bell@opensync.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 */

#include "sml_data_sync_private.h"
#include "sml_data_sync_session_internals.h"
#include "sml_data_sync_data_store_internals.h"
#include "sml_data_sync_enum_types.h"
#include "sml_data_sync_defines.h"
#include "data_sync_client.h"
#include "data_sync_server.h"
#include "data_sync_devinf.h"
#include "data_sync_loop.h"
#include "transport.h"

#include "../sml_error_internals.h"
#include "../sml_defines.h"

G_DEFINE_TYPE (SmlDataSync, sml_data_sync, G_TYPE_OBJECT)

enum
{
	PROP_0,
	PROP_TRANSPORT_TYPE,
	PROP_SESSION_TYPE
};

static void
sml_data_sync_get_property (GObject    *object,
                            guint       property_id,
                            GValue     *value,
                            GParamSpec *pspec)
{
	switch (property_id) {
	case PROP_TRANSPORT_TYPE:
		g_value_set_enum (value, SML_DATA_SYNC (object)->priv->tsp_type);
		break;
	case PROP_SESSION_TYPE:
		g_value_set_enum (value, SML_DATA_SYNC (object)->priv->session_type);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
	}
}

static void
sml_data_sync_set_property (GObject      *object,
                            guint         property_id,
                            const GValue *value,
                            GParamSpec   *pspec)
{
	switch (property_id) {
	case PROP_TRANSPORT_TYPE:
		SML_DATA_SYNC (object)->priv->tsp_type = g_value_get_enum (value);
		break;
	case PROP_SESSION_TYPE:
		SML_DATA_SYNC(object)->priv->session_type = g_value_get_enum (value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
	}
}

static void
sml_data_sync_free_session(gpointer key)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, key);
	SmlSession *session = key;
	smlSessionUnref(session);
	smlTrace(TRACE_EXIT, "%s", __func__);
}

static void
sml_data_sync_free_data_sync_session(gpointer key)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, key);
	SmlDataSyncSession *session = key;
	g_object_unref(session);
	smlTrace(TRACE_EXIT, "%s", __func__);
}

static void
sml_data_sync_finalize (GObject *object)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, object);
	SmlDataSync *self = SML_DATA_SYNC (object);

	/* stop the dispatcher loop */
	if (self->priv->thread)
		sml_data_sync_loop_stop(self);

	/* stop and free manager */
	if (self->priv->manager) {
		smlManagerStop(self->priv->manager);
		smlManagerFree(self->priv->manager);
	}
	if (self->priv->manager_mutex)
		g_mutex_free(self->priv->manager_mutex);

	/* cleanup transport */
	if (self->priv->tsp) {
		GError *error = NULL;
		if (self->priv->session_type == SML_SESSION_TYPE_CLIENT &&
		    SML_DATA_SYNC_STATE_CONNECTED <= self->priv->state &&
		    self->priv->state < SML_DATA_SYNC_STATE_DISCONNECTED &&
		    !smlTransportDisconnect(self->priv->tsp, NULL, &error))
		{
			/* just ignore the error */
			smlTrace(TRACE_ERROR, "%s: %s", __func__, error->message);
			g_error_free(error);
			error = NULL;
		}
		/* FIXME: we have to wait here for the disconnect ... */
		if (self->priv->state >= SML_DATA_SYNC_STATE_INITIALIZED &&
		    !smlTransportFinalize(self->priv->tsp, &error)) {
			/* just ignore the error */
			g_warning("%s: %s", __func__, error->message);
			smlTrace(TRACE_ERROR, "%s: %s", __func__, error->message);
			g_error_free(error);
			error = NULL;
		}
		smlTransportFree(self->priv->tsp);
		self->priv->tsp = NULL;
	}

	/* cleanup configuration */
	if (self->priv->url)
		smlSafeCFree(&(self->priv->url));
	if (self->priv->remote) {
		g_object_unref(self->priv->remote);
		self->priv->remote = NULL;
	}
	if (self->priv->local) {
		g_object_unref(self->priv->local);
		self->priv->local = NULL;
	}
	if (self->priv->username)
		smlSafeCFree(&(self->priv->username));
	if (self->priv->password)
		smlSafeCFree(&(self->priv->password));
	if (self->priv->fake_device)
		smlSafeCFree(&(self->priv->fake_device));
	if (self->priv->fake_manufacturer)
		smlSafeCFree(&(self->priv->fake_manufacturer));
	if (self->priv->fake_model)
		smlSafeCFree(&(self->priv->fake_model));
	if (self->priv->fake_software_version)
		smlSafeCFree(&(self->priv->fake_software_version));

	/* cleanup data stores */
	while(self->priv->data_stores) {
		SmlDataSyncDataStore *datastore = self->priv->data_stores->data;
		sml_data_sync_data_store_set_data_sync(datastore, NULL);
		self->priv->data_stores = g_list_remove(self->priv->data_stores, datastore);
		g_object_unref(datastore);
	}

	/* cleanup sessions */
	g_hash_table_remove_all(self->priv->data_sync_sessions);
	g_hash_table_remove_all(self->priv->sessions);
	g_hash_table_unref(self->priv->data_sync_sessions);
	g_hash_table_unref(self->priv->sessions);
	self->priv->data_sync_sessions = NULL;
	self->priv->sessions = NULL;
	if (self->priv->session_mutex)
		g_mutex_free(self->priv->session_mutex);

	/* cleanup authentication */
	if (self->priv->auth)
		smlAuthFree(self->priv->auth);

	/* cleanup device information */
	if (self->priv->local_dev_inf) {
		g_object_unref(self->priv->local_dev_inf);
		self->priv->local_dev_inf = NULL;
	}
	if (self->priv->agent) {
		smlDevInfAgentFree(self->priv->agent);
		self->priv->agent = NULL;
	}

	/* reset everythig to NULL */

	self->priv->tsp_type = SML_TRANSPORT_UNKNOWN;
	self->priv->session_type = SML_SESSION_TYPE_UNKNOWN;
	G_OBJECT_CLASS (sml_data_sync_parent_class)->finalize (object);

	smlTrace(TRACE_EXIT, "%s", __func__, object);
}

static void
sml_data_sync_class_init (SmlDataSyncClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	g_type_class_add_private (klass, sizeof (SmlDataSyncPrivate));

	object_class->get_property = sml_data_sync_get_property;
	object_class->set_property = sml_data_sync_set_property;
	object_class->finalize     = sml_data_sync_finalize;

	/**
	 * SmlDataSync:tsp_type:
	 *
	 * The transport type property.
	 */
	g_object_class_install_property (object_class,
	                                 PROP_TRANSPORT_TYPE,
	                                 g_param_spec_enum ("tsp_type",
	                                                    "transport type",
	                                                    "transport type",
	                                                    SML_DATA_SYNC_TYPE_TRANSPORT_TYPE,
	                                                    SML_TRANSPORT_UNKNOWN,
	                                                    G_PARAM_READWRITE));
	/**
	 * SmlDataSync:session_type:
	 *
	 * The session type property.
	 */
	g_object_class_install_property (object_class,
	                                 PROP_SESSION_TYPE,
	                                 g_param_spec_enum ("session_type",
	                                                    "session type",
	                                                    "session type",
	                                                    SML_DATA_SYNC_TYPE_SESSION_TYPE,
	                                                    SML_SESSION_TYPE_UNKNOWN,
	                                                    G_PARAM_READWRITE));

}

static void
sml_data_sync_init (SmlDataSync *self)
{
	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
	                                          SML_TYPE_DATA_SYNC,
	                                          SmlDataSyncPrivate);

	self->priv->version = SML_VERSION_11;
	self->priv->state = SML_DATA_SYNC_STATE_NEW;

	self->priv->use_number_of_changes = TRUE;
	self->priv->use_timestamp_anchor = TRUE;
	self->priv->max_obj_size = SML_DEFAULT_MAX_OBJ_SIZE;
	self->priv->max_msg_size = SML_DEFAULT_MAX_MSG_SIZE;
	self->priv->max_msg_changes = SML_DEFAULT_MAX_MSG_CHANGES;
	self->priv->sessions = g_hash_table_new_full(g_direct_hash,
	                                             g_direct_equal,
	                                             sml_data_sync_free_session,
	                                             sml_data_sync_free_data_sync_session);
	self->priv->data_sync_sessions = g_hash_table_new_full(g_direct_hash,
	                                                       g_direct_equal,
	                                                       sml_data_sync_free_data_sync_session,
	                                                       sml_data_sync_free_data_sync_session);
}

/**
 * sml_data_sync_new:
 *
 * Creates a new instance of #SmlDataSync.
 *
 * Return value: the newly created #SmlDataSync instance
 */
SmlDataSync*
sml_data_sync_new (void)
{
	smlTrace(TRACE_INTERNAL, "%s", __func__);
	return g_object_new (SML_TYPE_DATA_SYNC, NULL);
}

/**
 * sml_data_sync_get_transport_type:
 * @self: A #SmlDataSync
 *
 * Gets the transport type property.
 *
 * Return value: 
 */
SmlTransportType
sml_data_sync_get_transport_type (SmlDataSync *self)
{
	g_return_val_if_fail (SML_IS_DATA_SYNC (self), SML_TRANSPORT_UNKNOWN);
	return self->priv->tsp_type;
}

/**
 * sml_data_sync_set_transport_type:
 * @self: A #SmlDataSync
 * @missing_data:
 *
 * Sets the transport type property.
 */
gboolean
sml_data_sync_set_transport_type (SmlDataSync *self,
                                  SmlTransportType type,
                                  GError **error)
{
	sml_return_val_error_if_fail (SML_IS_DATA_SYNC (self), FALSE, error, SML_ERROR_GENERIC, "There must be a SmlDataSync object.");
	self->priv->tsp_type = type;

	self->priv->tsp = smlTransportNew(self->priv->tsp_type, error);
	if (!self->priv->tsp)
		return FALSE;
	switch(self->priv->tsp_type)
	{
		case SML_TRANSPORT_OBEX_CLIENT:
			self->priv->func_tsp_init    = smlDataSyncTransportObexClientInit;
			self->priv->func_tsp_connect = smlDataSyncTransportObexClientConnect;
			break;
		case SML_TRANSPORT_OBEX_SERVER:
			self->priv->func_tsp_init    = NULL;
			self->priv->func_tsp_connect = NULL;
			break;
		case SML_TRANSPORT_HTTP_SERVER:
			self->priv->func_tsp_init    = smlDataSyncTransportHttpServerInit;
			self->priv->func_tsp_connect = NULL;
			break;
		case SML_TRANSPORT_HTTP_CLIENT:
			self->priv->func_tsp_init    = smlDataSyncTransportHttpClientInit;
			self->priv->func_tsp_connect = smlDataSyncTransportHttpClientConnect;
			break;
		default:
			g_set_error(error, SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION,
				"Unknown transport type %d used in %s.",
				self->priv->tsp_type, __func__);
			return FALSE;;
			break;
	}
	return TRUE;
}

/**
 * sml_data_sync_get_session_type:
 * @self: A #SmlDataSync
 *
 * Gets the session type property.
 *
 * Return value: 
 */
SmlSessionType
sml_data_sync_get_session_type (SmlDataSync *self)
{
	g_return_val_if_fail (SML_IS_DATA_SYNC (self), SML_SESSION_TYPE_UNKNOWN);
	return self->priv->session_type;
}

/**
 * sml_data_sync_set_action:
 * @self: A #SmlDataSync
 * @missing_data:
 *
 * Sets the session type property.
 */
gboolean
sml_data_sync_set_session_type (SmlDataSync *self,
                                SmlSessionType type,
                                GError **error)
{
	sml_return_val_error_if_fail (SML_IS_DATA_SYNC (self), FALSE, error, SML_ERROR_GENERIC, "There must be a SmlDataSync object.");
	self->priv->session_type = type;

	switch(self->priv->session_type)
	{
		case SML_SESSION_TYPE_SERVER:
			self->priv->func_data_sync_init    = sml_data_sync_server_init;
			self->priv->func_data_sync_connect = NULL;
			break;
		case SML_SESSION_TYPE_CLIENT:
			self->priv->func_data_sync_init    = sml_data_sync_client_init;
			self->priv->func_data_sync_connect = NULL;
			break;
		default:
			g_set_error(error, SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION,
				"Unknown data sync type %d used in %s.",
				self->priv->session_type, __func__);
			return FALSE;
			break;
	}

	return TRUE;
}

/**
 * sml_data_sync_set_option:
 * @self: A #SmlDataSync
 *
 * 
 */
gboolean
sml_data_sync_set_option (SmlDataSync *self,
                          const gchar *name,
                          const gchar *value,
                          GError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	CHECK_ERROR_REF
	sml_return_val_error_if_fail (SML_IS_DATA_SYNC (self), FALSE, error, SML_ERROR_GENERIC, "There must be a SmlDataSync object.");
	sml_return_val_error_if_fail (name != NULL, FALSE, error, SML_ERROR_GENERIC, "There must be a name.");
	sml_return_val_error_if_fail (strlen(name) > 0, FALSE, error, SML_ERROR_GENERIC, "The name cannot be the empty word.");

	if (!strcmp(SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, name)) {
		self->priv->con_type = SML_TRANSPORT_CONNECTION_TYPE_UNKNOWN;
		if (!strcmp(SML_DATA_SYNC_CONFIG_CONNECTION_SERIAL, value))
			self->priv->con_type = SML_TRANSPORT_CONNECTION_TYPE_SERIAL;
		if (!strcmp(SML_DATA_SYNC_CONFIG_CONNECTION_BLUETOOTH, value))
			self->priv->con_type = SML_TRANSPORT_CONNECTION_TYPE_BLUETOOTH;
		if (!strcmp(SML_DATA_SYNC_CONFIG_CONNECTION_IRDA, value))
			self->priv->con_type = SML_TRANSPORT_CONNECTION_TYPE_IRDA;
		if (!strcmp(SML_DATA_SYNC_CONFIG_CONNECTION_NET, value))
			self->priv->con_type = SML_TRANSPORT_CONNECTION_TYPE_NET;
		if (!strcmp(SML_DATA_SYNC_CONFIG_CONNECTION_USB, value))
			self->priv->con_type = SML_TRANSPORT_CONNECTION_TYPE_USB;
		if (self->priv->con_type == SML_TRANSPORT_CONNECTION_TYPE_UNKNOWN) {
			g_set_error(error,
			            SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION,
		                    "The connection type '%s' is unknown.", value);
			goto error;
		}
		if (!smlTransportSetConnectionType(self->priv->tsp, self->priv->con_type, error))
			goto error;
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_VERSION, name)) {
		self->priv->version = SML_VERSION_UNKNOWN;
		if (!strcmp("1.0", value))
			self->priv->version = SML_VERSION_10;
		if (!strcmp("1.1", value))
			self->priv->version = SML_VERSION_11;
		if (!strcmp("1.2", value))
			self->priv->version = SML_VERSION_12;
		if (self->priv->version == SML_VERSION_UNKNOWN) {
			g_set_error(error, SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION,
				"Unknown SyncML version %s.", value);
			goto error;
		}
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_AUTH_TYPE, name)) {
		if (!strcmp(value, SML_DATA_SYNC_CONFIG_AUTH_BASIC)) {
			self->priv->auth_type = SML_AUTH_TYPE_BASIC;
		} else if (!strcmp(value, SML_DATA_SYNC_CONFIG_AUTH_MD5)) {
			self->priv->auth_type = SML_AUTH_TYPE_MD5;
		} else if (!strcmp(value, SML_DATA_SYNC_CONFIG_AUTH_NONE)) {
			self->priv->auth_type = SML_AUTH_TYPE_UNKNOWN;
		} else {
			// this is an illegal keyword
			g_set_error(error, SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION,
				 "Unknown authentication type %s.", value);
			goto error;
		}
	} else if (!strcmp(SML_TRANSPORT_CONFIG_URL, name)) {
		self->priv->url = g_strdup(value);
		/* If the Target is not set explicitly
		 * then the URL is used as the Target in the session header.
		 * This is only relevant for HTTP clients today.
		 */
		if (!self->priv->remote) {
			self->priv->remote = sml_location_new();
			sml_location_set_uri(self->priv->remote, value);
		}
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_TARGET, name)) {
		if (!self->priv->remote)
			self->priv->remote = sml_location_new();
		sml_location_set_uri(self->priv->remote, value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_IDENTIFIER, name)) {
		if (self->priv->local) {
			g_object_unref(self->priv->local);
			self->priv->local = NULL;
		}
		if (value && strlen(value)) {
			self->priv->local = sml_location_new();
			sml_location_set_uri(self->priv->local, value);
		} else {
			smlTrace(TRACE_INTERNAL,
				"%s: set identifier to NULL", __func__);
		}
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_AUTH_USERNAME, name)) {
		self->priv->username = g_strdup(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_AUTH_PASSWORD, name)) {
		self->priv->password = g_strdup(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_USE_WBXML, name)) {
		self->priv->use_wbxml = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_USE_STRING_TABLE, name)) {
		self->priv->use_string_table = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_USE_TIMESTAMP_ANCHOR, name)) {
		self->priv->use_timestamp_anchor = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_USE_NUMBER_OF_CHANGES, name)) {
		self->priv->use_number_of_changes = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_USE_LOCALTIME, name)) {
		self->priv->only_localtime = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_ONLY_REPLACE, name)) {
		self->priv->only_replace = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_MAX_OBJ_SIZE, name)) {
		self->priv->max_obj_size = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_MAX_MSG_SIZE, name)) {
		self->priv->max_msg_size = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_MAX_MSG_CHANGES, name)) {
		self->priv->max_msg_changes = atoi(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_FAKE_DEVICE, name)) {
		self->priv->fake_device = g_strdup(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_FAKE_MANUFACTURER, name)) {
		self->priv->fake_manufacturer = g_strdup(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_FAKE_MODEL, name)) {
		self->priv->fake_model = g_strdup(value);
	} else if (!strcmp(SML_DATA_SYNC_CONFIG_FAKE_SOFTWARE_VERSION, name)) {
		self->priv->fake_software_version = g_strdup(value);
	} else {
		if (!self->priv->tsp) {
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
			            "You have to configure the transport type first.");
			goto error;
		}
		if (!smlTransportSetConfigOption(self->priv->tsp, name, value, error))
			goto error;
	}

	// the default is syncml:auth-basic
	if ((self->priv->username || self->priv->password) &&
	     self->priv->auth_type == SML_AUTH_TYPE_UNKNOWN)
	{
		smlTrace(TRACE_INTERNAL,
			"%s: auth_type is set to default (syncml:auth-basic)", __func__);
		self->priv->auth_type = SML_AUTH_TYPE_BASIC;
	}
			
	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
	return FALSE;
}

gboolean
sml_data_sync_get_only_localtime (SmlDataSync *self)
{
	return self->priv->only_localtime;
}

/**
 * sml_data_sync_add_data_store:
 * @self: A #SmlDataSync
 * @ds: A #SmlDataSyncDataStore
 * 
 */
gboolean
sml_data_sync_add_data_store (SmlDataSync *self,
                              SmlDataSyncDataStore *ds,
                              GError **error)
{
	sml_return_val_error_if_fail (SML_IS_DATA_SYNC (self), FALSE, error, SML_ERROR_GENERIC, "There must be a SmlDataSync object.");
	sml_return_val_error_if_fail (SML_IS_DATA_SYNC_DATA_STORE (ds), FALSE, error, SML_ERROR_GENERIC, "There must be a SmlDataSyncDataStore object.");

	self->priv->data_stores = g_list_append(self->priv->data_stores, ds);
	g_object_ref(ds);
	sml_data_sync_data_store_set_data_sync(ds, self);
	return TRUE;
}

SmlLocation*
sml_data_sync_get_local (SmlDataSync *self)
{
	return self->priv->local;
}

SmlLocation*
sml_data_sync_get_remote (SmlDataSync *self)
{
	return self->priv->remote;
}

/**
 * sml_data_sync_get_data_store_from_local_uri:
 * @self: A #SmlDataSync
 * @local: A #String
 * 
 */
SmlDataSyncDataStore*
sml_data_sync_get_data_store_from_local_uri (SmlDataSync *self,
                                             const gchar *local,
                                             GError **error)
{
	CHECK_ERROR_REF
	sml_return_val_error_if_fail (SML_IS_DATA_SYNC (self), FALSE, error, SML_ERROR_GENERIC, "There must be a SmlDataSync object.");
	sml_return_val_error_if_fail (local != NULL, FALSE, error, SML_ERROR_GENERIC, "There must be a local URI.");
	sml_return_val_error_if_fail (strlen(local) > 0, FALSE, error, SML_ERROR_GENERIC, "The local URI cannot be the empty word.");
	smlTrace(TRACE_INTERNAL, "%s: searching %s", __func__, local);

	GList *o = self->priv->data_stores;
	for (; o; o = o->next) { 
		SmlDataSyncDataStore *datastore = o->data;
		SmlLocation *loc = sml_data_sync_data_store_get_local(datastore);
		smlTrace(TRACE_INTERNAL, "%s: checking %s", __func__, sml_location_get_full_uri(loc));
		if (!strcmp(sml_location_get_full_uri(loc), local))
			return datastore;
	}

	g_set_error(error, SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION,
		"Cannot find datastore for local name %s.", local);
	return NULL;
}

/**
 * sml_data_sync_initialize:
 * @self: A #SmlDataSync
 *
 * 
 */
gboolean
sml_data_sync_initialize (SmlDataSync *self,
                          GError **error)
{
	smlTrace(TRACE_ENTRY, "%s (%p, %p)", __func__, self, error);
	sml_return_val_error_if_fail (SML_IS_DATA_SYNC (self), FALSE, error, SML_ERROR_GENERIC, "There must be a SmlDataSync object.");

	GList *h = self->priv->data_stores;
	for (; h; h = h->next) {
		SmlDataSyncDataStore *ds = h->data;
		if (!sml_data_sync_data_store_initialize (ds, error))
			return FALSE;
	}

	if (!self->priv->local) {
		self->priv->local = sml_location_new();
		gchar *ident = sml_data_sync_dev_inf_get_identifier();
		sml_location_set_uri(self->priv->local, ident);
		smlSafeCFree(&ident);
	}

	self->priv->manager_mutex = g_mutex_new();
	self->priv->session_mutex = g_mutex_new();

	if (self->priv->func_tsp_init &&
	    !self->priv->func_tsp_init(self, error))
		goto error;
	if (!self->priv->func_data_sync_init(self, error))
		goto error;

	self->priv->state = SML_DATA_SYNC_STATE_INITIALIZED;

	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT, "%s - FALSE", __func__);
	return FALSE;
}

/**
 * sml_data_sync_run:
 * @self: A #SmlDataSync
 *
 * 
 */
gboolean
sml_data_sync_run (SmlDataSync *self,
                   GError **error)
{
	CHECK_ERROR_REF
	sml_return_val_error_if_fail (SML_IS_DATA_SYNC (self), FALSE, error, SML_ERROR_GENERIC, "There must be a SmlDataSync object.");

	/* If a transport server was initialized then you cannot do more
	 * than waiting for the remote client.
	 */

	if (self->priv->func_tsp_connect &&
	    !self->priv->func_tsp_connect(self, error))
                goto error;
	if (self->priv->func_data_sync_connect &&
	    !self->priv->func_data_sync_connect(self, error))
                goto error;

	if (!sml_data_sync_loop_start(self, error))
		goto error;

	return TRUE;
error:
	return FALSE;
}

/**
 * sml_data_sync_register_event_callback:
 * @self: A #SmlDataSync
 * @callback: A #SmlDataSyncSessionEventCallback
 * @userdata: A #gpointer
 * 
 */
void
sml_data_sync_register_event_callback (SmlDataSync *self,
                                       SmlDataSyncSessionEventCallback callback,
                                       void *userdata)
{
	g_return_if_fail (SML_IS_DATA_SYNC (self));
	g_return_if_fail (callback);

	self->priv->event_callback = callback;
	self->priv->event_userdata = userdata;
}

/**
 * sml_data_sync_register_write_dev_inf_callback:
 * @self: A #SmlDataSync
 * @callback: A #SmlDataSyncSessionWriteDevInfCallback
 * @userdata: A #gpointer
 * 
 */
void
sml_data_sync_register_write_dev_inf_callback (SmlDataSync *self,
                                               SmlDataSyncSessionWriteDevInfCallback callback,
                                               void *userdata)
{
	g_return_if_fail (SML_IS_DATA_SYNC (self));
	g_return_if_fail (callback);

	self->priv->write_devinf_callback = callback;
	self->priv->write_devinf_userdata = userdata;
}

/**
 * sml_data_sync_register_read_dev_inf_callback:
 * @self: A #SmlDataSync
 * @callback: A #SmlDataSyncSessionReadDevInfCallback
 * @userdata: A #gpointer
 * 
 */
void
sml_data_sync_register_read_dev_inf_callback (SmlDataSync *self,
                                              SmlDataSyncSessionReadDevInfCallback callback,
                                               void *userdata)
{
	g_return_if_fail (SML_IS_DATA_SYNC (self));
	g_return_if_fail (callback);

	self->priv->read_devinf_callback = callback;
	self->priv->read_devinf_userdata = userdata;
}

/**
 * sml_data_sync_register_handle_remote_dev_inf_callback:
 * @self: A #SmlDataSync
 * @callback: A #SmlDataSyncSessionHandleRemoteDevInfCallback
 * @userdata: A #gpointer
 * 
 */
void
sml_data_sync_register_handle_remote_dev_inf_callback (SmlDataSync *self,
                                                       SmlDataSyncSessionHandleRemoteDevInfCallback callback,
                                                       void *userdata)
{
	g_return_if_fail (SML_IS_DATA_SYNC (self));
	g_return_if_fail (callback);

	self->priv->handle_remote_devinf_callback = callback;
	self->priv->handle_remote_devinf_userdata = userdata;
}

SmlDataSyncSessionEventCallback
sml_data_sync_get_event_callback (SmlDataSync *self)
{
	return self->priv->event_callback;
}

void*
sml_data_sync_get_event_userdata (SmlDataSync *self)
{
	return self->priv->event_userdata;
}

SmlDataSyncSession*
sml_data_sync_get_session (SmlDataSync *self,
                           SmlSession *session,
                           GError **error)
{
	smlTrace(TRACE_ENTRY, "%s (%p, %p, %p)", __func__, self, session, error);
	g_mutex_lock(self->priv->session_mutex);
	SmlDataSyncSession *data_sync_session = g_hash_table_lookup(self->priv->sessions, session);
	if (!data_sync_session) {
		data_sync_session = sml_data_sync_session_new(session, self, error);
		if (!data_sync_session)
			goto error;
		g_hash_table_insert(self->priv->data_sync_sessions, data_sync_session, data_sync_session);
		g_hash_table_insert(self->priv->sessions, session, data_sync_session);
		g_object_ref(data_sync_session);
		g_object_ref(data_sync_session);
		g_object_ref(data_sync_session);
		smlSessionRef(session);
	}
	g_mutex_unlock(self->priv->session_mutex);
	smlTrace(TRACE_EXIT, "%s - %p", __func__, data_sync_session);
	return data_sync_session;
error:
	g_mutex_unlock(self->priv->session_mutex);
	return NULL;
}

SmlDsSessionAlertCb
sml_data_sync_get_alert_callback (SmlDataSync *self)
{
	return self->priv->alert_callback;
}

gboolean
sml_data_sync_configure_session (SmlDataSync *self,
                                 SmlSession *session,
                                 GError **error)
{
	smlTrace(TRACE_ENTRY, "%s (%p, %p, %p)", __func__, self, session, error);
	CHECK_ERROR_REF
	sml_return_val_error_if_fail (SML_IS_DATA_SYNC (self), FALSE, error, SML_ERROR_GENERIC, "There must be a SmlDataSync object.");
	sml_return_val_error_if_fail (session, FALSE, error, SML_ERROR_GENERIC, "There must be a SmlSession object.");

	smlSessionUseStringTable(session, self->priv->use_string_table);
	smlSessionUseOnlyReplace(session, self->priv->only_replace);
	smlSessionUseNumberOfChanges(session, self->priv->use_number_of_changes);

	/* enforce own source after a SAN was received */
	/* clients are generally stupid and enforce their stuff */
	if (self->priv->session_type == SML_SESSION_TYPE_CLIENT)
		smlSessionSetSource(session, self->priv->local);

	/* authentication management for OMA DS clients*/
	if (self->priv->session_type == SML_SESSION_TYPE_CLIENT &&
	    (self->priv->username || self->priv->password))
	{
		/* prepare credential */
		SmlCred *cred = smlCredNewAuth (self->priv->auth_type,
		                                self->priv->username,
		                                self->priv->password,
		                                error);
		if (!cred) {
			smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
			return FALSE;
			/* FIXME: Memory Leak !!! */
		}

		smlSessionRegisterCred(session, cred);
	}
	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;
}

gboolean
sml_data_sync_disconnect_session (SmlDataSync *self,
                                  SmlSession *session,
                                  GError **error)
{
	SmlLink *link_ = smlManagerSessionGetLink (self->priv->manager,
	                                           session,
	                                           error);
	if (!link_ && *error)
		return FALSE;

	/* OBEX is a stateful protocol and the client should
	 * init the disconnect. The problem is what happens
	 * when the client hangs?
	 *
	 * if (dsObject->tspType != SML_TRANSPORT_OBEX_SERVER &&
	 *     !smlTransportDisconnect(dsObject->tsp, link, &error))
	 */
	if (!smlTransportDisconnect(self->priv->tsp, link_, error))
	{
		if (link_)
			smlLinkDeref(link_);
		return FALSE;
	}
	if (link_)
		smlLinkDeref(link_);

	return TRUE;
}

/* *************************************** */
/* *****     Management Callback     ***** */
/* *************************************** */

static void
sml_data_sync_disconnect_data_sync_session (gpointer key, gpointer value, void *userdata)
{
	SmlDataSyncSession *dss = key; /* value is the same */
	SmlDataSync *ds = userdata;
	GError *error = NULL;

	SmlSession *session = sml_data_sync_session_get_session(dss);
	SmlLink *link_ = smlManagerSessionGetLink(ds->priv->manager, session, &error);

	if (link_ || !error)
		smlTransportDisconnect(ds->priv->tsp, link_, &error);
	if (link_)
		smlLinkDeref(link_);
	/* error is already tracked.
	 * So locerror can be ignored.
	 */
	if (error)
		g_error_free(error);
	/* It is not a good idea to wait for the
	 * disconnect here. First this is an
	 * asynchronous software so it is always
	 * bad if the software blocks. Second it
	 * is dangerous to call smlManagerDispatch
	 * here because an error during these
	 * dispatch activities can lead to another
	 * error which overwrites the original
	 * error.
	 *
	 * Deadlock must be handled in another way.
	 * The SyncML protocol is usually already
	 * broken if this happens (TRANSPORT_ERROR).
	 *
	 * So yes, it is important to disconnect
	 * and no, it must not run dispatch.
	 */
}

void
sml_data_sync_event_callback (SmlManager *manager,
                              SmlManagerEventType type,
                              SmlSession *session,
                              const GError *error,
                              void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p, %p)", __func__, manager, type, session, error, userdata);
	SmlDataSync *self = userdata;
	smlAssert(self);
	GError *locerror = NULL;
	/* FIXME: Is this lock really needed? */
	/* FIXME: Who is allowed to call smlManagerDispatch? */
	/* FIXME: The SmlManager must be synchronized and not the callback. */
	g_mutex_lock(self->priv->manager_mutex);

	SmlDataSyncSession *data_sync_session = NULL;
	if (session) {
		data_sync_session = sml_data_sync_get_session (self, session, &locerror);
		if (!data_sync_session)
			goto error;
	}

	switch (type) {
		case SML_MANAGER_SESSION_NEW:
		case SML_MANAGER_SESSION_ESTABLISHED:
		case SML_MANAGER_SESSION_FLUSH:
		case SML_MANAGER_SESSION_FINAL:
		case SML_MANAGER_SESSION_END:
		case SML_MANAGER_SESSION_ERROR:
		case SML_MANAGER_SESSION_WARNING:
			smlTrace(TRACE_INTERNAL, "%s: forwarding session %p event %d", __func__, data_sync_session, type);
			sml_data_sync_session_event_callback(data_sync_session, type, error);
			break;
		case SML_MANAGER_CONNECT_DONE:
			smlTrace(TRACE_INTERNAL, "%s: the transport was connected", __func__);
			self->priv->state = SML_DATA_SYNC_STATE_CONNECTED;
			break;
		case SML_MANAGER_DISCONNECT_DONE:
			smlTrace(TRACE_INTERNAL, "%s: the transport was disconnected", __func__);
			/* If the transport was never connected
			 * then it is useless to handle the disconnect
			 * event.
			 */
			if (self->priv->state < SML_DATA_SYNC_STATE_CONNECTED)
			{
				smlTrace(TRACE_INTERNAL,
					"%s: ignored disconnect because never connected",
					__func__);
				break;
			}
			self->priv->state = SML_DATA_SYNC_STATE_DISCONNECTED;
			sml_data_sync_session_send_event(NULL, self, SML_DATA_SYNC_SESSION_EVENT_DISCONNECT, NULL);
			break;
		case SML_MANAGER_TRANSPORT_ERROR:
			smlTrace(TRACE_INTERNAL,
				"There was an error in the transport: %s",
				error->message);
			if (SML_DATA_SYNC_STATE_CONNECTED <= self->priv->state &&
			    self->priv->state < SML_DATA_SYNC_STATE_DISCONNECTED) {
				if (self->priv->state < SML_DATA_SYNC_STATE_DISCONNECT_IN_PROGRESS) {
					self->priv->state = SML_DATA_SYNC_STATE_DISCONNECT_IN_PROGRESS;
					g_hash_table_foreach (self->priv->data_sync_sessions, sml_data_sync_disconnect_data_sync_session, self);
				} else {
					/* disconnect failed */
					self->priv->state = SML_DATA_SYNC_STATE_DISCONNECTED;
				}
			}
			goto error;
			break;
		default:
			g_warning("%s: Unknown event received: %d.", __func__, type);
			break;
	}
	
	g_mutex_unlock(self->priv->manager_mutex);
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
error:
	g_mutex_unlock(self->priv->manager_mutex);
	if (error && locerror) {
		/* The locerror is only a resulting error from the original error. */
		g_error_free(locerror);
		locerror = NULL;
	}
	if (locerror)
		error = locerror;
	sml_data_sync_session_send_event(NULL, self, SML_DATA_SYNC_SESSION_EVENT_ERROR, error);
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, error->message);
	if (locerror)
		g_error_free(locerror);
}

/* ******************************************* */
/* *****     Authentication Callback     ***** */
/* ******************************************* */

gboolean sml_data_sync_verify_user_callback (SmlChal *chal,
                                             SmlCred *cred,
                                             const gchar *username,
                                             void *userdata,
                                             GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %s, %p)", __func__, chal, cred, VA_STRING(username), userdata);
	CHECK_ERROR_REF

	SmlDataSync *self = userdata;

	/* We have only one user and not a whole user database. */
	smlTrace(TRACE_EXIT, "%s", __func__);
	return smlAuthVerify(chal, cred, self->priv->username, self->priv->password, error);
}

