/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2008  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 "../support.h"

/* ************************************
 * *********** OBEX SERVER ************
 * ************************************
 *         (mobile simulator)
 */

#include <libsyncml/sml_transport_internals.h>
#include <wbxml.h>

/* external configuration */

extern const char *obex_port;
extern const char *enableWbxml;
extern char *testDirectory;

/* supported messages */

char *libsyncml_msg_notification_data;
size_t libsyncml_msg_notification_size;
char *mobile_msg_alert_data;
size_t mobile_msg_alert_size;
char *libsyncml_msg_alert_data;
size_t libsyncml_msg_alert_size;
char *mobile_msg_sync_data;
size_t mobile_msg_sync_size;
char *libsyncml_msg_sync_data;
size_t libsyncml_msg_sync_size;
char *mobile_msg_map_data;
size_t mobile_msg_map_size;
char *libsyncml_msg_end_data;
size_t libsyncml_msg_end_size;

/* mobile status variables */

typedef enum {
	TEST_MOBILE_STATE_UNKNOWN,
	TEST_MOBILE_STATE_CONNECTED,
	TEST_MOBILE_STATE_WAIT_FOR_NOTIFICATION,
	TEST_MOBILE_STATE_WAIT_FOR_ALERT,
	TEST_MOBILE_STATE_WAIT_FOR_SYNC,
	TEST_MOBILE_STATE_WAIT_FOR_END,
	TEST_MOBILE_STATE_FINISHED,
	TEST_MOBILE_STATE_DISCONNECTED,
	TEST_MOBILE_STATE_FAILED
} SmlTestMobileStateType;

SmlTestMobileStateType mobile_state = TEST_MOBILE_STATE_UNKNOWN;

SmlTransport *mobile_tsp = NULL;

void obex_mobile_send(SmlLink *link_, const char *data, unsigned int size)
{
	SmlError *error = NULL;

	SmlMimeType mimetype = SML_MIMETYPE_XML;
	if (enableWbxml)
		mimetype = SML_MIMETYPE_WBXML;

	char *msg = smlTryMalloc0(size, &error);
	sml_fail_unless(msg != NULL, "%s", smlErrorPrint(&error));
	memcpy(msg, data, size);

	SmlTransportData *tsp_data = smlTransportDataNew(msg, size, mimetype, TRUE, &error);
	sml_fail_unless(tsp_data != NULL, NULL);
	sml_fail_unless(error == NULL, NULL);

	sml_fail_unless(smlTransportSend(mobile_tsp, link_, tsp_data, &error), "%s", smlErrorPrint(&error));
	sml_fail_unless(error == NULL, NULL);
	
	smlTransportDataDeref(tsp_data);
}

static char *_get_xml(
		const char *recv_data,
		size_t recv_size)
{
	char *result = NULL;
	WBXMLConvWBXML2XMLParams params = {WBXML_ENCODER_XML_GEN_COMPACT, WBXML_LANG_UNKNOWN, 0, TRUE};
	WBXMLError wberror;
	wberror = wbxml_conv_wbxml2xml((WB_UTINY*)recv_data, recv_size, (WB_UTINY**)&result, &params);
	if (wberror != WBXML_OK)
		return NULL;
	else
		return result;
}

static char *replace_string(
		const char *expr,
		GRegexCompileFlags flags,
		char *original,
		const char* replacement)
{
	smlTrace(TRACE_ENTRY, "%s(%s, %s)", __func__, VA_STRING(expr), VA_STRING(original));

	GRegex *regex = NULL;
	GError *error = NULL;
	char *result = NULL;

	regex = g_regex_new(expr, flags, 0, &error);
	sml_fail_unless(regex != NULL, "Filter \"%s\" cannot be established.", expr);

	result = g_regex_replace_literal(regex, original, strlen(original),
			0, replacement, 0,
			&error);
	sml_fail_unless(result != NULL, "Cannot filter \"%s\" from message.", expr);

	smlSafeCFree(&original);
	g_regex_unref(regex);

	return result;
}

