/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */

#include "test-utils.h"
#include "soup-message-private.h"
#include "soup-connection.h"
#include "soup-uri-utils-private.h"

static gboolean slow_https;

static void
message_finished (SoupMessage *msg, gpointer user_data)
{
	gboolean *finished = user_data;

	*finished = TRUE;
}

static void
message_starting_cb (SoupMessage *msg,
		     GSocket    **ret)
{
        SoupConnection *conn = soup_message_get_connection (msg);

	*ret = soup_connection_get_socket (conn);
}

static void
request_queued_cb (SoupSession *session,
		   SoupMessage *msg,
		   GSocket    **ret)
{
	g_signal_connect (msg, "starting",
			  G_CALLBACK (message_starting_cb),
			  ret);
}

static void
do_message_to_session (SoupSession *session, GUri *uri,
		       const char *comment, guint expected_status)
{
	SoupMessage *msg;
	GBytes *body;
	gboolean finished = FALSE;
	GError *error = NULL;

	if (comment)
		debug_printf (1, "    msg %s\n", comment);
	msg = soup_message_new_from_uri ("GET", uri);

	g_signal_connect (msg, "finished",
			  G_CALLBACK (message_finished), &finished);

	body = soup_test_session_async_send (session, msg, NULL, &error);
	if (expected_status != SOUP_STATUS_NONE) {
		g_assert_no_error (error);
		soup_test_assert_message_status (msg, expected_status);
		g_assert_true (soup_message_is_keepalive (msg));
	} else {
		g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT);
	}
	g_clear_error (&error);
	g_assert_true (finished);

	g_signal_handlers_disconnect_by_func (msg,
					      G_CALLBACK (message_finished),
					      &finished);
	g_clear_pointer (&body, g_bytes_unref);
	g_object_unref (msg);
}

static void
do_msg_tests_for_session (SoupSession *timeout_session,
			  SoupSession *idle_session,
			  SoupSession *plain_session,
			  GUri *fast_uri,
			  GUri *slow_uri)
{
	GSocket *ret, *idle_first = NULL, *idle_second;
	GSocket *plain_first = NULL, *plain_second;

	if (idle_session) {
		g_signal_connect (idle_session, "request-queued",
				  G_CALLBACK (request_queued_cb), &ret);
		do_message_to_session (idle_session, fast_uri, "fast to idle", SOUP_STATUS_OK);
		idle_first = g_object_ref (ret);
	}

	if (plain_session) {
		g_signal_connect (plain_session, "request-queued",
				  G_CALLBACK (request_queued_cb), &ret);
		do_message_to_session (plain_session, fast_uri, "fast to plain", SOUP_STATUS_OK);
		plain_first = g_object_ref (ret);
	}

	do_message_to_session (timeout_session, fast_uri, "fast to timeout", SOUP_STATUS_OK);
	do_message_to_session (timeout_session, slow_uri, "slow to timeout", SOUP_STATUS_NONE);

	if (idle_session) {
		do_message_to_session (idle_session, fast_uri, "fast to idle", SOUP_STATUS_OK);
		idle_second = ret;
		g_signal_handlers_disconnect_by_func (idle_session,
						      (gpointer)request_queued_cb,
						      &ret);

		soup_test_assert (idle_first != idle_second,
				  "idle_session did not close first connection");
		g_object_unref (idle_first);
	}

	if (plain_session) {
		do_message_to_session (plain_session, fast_uri, "fast to plain", SOUP_STATUS_OK);
		plain_second = ret;
		g_signal_handlers_disconnect_by_func (plain_session,
						      (gpointer)request_queued_cb,
						      &ret);

		soup_test_assert (plain_first == plain_second,
				  "plain_session closed connection");
		g_object_unref (plain_first);
	}
}

static void
do_async_timeout_tests (gconstpointer data)
{
	SoupSession *timeout_session, *idle_session, *plain_session;
	GUri *fast_uri = (GUri *)data;
	GUri *slow_uri = g_uri_parse_relative (fast_uri, "/slow", SOUP_HTTP_URI_FLAGS, NULL);
	gboolean extra_slow;

	if (soup_uri_is_https (fast_uri)) {
		SOUP_TEST_SKIP_IF_NO_TLS;
		extra_slow = slow_https;
	} else
		extra_slow = FALSE;

	timeout_session = soup_test_session_new ("timeout", extra_slow ? 3 : 1, NULL);
	idle_session = soup_test_session_new ("idle-timeout", extra_slow ? 2 : 1, NULL);
	/* The "plain" session also has an idle timeout, but it's longer
	 * than the test takes, so for our purposes it should behave like
	 * it has no timeout.
	 */
	plain_session = soup_test_session_new ("idle-timeout", 20, NULL);

	do_msg_tests_for_session (timeout_session, idle_session, plain_session,
				  fast_uri, slow_uri);
	soup_test_session_abort_unref (timeout_session);
	soup_test_session_abort_unref (idle_session);
	soup_test_session_abort_unref (plain_session);

	g_uri_unref (slow_uri);
}

