/*
 * libsyncml - A syncml protocol implementation
 * 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 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.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */

#include "data_sync_client.h"

#include "sml_data_sync_private.h"
#include "sml_data_sync_session_private.h"
#include "sml_data_sync_data_store_private.h"
#include "sml_data_sync_data_store_session_private.h"

#include "libsyncml/sml_support.h"
#include "libsyncml/sml_error_internals.h"

#include "data_sync_common.h"
#include "data_sync_devinf.h"

#include "libsyncml/objects/sml_ds_server.h"

static gboolean
sml_data_sync_data_store_session_client_alert_callback (SmlDsSession *dsession,
                                                        SmlAlertType type,
                                                        const gchar *last,
                                                        const gchar *next,
                                                        void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %s, %s, %p)", __func__, dsession, type, VA_STRING(last), VA_STRING(next), userdata);

	SmlDataSyncDataStoreSession *self = userdata;
	SmlDataSyncDataStore *datastore = self->priv->data_store;
	gboolean ret = TRUE;
	GError *error = NULL;

	/* If this is an OBEX server then the dsession is not known until now. */

	if (!self->priv->data_store_session)
		self->priv->data_store_session = dsession;

	/* libsyncml only supports SML_ALERT_TWO_WAY and SML_ALERT_SLOW_SYNC
	 * but some old phones reply on a SAN alert 206 with a slow sync 201
	 * alert or a SAN alert 206 (insteed of a normal two way alert 200).
	 * Therefore it is necessary to check for TWO_WAY and TWO_WAY_BY_SERVER.
	 */

	if (type != SML_ALERT_TWO_WAY &&
	    type != SML_ALERT_SLOW_SYNC &&
	    type != SML_ALERT_TWO_WAY_BY_SERVER)
	{
		g_set_error(&error, SML_ERROR, SML_ERROR_NOT_IMPLEMENTED,
		            "Unsupported alert type %d.", type);
		goto error;
	}

	self->priv->remote_next = g_strdup(next);

	/* We return FALSE if we need a special return code as answer:
	 * SML_ERROR_REQUIRE_REFRESH 508
	 * This return code enforces a SLOW-SYNC.
	 */
	if (type == SML_ALERT_TWO_WAY || type == SML_ALERT_TWO_WAY_BY_SERVER)
	{
		if (!last)
		{
			smlTrace(TRACE_INTERNAL, "%s: TWO-WAY-SYNC is requested but there is no LAST anchor.", __func__);
			type = SML_ALERT_SLOW_SYNC;
			ret = FALSE;
		} else {
			char *cached = NULL;
			if (datastore->priv->get_anchor_callback)
				cached = datastore->priv->get_anchor_callback(
							self,
				                        TRUE,
				                        datastore->priv->get_anchor_userdata,
							&error);
			if (!cached && error)
				goto error;
			if (!cached || strcmp(cached, last))
			{
				smlTrace(TRACE_INTERNAL,
                			"%s: TWO-WAY-SYNC is requested but the cached LAST anchor (%s) does not match the presented LAST anchor from the remote peer (%s).",
					__func__, last, cached);
				if (cached)
					smlSafeCFree(&cached);
				type = SML_ALERT_SLOW_SYNC;
				ret = FALSE;
			}
		}
	}
	self->priv->alert_type = type;

	if (datastore->priv->get_alert_type_callback)
	{
		SmlAlertType h = datastore->priv->get_alert_type_callback(
					self,
					type,
		                        datastore->priv->get_alert_type_userdata,
					&error);
		if (h == SML_ALERT_UNKNOWN || error)
			goto error;
		/* There is only one situation where the callback can return
		 * something different than type. If the client requested an
		 * alert type 200, the server requested an alert type 200 too
		 * and after this there is a problem detected on the client
		 * then the returned type can be 201 SLOW-SYNC.
		 *
		 * If this happens then a new alert 201 and a status 508 must
		 * be send.
		 */
		if (h != type) {
			if (h != SML_ALERT_SLOW_SYNC) {
				g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC,
					"It is not possible to change the alert type after an OMA DS client received the alerts from the OMA DS server.");
				goto error;
			} else {
				/* send alert 201 */
				SmlCommand *alert = NULL;
				alert = smlCommandNewAlert(
						SML_ALERT_SLOW_SYNC,
						smlDsSessionGetTarget(self->priv->data_store_session),
						datastore->priv->local,
						NULL, self->priv->local_next,
						NULL, &error);
				if (!alert)
					goto error;
				if (!smlSessionSendCommand(self->priv->data_sync_session->priv->session, alert, NULL, NULL, NULL, &error)) {
					smlCommandUnref(alert);
					goto error;
				}
				smlCommandUnref(alert);
	
				/* send status 508 */
				ret = FALSE;
				self->priv->alert_type = SML_ALERT_SLOW_SYNC;
			}
		}
	}

	smlTrace(TRACE_EXIT, "%s: %i", __func__, ret);
	return ret;
