/*
 * Copyright (C) Birb Developers
 *
 * Birb is the legal property of its developers, whose names are too numerous
 * to list here. Please refer to the AUTHORS file distributed with this source
 * distribution
 *
 * This program 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 program 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 program; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib.h>

#include <birb.h>

#define BUFFER_SIZE 4096

/******************************************************************************
 * basic tests
 *****************************************************************************/
static void
test_response_stream_new(void) {
	GInputStream *input = NULL;
	GOutputStream *response = NULL;

	response = birb_response_stream_new();
	g_assert_true(BIRB_IS_RESPONSE_STREAM(response));
	g_assert_true(G_IS_OUTPUT_STREAM(response));

	input = birb_response_stream_get_input_stream(BIRB_RESPONSE_STREAM(response));
	g_assert_true(G_IS_INPUT_STREAM(input));

	g_assert_finalize_object(response);
}

/******************************************************************************
 * can_write test
 *****************************************************************************/
static void
test_response_stream_can_write_cb(GObject *source, GAsyncResult *result,
                                  gpointer data)
{
	GError *error = NULL;
	gssize n_bytes = 0;

	n_bytes = g_output_stream_write_finish(G_OUTPUT_STREAM(source), result,
	                                       &error);
	g_assert_no_error(error);

	g_assert_cmpint(n_bytes, ==, strlen(data));
}

static void
test_response_stream_can_write(void) {
	GOutputStream *response = NULL;
	char *data = NULL;

	response = birb_response_stream_new();
	data = g_strdup("Hello World!");

	g_output_stream_write_async(response, data, strlen(data),
	                            G_PRIORITY_DEFAULT, NULL,
	                            test_response_stream_can_write_cb, data);

	g_main_context_iteration(NULL, TRUE);

	g_clear_pointer(&data, g_free);

	g_assert_finalize_object(response);
}

/******************************************************************************
 * example test
 *
 * This is the code in the documentation block.
 *
 *****************************************************************************/
static void
test_response_stream_example(void) {
	GBytes *bytes = NULL;
	GBytes *received = NULL;
	GError *error = NULL;
	GInputStream *input = NULL;
	GOutputStream *response = NULL;
	gconstpointer read_data = NULL;
	gsize read_data_size = 0;

	response = birb_response_stream_new();
	birb_response_stream_add_response(BIRB_RESPONSE_STREAM(response),
	                                  "\r\n\r\n",
	                                  "200 OK\r\nContent-Length: 0\r\n\r\n",
	                                  &error);
	g_assert_no_error(error);

	input = birb_response_stream_get_input_stream(BIRB_RESPONSE_STREAM(response));

	bytes = g_bytes_new_static("GET / HTTP/1.1\r\n\r\n", 18);
	g_output_stream_write_bytes(response, bytes, NULL, NULL);
	g_bytes_unref(bytes);

	received = g_input_stream_read_bytes(input, 4096, NULL, NULL);
	read_data = g_bytes_get_data(received, &read_data_size);
	g_assert_cmpmem(read_data, read_data_size,
	                "200 OK\r\nContent-Length: 0\r\n\r\n", 29);

	g_bytes_unref(received);

	g_object_unref(response);
}

/******************************************************************************
 * remote_first test
 *****************************************************************************/
static void
test_response_stream_response_remote_first_read_cb(GObject *source,
                                                   GAsyncResult *result,
                                                   gpointer data)
{
	GBytes *bytes = NULL;
	GError *error = NULL;
	gconstpointer read_data = NULL;
	gsize read_data_size = 0;
	guint *counter = data;

	g_assert_true(G_IS_INPUT_STREAM(source));

	bytes = g_input_stream_read_bytes_finish(G_INPUT_STREAM(source), result,
	                                         &error);
	g_assert_no_error(error);
	g_assert_nonnull(bytes);

	read_data = g_bytes_get_data(bytes, &read_data_size);
	if(read_data_size == 0) {
		g_clear_pointer(&bytes, g_bytes_unref);

		g_input_stream_read_bytes_async(G_INPUT_STREAM(source), BUFFER_SIZE,
		                                G_PRIORITY_DEFAULT, NULL,
		                                test_response_stream_response_remote_first_read_cb,
		                                data);
		return;
	}

	g_assert_cmpmem(read_data, read_data_size, "user@host:~$\n", 13);
	g_clear_pointer(&bytes, g_bytes_unref);

	*counter = *counter + 1;
}

