/*
 * Copyright (C) 2023 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 General Public License as published by
 * the Free Software Foundation; either version 2 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
 */

#include <glib.h>
#include <string.h>

#include <birb.h>

/* These values are used in all of the push_bytes tests. */
static const gsize test_bytes_data_len = 5;
static const guint8 test_bytes_data[] = "12345";

static const gsize test_bytes_data_len2 = 4;
static const guint8 test_bytes_data2[] = "6789";

static const gsize test_bytes_data_len3 = 12;
static const guint8 test_bytes_data3[] = "101112131415";

/******************************************************************************
 * _new test
 *****************************************************************************/
static void
test_queued_output_stream_new(void) {
	GError *error = NULL;
	GOutputStream *output = NULL;
	GOutputStream *queued = NULL;
	gboolean ret = FALSE;

	output = g_memory_output_stream_new_resizable();
	birb_assert_type(output, G_TYPE_MEMORY_OUTPUT_STREAM);

	queued = birb_queued_output_stream_new(output);
	birb_assert_type(queued, BIRB_TYPE_QUEUED_OUTPUT_STREAM);

	ret = g_output_stream_close(G_OUTPUT_STREAM(queued), NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	g_assert_finalize_object(queued);
	g_assert_finalize_object(output);
}

/******************************************************************************
 * push_bytes_async_single test
 *****************************************************************************/
static void
test_queued_output_stream_push_bytes_async_single_cb(GObject *source,
                                                     GAsyncResult *res,
                                                     gpointer user_data)
{
	BirbQueuedOutputStream *queued = BIRB_QUEUED_OUTPUT_STREAM(source);
	GError *error = NULL;
	gboolean ret = FALSE;
	int *counter = user_data;

	ret = birb_queued_output_stream_push_bytes_finish(queued, res, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	*counter = *counter + 1;
}

static void
test_queued_output_stream_push_bytes_async_single(void) {
	GBytes *bytes = NULL;
	GError *error = NULL;
	GOutputStream *output = NULL;
	GOutputStream *queued = NULL;
	gboolean ret = FALSE;
	int counter = 0;

	output = g_memory_output_stream_new_resizable();
	birb_assert_type(output, G_TYPE_MEMORY_OUTPUT_STREAM);

	queued = birb_queued_output_stream_new(output);
	birb_assert_type(queued, BIRB_TYPE_QUEUED_OUTPUT_STREAM);

	bytes = g_bytes_new_static(test_bytes_data, test_bytes_data_len);
	birb_queued_output_stream_push_bytes_async(BIRB_QUEUED_OUTPUT_STREAM(queued),
	                                           bytes,
	                                           G_PRIORITY_DEFAULT,
	                                           NULL,
	                                           test_queued_output_stream_push_bytes_async_single_cb,
	                                           &counter);
	g_clear_pointer(&bytes, g_bytes_unref);

	while(counter < 1) {
		g_main_context_iteration(NULL, TRUE);
	}

	g_assert_cmpmem(g_memory_output_stream_get_data(G_MEMORY_OUTPUT_STREAM(output)),
	                g_memory_output_stream_get_data_size(G_MEMORY_OUTPUT_STREAM(output)),
	                test_bytes_data,
	                test_bytes_data_len);

	ret = g_output_stream_close(queued, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	g_assert_finalize_object(queued);
	g_assert_finalize_object(output);
}

/******************************************************************************
 * push_bytes_async_multiple test
 *****************************************************************************/
static void
test_queued_output_stream_push_bytes_async_multiple_cb(GObject *source,
                                                       GAsyncResult *res,
                                                       gpointer user_data)
{
	BirbQueuedOutputStream *queued = BIRB_QUEUED_OUTPUT_STREAM(source);
	GError *err = NULL;
	gboolean ret = FALSE;
	int *counter = user_data;

	ret = birb_queued_output_stream_push_bytes_finish(queued, res, &err);
	g_assert_no_error(err);
	g_assert_true(ret);

	*counter = *counter - 1;
}

static void
test_queued_output_stream_push_bytes_async_multiple(void) {
	GBytes *bytes;
	GError *err = NULL;
	GOutputStream *output;
	GOutputStream *queued;
	gboolean ret = FALSE;
	char *all_test_bytes_data;
	int counter = 0;

	output = g_memory_output_stream_new_resizable();
	birb_assert_type(output, G_TYPE_MEMORY_OUTPUT_STREAM);

	queued = birb_queued_output_stream_new(output);
	birb_assert_type(queued, BIRB_TYPE_QUEUED_OUTPUT_STREAM);

	bytes = g_bytes_new_static(test_bytes_data, test_bytes_data_len);
	birb_queued_output_stream_push_bytes_async(BIRB_QUEUED_OUTPUT_STREAM(queued),
	                                           bytes,
	                                           G_PRIORITY_DEFAULT,
	                                           NULL,
	                                           test_queued_output_stream_push_bytes_async_multiple_cb,
	                                           &counter);
	g_bytes_unref(bytes);
	counter++;

	bytes = g_bytes_new_static(test_bytes_data2, test_bytes_data_len2);
	birb_queued_output_stream_push_bytes_async(BIRB_QUEUED_OUTPUT_STREAM(queued),
	                                           bytes,
	                                           G_PRIORITY_DEFAULT,
	                                           NULL,
	                                           test_queued_output_stream_push_bytes_async_multiple_cb,
	                                           &counter);
	g_bytes_unref(bytes);
	counter++;

	bytes = g_bytes_new_static(test_bytes_data3, test_bytes_data_len3);
	birb_queued_output_stream_push_bytes_async(BIRB_QUEUED_OUTPUT_STREAM(queued),
	                                           bytes,
	                                           G_PRIORITY_DEFAULT,
	                                           NULL,
	                                           test_queued_output_stream_push_bytes_async_multiple_cb,
	                                           &counter);
	g_bytes_unref(bytes);
	counter++;

	while(counter > 0) {
		g_main_context_iteration(NULL, TRUE);
	}

	g_assert_cmpint(counter, ==, 0);

	all_test_bytes_data = g_strconcat((const char *)test_bytes_data,
	                                  test_bytes_data2,
	                                  test_bytes_data3,
	                                  NULL);

	g_assert_cmpmem(g_memory_output_stream_get_data(G_MEMORY_OUTPUT_STREAM(output)),
	                g_memory_output_stream_get_data_size(G_MEMORY_OUTPUT_STREAM(output)),
	                all_test_bytes_data,
	                strlen(all_test_bytes_data));

	g_free(all_test_bytes_data);

	ret = g_output_stream_close(queued, NULL, &err);
	g_assert_no_error(err);
	g_assert_true(ret);

	g_assert_finalize_object(queued);
	g_assert_finalize_object(output);
}

/******************************************************************************
 * push_bytes_async_error test
 *****************************************************************************/
static void
test_queued_output_stream_push_bytes_async_error_cb(GObject *source,
                                                    GAsyncResult *res,
                                                    gpointer user_data)
{
	BirbQueuedOutputStream *queued = BIRB_QUEUED_OUTPUT_STREAM(source);
	GError *error = NULL;
	gboolean ret = FALSE;
	int *counter = user_data;

	ret = birb_queued_output_stream_push_bytes_finish(queued, res, &error);
	g_assert_error(error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
	g_clear_error(&error);

	g_assert_false(ret);

	*counter = *counter - 1;
}

static void
test_queued_output_stream_push_bytes_async_error(void) {
	GBytes *bytes = NULL;
	GCancellable *cancellable = NULL;
	GError *error = NULL;
	GOutputStream *output = NULL;
	GOutputStream *queued = NULL;
	gboolean ret = FALSE;
	int counter = 0;

	output = g_memory_output_stream_new_resizable();
	birb_assert_type(output, G_TYPE_MEMORY_OUTPUT_STREAM);

	queued = birb_queued_output_stream_new(output);
	birb_assert_type(queued, BIRB_TYPE_QUEUED_OUTPUT_STREAM);

	cancellable = g_cancellable_new();
	g_assert_nonnull(cancellable);

	g_cancellable_cancel(cancellable);
	g_assert_true(g_cancellable_is_cancelled(cancellable));

	/* bytes get's referenced by push_bytes_async so we can use it multiple
	 * times.
	 */
	bytes = g_bytes_new_static(test_bytes_data, test_bytes_data_len);

	birb_queued_output_stream_push_bytes_async(BIRB_QUEUED_OUTPUT_STREAM(queued),
	                                           bytes,
	                                           G_PRIORITY_DEFAULT,
	                                           cancellable,
	                                           test_queued_output_stream_push_bytes_async_error_cb,
	                                           &counter);
	counter++;

	birb_queued_output_stream_push_bytes_async(BIRB_QUEUED_OUTPUT_STREAM(queued),
	                                           bytes,
	                                           G_PRIORITY_DEFAULT,
	                                           cancellable,
	                                           test_queued_output_stream_push_bytes_async_error_cb,
	                                           &counter);
	counter++;

	birb_queued_output_stream_push_bytes_async(BIRB_QUEUED_OUTPUT_STREAM(queued),
	                                           bytes,
	                                           G_PRIORITY_DEFAULT,
	                                           cancellable,
	                                           test_queued_output_stream_push_bytes_async_error_cb,
	                                           &counter);
	counter++;

	g_bytes_unref(bytes);

	while(counter > 0) {
		g_main_context_iteration(NULL, TRUE);
	}

	g_assert_cmpint(counter, ==, 0);

	g_assert_cmpmem(g_memory_output_stream_get_data(G_MEMORY_OUTPUT_STREAM(output)),
	                g_memory_output_stream_get_data_size(G_MEMORY_OUTPUT_STREAM(output)),
	                NULL,
	                0);

	ret = g_output_stream_close(queued, NULL, &error);
	g_assert_no_error(error);
	g_assert_true(ret);

	g_assert_finalize_object(cancellable);
	g_assert_finalize_object(queued);
	g_assert_finalize_object(output);
}

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

	g_test_set_nonfatal_assertions();

	g_test_add_func("/queued-output-stream/new",
	                test_queued_output_stream_new);
	g_test_add_func("/queued-output-stream/push-bytes-async-single",
	                test_queued_output_stream_push_bytes_async_single);
	g_test_add_func("/queued-output-stream/push-bytes-async-multiple",
	                test_queued_output_stream_push_bytes_async_multiple);
	g_test_add_func("/queued-output-stream/push-bytes-async-error",
	                test_queued_output_stream_push_bytes_async_error);

	return g_test_run();
}