error:
	sml_data_sync_session_send_event(self->priv->data_sync_session, NULL, SML_DATA_SYNC_SESSION_EVENT_ERROR, error);
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, error->message);
	g_error_free(error);
	return FALSE;
}

gboolean
sml_data_sync_session_client_send_alert(SmlDataSyncSession *self,
                                        SmlDataSyncDataStore *datastore,
                                        SmlAlertType type,
                                        GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %d, %p)", __func__, self, datastore, type, error);
	CHECK_ERROR_REF

	/* create new data store session */

	SmlDataSyncDataStoreSession *session = sml_data_sync_session_get_data_store_session(self, datastore, error);
	if (!session)
		goto error;

	/* initialize the last anchor */
	SmlAlertType alertType = type;
	char *local_last = NULL;
	if (datastore->priv->get_alert_type_callback)
	{
		alertType = datastore->priv->get_alert_type_callback(
					session,
					type,
					datastore->priv->get_alert_type_userdata,
					error);
		if (*error)
			goto error;
	}
	if (alertType == SML_ALERT_UNKNOWN)
	{
		smlTrace(TRACE_INTERNAL, "%s: no alert type => slow-sync", __func__);
		alertType = SML_ALERT_SLOW_SYNC;
	}
	if (alertType != SML_ALERT_SLOW_SYNC)
	{
		/* this must be a two-way-sync */
		alertType = SML_ALERT_TWO_WAY;
		if (datastore->priv->get_anchor_callback)
			local_last = datastore->priv->get_anchor_callback(
						session,
			                        FALSE,
						datastore->priv->get_anchor_userdata,
						error);
	}

	/* initialize the next anchor */
	if (session->priv->local_next)
		smlSafeCFree(&(session->priv->local_next));
	session->priv->local_next = sml_data_sync_session_get_next(self, local_last, error);

	/* The session is required here. So if the session is missing
	 * then the function has to block here. This is no real problem
	 * because the SESSION_EVENT_NEW must already be in the queue of
	 * the manager object.
	 */
	while (!self->priv->session) {
		smlManagerDispatch(self->priv->data_sync->priv->manager);
	}

	/* The OMA DS client starts the synchronization so there should be no
	 * DsSession (datastore session) present.
	 */
	session->priv->alert_type = alertType;
	session->priv->data_store_session = smlDsServerSendAlert(
				datastore->priv->data_store_server,
				self->priv->session,
				alertType,
				local_last, session->priv->local_next,
				sml_data_sync_data_store_session_alert_status_callback,
	                        session,
				error);
	if (local_last)
		smlSafeCFree(&local_last);
	if (!session->priv->data_store_session)
		goto error;

	/* Only the alert callback is registered here because the changes should
	 * be managed after the function ds_client_get_changeinfo was called.
	 */
	smlDsSessionGetAlert(session->priv->data_store_session, sml_data_sync_data_store_session_client_alert_callback, session);
	if (datastore->priv->change_callback) {
		smlDsSessionGetSync(session->priv->data_store_session,
			sml_data_sync_data_store_session_sync_callback, session);
		smlDsSessionGetChanges(session->priv->data_store_session,
			sml_data_sync_data_store_session_change_callback, session);
	}

	/* If a slow-sync alert is initiated and the remote peer
	 * did not request it then the function must return false
	 * to trigger a status 508 (REFRESH_REQUIRED).
	 * If the requested alert type is not known then there
	 * is no need for a status and the function returns true.
	 * This can happen if the remote peer did not trigger
	 * the client via a SAN (e.g. OMA DS client over HTTP).
	 */
	gboolean ret = TRUE;
	if (alertType == SML_ALERT_SLOW_SYNC &&
	    alertType != type &&
	    type != SML_ALERT_UNKNOWN) {
		ret = FALSE;
	}

	smlTrace(TRACE_EXIT, "%s: %i", __func__, ret);
	return ret;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
	return FALSE;
}