static SmlBool equal_messages(
		const char *recv_data, size_t recv_size,
		const char *orig_data, size_t orig_size)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %d, %p, %d)", __func__, recv_data, recv_size, orig_data, orig_size);

	/* convert wbxml messages to xml */

	/* WARNING: we cannot detect libwbxml problems by this way 
	 *
	 * I (bellmich) tried to implement this in a native way
	 * but this would nearly require a WBXML parser :(
	 */

	char *recv = _get_xml(recv_data, recv_size);
	char *orig = _get_xml(orig_data, orig_size);

	/* fix the content */	

	/* ignore SessionID */

	recv = replace_string(
			"<SessionID>[0-9]+</SessionID>", 0,
			recv,
			"<SessionID>2346</SessionID>");
	orig = replace_string(
			"<SessionID>[0-9]+</SessionID>", 0,
			orig,
			"<SessionID>2346</SessionID>");

	/* ignore trailing whitespaces */

	recv = replace_string(
			"[\\s\\n]*$", G_REGEX_DOLLAR_ENDONLY,
			recv,
			"");
	orig = replace_string(
			"[\\s\\n]*$", G_REGEX_DOLLAR_ENDONLY,
			orig,
			"");

	/* ignore newlines (not carriage return) */

	recv = replace_string(
			">\\n<", 0,
			recv,
			"><");
	orig = replace_string(
			">\\n<", 0,
			orig,
			"><");

	/* ignore numbered next values */

	recv = replace_string(
			"<Next xmlns=\"syncml:metinf\">[0-9]+</Next>", 0,
			recv,
			"<Next xmlns=\"syncml:metinf\">1357</Next>");
	orig = replace_string(
			"<Next xmlns=\"syncml:metinf\">[0-9]+</Next>", 0,
			orig,
			"<Next xmlns=\"syncml:metinf\">1357</Next>");

	/* ignore timestamp next values */

	recv = replace_string(
			"<Next>[0-9]{8}T[0-9]{6}Z</Next>", 0,
			recv,
			"<Next>20080101T140000Z</Next>");
	orig = replace_string(
			"<Next>[0-9]{8}T[0-9]{6}Z</Next>", 0,
			orig,
			"<Next>20080101T140000Z</Next>");

	/* ignore FwV values */

	recv = replace_string(
			"<FwV>[^<]+</FwV>", 0,
			recv,
			"<FwV>2.6.25-2-686</FwV>");
	orig = replace_string(
			"<FwV>[^<]+</FwV>", 0,
			orig,
			"<FwV>2.6.25-2-686</FwV>");

	/* ignore SwV values */

	recv = replace_string(
			"<SwV>[^<]+</SwV>", 0,
			recv,
			"<SwV>0.4.7</SwV>");
	orig = replace_string(
			"<SwV>[^<]+</SwV>", 0,
			orig,
			"<SwV>0.4.7</SwV>");

	/* ignore OEM values */

	recv = replace_string(
			"<OEM>[^<]+</OEM>", 0,
			recv,
			"<OEM>Unix</OEM>");
	orig = replace_string(
			"<OEM>[^<]+</OEM>", 0,
			orig,
			"<OEM>Unix</OEM>");

	/* compare strings */

	SmlBool notEqual = strcmp(recv, orig);

	if (notEqual) {
		smlLog("original-%d.xml", orig, strlen(orig));
		smlLog("validated-%d.xml", recv, strlen(recv));
	}
	smlSafeCFree(&recv);
	smlSafeCFree(&orig);

	smlTrace(TRACE_EXIT, "%s - %d", __func__, (!notEqual));
	return (!notEqual);
}

