#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "libsoup/soup.h"
#include "libsoup/soup-session.h"

#include "apache-wrapper.h"

int errors = 0;
int debug = 0;
char *correct_response;
guint correct_response_len;

static void
dprintf (int level, const char *format, ...)
{
	va_list args;

	if (debug < level)
		return;

	va_start (args, format);
	vprintf (format, args);
	va_end (args);
}

static void
authenticate (SoupSession *session, SoupMessage *msg,
	      const char *auth_type, const char *auth_realm,
	      char **username, char **password, gpointer data)
{
	*username = g_strdup ("user2");
	*password = g_strdup ("realm2");
}

static void
get_correct_response (const char *uri)
{
	SoupSession *session;
	SoupMessage *msg;

	session = soup_session_async_new ();
	msg = soup_message_new (SOUP_METHOD_GET, uri);
	soup_session_send_message (session, msg);
	if (msg->status_code != SOUP_STATUS_OK) {
		fprintf (stderr, "Could not fetch %s: %d %s\n", uri,
			 msg->status_code, msg->reason_phrase);
		exit (1);
	}

	correct_response_len = msg->response.length;
	correct_response = g_strndup (msg->response.body, correct_response_len);

	g_object_unref (msg);
	soup_session_abort (session);
	g_object_unref (session);
}

/* Pull API version 1: fully-async. More like a "poke" API. Rather
 * than having SoupMessage emit "got_chunk" signals whenever it wants,
 * we stop it after it finishes reading the message headers, and then
 * tell it when we want to hear about new chunks.
 */

typedef struct {
	GMainLoop *loop;
	SoupMessage *msg;
	guint timeout;
	gboolean chunks_ready;
	gboolean chunk_wanted;
	gboolean did_first_timeout;
	gsize read_so_far;
	guint expected_status;
} FullyAsyncData;

static void fully_async_got_headers (SoupMessage *msg, gpointer user_data);
static void fully_async_got_chunk   (SoupMessage *msg, gpointer user_data);
static void fully_async_finished    (SoupMessage *msg, gpointer user_data);
static gboolean fully_async_request_chunk (gpointer user_data);

static void
do_fully_async_test (SoupSession *session,
		     const char *base_uri, const char *sub_uri,
		     gboolean fast_request, guint expected_status)
{
	GMainLoop *loop;
	FullyAsyncData ad;
	SoupMessage *msg;
	char *uri;

	loop = g_main_loop_new (NULL, FALSE);

	uri = g_build_filename (base_uri, sub_uri, NULL);
	dprintf (1, "GET %s\n", uri);

	msg = soup_message_new (SOUP_METHOD_GET, uri);
	g_free (uri);

	ad.loop = loop;
	ad.msg = msg;
	ad.chunks_ready = FALSE;
	ad.chunk_wanted = FALSE;
	ad.did_first_timeout = FALSE;
	ad.read_so_far = 0;
	ad.expected_status = expected_status;

	/* Since we aren't going to look at the final value of
	 * msg->response.body, we set OVERWRITE_CHUNKS, to tell
	 * libsoup to not even bother generating it.
	 */
	soup_message_set_flags (msg, SOUP_MESSAGE_OVERWRITE_CHUNKS);

	/* Connect to "got_headers", from which we'll decide where to
	 * go next.
	 */
	g_signal_connect (msg, "got_headers",
			  G_CALLBACK (fully_async_got_headers), &ad);

	/* Queue the request */
	soup_session_queue_message (session, msg, fully_async_finished, &ad);

	/* In a real program, we'd probably just return at this point.
	 * Eventually the caller would return all the way to the main
	 * loop, and then eventually, some event would cause the
	 * application to request a chunk of data from the message
	 * response.
	 *
	 * In our test program, there is no "real" main loop, so we
	 * had to create our own. We use a timeout to represent the
	 * event that causes the app to decide to request another body
	 * chunk. We use short timeouts in one set of tests, and long
	 * ones in another, to test both the
	 * chunk-requested-before-its-been-read and
	 * chunk-read-before-its-been-requested cases.
	 */
	ad.timeout = g_timeout_add (fast_request ? 0 : 100,
				    fully_async_request_chunk, &ad);
	g_main_loop_run (ad.loop);
	g_main_loop_unref (ad.loop);
}

