/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 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 "tests/support.h"

#include <libsyncml/syncml.h>
#include <libsyncml/syncml_internals.h>
#include <libsyncml/data_sync_api/defines.h>
#include <libsyncml/data_sync_api/standard.h>
#include <libsyncml/data_sync_api/callbacks.h>
#include <glib.h>
// #include <glib/gstdio.h>

//#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>

GMutex *runMutex = NULL;
int locks;

GList *client_items;
SmlDataSyncObject *client;
SmlDataSyncObject *server;
const char *client_source;
const char *server_source;
const char *transport;


/* ************************************ */
/* *********** CALLBACKS ************** */
/* ************************************ */

SmlBool sendAllChanges(
		SmlDataSyncObject *dsObject,
		SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);

	if (dsObject == client)
	{
		GList *list = client_items;
		size_t count = 0;
		for (;list;list = list->next)
		{
			count++;
			const char *test_data = list->data;
			char *name = g_strdup_printf("%d", count);
			if (!smlDataSyncAddChange(client, client_source, SML_CHANGE_ADD,
					name , test_data, strlen(test_data), NULL, error)) {
				sml_fail_unless(FALSE, "%s", smlErrorPrint(error));
			}
			smlSafeCFree(&name);
		}
	}

	smlTrace(TRACE_EXIT, "%s", __func__);
	return smlDataSyncSendChanges(dsObject, error);
}

static void recvEventCallback(
			SmlDataSyncObject *dsObject,
			SmlDataSyncEventType type,
			void *userdata,
			SmlError *error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p)", __func__, dsObject, type, userdata, error);
	SmlError *locerror = NULL;
	
	switch (type) {
		case SML_DATA_SYNC_EVENT_ERROR:
			if (dsObject == client) {
				sml_fail_unless(FALSE, "OMA DS client failed: %s", smlErrorPrint(&error));
			} else {
				sml_fail_unless(FALSE, "OMA DS server failed: %s", smlErrorPrint(&error));
			}
			break;
		case SML_DATA_SYNC_EVENT_CONNECT:
			/* g_message("Remote device was successfully connected."); */
			break;
		case SML_DATA_SYNC_EVENT_DISCONNECT:
			/* g_message("Remote device was successfully disconnected."); */
			break;
		case SML_DATA_SYNC_EVENT_FINISHED:
			/* g_message("SyncML session finished successfully."); */
			if (g_atomic_int_dec_and_test(&locks))
				g_mutex_unlock(runMutex);
			break;
		case SML_DATA_SYNC_EVENT_GOT_ALL_ALERTS:
			/* g_message("All alerts of the remote device were received."); */
			if (dsObject == client)
			{
				if (!sendAllChanges(dsObject, &locerror))
					goto error;
			}
			break;
		case SML_DATA_SYNC_EVENT_GOT_ALL_CHANGES:
			/* g_message("All changes of the remote device were received."); */
			if (dsObject == server)
			{
				if (!sendAllChanges(dsObject, &locerror))
					goto error;
			}
			/* the map of the client is send automatically */
			break;
		case SML_DATA_SYNC_EVENT_GOT_ALL_MAPPINGS:
			if (dsObject == server)
			{
				/* g_message("All mappings of the remote device were received."); */
			} else {
				g_error("Received a map but I'm a client!");
			}
			break;
		default:
			g_error("Unknown event(%d).\n", type);
			break;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;

error:
	fprintf(stderr, "An error occured while handling events: %s\n", smlErrorPrint(&locerror));
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&locerror));
	smlErrorDeref(&locerror);
	exit(3);
}

