/* SPDX-FileCopyrightText: 2025 - Sébastien Wilmet
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#include <gfls/gfls.h>

static void
test_new_and_free (void)
{
	GflsIconv *conv;

	conv = gfls_iconv_new ();
	gfls_iconv_free (conv);

	gfls_iconv_free (NULL);
}

static void
test_open_and_close (void)
{
	GflsIconv *conv;
	gboolean ok;
	GError *error = NULL;

	/* All OK */

	conv = gfls_iconv_new ();
	ok = gfls_iconv_open (conv, "UTF-8", "UTF-8", &error);
	g_assert_true (ok);
	g_assert_no_error (error);

	ok = gfls_iconv_close (conv, &error);
	g_assert_true (ok);
	g_assert_no_error (error);

	gfls_iconv_free (conv);

	/* Open not OK */

	conv = gfls_iconv_new ();
	ok = gfls_iconv_open (conv, "UTF-8", "CANNOT_POSSIBLY_WORK", &error);
	g_assert_false (ok);
	g_assert_nonnull (error);
	g_clear_error (&error);

	ok = gfls_iconv_close (conv, &error);
	g_assert_true (ok);
	g_assert_no_error (error);

	gfls_iconv_free (conv);
}

static void
test_feed_ok (void)
{
	GflsIconv *conv;
	gboolean ok;
	gsize input_buffer_size;
	gsize output_buffer_size;
	gchar *input_buffer;
	gchar *output_buffer;
	gchar *input_buffer_pos;
	gchar *output_buffer_pos;
	gsize input_buffer_n_bytes_left;
	gsize output_buffer_n_bytes_left;
	GflsIconvResult result;
	GError *error = NULL;

	conv = gfls_iconv_new ();
	ok = gfls_iconv_open (conv, "UTF-8", "UTF-8", NULL);
	g_assert_true (ok);

	input_buffer_size = 1;
	input_buffer = g_malloc (input_buffer_size);
	input_buffer[0] = 'a';

	output_buffer_size = 32;
	output_buffer = g_malloc (output_buffer_size);

	input_buffer_pos = input_buffer;
	output_buffer_pos = output_buffer;
	input_buffer_n_bytes_left = input_buffer_size;
	output_buffer_n_bytes_left = output_buffer_size;

	result = gfls_iconv_feed (conv,
				  &input_buffer_pos, &input_buffer_n_bytes_left,
				  &output_buffer_pos, &output_buffer_n_bytes_left,
				  &error);

	g_assert_true (result == GFLS_ICONV_RESULT_OK);
	g_assert_no_error (error);
	g_assert_cmpuint (input_buffer_n_bytes_left, ==, 0);
	g_assert_cmpuint (output_buffer_n_bytes_left, ==, 31);
	g_assert_true (input_buffer_pos == input_buffer + 1);
	g_assert_true (output_buffer_pos == output_buffer + 1);

	gfls_iconv_free (conv);
	g_free (input_buffer);
	g_free (output_buffer);
}

static void
test_feed_output_buffer_full (void)
{
	GflsIconv *conv;
	gboolean ok;
	gsize input_buffer_size;
	gsize output_buffer_size;
	gchar *input_buffer;
	gchar *output_buffer;
	gchar *input_buffer_pos;
	gchar *output_buffer_pos;
	gsize input_buffer_n_bytes_left;
	gsize output_buffer_n_bytes_left;
	GflsIconvResult result;
	GError *error = NULL;

	conv = gfls_iconv_new ();
	ok = gfls_iconv_open (conv, "UTF-8", "UTF-8", NULL);
	g_assert_true (ok);

	input_buffer_size = 20;
	input_buffer = g_malloc (input_buffer_size);
	memset (input_buffer, 'a', input_buffer_size);

	output_buffer_size = 16;
	output_buffer = g_malloc (output_buffer_size);

	input_buffer_pos = input_buffer;
	output_buffer_pos = output_buffer;
	input_buffer_n_bytes_left = input_buffer_size;
	output_buffer_n_bytes_left = output_buffer_size;

	result = gfls_iconv_feed (conv,
				  &input_buffer_pos, &input_buffer_n_bytes_left,
				  &output_buffer_pos, &output_buffer_n_bytes_left,
				  &error);

	g_assert_true (result == GFLS_ICONV_RESULT_OUTPUT_BUFFER_FULL);
	g_assert_no_error (error);
	g_assert_cmpuint (input_buffer_n_bytes_left, ==, 4);
	g_assert_cmpuint (output_buffer_n_bytes_left, ==, 0);
	g_assert_true (input_buffer_pos == input_buffer + 16);
	g_assert_true (output_buffer_pos == output_buffer + 16);

	gfls_iconv_free (conv);
	g_free (input_buffer);
	g_free (output_buffer);
}