static gboolean
fully_async_request_chunk (gpointer user_data)
{
	FullyAsyncData *ad = user_data;

	if (!ad->did_first_timeout) {
		dprintf (1, "  first timeout\n");
		ad->did_first_timeout = TRUE;
	} else
		dprintf (2, "  timeout\n");
	ad->timeout = 0;

	/* ad->chunks_ready and ad->chunk_wanted are used because
	 * there's a race condition between the application requesting
	 * the first chunk, and the message reaching a point where
	 * it's actually ready to read chunks. If chunks_ready has
	 * been set, we can just call soup_message_io_unpause() to
	 * cause the first chunk to be read. But if it's not, we just
	 * set chunk_wanted, to let the got_headers handler below know
	 * that a chunk has already been requested.
	 */
	if (ad->chunks_ready)
		soup_message_io_unpause (ad->msg);
	else
		ad->chunk_wanted = TRUE;

	return FALSE;
}

static void
fully_async_got_headers (SoupMessage *msg, gpointer user_data)
{
	FullyAsyncData *ad = user_data;

	dprintf (1, "  %d %s\n", msg->status_code, msg->reason_phrase);
	if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
		/* Let soup handle this one; this got_headers handler
		 * will get called again next time around.
		 */
		return;
	} else if (msg->status_code != SOUP_STATUS_OK) {
		dprintf (1, "  unexpected status: %d %s\n",
			 msg->status_code, msg->reason_phrase);
		errors++;
		return;
	}

	/* OK, we're happy with the response. So, we connect to
	 * "got_chunk". If there has already been a chunk requested,
	 * we let I/O continue; but if there hasn't, we pause now
	 * until one is requested.
	 */
	ad->chunks_ready = TRUE;
	g_signal_connect (msg, "got_chunk",
			  G_CALLBACK (fully_async_got_chunk), ad);
	if (!ad->chunk_wanted)
		soup_message_io_pause (msg);
}

static void
fully_async_got_chunk (SoupMessage *msg, gpointer user_data)
{
	FullyAsyncData *ad = user_data;

	dprintf (2, "  got chunk from %lu - %lu\n",
		 (unsigned long) ad->read_so_far,
		 (unsigned long) ad->read_so_far + msg->response.length);

	/* We've got a chunk, let's process it. In the case of the
	 * test program, that means comparing it against
	 * correct_response to make sure that we got the right data.
	 * We're using SOUP_MESSAGE_OVERWRITE_CHUNKS, so msg->response
	 * contains just the latest chunk. ad->read_so_far tells us
	 * how far we've read so far.
	 *
	 * Note that since we're using OVERWRITE_CHUNKS, msg->response
	 * is only good until we return from this signal handler; if
	 * you wanted to process it later, you'd need to copy it
	 * somewhere.
	 */
	if (ad->read_so_far + msg->response.length > correct_response_len) {
		dprintf (1, "  read too far! (%lu > %lu)\n",
			 (unsigned long) (ad->read_so_far + msg->response.length),
			 (unsigned long) correct_response_len);
		errors++;
	} else if (memcmp (msg->response.body, correct_response + ad->read_so_far,
			   msg->response.length) != 0) {
		dprintf (1, "  data mismatch in block starting at %lu\n",
			 (unsigned long) ad->read_so_far);
		errors++;
	}
	ad->read_so_far += msg->response.length;

	/* Now pause I/O, and prepare to read another chunk later.
	 * (Again, the timeout just abstractly represents the idea of
	 * the application requesting another chunk at some random
	 * point in the future. You wouldn't be using a timeout in a
	 * real program.)
	 */
	soup_message_io_pause (msg);
	ad->chunk_wanted = FALSE;

	ad->timeout = g_timeout_add (10, fully_async_request_chunk, ad);
}

static void
fully_async_finished (SoupMessage *msg, gpointer user_data)
{
	FullyAsyncData *ad = user_data;

	if (msg->status_code != ad->expected_status) {
		dprintf (1, "  unexpected final status: %d %s !\n",
			 msg->status_code, msg->reason_phrase);
		errors++;
	}

	if (ad->timeout != 0)
		g_source_remove (ad->timeout);

	/* Since our test program is only running the loop for the
	 * purpose of this one test, we quit the loop once the
	 * test is done.
	 */
	g_main_loop_quit (ad->loop);
}


/* Pull API version 2: synchronous pull API via async I/O. */

typedef struct {
	GMainLoop *loop;
	GByteArray *chunk;
} SyncAsyncData;

static void        sync_async_send       (SoupSession *session,
					  SoupMessage *msg);
static GByteArray *sync_async_read_chunk (SoupMessage *msg);
static void        sync_async_cleanup    (SoupMessage *msg);

static void sync_async_got_headers (SoupMessage *msg, gpointer user_data);
static void sync_async_copy_chunk  (SoupMessage *msg, gpointer user_data);
static void sync_async_finished    (SoupMessage *msg, gpointer user_data);