static void
test_response_stream_response_remote_first(void) {
	GError *error = NULL;
	GInputStream *input = NULL;
	GOutputStream *response = NULL;
	gboolean result = FALSE;
	guint counter = 0;

	/* Create the stream and add our response. */
	response = birb_response_stream_new();
	result = birb_response_stream_add_response(BIRB_RESPONSE_STREAM(response),
	                                           NULL,
	                                           "user@host:~$\n",
	                                           &error);
	g_assert_no_error(error);
	g_assert_true(result);

	input = birb_response_stream_get_input_stream(BIRB_RESPONSE_STREAM(response));

	g_input_stream_read_bytes_async(input, BUFFER_SIZE,
	                                G_PRIORITY_DEFAULT, NULL,
	                                test_response_stream_response_remote_first_read_cb,
	                                &counter);

	/* Iterate until there are no more events. */
	while(g_main_context_iteration(NULL, FALSE));

	g_assert_cmpuint(counter, ==, 1);

	result = g_output_stream_close(response, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	g_assert_finalize_object(response);
}

/******************************************************************************
 * local_first test
 *****************************************************************************/
static void
test_response_stream_response_local_first_read_cb(GObject *source,
                                                  GAsyncResult *result,
                                                  gpointer data)
{
	GBytes *bytes = NULL;
	GError *error = NULL;
	guint *counter = data;
	gconstpointer read_data = NULL;
	gsize read_data_size = 0;

	g_assert_true(G_IS_INPUT_STREAM(source));

	bytes = g_input_stream_read_bytes_finish(G_INPUT_STREAM(source), result,
	                                         &error);
	g_assert_no_error(error);

	read_data = g_bytes_get_data(bytes, &read_data_size);
	if(read_data_size == 0) {
		g_clear_pointer(&bytes, g_bytes_unref);

		g_input_stream_read_bytes_async(G_INPUT_STREAM(source), BUFFER_SIZE,
	                                    G_PRIORITY_DEFAULT, NULL,
	                                    test_response_stream_response_local_first_read_cb,
	                                    data);
		return;
	}

	g_assert_cmpmem(read_data, read_data_size, "PONG\n", 5);
	g_clear_pointer(&bytes, g_bytes_unref);

	*counter = *counter + 1;
}

static void
test_response_stream_response_local_first(void) {
	GError *error = NULL;
	GInputStream *input = NULL;
	GOutputStream *response = NULL;
	gboolean result = FALSE;
	gssize n_bytes = 0;
	const char *output_data = "PING\n";
	guint counter = 0;

	/* Create the stream and add our response. */
	response = birb_response_stream_new();
	result = birb_response_stream_add_response(BIRB_RESPONSE_STREAM(response),
	                                           output_data,
	                                           "PONG\n",
	                                           &error);
	g_assert_no_error(error);
	g_assert_true(result);

	input = birb_response_stream_get_input_stream(BIRB_RESPONSE_STREAM(response));

	g_input_stream_read_bytes_async(input, BUFFER_SIZE,
	                                G_PRIORITY_DEFAULT, NULL,
	                                test_response_stream_response_local_first_read_cb,
	                                &counter);

	/* Write our initial message. */
	n_bytes = g_output_stream_write(response, output_data, strlen(output_data),
	                                NULL, &error);
	g_assert_no_error(error);
	g_assert_cmpint(n_bytes, ==, strlen(output_data));

	/* Iterate until there are no more events. */
	while(g_main_context_iteration(NULL, FALSE));

	g_assert_cmpuint(counter, ==, 1);

	result = g_output_stream_close(response, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	g_assert_finalize_object(response);
}

/******************************************************************************
 * no_match test
 *****************************************************************************/
static void
test_response_stream_response_no_match_read_cb(GObject *source,
                                               GAsyncResult *result,
                                               gpointer data)
{
	GBytes *bytes = NULL;
	GError *error = NULL;
	guint *counter = data;

	/* We get an empty read because the GMemoryInputStream returns immediately
	 * if there's no data.
	 */
	bytes = g_input_stream_read_bytes_finish(G_INPUT_STREAM(source), result,
	                                         &error);
	g_assert_no_error(error);
	g_assert_nonnull(bytes);

	g_assert_cmpuint(g_bytes_get_size(bytes), ==, 0);

	g_clear_pointer(&bytes, g_bytes_unref);

	*counter = *counter + 1;
}

static void
test_response_stream_response_no_match_idle(gpointer data) {
	g_cancellable_cancel(data);
}

static void
test_response_stream_response_no_match(void) {
	GCancellable *cancellable = NULL;
	GError *error = NULL;
	GInputStream *input = NULL;
	GOutputStream *response = NULL;
	gboolean result = FALSE;
	gssize n_bytes = 0;
	guint counter = 0;

	/* Create our cancellable. */
	cancellable = g_cancellable_new();

	/* Create the stream and add our response. */
	response = birb_response_stream_new();
	result = birb_response_stream_add_response(BIRB_RESPONSE_STREAM(response),
	                                           "bar",
	                                           "baz",
	                                           &error);
	g_assert_no_error(error);
	g_assert_true(result);

	input = birb_response_stream_get_input_stream(BIRB_RESPONSE_STREAM(response));

	g_input_stream_read_bytes_async(input, BUFFER_SIZE,
	                                G_PRIORITY_DEFAULT, cancellable,
	                                test_response_stream_response_no_match_read_cb,
	                                &counter);

	/* Write our initial message. */
	n_bytes = g_output_stream_write(response, "foo", 3, cancellable, &error);
	g_assert_no_error(error);
	g_assert_cmpint(n_bytes, ==, 3);

	/* Add an idle callback to cancel everything. */
	g_idle_add_once(test_response_stream_response_no_match_idle, cancellable);

	/* Iterate until there are no more events. */
	while(g_main_context_iteration(NULL, FALSE));

	/* Verify that the read callback was called once. */
	g_assert_cmpuint(counter, ==, 1);

	/* Verify that the cancellable was cancelled. */
	g_assert_true(g_cancellable_is_cancelled(cancellable));

	result = g_output_stream_close(response, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	g_assert_finalize_object(response);
	g_assert_finalize_object(cancellable);
}

/******************************************************************************
 * multiple responses
 *****************************************************************************/
static void
test_response_stream_response_multiples_read_cb(GObject *source,
                                                GAsyncResult *result,
                                                gpointer data)
{
	BirbResponseStream *response = data;
	GBytes *bytes = NULL;
	GError *error = NULL;
	gconstpointer read_data = NULL;
	const char *writes[] = {"foo", "baz", "qux"};
	const char *reads[] = {"", "bar", "", "quux"};
	gsize read_data_size = 0;
	static guint index = 0;

	/* We get an empty read because the GMemoryInputStream returns immediately
	 * if there's no data.
	 */
	bytes = g_input_stream_read_bytes_finish(G_INPUT_STREAM(source), result,
	                                         &error);
	g_assert_no_error(error);
	g_assert_nonnull(bytes);

	read_data = g_bytes_get_data(bytes, &read_data_size);
	g_assert_cmpmem(read_data, (gssize)read_data_size, reads[index],
	                strlen(reads[index]));

	g_clear_pointer(&bytes, g_bytes_unref);

	if(index < G_N_ELEMENTS(writes)) {
		gssize n_bytes = 0;

		n_bytes = g_output_stream_write(G_OUTPUT_STREAM(response),
		                                writes[index], strlen(writes[index]),
		                                NULL, &error);
		g_assert_no_error(error);
		g_assert_cmpint(n_bytes, ==, strlen(writes[index]));

		g_input_stream_read_bytes_async(G_INPUT_STREAM(source), BUFFER_SIZE,
		                                G_PRIORITY_DEFAULT, NULL,
		                                test_response_stream_response_multiples_read_cb,
		                                response);
	}

	/* Increment our index. */
	index++;
}

static void
test_response_stream_response_multiples(void) {
	GError *error = NULL;
	GInputStream *input = NULL;
	GOutputStream *response = NULL;
	gboolean result = FALSE;

	/* Create the stream and add our responses. */
	response = birb_response_stream_new();
	result = birb_response_stream_add_response(BIRB_RESPONSE_STREAM(response),
	                                           "foo",
	                                           "bar",
	                                           &error);
	g_assert_no_error(error);
	g_assert_true(result);

	result = birb_response_stream_add_response(BIRB_RESPONSE_STREAM(response),
	                                           "qux",
	                                           "quux",
	                                           &error);
	g_assert_no_error(error);
	g_assert_true(result);

	/* Create the stream. */
	input = birb_response_stream_get_input_stream(BIRB_RESPONSE_STREAM(response));

	g_input_stream_read_bytes_async(input, BUFFER_SIZE,
	                                G_PRIORITY_DEFAULT, NULL,
	                                test_response_stream_response_multiples_read_cb,
	                                response);

	/* Iterate until there are no more events. */
	while(g_main_context_iteration(NULL, FALSE));

	result = g_output_stream_close(response, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	g_assert_finalize_object(response);
}

/******************************************************************************
 * partial_writes test
 *****************************************************************************/
static void
test_response_stream_response_partial_writes_read_cb(GObject *source,
                                                     GAsyncResult *result,
                                                     gpointer data)
{
	GBytes *bytes = NULL;
	GError *error = NULL;
	gconstpointer read_data = NULL;
	gsize read_data_size = 0;
	guint *counter = data;

	g_assert_true(G_IS_INPUT_STREAM(source));

	bytes = g_input_stream_read_bytes_finish(G_INPUT_STREAM(source), result,
	                                         &error);
	g_assert_no_error(error);
	g_assert_nonnull(bytes);

	read_data = g_bytes_get_data(bytes, &read_data_size);
	if(read_data_size == 0) {
		g_clear_pointer(&bytes, g_bytes_unref);

		g_input_stream_read_bytes_async(G_INPUT_STREAM(source), BUFFER_SIZE,
		                                G_PRIORITY_DEFAULT, NULL,
		                                test_response_stream_response_partial_writes_read_cb,
		                                data);
		return;
	}

	g_assert_cmpmem(read_data, read_data_size, "bar", 3);
	g_clear_pointer(&bytes, g_bytes_unref);

	*counter = *counter + 1;
}

static void
test_response_stream_response_partial_writes(void) {
	GError *error = NULL;
	GInputStream *input = NULL;
	GOutputStream *response = NULL;
	gboolean result = FALSE;
	gssize n_bytes = 0;
	guint counter = 0;

	/* Create the stream and add our response. */
	response = birb_response_stream_new();
	result = birb_response_stream_add_response(BIRB_RESPONSE_STREAM(response),
	                                           "foo",
	                                           "bar",
	                                           &error);
	g_assert_no_error(error);
	g_assert_true(result);

	/* Setup the reader */
	input = birb_response_stream_get_input_stream(BIRB_RESPONSE_STREAM(response));

	g_input_stream_read_bytes_async(input, BUFFER_SIZE,
	                                G_PRIORITY_DEFAULT, NULL,
	                                test_response_stream_response_partial_writes_read_cb,
	                                &counter);

	/* Write the first part of our data. */
	n_bytes = g_output_stream_write(response, "fo", 2, NULL, &error); /* codespell:ignore */
	g_assert_no_error(error);
	g_assert_cmpuint(n_bytes, ==, 2);

	/* Write the second part of our data. */
	n_bytes = g_output_stream_write(response, "o", 1, NULL, &error);
	g_assert_no_error(error);
	g_assert_cmpuint(n_bytes, ==, 1);

	/* Iterate until there are no more events. */
	while(g_main_context_iteration(NULL, FALSE));

	g_assert_cmpuint(counter, ==, 1);

	result = g_output_stream_close(response, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	g_assert_finalize_object(response);
}

/******************************************************************************
 * null_response test
 *****************************************************************************/
static void
test_response_stream_response_null_response_read_cb(GObject *source,
                                                    GAsyncResult *result,
                                                    gpointer data)
{
	GBytes *bytes = NULL;
	GError *error = NULL;
	gconstpointer read_data = NULL;
	gsize read_data_size = 0;
	guint *counter = data;

	g_assert_true(G_IS_INPUT_STREAM(source));

	bytes = g_input_stream_read_bytes_finish(G_INPUT_STREAM(source), result,
	                                         &error);
	g_assert_no_error(error);
	g_assert_nonnull(bytes);

	read_data = g_bytes_get_data(bytes, &read_data_size);
	if(read_data_size == 0) {
		g_clear_pointer(&bytes, g_bytes_unref);

		g_input_stream_read_bytes_async(G_INPUT_STREAM(source), BUFFER_SIZE,
		                                G_PRIORITY_DEFAULT, NULL,
		                                test_response_stream_response_null_response_read_cb,
		                                data);
		return;
	}

	g_assert_cmpmem(read_data, read_data_size, "baz", 3);
	g_clear_pointer(&bytes, g_bytes_unref);

	*counter = *counter + 1;
}

static void
test_response_stream_response_null_response(void) {
	GError *error = NULL;
	GInputStream *input = NULL;
	GOutputStream *response = NULL;
	gboolean result = FALSE;
	gssize n_bytes = 0;
	guint counter = 0;

	/* Create the stream and add our responses. */
	response = birb_response_stream_new();
	result = birb_response_stream_add_response(BIRB_RESPONSE_STREAM(response),
	                                           "foo",
	                                           NULL,
	                                           &error);
	g_assert_no_error(error);
	g_assert_true(result);

	result = birb_response_stream_add_response(BIRB_RESPONSE_STREAM(response),
	                                           "bar",
	                                           "baz",
	                                           &error);
	g_assert_no_error(error);
	g_assert_true(result);

	/* Setup the reader */
	input = birb_response_stream_get_input_stream(BIRB_RESPONSE_STREAM(response));

	g_input_stream_read_bytes_async(input, BUFFER_SIZE,
	                                G_PRIORITY_DEFAULT, NULL,
	                                test_response_stream_response_null_response_read_cb,
	                                &counter);

	/* Write the first part of our data. */
	n_bytes = g_output_stream_write(response, "foo", 3, NULL, &error);
	g_assert_no_error(error);
	g_assert_cmpuint(n_bytes, ==, 3);

	/* Write the second part of our data. */
	n_bytes = g_output_stream_write(response, "bar", 3, NULL, &error);
	g_assert_no_error(error);
	g_assert_cmpuint(n_bytes, ==, 3);

	/* Iterate until there are no more events. */
	while(g_main_context_iteration(NULL, FALSE));

	g_assert_cmpuint(counter, ==, 1);

	result = g_output_stream_close(response, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	g_assert_finalize_object(response);
}

/******************************************************************************
 * Main
 *****************************************************************************/
int
main(int argc, char **argv) {
	g_test_init(&argc, &argv, NULL);

	g_test_set_nonfatal_assertions();

	g_test_add_func("/response-stream/new", test_response_stream_new);

	g_test_add_func("/response-stream/can-write",
	                test_response_stream_can_write);

	g_test_add_func("/response-stream/example",
	                test_response_stream_example);

	g_test_add_func("/response-stream/response/remote_first",
	                test_response_stream_response_remote_first);
	g_test_add_func("/response-stream/response/local_first",
	                test_response_stream_response_local_first);
	g_test_add_func("/response-stream/response/no_match",
	                test_response_stream_response_no_match);
	g_test_add_func("/response-stream/response/multiples",
	                test_response_stream_response_multiples);
	g_test_add_func("/response-stream/response/partial-writes",
	                test_response_stream_response_partial_writes);
	g_test_add_func("/response-stream/response/null-response",
	                test_response_stream_response_null_response);

	return g_test_run();
}