static void
do_sync_timeout_tests (gconstpointer data)
{
	SoupSession *timeout_session, *plain_session;
	GUri *fast_uri = (GUri *)data;
	GUri *slow_uri = g_uri_parse_relative (fast_uri, "/slow", SOUP_HTTP_URI_FLAGS, NULL);
	gboolean extra_slow;

	if (soup_uri_is_https (fast_uri)) {
		SOUP_TEST_SKIP_IF_NO_TLS;

		extra_slow = slow_https;
	} else
		extra_slow = FALSE;

	timeout_session = soup_test_session_new ("timeout", extra_slow ? 3 : 1, NULL);
	/* SoupSession:timeout doesn't work with sync sessions */
	plain_session = soup_test_session_new (NULL);
	do_msg_tests_for_session (timeout_session, NULL, plain_session, fast_uri, slow_uri);
	soup_test_session_abort_unref (timeout_session);
	soup_test_session_abort_unref (plain_session);

	g_uri_unref (slow_uri);
}

static gboolean
timeout_finish_message (gpointer msg)
{
	SoupServer *server = g_object_get_data (G_OBJECT (msg), "server");

	soup_server_unpause_message (server, msg);
	return FALSE;
}

static void
server_handler (SoupServer        *server,
		SoupServerMessage *msg,
		const char        *path,
		GHashTable        *query,
		gpointer           user_data)
{
	soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
	soup_server_message_set_response (msg, "text/plain",
					  SOUP_MEMORY_STATIC,
					  "ok\r\n", 4);

	if (!strcmp (path, "/slow")) {
                GSource *timeout;
		soup_server_pause_message (server, msg);
		g_object_set_data (G_OBJECT (msg), "server", server);
		timeout = soup_add_timeout (g_main_context_get_thread_default (),
                                            4000, timeout_finish_message, msg);
                g_source_unref (timeout);
	}
}

int
main (int argc, char **argv)
{
	SoupServer *server, *https_server = NULL;
	GUri *uri, *https_uri = NULL;
	int ret;

	test_init (argc, argv, NULL);

	server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
	soup_server_add_handler (server, NULL, server_handler, NULL, NULL);
	uri = soup_test_server_get_uri (server, "http", NULL);

	if (tls_available) {
		SoupSession *test_session;
		gint64 start, end;

		https_server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
		soup_server_add_handler (https_server, NULL, server_handler, NULL, NULL);
		https_uri = soup_test_server_get_uri (server, "https", "127.0.0.1");

		/* The 1-second timeouts are too fast for some machines... */
		test_session = soup_test_session_new (NULL);
		start = g_get_monotonic_time ();
		do_message_to_session (test_session, uri, NULL, SOUP_STATUS_OK);
		end = g_get_monotonic_time ();
		soup_test_session_abort_unref (test_session);
		debug_printf (2, "  (https request took %0.3fs)\n", (end - start) / 1000000.0);
		if (end - start > 750000) {
			debug_printf (1, "  (using extra-slow mode)\n\n");
			slow_https = TRUE;
		} else {
			debug_printf (2, "\n");
			slow_https = FALSE;
		}
	} else
		https_uri = g_uri_parse ("https://fail.", SOUP_HTTP_URI_FLAGS, NULL);

	g_test_add_data_func ("/timeout/http/async", uri, do_async_timeout_tests);
	g_test_add_data_func ("/timeout/http/sync", uri, do_sync_timeout_tests);
	g_test_add_data_func ("/timeout/https/async", https_uri, do_async_timeout_tests);
	g_test_add_data_func ("/timeout/https/sync", https_uri, do_sync_timeout_tests);

	ret = g_test_run ();

	g_uri_unref (uri);
	g_uri_unref (https_uri);
	soup_test_server_quit_unref (server);
	if (https_server)
		soup_test_server_quit_unref (https_server);

	test_cleanup ();
	return ret;
}
