/*
 * 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 "data_sync_common.h"
#include "libsyncml/sml_error_internals.h"
#include "data_sync_callbacks.h"
#include "libsyncml/objects/sml_ds_server.h"
#include "data_sync_devinf.h"
#include "libsyncml/sml_support.h"

static SmlBool smlDataSyncServerAlertCallback(
	SmlDsSession *dsession,
	SmlAlertType recvType,
	const char *last,
	const char *next,
	void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %s, %s, %p)", __func__, dsession, recvType, VA_STRING(last), VA_STRING(next), userdata);

	SmlDataSyncDatastore *datastore = userdata;
	SmlDataSyncObject *dsObject = datastore->dsObject;
	SmlBool ret = TRUE;
	SmlError *error = NULL;
	SmlAlertType sentType = recvType;

	/* 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)
	{
		smlErrorSet(&error, SML_ERROR_NOT_IMPLEMENTED, "Unsupported alert type %d.", recvType);
		goto error;
	}

	char *remote_key = g_strdup_printf("remoteanchor%s", smlDsSessionGetLocation(dsession));
	datastore->remoteNext = 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 (dsObject->getAnchorCallback)
				cached = dsObject->getAnchorCallback(
							dsObject,
							remote_key,
							dsObject->getAnchorUserdata,
							&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;
			}
		}
	}

	if (dsObject->getAlertTypeCallback)
	{
		SmlAlertType alertType;
		alertType = dsObject->getAlertTypeCallback(
					dsObject,
					datastore->sourceUri,
					sentType,
					dsObject->getAlertTypeUserdata,
					&error);
		if (alertType == SML_ALERT_UNKNOWN || error)
			goto error;
		if (alertType == SML_ALERT_SLOW_SYNC &&
		    alertType != recvType)
			ret = FALSE; /* REQUIRE REFRESH */
		sentType = alertType;
	}

	smlSafeCFree(&remote_key);

	/* 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) {
			smlErrorSet(&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 {
			smlErrorSet(&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_key = g_strdup_printf("localanchor%s", smlDsSessionGetLocation(dsession));
	char *local_last = NULL;
	if (dsObject->getAnchorCallback)
		local_last = dsObject->getAnchorCallback(
					dsObject,
					local_key,
					dsObject->getAnchorUserdata,
					&error);
	if (!local_last && error)
		goto error;
	if (datastore->localNext)
		smlSafeCFree(&(datastore->localNext));
	if (local_last == NULL || strlen(local_last) == 0)
	{
		/* this is the first sync
		 * let's respect the remote's anchor style
		 */
		if (smlDataSyncIsTimestamp(next, dsObject->useTimestampAnchor) != dsObject->useTimestampAnchor)
		{
			/* 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__);
		}
	}
	datastore->localNext = smlDataSyncGetNextAnchor(datastore, local_last, &error);
	if (!datastore->localNext)
		goto error;

	/* send alert */
	if (!smlDsSessionSendAlert(
			dsession, sentType,
			local_last, datastore->localNext,
			smlDataSyncAlertStatusCallback,
			datastore, &error))
		goto error;

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

	smlTrace(TRACE_EXIT, "%s: %i", __func__, ret);
	return ret;
error:
	smlErrorRef(&error);
	smlDataSyncSendEvent(
		dsObject, SML_DATA_SYNC_EVENT_ERROR,
		dsObject->eventUserdata, error);
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(&error));
	smlErrorDeref(&error);
	return FALSE;
}

SmlBool smlDataSyncServerInit(SmlDataSyncObject *dsObject, SmlError **error)
{
	CHECK_ERROR_REF
	/* The manager responsible for handling the other objects */
	dsObject->manager = smlManagerNew(dsObject->tsp, error);
	if (!dsObject->manager)
		goto error;
	smlManagerSetEventCallback(dsObject->manager, smlDataSyncEventCallback, dsObject);
	smlManagerSetLocalMaxMsgSize(dsObject->manager, dsObject->maxMsgSize);
	smlManagerSetLocalMaxObjSize(dsObject->manager, dsObject->maxObjSize);

	/* set server specific callbacks */
	dsObject->funcDatastoreAlert = smlDataSyncServerAlertCallback;

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

	/* prepare device info */
	if (!smlDataSyncDevInfInit(dsObject, SML_DEVINF_DEVTYPE_SERVER, error))
		goto error;

	/* prepare datastore server */
	GList *o = dsObject->datastores;
	for (; o; o = o->next) { 
		SmlDataSyncDatastore *datastore = o->data;

		/* We now create the ds server hat the given location */
		SmlLocation *loc = smlLocationNew(datastore->sourceUri, NULL, error);
		if (!loc)
			goto error;
		
		datastore->server = smlDsServerNew(datastore->contentType, loc, error);
		if (!datastore->server) {
			smlLocationUnref(loc);
			goto error;
		}
		smlLocationUnref(loc);

		if (!smlDsServerRegister(datastore->server, dsObject->manager, error))
			goto error;
		
		smlDsServerSetConnectCallback(
			datastore->server,
			smlDataSyncDatastoreConnectCallback,
			datastore);

		/* And we also add the devinfo to the devinf agent */
		if (!smlDataSyncDevInfAddDatastore(dsObject->localDevInf, datastore, error))
			goto error;
	}

	/* Run the manager */
	if (!smlManagerStart(dsObject->manager, error))
		goto error;
	
	/* Initialize the Transport */
	if (!smlTransportInitialize(dsObject->tsp, error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
	return FALSE;
}