static void
do_synchronously_async_test (SoupSession *session,
			     const char *base_uri, const char *sub_uri,
			     guint expected_status)
{
	SoupMessage *msg;
	char *uri;
	gsize read_so_far;
	GByteArray *chunk;

	uri = g_build_filename (base_uri, sub_uri, NULL);
	dprintf (1, "GET %s\n", uri);

	msg = soup_message_new (SOUP_METHOD_GET, uri);
	g_free (uri);

	/* As in the fully-async case, we set OVERWRITE_CHUNKS as an
	 * optimization.
	 */
	soup_message_set_flags (msg, SOUP_MESSAGE_OVERWRITE_CHUNKS);

	/* Send the message, get back headers */
	sync_async_send (session, msg);
	if (msg->status == SOUP_MESSAGE_STATUS_FINISHED &&
	    expected_status == SOUP_STATUS_OK) {
		dprintf (1, "  finished without reading response!\n");
		errors++;
	} else if (msg->status != SOUP_MESSAGE_STATUS_FINISHED &&
		   expected_status != SOUP_STATUS_OK) {
		dprintf (1, "  request failed to fail!\n");
		errors++;
	}

	/* Now we're ready to read the response body (though we could
	 * put that off until later if we really wanted).
	 */
	read_so_far = 0;
	while ((chunk = sync_async_read_chunk (msg))) {
		dprintf (2, "  read chunk from %lu - %lu\n",
			 (unsigned long) read_so_far,
			 (unsigned long) read_so_far + chunk->len);

		if (read_so_far + chunk->len > correct_response_len) {
			dprintf (1, "  read too far! (%lu > %lu)\n",
				 (unsigned long) read_so_far + chunk->len,
				 (unsigned long) correct_response_len);
			errors++;
		} else if (memcmp (chunk->data,
				   correct_response + read_so_far,
				   chunk->len) != 0) {
			dprintf (1, "  data mismatch in block starting at %lu\n",
				 (unsigned long) read_so_far);
			errors++;
		}
		read_so_far += chunk->len;
		g_byte_array_free (chunk, TRUE);
	}

	if (msg->status != SOUP_MESSAGE_STATUS_FINISHED ||
	    (msg->status_code == SOUP_STATUS_OK &&
	     read_so_far != correct_response_len)) {
		dprintf (1, "  loop ended before message was fully read!\n");
		errors++;
	} else if (msg->status_code != expected_status) {
		dprintf (1, "  unexpected final status: %d %s !\n",
			 msg->status_code, msg->reason_phrase);
		errors++;
	}

	sync_async_cleanup (msg);
	g_object_unref (msg);
}

/* Sends @msg on async session @session and returns after the headers
 * of a successful response (or the complete body of a failed
 * response) have been read.
 */
static void
sync_async_send (SoupSession *session, SoupMessage *msg)
{
	SyncAsyncData *ad;

	ad = g_new0 (SyncAsyncData, 1);
	g_object_set_data (G_OBJECT (msg), "SyncAsyncData", ad);

	/* In this case, unlike the fully-async case, the loop
	 * actually belongs to us, not the application; it will only
	 * be run when we're waiting for chunks, not at other times.
	 *
	 * If session has an async_context associated with it, we'd
	 * want to pass that, rather than NULL, here.
	 */
	ad->loop = g_main_loop_new (NULL, FALSE);

	g_signal_connect (msg, "got_headers",
			  G_CALLBACK (sync_async_got_headers), ad);

	/* Start the request by queuing it and then running our main
	 * loop. Note: we have to use soup_session_queue_message()
	 * here; soup_session_send_message() won't work, for several
	 * reasons. Also, since soup_session_queue_message() steals a
	 * ref to the message and then unrefs it after invoking the
	 * callback, we have to add an extra ref before calling it.
	 */
	g_object_ref (msg);
	soup_session_queue_message (session, msg, sync_async_finished, ad);
	g_main_loop_run (ad->loop);

	/* At this point, one of two things has happened; either the
	 * got_headers handler got headers it liked, and so stopped
	 * the loop, or else the message was fully processed without
	 * the got_headers handler interrupting it, and so the final
	 * callback (sync_async_finished) was invoked, and stopped the
	 * loop.
	 *
	 * Either way, we're done, so we return to the caller.
	 */
}

static void
sync_async_got_headers (SoupMessage *msg, gpointer user_data)
{
	SyncAsyncData *ad = user_data;

	dprintf (1, "  %d %s\n", msg->status_code, msg->reason_phrase);
	if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
		/* Let soup handle this one; this got_headers handler
		 * will get called again next time around.
		 */
		return;
	} else if (msg->status_code != SOUP_STATUS_OK) {
		dprintf (1, "  unexpected status: %d %s\n",
			 msg->status_code, msg->reason_phrase);
		errors++;
		return;
	}

	/* Stop I/O and return to the caller */
	soup_message_io_pause (msg);
	g_main_loop_quit (ad->loop);
}