static SmlBool recvChangeCallback(
			SmlDataSyncObject *dsObject,
			const char *source,
			SmlChangeType type,
			const char *uid,
			char *data,
			unsigned int size,
			void *userdata,
			SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s: %s ==> %s(%p, %d, %p, %d, %p, %p)", __func__, VA_STRING(source), VA_STRING(uid), dsObject, type, data, size, userdata, error);

	/* clients should never receive a change in this test */

	if (dsObject == client) {
		sml_fail_unless(FALSE, "A change was received from the server.");
	}

	/* check the source */

	if (strcmp(server_source, source)) {
		sml_fail_unless(FALSE, "The source %s is wrong.", server_source);
	}

	/* handle the item */
	GList *list = client_items;
	size_t count = 0;
	for (; list; list = list->next) {
		count++;

		/* test the uid */
		char *name = g_strdup_printf("%d", count);
		if (strcmp(name, uid)) {
			smlSafeCFree(&name);
			continue;
		}
		smlSafeCFree(&name);

		/* item with correct uid */
		if (strcmp(list->data, data)) {
			sml_fail_unless(FALSE, "The data of the item %s was wrong(%s != %s).", uid, data, list->data);
		}
	}
	smlSafeCFree(&data);

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

//static SmlAlertType recvAlertTypeCallback(
//                        SmlDataSyncObject *dsObject,
//                        const char *source,
//                        SmlAlertType type,
//                        void *userdata,
//                        SmlError **error)
//{
//	smlTrace(TRACE_ENTRY, "%s - %s: %d", __func__, VA_STRING(source), type);
//	/* find the appropriate datasoure */
//	SmlDsToolLocationType *datastore = NULL;
//	GList *o = datastores;
//	while (o) {
//		datastore = o->data;
//		if (!strcmp(datastore->source, source)) {
//			/* abort the scan */
//			o = NULL;
//		} else {
//			datastore = NULL;
//		}
//		if (o) o = o->next;
//	}
//	if (!datastore) {
//		smlErrorSet(error, SML_ERROR_GENERIC,
//			"Cannot found datastore %s.",
//			source);
//		goto error;
//	}
//	smlTrace(TRACE_INTERNAL, "%s: datastores scanned", __func__);
//
//	/* synchronize the alert type */
//	if (type == SML_ALERT_SLOW_SYNC)
//		datastore->slow = TRUE;
//	if (datastore->slow)
//		type = SML_ALERT_SLOW_SYNC;
//
//	smlTrace(TRACE_EXIT, "%s - slow == %d", __func__, datastore->slow);
//	return type;
//error:
//	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
//	return SML_ALERT_UNKNOWN;
//}

void init_testbed(const char *transport_type, const char *port)
{
	/* general init */
	setup_testbed(NULL);
	SmlError *error = NULL;
	client_items = NULL;
	
#ifdef ENABLE_OPENOBEX_TCP
	transport = "OBEX";
#endif
#ifdef ENABLE_HTTP
	transport = "HTTP";
#endif
#ifdef ENABLE_OPENOBEX_TCP
	if (!strcasecmp(transport_type, "OBEX")) {
		transport = "OBEX";
	}
#endif
#ifdef ENABLE_HTTP
	if (!strcasecmp(transport_type, "HTTP")) {
		transport = "HTTP";
	}
#endif
	if (!strcmp(transport, "HTTP"))
	{
		client = smlDataSyncNew(SML_SESSION_TYPE_CLIENT, SML_TRANSPORT_HTTP_CLIENT, &error);
		if (!client)
			goto error;
		server = smlDataSyncNew(SML_SESSION_TYPE_SERVER, SML_TRANSPORT_HTTP_SERVER, &error);
		if (!server)
			goto error;
	} else {
		client = smlDataSyncNew(SML_SESSION_TYPE_CLIENT, SML_TRANSPORT_OBEX_SERVER, &error);
		if (!client)
			goto error;
		server = smlDataSyncNew(SML_SESSION_TYPE_SERVER, SML_TRANSPORT_OBEX_CLIENT, &error);
		if (!server)
			goto error;
	}

	/* default configuration of callbacks */
	smlDataSyncRegisterEventCallback(client, recvEventCallback, NULL);
	smlDataSyncRegisterEventCallback(server, recvEventCallback, NULL);
	// smlDataSyncRegisterGetAlertTypeCallback(client, recvAlertTypeCallback, NULL);
	// smlDataSyncRegisterGetAlertTypeCallback(server, recvAlertTypeCallback, NULL);
	smlDataSyncRegisterChangeCallback(client, recvChangeCallback, NULL);
	smlDataSyncRegisterChangeCallback(server, recvChangeCallback, NULL);
	// smlDataSyncRegisterChangeStatusCallback(dsObject, recvChangeStatusCallback);
	// smlDataSyncRegisterMappingCallback(dsObject, recvMappingCallback, NULL);

	/* configure transport */
	if (!strcmp(transport, "HTTP")) {
		/* HTTP */
		char *url = g_strdup_printf("http://127.0.0.1:%s", port);
		if (!smlDataSyncSetOption(
				client,
				SML_TRANSPORT_CONFIG_URL,
				url, &error))
			goto error;
		smlSafeCFree(&url);
	} else {
		/* OBEX */
		if (!smlDataSyncSetOption(
				client,
				SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
				SML_DATA_SYNC_CONFIG_CONNECTION_NET,
				&error))
			goto error;
		if (!smlDataSyncSetOption(
				server,
				SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
				SML_DATA_SYNC_CONFIG_CONNECTION_NET,
				&error))
			goto error;
		if (!smlDataSyncSetOption(
				server,
				SML_TRANSPORT_CONFIG_URL, 
				"127.0.0.1", &error))
			goto error;
		if (!smlDataSyncSetOption(
				client,
				SML_TRANSPORT_CONFIG_PORT, 
				port, &error))
			goto error;
	}
	if (!smlDataSyncSetOption(
			server,
			SML_TRANSPORT_CONFIG_PORT, 
			port, &error))
		goto error;

	return;
error:
	sml_fail_unless(FALSE, "%s", smlErrorPrint(&error));
}

void run_testbed()
{
	SmlError *error = NULL;
	locks = 2;

	/* init the sync */
	if (!strcmp(transport, "HTTP"))
	{
		if (!smlDataSyncInit(server, &error))
			goto error;
		if (!smlDataSyncInit(client, &error))
			goto error;
		if (!smlDataSyncRun(server, &error))
			goto error;
		if (!smlDataSyncRun(client, &error))
			goto error;
	} else {
		if (!smlDataSyncInit(client, &error))
			goto error;
		if (!smlDataSyncInit(server, &error))
			goto error;
		if (!smlDataSyncRun(client, &error))
			goto error;
		/* The OBEX server needs some time to start. */
		if (g_getenv("SYNCML_TRACE"))
			sleep(2);
		else
			sleep(5);
		if (!smlDataSyncRun(server, &error))
			goto error;
	}

	runMutex = g_mutex_new();
	g_mutex_lock(runMutex);
	g_mutex_lock(runMutex);
	g_mutex_unlock(runMutex);
	g_mutex_free(runMutex);
	runMutex = NULL;

	/* close the object */
	smlDataSyncObjectUnref(&client);
	smlDataSyncObjectUnref(&server);
	g_list_free(client_items);

	return;
error:
	sml_fail_unless(FALSE, "%s", smlErrorPrint(&error));
}

START_TEST (ds_api_text_vcard_21)
{
	SmlError *error = NULL;
	init_testbed("HTTP", "17001");

	/* register datastore
	 * the source must be identical because this is http
	 */
	client_source = "contacts";
	server_source = "contacts";
	if (!smlDataSyncAddDatastore(
			client,
			"text/x-vcard",
			NULL,
			client_source,
			&error))
		goto error;
	if (!smlDataSyncAddDatastore(
			server,
			"text/x-vcard",
			NULL,
			server_source,
			&error))
		goto error;

	/* configure test data */
	client_items = g_list_append(client_items, (char *) "blabla");

	run_testbed();

	return;
error:
	sml_fail_unless(FALSE, "%s", smlErrorPrint(&error));
}
END_TEST

START_TEST (ds_api_image_jpeg)
{
	SmlError *error = NULL;
	init_testbed("OBEX", "17002");

	/* register datastore
	 * the source must be identical if this is http
	 */
	client_source = "dcim";
	if (!strcmp(transport, "OBEX"))
		server_source = "photos";
	else
		server_source = "dcim";
	if (!smlDataSyncAddDatastore(
			client,
			"image/jpeg",
			NULL,
			client_source,
			&error))
		goto error;
	if (!smlDataSyncAddDatastore(
			server,
			"image/jpeg",
			NULL,
			server_source,
			&error))
		goto error;

	/* configure test data */
	client_items = g_list_append(client_items, (char *) "this is an image");

	run_testbed();

	return;
error:
	sml_fail_unless(FALSE, "%s", smlErrorPrint(&error));
}
END_TEST

START_TEST (ds_api_unknown_ct)
{
	SmlError *error = NULL;
	init_testbed("HTTP", "17003");

	/* register datastore
	 * the source must be identical because this is http
	 */
	client_source = "data";
	server_source = "data";
	if (!smlDataSyncAddDatastore(
			client,
			"unknown/content-type",
			NULL,
			client_source,
			&error))
		goto error;
	if (!smlDataSyncAddDatastore(
			server,
			"unknown/content-type",
			NULL,
			server_source,
			&error))
		goto error;

	/* configure test data */
	client_items = g_list_append(client_items, (char *) "this is some data");

	run_testbed();

	return;
error:
	sml_fail_unless(FALSE, "%s", smlErrorPrint(&error));
}
END_TEST

START_TEST (ds_api_multi_text_vcard_21)
{
	SmlError *error = NULL;
	init_testbed("HTTP", "17004");

	/* register datastore
	 * the source must be identical because this is http
	 */
	client_source = "contacts";
	server_source = "contacts";
	if (!smlDataSyncAddDatastore(
			client,
			"text/x-vcard",
			NULL,
			client_source,
			&error))
		goto error;
	if (!smlDataSyncAddDatastore(
			server,
			"text/x-vcard",
			NULL,
			server_source,
			&error))
		goto error;

	/* configure test data */
	int max_items = 10000;
	int i;
	for(i = 0; i < max_items; i++) {
		client_items = g_list_append(client_items, (char *) "client data");
	}

	run_testbed();

	return;
error:
	sml_fail_unless(FALSE, "%s", smlErrorPrint(&error));
}
END_TEST

@SML_TESTCASE_CODE@

