/*
 * 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 smlDataSyncClientAlertCallback(
	SmlDsSession *dsession,
	SmlAlertType type,
	const char *last,
	const char *next,
	void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %s, %s, %p)", __func__, dsession, type, VA_STRING(last), VA_STRING(next), userdata);

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

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

	if (dsObject->getAlertTypeCallback)
	{
		SmlAlertType h = dsObject->getAlertTypeCallback(
					dsObject,
					datastore->sourceUri,
					type,
					dsObject->getAlertTypeUserdata,
					&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) {
				smlErrorSet(&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 */
				SmlLocation *source = NULL;
				source = smlLocationNew(datastore->sourceUri, NULL, &error);
				if (!source)
					goto error;
				SmlCommand *alert = NULL;
				alert = smlCommandNewAlert(
						SML_ALERT_SLOW_SYNC,
						smlDsSessionGetTarget(datastore->session),
						source,
						NULL, datastore->localNext,
						NULL, &error);
				if (!alert) {
					smlLocationUnref(source);
					goto error;
				}
				smlLocationUnref(source);
				if (!smlSessionSendCommand(dsObject->session, alert, NULL, NULL, NULL, &error)) {
					smlCommandUnref(alert);
					goto error;
				}
				smlCommandUnref(alert);
	
				/* send status 508 */
				ret = FALSE;
			}
		}
	}

	smlSafeCFree(&remote_key);

	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 smlDataSyncClientSendAlert(
		SmlDataSyncDatastore *datastore,
		SmlAlertType type,
		SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %d, %p)", __func__, datastore, type, error);
	CHECK_ERROR_REF
	SmlDataSyncObject *dsObject = datastore->dsObject;

	/* initialize the last anchor */
	SmlAlertType alertType = type;
	char *local_last = NULL; // perhaps NULL is better
	if (dsObject->getAlertTypeCallback)
	{
		alertType = dsObject->getAlertTypeCallback(
					dsObject,
					datastore->sourceUri,
					type,
					dsObject->getAlertTypeUserdata,
					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;
		char *local_key = g_strdup_printf("localanchor%s", smlDsServerGetLocation(datastore->server));
		if (dsObject->getAnchorCallback)
			local_last = dsObject->getAnchorCallback(
						dsObject,
						local_key,
						dsObject->getAnchorUserdata,
						error);
		smlSafeCFree(&local_key);
	}

	/* initialize the next anchor */
	if (datastore->localNext)
		smlSafeCFree(&(datastore->localNext));
	datastore->localNext = smlDataSyncGetNextAnchor(datastore, 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 (!dsObject->session) {
		smlManagerDispatch(dsObject->manager);
	}

	/* The OMA DS client starts the synchronization so there should be no
	 * DsSession (datastore session) present.
	 */
	datastore->session = smlDsServerSendAlert(
				datastore->server,
				dsObject->session,
				alertType,
				local_last, datastore->localNext,
				smlDataSyncAlertStatusCallback, datastore,
				error);
	if (local_last)
		smlSafeCFree(&local_last);
	if (!datastore->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(datastore->session, smlDataSyncClientAlertCallback, datastore);
	if (dsObject->changeCallback) {
		smlDsSessionGetSync(datastore->session,
			smlDataSyncSyncCallback, datastore);
		smlDsSessionGetChanges(datastore->session,
			smlDataSyncChangeCallback, datastore);
	}

	/* 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).
	 */
	SmlBool 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__, smlErrorPrint(error));
	return FALSE;
}

static SmlErrorType smlDataSyncSanCallback(
			SmlDsServer *server,
			SmlSession *session,
			SmlAlertType type,
			void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %d, %p)", __func__, server, session, type, userdata);
	SmlDataSyncDatastore *datastore = userdata;
	SmlDataSyncObject *dsObject = datastore->dsObject;
	SmlError *error = NULL;
	SmlErrorType ret = SML_NO_ERROR;

	/* soemtimes the event manager thread is too slow */
	if (!dsObject->session) {
		dsObject->session = session;
		smlSessionRef(session);
	}

	if (!smlDataSyncClientSendAlert(datastore, type, &error)) {
		if (!error)
			ret = SML_ERROR_REQUIRE_REFRESH;
		else
			goto error;
	}

	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 SML_ERROR_GENERIC;
}

SmlBool smlDataSyncClientInit(SmlDataSyncObject *dsObject, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, dsObject, 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 = smlDataSyncClientAlertCallback;

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

	/* prepare datastore server */
	GList *o = dsObject->datastores;
	for (; o; o = o->next) { 
		SmlDataSyncDatastore *datastore = o->data;
		smlTrace(TRACE_INTERNAL, "preparing DsServer (datastore) %s", datastore->sourceUri);

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

		if (!smlDsServerRegister(datastore->server, dsObject->manager, error))
			goto error;
		
		/* this is a client and not a server
		 * but the callback initializes only database->session (DsSession)
		 */
		smlDsServerSetConnectCallback(
			datastore->server,
			smlDataSyncDatastoreConnectCallback,
			datastore);

		smlDsServerSetSanSessionCallback(
			datastore->server,
			smlDataSyncSanCallback,
			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;
}