/* Tries to read a chunk. Returns %NULL on error/end-of-response. (The
 * cases can be distinguished by looking at msg->status and
 * msg->status_code.)
 */
static GByteArray *
sync_async_read_chunk (SoupMessage *msg)
{
	SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
	guint handler;

	if (msg->status == SOUP_MESSAGE_STATUS_FINISHED)
		return NULL;

	ad->chunk = NULL;
	handler = g_signal_connect (msg, "got_chunk",
				    G_CALLBACK (sync_async_copy_chunk),
				    ad);
	soup_message_io_unpause (msg);
	g_main_loop_run (ad->loop);
	g_signal_handler_disconnect (msg, handler);

	return ad->chunk;
}

static void
sync_async_copy_chunk (SoupMessage *msg, gpointer user_data)
{
	SyncAsyncData *ad = user_data;

	/* It's unfortunate that we have to do an extra copy here,
	 * but the data in msg->response.body won't last beyond
	 * the invocation of this handler.
	 */
	ad->chunk = g_byte_array_new ();
	g_byte_array_append (ad->chunk, (gpointer)msg->response.body,
			     msg->response.length);

	/* Now pause and return from the g_main_loop_run() call in
	 * sync_async_read_chunk().
	 */
	soup_message_io_pause (msg);
	g_main_loop_quit (ad->loop);
}

static void
sync_async_finished (SoupMessage *msg, gpointer user_data)
{
	SyncAsyncData *ad = user_data;

	/* Unlike in the fully_async_case, we don't need to do much
	 * here, because control will return to
	 * do_synchronously_async_test() when we're done, and we do
	 * the final tests there.
	 */
	g_main_loop_quit (ad->loop);
}

static void
sync_async_cleanup (SoupMessage *msg)
{
	SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");

	g_main_loop_unref (ad->loop);
	g_free (ad);
}


int
main (int argc, char **argv)
{
	SoupSession *session;
	char *base_uri;
	int opt;

	g_type_init ();
	g_thread_init (NULL);

	while ((opt = getopt (argc, argv, "d")) != -1) {
		switch (opt) {
		case 'd':
			debug++;
			break;
		default:
			fprintf (stderr, "Usage: %s [-d [-d]]\n", argv[0]);
			return 1;
		}
	}

	if (!apache_init ()) {
		fprintf (stderr, "Could not start apache\n");
		return 1;
	}
	base_uri = "http://localhost:47524/";
	get_correct_response (base_uri);

	dprintf (1, "\nFully async, fast requests\n");
	session = soup_session_async_new ();
	g_signal_connect (session, "authenticate",
			  G_CALLBACK (authenticate), NULL);
	do_fully_async_test (session, base_uri, "/",
			     TRUE, SOUP_STATUS_OK);
	do_fully_async_test (session, base_uri, "/Basic/realm1/",
			     TRUE, SOUP_STATUS_UNAUTHORIZED);
	do_fully_async_test (session, base_uri, "/Basic/realm2/",
			     TRUE, SOUP_STATUS_OK);
	soup_session_abort (session);
	g_object_unref (session);

	dprintf (1, "\nFully async, slow requests\n");
	session = soup_session_async_new ();
	g_signal_connect (session, "authenticate",
			  G_CALLBACK (authenticate), NULL);
	do_fully_async_test (session, base_uri, "/",
			     FALSE, SOUP_STATUS_OK);
	do_fully_async_test (session, base_uri, "/Basic/realm1/",
			     FALSE, SOUP_STATUS_UNAUTHORIZED);
	do_fully_async_test (session, base_uri, "/Basic/realm2/",
			     FALSE, SOUP_STATUS_OK);
	soup_session_abort (session);
	g_object_unref (session);

	dprintf (1, "\nSynchronously async\n");
	session = soup_session_async_new ();
	g_signal_connect (session, "authenticate",
			  G_CALLBACK (authenticate), NULL);
	do_synchronously_async_test (session, base_uri, "/",
				     SOUP_STATUS_OK);
	do_synchronously_async_test (session, base_uri, "/Basic/realm1/",
				     SOUP_STATUS_UNAUTHORIZED);
	do_synchronously_async_test (session, base_uri, "/Basic/realm2/",
				     SOUP_STATUS_OK);

	soup_session_abort (session);
	g_object_unref (session);

	g_free (correct_response);

	apache_cleanup ();
	g_main_context_unref (g_main_context_default ());

	dprintf (1, "\n");
	if (errors) {
		printf ("pull-api: %d error(s). Run with '-d' for details\n",
			errors);
	} else
		printf ("pull-api: OK\n");
	return errors;
}


syntax highlighted by Code2HTML, v. 0.9.1