SmlBool _recv_server_event(SmlTransport *tsp, SmlLink *link_, SmlTransportEventType type, SmlTransportData *data, SmlError *error, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, userdata);
		
	switch (type) {
		case SML_TRANSPORT_EVENT_DATA:

			if (mobile_state == TEST_MOBILE_STATE_WAIT_FOR_NOTIFICATION) {
				smlTrace(TRACE_INTERNAL, "%s: mobile received notification", __func__);
				if (!equal_messages(
						data->data, data->size,
						libsyncml_msg_notification_data,
						libsyncml_msg_notification_size)) {
					sml_fail_unless(FALSE, "wrong notification message");
				}
				mobile_state = TEST_MOBILE_STATE_WAIT_FOR_ALERT;
				obex_mobile_send(link_, mobile_msg_alert_data, mobile_msg_alert_size);
			} else if (mobile_state == TEST_MOBILE_STATE_WAIT_FOR_ALERT) {
				smlTrace(TRACE_INTERNAL, "%s: mobile received alert", __func__);
				if (!equal_messages(
						data->data, data->size,
						libsyncml_msg_alert_data,
						libsyncml_msg_alert_size)) {
					sml_fail_unless(FALSE, "wrong alert message");
				}
				mobile_state = TEST_MOBILE_STATE_WAIT_FOR_SYNC;
				obex_mobile_send(link_, mobile_msg_sync_data, mobile_msg_sync_size);
			} else if (mobile_state == TEST_MOBILE_STATE_WAIT_FOR_SYNC) {
				smlTrace(TRACE_INTERNAL, "%s: mobile received sync", __func__);
				if (!equal_messages(
						data->data, data->size,
						libsyncml_msg_sync_data,
						libsyncml_msg_sync_size)) {
					sml_fail_unless(FALSE, "wrong sync message");
				}
				mobile_state = TEST_MOBILE_STATE_WAIT_FOR_END;
				obex_mobile_send(link_, mobile_msg_map_data, mobile_msg_map_size);
			} else if (mobile_state == TEST_MOBILE_STATE_WAIT_FOR_END) {
				smlTrace(TRACE_INTERNAL, "%s: mobile received end", __func__);
				if (!equal_messages(
						data->data, data->size,
						libsyncml_msg_end_data,
						libsyncml_msg_end_size)) {
					sml_fail_unless(FALSE, "wrong end message");
				}
				mobile_state = TEST_MOBILE_STATE_FINISHED;
				/* The disconnect must be issued by obex client
				 * but the mobile is the OBEX server.
				 */
			} else {
				sml_fail_unless(FALSE, "The unexpected mobile state %d is set.", mobile_state);
			}
			break;
		case SML_TRANSPORT_EVENT_CONNECT_DONE:
			mobile_state = TEST_MOBILE_STATE_CONNECTED;
			sml_fail_unless(libsyncml_msg_notification_data != NULL, "Missing notification message");
			mobile_state = TEST_MOBILE_STATE_WAIT_FOR_NOTIFICATION;
			break;
		case SML_TRANSPORT_EVENT_DISCONNECT_DONE:
			mobile_state = TEST_MOBILE_STATE_DISCONNECTED;
			break;
		case SML_TRANSPORT_EVENT_ERROR:
			sml_fail_unless(FALSE, "%s", smlErrorPrint(&error));
			break;
		default:
			sml_fail_unless(FALSE, "The unexpected transport event %d was received.", type);
			break;
	}
	
	smlTrace(TRACE_EXIT, "%s()", __func__);
	return TRUE;
}