static SmlErrorType
sml_data_sync_data_store_client_san_callback (SmlDsServer *server,
                                              SmlSession *session,
                                              SmlAlertType type,
                                              void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %d, %p)", __func__, server, session, type, userdata);
	SmlDataSyncDataStore *self = userdata;
	SmlDataSync *data_sync = self->priv->data_sync;
	GError *error = NULL;
	SmlErrorType ret = SML_NO_ERROR;

	/* soemtimes the event manager thread is too slow */
	SmlDataSyncSession *data_sync_session = NULL;
	data_sync_session = sml_data_sync_get_session (data_sync, session, &error);
	if (!data_sync_session)
		goto error;

	if (!sml_data_sync_session_client_send_alert(data_sync_session, self, type, &error)) {
		if (!error)
			ret = SML_ERROR_REQUIRE_REFRESH;
		else
			goto error;
	}

	smlTrace(TRACE_EXIT, "%s: %i", __func__, ret);
	return ret;
error:
	sml_data_sync_session_send_event(data_sync_session, NULL, SML_DATA_SYNC_SESSION_EVENT_ERROR, error);
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, error->message);
	g_error_free(error);
	return SML_ERROR_GENERIC;
}

gboolean
sml_data_sync_data_store_client_init (SmlDataSyncDataStore *self,
                                      GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, self, error);
	CHECK_ERROR_REF

	/* We now create the ds server at the given location. */
		
	self->priv->data_store_server = smlDsClientNew(self->priv->content_type, self->priv->local, self->priv->local, error);
	if (!self->priv->data_store_server)
		goto error;

	if (!smlDsServerRegister(self->priv->data_store_server, self->priv->data_sync->priv->manager, error))
		goto error;
		
	/* this is a client and not a server
	 * but the callback initializes only database->session (DsSession)
	 */
	smlDsServerSetConnectCallback(
		self->priv->data_store_server,
		sml_data_sync_data_store_connect_callback,
		self);

	smlDsServerSetSanSessionCallback(
		self->priv->data_store_server,
		sml_data_sync_data_store_client_san_callback,
		self);

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

gboolean
sml_data_sync_client_init (SmlDataSync *self,
                           GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, self, error);
	CHECK_ERROR_REF

	/* The manager responsible for handling the other objects */
	self->priv->manager = smlManagerNew(self->priv->tsp, error);
	if (!self->priv->manager)
		goto error;
	smlManagerSetEventCallback(self->priv->manager, sml_data_sync_event_callback, self);
	smlManagerSetLocalMaxMsgSize(self->priv->manager, self->priv->max_msg_size);
	smlManagerSetLocalMaxObjSize(self->priv->manager, self->priv->max_obj_size);
	smlManagerSetLocalMaxMsgChanges(self->priv->manager, self->priv->max_msg_changes);

	/* set server specific callbacks */
	self->priv->alert_callback = sml_data_sync_data_store_session_client_alert_callback;

	/* prepare device info */
	if (!sml_data_sync_dev_inf_init(self, SML_DEVINF_DEVTYP_WORKSTATION, error))
		goto error;

	/* prepare datastore server */
	GList *o = self->priv->data_stores;
	for (; o; o = o->next) { 
		SmlDataSyncDataStore *datastore = o->data;
		smlTrace(TRACE_INTERNAL, "preparing DsServer (datastore) %s",
		         sml_location_get_uri(sml_data_sync_data_store_get_local(datastore)));

		if (!sml_data_sync_data_store_client_init(datastore, error))
			goto error;

		/* And we also add the devinfo to the devinf agent */
		if (!sml_data_sync_dev_inf_add_data_store(self->priv->local_dev_inf, datastore, error))
			goto error;
	}

	/* Run the manager */
	if (!smlManagerStart(self->priv->manager, error))
		goto error;
	
	/* Initialize the Transport */
	if (!smlTransportInitialize(self->priv->tsp, error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
	return FALSE;
}