static void
test_feed_incomplete_input (void)
{
	GflsIconv *conv;
	gboolean ok;
	gsize input_buffer_size;
	gsize output_buffer_size;
	guint8 *input_buffer_raw;
	gchar *input_buffer;
	gchar *output_buffer;
	gchar *input_buffer_pos;
	gchar *output_buffer_pos;
	gsize input_buffer_n_bytes_left;
	gsize output_buffer_n_bytes_left;
	GflsIconvResult result;
	GError *error = NULL;

	conv = gfls_iconv_new ();
	ok = gfls_iconv_open (conv, "UTF-8", "UTF-8", NULL);
	g_assert_true (ok);

	input_buffer_size = 1;
	input_buffer_raw = g_malloc (input_buffer_size);
	input_buffer = (gchar *) input_buffer_raw;

	// 195, aka "1100 0011" in binary and 0xC3 in hexadecimal.
	// In UTF-8, this is a first (valid) byte of a two-bytes encoded
	// character.
	//
	// For example this is the first byte of "é"
	// Latin Small Letter E With Acute (U+00E9)
	// which is encoded in UTF-8 as 0xC3 0xA9.
	//
	// For a two-bytes UTF-8 character, refer to Wikipedia:
	// First code point: U+0080
	// Last code point: U+07FF
	// Byte 1: 110xxxyy
	// Byte 2: 10yyzzzz
	// See https://en.wikipedia.org/wiki/UTF-8
	input_buffer_raw[0] = 195;

	output_buffer_size = 32;
	output_buffer = g_malloc (output_buffer_size);

	input_buffer_pos = input_buffer;
	output_buffer_pos = output_buffer;
	input_buffer_n_bytes_left = input_buffer_size;
	output_buffer_n_bytes_left = output_buffer_size;

	result = gfls_iconv_feed (conv,
				  &input_buffer_pos, &input_buffer_n_bytes_left,
				  &output_buffer_pos, &output_buffer_n_bytes_left,
				  &error);

	g_assert_true (result == GFLS_ICONV_RESULT_INCOMPLETE_INPUT);
	g_assert_no_error (error);
	g_assert_cmpuint (input_buffer_n_bytes_left, ==, 1);
	g_assert_cmpuint (output_buffer_n_bytes_left, ==, 32);
	g_assert_true (input_buffer_pos == input_buffer);
	g_assert_true (output_buffer_pos == output_buffer);

	gfls_iconv_free (conv);
	g_free (input_buffer_raw);
	g_free (output_buffer);
}

static void
test_feed_illegal_sequence (void)
{
	GflsIconv *conv;
	gboolean ok;
	gsize input_buffer_size;
	gsize output_buffer_size;
	guint8 *input_buffer_raw;
	gchar *input_buffer;
	gchar *output_buffer;
	gchar *input_buffer_pos;
	gchar *output_buffer_pos;
	gsize input_buffer_n_bytes_left;
	gsize output_buffer_n_bytes_left;
	GflsIconvResult result;
	GError *error = NULL;

	conv = gfls_iconv_new ();
	ok = gfls_iconv_open (conv, "UTF-8", "UTF-8", NULL);
	g_assert_true (ok);

	input_buffer_size = 1;
	input_buffer_raw = g_malloc (input_buffer_size);
	input_buffer = (gchar *) input_buffer_raw;

	// 255, aka "1111 1111" in binary and 0xFF in hexadecimal.
	// In UTF-8, this is an invalid byte.
	// See https://en.wikipedia.org/wiki/UTF-8
	input_buffer_raw[0] = 255;

	output_buffer_size = 32;
	output_buffer = g_malloc (output_buffer_size);

	input_buffer_pos = input_buffer;
	output_buffer_pos = output_buffer;
	input_buffer_n_bytes_left = input_buffer_size;
	output_buffer_n_bytes_left = output_buffer_size;

	result = gfls_iconv_feed (conv,
				  &input_buffer_pos, &input_buffer_n_bytes_left,
				  &output_buffer_pos, &output_buffer_n_bytes_left,
				  &error);

	g_assert_true (result == GFLS_ICONV_RESULT_ILLEGAL_SEQUENCE);
	g_assert_no_error (error);
	g_assert_cmpuint (input_buffer_n_bytes_left, ==, 1);
	g_assert_cmpuint (output_buffer_n_bytes_left, ==, 32);
	g_assert_true (input_buffer_pos == input_buffer);
	g_assert_true (output_buffer_pos == output_buffer);

	gfls_iconv_free (conv);
	g_free (input_buffer_raw);
	g_free (output_buffer);
}

