/*
 * 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_server.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_error_internals.h"
#include "libsyncml/sml_support.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_server_alert_callback (SmlDsSession *dsession,
                                                        SmlAlertType recvType,
                                                        const gchar *last,
                                                        const gchar *next,
                                                        void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %s, %s, %p)", __func__, dsession, recvType, VA_STRING(last), VA_STRING(next), userdata);

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

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

	smlAssert(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 (recvType != SML_ALERT_TWO_WAY &&
	    recvType != SML_ALERT_SLOW_SYNC &&
	    recvType != SML_ALERT_TWO_WAY_BY_SERVER)
	{
		g_set_error(&error, SML_ERROR, SML_ERROR_NOT_IMPLEMENTED,
		            "Unsupported alert type %d.", recvType);
		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 (recvType == SML_ALERT_TWO_WAY || recvType == SML_ALERT_TWO_WAY_BY_SERVER)
	{
		if (!last)
		{
			smlTrace(TRACE_INTERNAL, "%s: TWO-WAY-SYNC but last is missing", __func__);
			sentType = 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 but received LAST(%s) and cached LAST (%s) mismatch",
					__func__, VA_STRING(last), VA_STRING(cached));
				if (cached)
					smlSafeCFree(&cached);
				sentType = SML_ALERT_SLOW_SYNC;
				ret = FALSE;
			}
		}
	}
	self->priv->alert_type = sentType;

	if (datastore->priv->get_alert_type_callback)
	{
		SmlAlertType alertType;
		alertType = datastore->priv->get_alert_type_callback(
					self,
					sentType,
		                        datastore->priv->get_alert_type_userdata,
					&error);
		if (alertType == SML_ALERT_UNKNOWN || error)
			goto error;
		if (alertType == SML_ALERT_SLOW_SYNC &&
		    alertType != recvType)
		{
			/* REQUIRE REFRESH */
			ret = FALSE;
			self->priv->alert_type = SML_ALERT_SLOW_SYNC;
		}
		sentType = alertType;
	}

	/* If the getAnchorCallback and the getAlertTypeCallback
	 * return inconsistent data then this must be detected here.
	 * Inconsistent means that for example status 508 and alert
	 * type 200 are used together which is illegal.
	 */
	if (
	    ( /* alert 200 => alert 200 + status 508 */
	     recvType != SML_ALERT_SLOW_SYNC &&
	     sentType != SML_ALERT_SLOW_SYNC &&
	     ret == FALSE
	    ) ||
	    ( /* alert 200 => alert 201 + status 200 */
	     recvType != SML_ALERT_SLOW_SYNC &&
	     sentType == SML_ALERT_SLOW_SYNC &&
	     ret != FALSE
	    ) ||
	    ( /* alert 201 => alert 200 */
	     recvType == SML_ALERT_SLOW_SYNC &&
	     sentType != SML_ALERT_SLOW_SYNC
	    ) ||
	    ( /* alert 201 => alert 201 + status 508 */
	     recvType == SML_ALERT_SLOW_SYNC &&
	     sentType == SML_ALERT_SLOW_SYNC &&
	     ret == FALSE
	    )
	   )
	{
		if (ret) {
			g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC,
				"The library user tries to respond an alert %d " \
				"with an alert %d and status 200 which is illegal.",
				recvType, sentType);
		} else {
			g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC,
				"The library user tries to respond an alert %d " \
				"with an alert %d and status 508 which is illegal.",
				recvType, sentType);
		}
		goto error;
	}

	/* generate new timestamp for local anchors */
	char *local_last = NULL;
	if (datastore->priv->get_anchor_callback)
		local_last = datastore->priv->get_anchor_callback(
					self,
					FALSE,
					datastore->priv->get_anchor_userdata,
					&error);
	if (!local_last && error)
		goto error;
	if (self->priv->local_next)
		smlSafeCFree(&(self->priv->local_next));
	if (local_last == NULL || strlen(local_last) == 0)
	{
		/* this is the first sync
		 * let's respect the remote's anchor style
		 */
		if (smlDataSyncIsTimestamp(next, datastore->priv->data_sync->priv->use_timestamp_anchor) != datastore->priv->data_sync->priv->use_timestamp_anchor)
		{
			/* Many users are confused by warnings which can be ignored.
			 * Therefore the issue is only traced.
			 */
			smlTrace(TRACE_INTERNAL,
				"%s: libsyncml uses different timestamp anchor modes.",
				__func__);
		}
	}
	self->priv->local_next = sml_data_sync_session_get_next(self->priv->data_sync_session, local_last, &error);
	if (!self->priv->local_next)
		goto error;

	/* send alert */
	if (!smlDsSessionSendAlert(
			dsession, sentType,
			local_last, self->priv->local_next,
			sml_data_sync_data_store_session_alert_status_callback,
			self, &error))
		goto error;

	/* free local anchor stuff */
	if (local_last)
		smlSafeCFree(&local_last);

	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_server_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_server_alert_callback;

	/* The authenticator */
	self->priv->auth = smlAuthNew(error);
	if (!self->priv->auth)
		goto error;
	smlAuthSetVerifyCallback(self->priv->auth, sml_data_sync_verify_user_callback, self);
	if (!self->priv->username) {
		smlAuthSetEnable(self->priv->auth, FALSE);
	} else {
		smlAuthSetEnable(self->priv->auth, TRUE);
		smlAuthSetType(self->priv->auth, self->priv->auth_type);
	}
	if (!smlAuthRegister(self->priv->auth, self->priv->manager, error))
		goto error;

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

	/* prepare datastore server */
	GList *o = self->priv->data_stores;
	for (; o; o = o->next) { 
		SmlDataSyncDataStore *datastore = o->data;

		/* We now create the ds server hat the given location */
		datastore->priv->data_store_server = smlDsServerNew(datastore->priv->content_type, datastore->priv->local, error);
		if (!datastore->priv->data_store_server)
			goto error;

		if (!smlDsServerRegister(datastore->priv->data_store_server, self->priv->manager, error))
			goto error;
		
		smlDsServerSetConnectCallback(
			datastore->priv->data_store_server,
			sml_data_sync_data_store_connect_callback,
			datastore);

		/* 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;
}

