/*
 * 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 <config.h>

#include "tests/support.h"
#include <libsyncml/sml_queue_internals.h>
#include <libsyncml/sml_transport_internals.h>

#include <libsoup/soup-session-async.h>
#include <libsoup/soup-uri.h>
#include <libsoup/soup-server.h>
#include <libsoup/soup-message.h>
#include <libsoup/soup-session-sync.h>

#include <string.h>

#ifdef HAVE_LIBSOUP22
#define soup_message_headers_get soup_message_get_header
#define soup_message_headers_append soup_message_add_header
#endif

static int server_messages;
static int server_connects;
static int server_disconnects;
static int server_errors;

static int client_messages;
static int client_connects;
static int client_disconnects;
static int client_errors;

static void init_testbed()
{
	setup_testbed(NULL);

	g_type_init();

	server_messages = 0;
	server_errors = 0;

	client_messages = 0;
	client_connects = 0;
	client_disconnects = 0;
	client_errors = 0;
}

/* dummy callback for the transport queue */
void transport_dummy_callback(void *message, void *userdata)
{
	/* this function should never be called */
	smlTrace(TRACE_ERROR, "%s(%p, %p)", __func__, message, userdata);
	g_atomic_int_inc(&server_errors);
}

#ifdef HAVE_LIBSOUP22
static void server_callback(
		SoupServerContext *context,
		SoupMessage *msg,
		gpointer data)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, context, msg, data);
#else
static void server_callback(
		SoupServer        *server,
		SoupMessage       *msg, 
		const char        *path,
		GHashTable        *query,
		SoupClientContext *client,
		gpointer           data)
{
	/* additional stuff is only traced to avoid warnings from gcc profiling */
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %s, %p, %p, %p)", __func__, server, msg, path, query, client, data);
#endif
	smlAssert(msg);
#ifdef HAVE_LIBSOUP22
	const char *content = msg->request.body;
	size_t length = msg->request.length;
#else
	smlAssert(msg->request_body);
	const char *content = msg->request_body->data;
	size_t length = msg->request_body->length;
#endif
	if (length < strlen("test")) {
		g_atomic_int_inc(&server_errors);
	} else if (memcmp("test", content, 4) == 0) {
		g_atomic_int_inc(&server_messages);
	} else {
		g_atomic_int_inc(&server_errors);
	}

	/*  prepare the response*/

	soup_message_set_status (msg, SOUP_STATUS_OK);
	soup_message_set_response (
		msg, SML_ELEMENT_XML,
#ifdef HAVE_LIBSOUP22
		SOUP_BUFFER_STATIC,
#else
		SOUP_MEMORY_STATIC,
#endif
		"answer", 6);
	smlTrace(TRACE_EXIT, "%s", __func__);
}

#ifdef HAVE_LIBSOUP22
static void client_callback(SoupMessage *msg, gpointer userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, msg, userdata);
#else
static void client_callback(
		SoupSession *session,
		SoupMessage *msg,
		gpointer userdata)
{
	/* session is only traced to avoid warnings from gcc profiling */
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, msg, userdata);
#endif
	g_atomic_int_inc(&client_messages);
	smlTrace(TRACE_EXIT, "%s", __func__);
}

GMainContext *server_ctx;
SmlThread *server_thread;
SmlQueue *queue;
SoupServer *server;
SmlLink *server_link;
static void init_server(unsigned int port)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	GError *error = NULL;

	/* prepare asynchronous runtime environment */

	server_ctx = g_main_context_new();
	sml_fail_unless(server_ctx != NULL, NULL);
	server_thread = smlThreadNew(server_ctx, &error);
	sml_fail_unless(server_thread != NULL, "%s", GET_ERROR_MESSAGE(error));

	/* simulate the transport layer behaviour */

	queue = smlQueueNew(&error);
	sml_fail_unless(queue != NULL, "%s", GET_ERROR_MESSAGE(error));
	smlQueueSetHandler(queue, (SmlQueueHandler)transport_dummy_callback, NULL);
	smlQueueAttach(queue, server_ctx);

	/* create async server */

	server = soup_server_new (
			SOUP_SERVER_PORT, port,
			SOUP_SERVER_ASYNC_CONTEXT, server_ctx,
			NULL);
	sml_fail_unless(server != NULL, NULL);
#ifdef HAVE_LIBSOUP22
	soup_server_add_handler(
			server, NULL, NULL,
			server_callback, NULL, NULL);
#else
	soup_server_add_handler (
			server, NULL,
			server_callback, NULL, NULL);
#endif
	soup_server_run_async (server);

	/* start thread */

	smlThreadStart(server_thread);

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

static void cleanup_server()
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	/* wait until message was received by server */

	int64_t sleep_max = 5000000000LL;
	int64_t sleep_interval = 50000000;
	int64_t sleep_total = 0;
	while (server_messages < 1 && server_errors < 1 && sleep_total < sleep_max)
	{
		sml_sleep(sleep_interval);
		sleep_total += sleep_interval;
	}

	/* check counter */

	sml_fail_unless(sleep_total < sleep_max, "timeout detected");
	sml_fail_unless(server_messages == 1, NULL);
	sml_fail_unless(server_errors == 0, NULL);

	/* cleanup */
	smlThreadStop(server_thread);
	smlThreadFree(server_thread);
	g_main_context_unref(server_ctx);
	soup_server_quit(server);
	g_object_unref(server);
 	smlQueueFree(queue);
	smlTrace(TRACE_EXIT, "%s", __func__);

}