static void
test_feed_discard_output (void)
{
	GflsIconv *conv;
	gboolean ok;
	gsize input_buffer_size;
	gchar *input_buffer;
	gchar *input_buffer_pos;
	gsize input_buffer_n_bytes_left;
	GflsIconvResult result;
	GError *error = NULL;

	conv = gfls_iconv_new ();
	ok = gfls_iconv_open (conv, "UTF-8", "UTF-8", NULL);
	g_assert_true (ok);

	// The internal output buffer of gfls_iconv_feed_discard_output()
	// has a size of 1024. So to trigger the loop condition, have something
	// greater than that.
	input_buffer_size = 2000;
	input_buffer = g_malloc (input_buffer_size);
	memset (input_buffer, 'a', input_buffer_size);

	input_buffer_pos = input_buffer;
	input_buffer_n_bytes_left = input_buffer_size;

	result = gfls_iconv_feed_discard_output (conv,
						 &input_buffer_pos, &input_buffer_n_bytes_left,
						 &error);

	g_assert_true (result == GFLS_ICONV_RESULT_OK);
	g_assert_no_error (error);
	g_assert_cmpuint (input_buffer_n_bytes_left, ==, 0);
	g_assert_true (input_buffer_pos == input_buffer + 2000);

	gfls_iconv_free (conv);
	g_free (input_buffer);
}

static void
test_feed_lossy_conversion (void)
{
	GflsIconv *conv;
	gboolean ok;
	gsize input_buffer_size;
	gsize output_buffer_size;
	guint8 *input_buffer_raw;
	gchar *input_buffer;
	gchar *output_buffer;
	gchar *input_buffer_pos;
	gchar *output_buffer_pos;
	gsize input_buffer_n_bytes_left;
	gsize output_buffer_n_bytes_left;
	GflsIconvResult result;
	GError *error = NULL;

	conv = gfls_iconv_new ();
	ok = gfls_iconv_open (conv, "ASCII//TRANSLIT", "UTF-8", NULL);
	g_assert_true (ok);

	input_buffer_size = 2;
	input_buffer_raw = g_malloc (input_buffer_size);
	input_buffer = (gchar *) input_buffer_raw;

	// "é": Latin Small Letter E With Acute (U+00E9)
	// which is encoded in UTF-8 as 0xC3 0xA9.
	input_buffer_raw[0] = 195;
	input_buffer_raw[1] = 169;

	output_buffer_size = 32;
	output_buffer = g_malloc (output_buffer_size);

	input_buffer_pos = input_buffer;
	output_buffer_pos = output_buffer;
	input_buffer_n_bytes_left = input_buffer_size;
	output_buffer_n_bytes_left = output_buffer_size;

	result = gfls_iconv_feed (conv,
				  &input_buffer_pos, &input_buffer_n_bytes_left,
				  &output_buffer_pos, &output_buffer_n_bytes_left,
				  &error);

	g_assert_true (result == GFLS_ICONV_RESULT_LOSSY_CONVERSION);
	g_assert_no_error (error);
	g_assert_cmpuint (input_buffer_n_bytes_left, ==, 0);
	g_assert_cmpuint (output_buffer_n_bytes_left, ==, 31);
	g_assert_true (input_buffer_pos == input_buffer + 2);
	g_assert_true (output_buffer_pos == output_buffer + 1);
	g_assert_cmpuint (output_buffer[0], ==, '?'); // Depends on the implementation.

	gfls_iconv_free (conv);
	g_free (input_buffer_raw);
	g_free (output_buffer);
}

int
main (int    argc,
      char **argv)
{
	int exit_status;

	gfls_init ();
	g_test_init (&argc, &argv, NULL);

	g_test_add_func ("/iconv/new_and_free", test_new_and_free);
	g_test_add_func ("/iconv/open_and_close", test_open_and_close);
	g_test_add_func ("/iconv/feed_ok", test_feed_ok);
	g_test_add_func ("/iconv/feed_output_buffer_full", test_feed_output_buffer_full);
	g_test_add_func ("/iconv/feed_incomplete_input", test_feed_incomplete_input);
	g_test_add_func ("/iconv/feed_illegal_sequence", test_feed_illegal_sequence);
	g_test_add_func ("/iconv/feed_discard_output", test_feed_discard_output);
	g_test_add_func ("/iconv/feed_lossy_conversion", test_feed_lossy_conversion);

	exit_status = g_test_run ();
	gfls_finalize ();

	return exit_status;
}