void obex_mobile_connect()
{
	char *filename = NULL;

	/* g_file_get_contents uses indirectly g_static_private_get
	 * which can be used without g_thread_init
	 * but g_thread_init avoids some memory leak warnings.
	 */
	if (!g_thread_supported ()) g_thread_init (NULL);

	/* load all the messages */
	filename = g_strdup_printf("%s/sent-0.wbxml", testDirectory);
	sml_fail_unless(
		g_file_get_contents(
			filename,
			&libsyncml_msg_notification_data,
			&libsyncml_msg_notification_size,
			NULL),
		"Cannot load %s/sent-0.wbxml.", testDirectory);
	smlSafeCFree(&filename);
	filename = g_strdup_printf("%s/received-0.wbxml", testDirectory);
	sml_fail_unless(
		g_file_get_contents(
			filename,
			&mobile_msg_alert_data,
			&mobile_msg_alert_size,
			NULL),
		"Cannot load %s/received-0.wbxml.", testDirectory);
	smlSafeCFree(&filename);
	filename = g_strdup_printf("%s/sent-1.wbxml", testDirectory);
	sml_fail_unless(
		g_file_get_contents(
			filename,
			&libsyncml_msg_alert_data,
			&libsyncml_msg_alert_size,
			NULL),
		"Cannot load %s/sent-1.wbxml.", testDirectory);
	smlSafeCFree(&filename);
	filename = g_strdup_printf("%s/received-1.wbxml", testDirectory);
	sml_fail_unless(
		g_file_get_contents(
			filename,
			&mobile_msg_sync_data,
			&mobile_msg_sync_size,
			NULL),
		"Cannot load %s/received-1.wbxml.", testDirectory);
	smlSafeCFree(&filename);
	filename = g_strdup_printf("%s/sent-2.wbxml", testDirectory);
	sml_fail_unless(
		g_file_get_contents(
			filename,
			&libsyncml_msg_sync_data,
			&libsyncml_msg_sync_size,
			NULL),
		"Cannot load %s/sent-2.wbxml.", testDirectory);
	smlSafeCFree(&filename);
	filename = g_strdup_printf("%s/received-2.wbxml", testDirectory);
	sml_fail_unless(
		g_file_get_contents(
			filename,
			&mobile_msg_map_data,
			&mobile_msg_map_size,
			NULL),
		"Cannot load %s/received-2.wbxml.", testDirectory);
	smlSafeCFree(&filename);
	filename = g_strdup_printf("%s/sent-3.wbxml", testDirectory);
	sml_fail_unless(
		g_file_get_contents(
			filename,
			&libsyncml_msg_end_data,
			&libsyncml_msg_end_size,
			NULL),
		"Cannot load %s/sent-3.wbxml.", testDirectory);
	smlSafeCFree(&filename);

	/* init the mobiles SyncML stack */

	SmlError *error = NULL;
	mobile_tsp = smlTransportNew(SML_TRANSPORT_OBEX_SERVER, &error);

	sml_fail_unless(smlTransportSetConnectionType(mobile_tsp, SML_TRANSPORT_CONNECTION_TYPE_NET, &error), NULL);
	sml_fail_unless(smlTransportSetConfigOption(mobile_tsp, SML_TRANSPORT_CONFIG_PORT, obex_port, &error), NULL);

	smlTransportSetEventCallback(mobile_tsp, _recv_server_event, GINT_TO_POINTER(1));

	sml_fail_unless(smlTransportInitialize(mobile_tsp, &error), NULL);
}

void obex_mobile_disconnect()
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	SmlError *error = NULL;
	while (mobile_state != TEST_MOBILE_STATE_DISCONNECTED)
	{
		usleep(100);
	}
	sml_fail_unless(smlTransportFinalize(mobile_tsp, &error), NULL);
	smlTransportFree(mobile_tsp);

	/* free configuration */
	smlSafeCFree(&libsyncml_msg_notification_data);
	smlSafeCFree(&mobile_msg_alert_data);
	smlSafeCFree(&libsyncml_msg_alert_data);
	smlSafeCFree(&mobile_msg_sync_data);
	smlSafeCFree(&libsyncml_msg_sync_data);
	smlSafeCFree(&mobile_msg_map_data);
	smlSafeCFree(&libsyncml_msg_end_data);

	smlTrace(TRACE_EXIT, "%s", __func__);
}