gboolean _recv_event(
		SmlTransport *tsp,
		SmlLink *link_,
		SmlTransportEventType type,
		SmlTransportData *data,
		const GError *error,
		void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%d)", __func__, type);
	
	int source = GPOINTER_TO_INT(userdata);
	
	switch (type) {
		case SML_TRANSPORT_EVENT_CONNECT_DONE:
			if (source == 1)
				g_atomic_int_inc(&client_connects);
			else
				g_atomic_int_inc(&server_connects);
			break;
		case SML_TRANSPORT_EVENT_DISCONNECT_DONE:
			if (source == 1)
				g_atomic_int_inc(&client_disconnects);
			else
				g_atomic_int_inc(&server_disconnects);
			break;
		case SML_TRANSPORT_EVENT_DATA:
			sml_fail_unless(1 <= source && source <= 2, NULL);
			if (source == 1) {
				sml_fail_unless(!strcmp(data->data, "answer"), NULL);
				sml_fail_unless(data->size == 6, NULL);
				sml_fail_unless(data->type == SML_MIMETYPE_XML, NULL);
				g_atomic_int_inc(&client_messages);
			} else {
				if (link_) {
					server_link = link_;
					smlLinkRef(server_link);
				}

				sml_fail_unless(!strcmp(data->data, "test"), NULL);
				sml_fail_unless(data->size == 4, NULL);
				sml_fail_unless(data->type == SML_MIMETYPE_XML, NULL);
				g_atomic_int_inc(&server_messages);

				GError *terror = NULL;
				data = smlTransportDataNew(
						(char *)"answer", 6,
						SML_MIMETYPE_XML, FALSE,
						&terror);
				sml_fail_unless(data != NULL, NULL);
				sml_fail_unless(terror == NULL, NULL);

				sml_fail_unless(smlTransportSend(tsp, server_link, data, &terror), NULL);
				sml_fail_unless(terror == NULL, NULL);
	
				smlTransportDataDeref(data);
			}
			break;
		case SML_TRANSPORT_EVENT_ERROR:
			sml_fail_unless(error != NULL, NULL);
			if (source == 1)
				g_atomic_int_inc(&client_errors);
			else
				g_atomic_int_inc(&server_errors);
			break;
		default:
			sml_fail_unless(FALSE, "An unexpected transport event %d was received.", type);
			break;
	}
	
	smlTrace(TRACE_EXIT, "%s()", __func__);
	return TRUE;
}

static void run_transport_client(const char *url)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	GError *error = NULL;

	/* init client */

	SmlTransport *client = smlTransportNew(SML_TRANSPORT_HTTP_CLIENT, &error);
	sml_fail_unless(smlTransportSetConfigOption(client, "URL", url, &error), NULL);
	smlTransportSetEventCallback(client, _recv_event, GINT_TO_POINTER(1));
	sml_fail_unless(smlTransportInitialize(client, &error), "%s", GET_ERROR_MESSAGE(error));

	/* send test message */

	SmlTransportData *data = smlTransportDataNew((char *)"test", 4, SML_MIMETYPE_XML, FALSE, &error);
	sml_fail_unless(data != NULL, NULL);
	sml_fail_unless(error == NULL, NULL);

	sml_fail_unless(smlTransportSend(client, NULL, data, &error), NULL);
	sml_fail_unless(error == NULL, NULL);
	
	smlTransportDataDeref(data);

	/* wait for client connect */

	int64_t sleep_max = 5000000000LL;
	int64_t sleep_interval = 50000000;
	int64_t sleep_total = 0;
	while (client_connects < 1 && sleep_total < sleep_max)
	{
		sml_sleep(sleep_interval);
		sleep_total += sleep_interval;
	}
	sml_fail_unless(sleep_total < sleep_max, "client connect timed out");

	/* cleanup client */

	sml_fail_unless(smlTransportDisconnect(client, NULL, &error), "%s", GET_ERROR_MESSAGE(error));
	sleep_total = 0;
	while (client_disconnects < 1 && client_errors < 1 && sleep_total < sleep_max)
	{
		sml_sleep(sleep_interval);
		sleep_total += sleep_interval;
	}
	sml_fail_unless(client_errors == 0, NULL);
	sml_fail_unless(sleep_total < sleep_max, "client disconnect timed out");
	sml_fail_unless(smlTransportFinalize(client, &error), NULL);
	smlTransportFree(client);

	/* verify results */

	sml_fail_unless(client_errors == 0, NULL);
	// sml_fail_unless(client_messages !=  0, NULL);

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

GMainContext *client_ctx;
SoupSession *client_session;

gboolean init_soup_session(gpointer data, GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, data, error);
	sml_fail_unless(data != NULL, NULL);
	SoupMessage *msg = data;
	client_session = soup_session_async_new_with_options(
					SOUP_SESSION_ASYNC_CONTEXT, client_ctx,
					SOUP_SESSION_TIMEOUT, 5,
					NULL);
	sml_fail_unless(client_session != NULL, NULL);
	soup_session_queue_message(client_session, msg, client_callback, NULL);
	return TRUE;
}

gboolean cleanup_soup_session(gpointer data, GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, data, error);

	smlTrace(TRACE_INTERNAL, "%s: aborting session", __func__);
	soup_session_abort(client_session);
	smlTrace(TRACE_INTERNAL, "%s: unref session", __func__);
	g_object_unref(client_session);

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

START_TEST (libsoup_async)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	init_testbed();
	GError *error = NULL;

	init_server(13001);

	/* prepare message */

	SoupMessage *msg = soup_message_new (SOUP_METHOD_POST, "http://127.0.0.1:13001/");
        soup_message_headers_append(msg->request_headers, "Accept", "text/plain");
        soup_message_set_request (msg, "text/plain",
#ifdef HAVE_LIBSOUP22
		SOUP_BUFFER_SYSTEM_OWNED,
#else
		SOUP_MEMORY_TAKE,
#endif
		g_memdup("test", 4), 4);

#ifdef HAVE_LIBSOUP22_SOLARIS
	/* create synchronous client */

	SoupSession *session = soup_session_sync_new();
	soup_session_send_message(session, msg);
	client_callback(msg, NULL);
#else
	/* create asynchronous client */

	client_ctx = g_main_context_new();
	sml_fail_unless(client_ctx != NULL, NULL);
	SmlThread *client_thread = smlThreadNew(client_ctx, &error);
	sml_fail_unless(client_thread != NULL, "%s", GET_ERROR_MESSAGE(error));
	smlThreadStart(client_thread);
	
	sml_fail_unless(smlThreadCallFunction(client_thread, init_soup_session, msg, &error), "%s", GET_ERROR_MESSAGE(error));
#endif

	/* test and cleanup server */
	cleanup_server();

	/* wait for client disconnect */

	int64_t sleep_max = 5000000000LL;
	int64_t sleep_interval = 50000000;
	int64_t sleep_total = 0;
	while (client_messages < 1 && sleep_total < sleep_max)
	{
		sml_sleep(sleep_interval);
		sleep_total += sleep_interval;
	}
	sml_fail_unless(sleep_total < sleep_max, "client disconnect timed out");

#ifdef HAVE_LIBSOUP22_SOLARIS
	soup_session_abort(session);
	g_object_unref(session);
#else
	/* cleanup session */

	sml_fail_unless(smlThreadCallFunction(client_thread, cleanup_soup_session, NULL, &error), "%s", GET_ERROR_MESSAGE(error));

	/* The client thread must be stopped first
	 * because libsoup is not thread safe.
	 * If the client thread is still running
	 * then the behaviour of the session is not predictable.
	 */
	smlTrace(TRACE_INTERNAL, "%s: cleanup client thread", __func__);
	smlThreadStop(client_thread);
	smlThreadFree(client_thread);
	smlTrace(TRACE_INTERNAL, "%s: cleanup client context", __func__);
	g_main_context_unref(client_ctx);
#endif

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

START_TEST (libsoup_http_client)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	init_testbed();

	init_server(13002);
	run_transport_client("http://127.0.0.1:13002");
	cleanup_server();
	smlTrace(TRACE_EXIT, "%s", __func__);
}
END_TEST

START_TEST (libsoup_http_server)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	init_testbed();
	GError *error = NULL;

	/* init server transport */
	
	SmlTransport *serverTsp = smlTransportNew(SML_TRANSPORT_HTTP_SERVER, &error);
	sml_fail_unless(serverTsp != NULL, "%s", GET_ERROR_MESSAGE(error));
	sml_fail_unless(smlTransportSetConfigOption(serverTsp, "PORT", "13003", &error), NULL);
	smlTransportSetEventCallback(serverTsp, _recv_event, GINT_TO_POINTER(2));
	sml_fail_unless(smlTransportInitialize(serverTsp, &error), "%s", GET_ERROR_MESSAGE(error));

	/* client stuff */

	run_transport_client("http://127.0.0.1:13003");

	/* cleanup server transport */

	sml_fail_unless(smlTransportDisconnect(serverTsp, server_link, &error), NULL);
	smlLinkDeref(server_link);
	int64_t sleep_max = 5000000000LL;
	int64_t sleep_interval = 50000000;
	int64_t sleep_total = 0;
	while (server_disconnects < 1 && server_errors < 1 && sleep_total < sleep_max)
	{
		sml_sleep(sleep_interval);
		sleep_total += sleep_interval;
	}
	sml_fail_unless(server_errors == 0, NULL);
	sml_fail_unless(sleep_total < sleep_max, "server disconnect timed out");
	sml_fail_unless(smlTransportFinalize(serverTsp, &error), NULL);
	smlTransportFree(serverTsp);
}
END_TEST

@SML_TESTCASE_CODE@

