/*
 * Ibis - IRCv3 Library
 * Copyright (C) 2022-2024 Ibis Developers
 *
 * Ibis is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This library 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 of the License, or (at your option) any later version.
 *
 * This library 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 library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib.h>

#include <ibis.h>

/******************************************************************************
 * Tests
 *****************************************************************************/
static void
test_ibis_tags_new(void) {
	IbisTags *tags = NULL;

	tags = ibis_tags_new();
	g_assert_true(IBIS_IS_TAGS(tags));

	g_assert_cmpuint(ibis_tags_get_size(tags), ==, 0);

	g_assert_finalize_object(tags);
}

/******************************************************************************
 * Serialize Tests
 *****************************************************************************/
static void
test_ibis_tags_serialize_empty(void) {
	IbisTags *tags = NULL;
	char *serialized = NULL;

	tags = ibis_tags_new();

	serialized = ibis_tags_serialize(tags);
	g_assert_cmpstr(serialized, ==, "");
	g_clear_pointer(&serialized, g_free);

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_serialize_single(void) {
	IbisTags *tags = NULL;
	char *serialized = NULL;

	tags = ibis_tags_new();
	ibis_tags_add(tags, "foo", "bar");

	serialized = ibis_tags_serialize(tags);
	g_assert_cmpstr(serialized, ==, "@foo=bar");
	g_clear_pointer(&serialized, g_free);

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_serialize_single_missing_value(void) {
	IbisTags *tags = NULL;
	char *serialized = NULL;

	tags = ibis_tags_new();
	ibis_tags_add(tags, "foo", NULL);

	serialized = ibis_tags_serialize(tags);
	g_assert_cmpstr(serialized, ==, "@foo");
	g_clear_pointer(&serialized, g_free);

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_serialize_single_empty_value(void) {
	IbisTags *tags = NULL;
	char *serialized = NULL;

	tags = ibis_tags_new();
	ibis_tags_add(tags, "foo", "");

	serialized = ibis_tags_serialize(tags);
	g_assert_cmpstr(serialized, ==, "@foo");
	g_clear_pointer(&serialized, g_free);

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_serialize_mixed_values(void) {
	IbisTags *tags = NULL;
	char *serialized = NULL;

	tags = ibis_tags_new();
	ibis_tags_add(tags, "foo", "bar");
	ibis_tags_add(tags, "baz", "");
	ibis_tags_add(tags, "quux", NULL);
	ibis_tags_add(tags, "corge", "");
	ibis_tags_add(tags, "grault", "garply");
	ibis_tags_add(tags, "waldo", NULL);

	serialized = ibis_tags_serialize(tags);
	g_assert_cmpstr(serialized, ==,
	                "@foo=bar;baz;quux;corge;grault=garply;waldo");
	g_clear_pointer(&serialized, g_free);

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_serialize_messed_with(void) {
	IbisTags *tags = NULL;
	char *serialized = NULL;
	gboolean removed = FALSE;

	tags = ibis_tags_new();
	ibis_tags_add(tags, "foo", "bar");
	ibis_tags_add(tags, "baz", "");
	ibis_tags_add(tags, "quux", NULL);
	removed = ibis_tags_remove(tags, "baz");
	g_assert_true(removed);
	ibis_tags_add(tags, "corge", "");
	ibis_tags_add(tags, "grault", "garply");
	removed = ibis_tags_remove(tags, "grault");
	g_assert_true(removed);
	ibis_tags_add(tags, "waldo", NULL);

	serialized = ibis_tags_serialize(tags);
	g_assert_cmpstr(serialized, ==,
	                "@foo=bar;quux;corge;waldo");
	g_clear_pointer(&serialized, g_free);

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_serialize_escapes(void) {
	IbisTags *tags = NULL;
	char *serialized = NULL;

	tags = ibis_tags_new();
	ibis_tags_add(tags, "foo", ";");
	ibis_tags_add(tags, "bar", " ");
	ibis_tags_add(tags, "baz", "\\");
	ibis_tags_add(tags, "quux", "\r");
	ibis_tags_add(tags, "qux", "\n");

	serialized = ibis_tags_serialize(tags);
	g_assert_cmpstr(serialized, ==,
	                "@foo=\\:;bar=\\s;baz=\\\\;quux=\\r;qux=\\n");
	g_clear_pointer(&serialized, g_free);

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_serialize_mixed_values_with_escapes(void) {
	IbisTags *tags = NULL;
	char *serialized = NULL;

	tags = ibis_tags_new();
	ibis_tags_add(tags, "foo", "a;b;c");
	ibis_tags_add(tags, "bar", "one two three");
	ibis_tags_add(tags, "baz", "c:\\Users");
	ibis_tags_add(tags, "quux", "hello world!\r\n");

	serialized = ibis_tags_serialize(tags);
	g_assert_cmpstr(serialized, ==,
	                "@foo=a\\:b\\:c;bar=one\\stwo\\sthree;baz=c:\\\\Users;"
	                "quux=hello\\sworld!\\r\\n");
	g_clear_pointer(&serialized, g_free);

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_serialize_duplicates(void) {
	IbisTags *tags = NULL;
	char *serialized = NULL;

	tags = ibis_tags_new();
	ibis_tags_add(tags, "foo", "a;b;c");
	ibis_tags_add(tags, "foo", "dog");
	ibis_tags_add(tags, "bar", NULL);
	ibis_tags_add(tags, "bar", "c:\\Users");
	ibis_tags_add(tags, "baz", NULL);
	ibis_tags_add(tags, "foo", "cat");

	serialized = ibis_tags_serialize(tags);
	g_assert_cmpstr(serialized, ==, "@foo=cat;bar=c:\\\\Users;baz");
	g_clear_pointer(&serialized, g_free);

	g_assert_finalize_object(tags);
}

/******************************************************************************
 * Parse tests
 *****************************************************************************/
static void
test_ibis_tags_parse_empty(void) {
	IbisTags *tags = NULL;
	GError *error = NULL;

	tags = ibis_tags_new();
	ibis_tags_parse_string(tags, "", &error);
	g_assert_no_error(error);

	g_assert_cmpuint(ibis_tags_get_size(tags), ==, 0);

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_parse_single(void) {
	IbisTags *tags = NULL;
	GError *error = NULL;
	const char *value = NULL;

	tags = ibis_tags_new();
	ibis_tags_parse_string(tags, "@foo=bar", &error);
	g_assert_no_error(error);

	g_assert_cmpuint(ibis_tags_get_size(tags), ==, 1);

	g_assert_true(ibis_tags_exists(tags, "foo"));
	value = ibis_tags_lookup(tags, "foo");
	g_assert_cmpstr(value, ==, "bar");

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_parse_single_missing_value(void) {
	IbisTags *tags = NULL;
	GError *error = NULL;
	const char *value = NULL;

	tags = ibis_tags_new();
	ibis_tags_parse_string(tags, "@foo", &error);
	g_assert_no_error(error);

	g_assert_cmpuint(ibis_tags_get_size(tags), ==, 1);

	g_assert_true(ibis_tags_exists(tags, "foo"));
	value = ibis_tags_lookup(tags, "foo");
	g_assert_null(value);

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_parse_single_empty_value(void) {
	IbisTags *tags = NULL;
	GError *error = NULL;
	const char *value = NULL;

	tags = ibis_tags_new();
	ibis_tags_parse_string(tags, "@foo=", &error);
	g_assert_no_error(error);

	g_assert_cmpuint(ibis_tags_get_size(tags), ==, 1);

	g_assert_true(ibis_tags_exists(tags, "foo"));
	value = ibis_tags_lookup(tags, "foo");
	g_assert_null(value);

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_parse_mixed_values(void) {
	IbisTags *tags = NULL;
	GError *error = NULL;
	const char *value = NULL;

	tags = ibis_tags_new();
	ibis_tags_parse_string(tags, "@foo=bar;baz;quux;corge;grault=garply;waldo",
	                       &error);
	g_assert_no_error(error);

	g_assert_cmpuint(ibis_tags_get_size(tags), ==, 6);

	g_assert_true(ibis_tags_exists(tags, "foo"));
	value = ibis_tags_lookup(tags, "foo");
	g_assert_cmpstr(value, ==, "bar");

	g_assert_true(ibis_tags_exists(tags, "baz"));
	value = ibis_tags_lookup(tags, "baz");
	g_assert_null(value);

	g_assert_true(ibis_tags_exists(tags, "quux"));
	value = ibis_tags_lookup(tags, "quux");
	g_assert_null(value);

	g_assert_true(ibis_tags_exists(tags, "corge"));
	value = ibis_tags_lookup(tags, "corge");
	g_assert_null(value);

	g_assert_true(ibis_tags_exists(tags, "grault"));
	value = ibis_tags_lookup(tags, "grault");
	g_assert_cmpstr(value, ==, "garply");

	g_assert_true(ibis_tags_exists(tags, "waldo"));
	value = ibis_tags_lookup(tags, "waldo");
	g_assert_null(value);

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_parse_escapes(void) {
	IbisTags *tags = NULL;
	GError *error = NULL;
	const char *value = NULL;

	tags = ibis_tags_new();
	ibis_tags_parse_string(tags, "@foo=\\:;bar=\\s;baz=\\\\;quux=\\r;qux=\\n",
	                       &error);
	g_assert_no_error(error);

	g_assert_cmpuint(ibis_tags_get_size(tags), ==, 5);

	g_assert_true(ibis_tags_exists(tags, "foo"));
	value = ibis_tags_lookup(tags, "foo");
	g_assert_cmpstr(value, ==, ";");

	g_assert_true(ibis_tags_exists(tags, "bar"));
	value = ibis_tags_lookup(tags, "bar");
	g_assert_cmpstr(value, ==, " ");

	g_assert_true(ibis_tags_exists(tags, "baz"));
	value = ibis_tags_lookup(tags, "baz");
	g_assert_cmpstr(value, ==, "\\");

	g_assert_true(ibis_tags_exists(tags, "quux"));
	value = ibis_tags_lookup(tags, "quux");
	g_assert_cmpstr(value, ==, "\r");

	g_assert_true(ibis_tags_exists(tags, "qux"));
	value = ibis_tags_lookup(tags, "qux");
	g_assert_cmpstr(value, ==, "\n");

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_parse_mixed_values_with_escapes(void) {
	IbisTags *tags = NULL;
	GError *error = NULL;
	const char *value = NULL;

	tags = ibis_tags_new();
	ibis_tags_parse_string(tags,
	                       "@foo=a\\:b\\:c;bar=one\\stwo\\sthree;"
	                       "baz=c:\\\\Users;quux=hello\\sworld!\\r\\n",
	                       &error);
	g_assert_no_error(error);

	g_assert_cmpuint(ibis_tags_get_size(tags), ==, 4);

	g_assert_true(ibis_tags_exists(tags, "foo"));
	value = ibis_tags_lookup(tags, "foo");
	g_assert_cmpstr(value, ==, "a;b;c");

	g_assert_true(ibis_tags_exists(tags, "bar"));
	value = ibis_tags_lookup(tags, "bar");
	g_assert_cmpstr(value, ==, "one two three");

	g_assert_true(ibis_tags_exists(tags, "baz"));
	value = ibis_tags_lookup(tags, "baz");
	g_assert_cmpstr(value, ==, "c:\\Users");

	g_assert_true(ibis_tags_exists(tags, "quux"));
	value = ibis_tags_lookup(tags, "quux");
	g_assert_cmpstr(value, ==, "hello world!\r\n");

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_parse_duplicates(void) {
	IbisTags *tags = NULL;
	GError *error = NULL;
	const char *value = NULL;

	tags = ibis_tags_new();
	ibis_tags_parse_string(tags,
	                       "@foo=seven\\seight\\snine;bar=one\\stwo\\sthree;"
	                       "foo=7,8,9;bar=1,2,3",
	                       &error);
	g_assert_no_error(error);

	g_assert_cmpuint(ibis_tags_get_size(tags), ==, 2);

	g_assert_true(ibis_tags_exists(tags, "foo"));
	value = ibis_tags_lookup(tags, "foo");
	g_assert_cmpstr(value, ==, "7,8,9");

	g_assert_true(ibis_tags_exists(tags, "bar"));
	value = ibis_tags_lookup(tags, "bar");
	g_assert_cmpstr(value, ==, "1,2,3");

	g_assert_finalize_object(tags);
}

/******************************************************************************
 * Spec tests
 *****************************************************************************/
static void
test_ibis_tags_spec_trailing_slash(void) {
	IbisTags *tags = NULL;
	GError *error = NULL;
	const char *value = NULL;

	tags = ibis_tags_new();
	ibis_tags_parse_string(tags, "@foo=test\\", &error);
	g_assert_no_error(error);

	g_assert_true(ibis_tags_exists(tags, "foo"));

	value = ibis_tags_lookup(tags, "foo");
	g_assert_cmpstr(value, ==, "test");

	g_assert_finalize_object(tags);
}

static void
test_ibis_tags_spec_invalid_escape(void) {
	IbisTags *tags = NULL;
	GError *error = NULL;
	const char *value = NULL;

	tags = ibis_tags_new();
	ibis_tags_parse_string(tags, "@foo=\\b", &error);
	g_assert_no_error(error);

	g_assert_true(ibis_tags_exists(tags, "foo"));

	value = ibis_tags_lookup(tags, "foo");
	g_assert_cmpstr(value, ==, "b");

	g_assert_finalize_object(tags);
}

/******************************************************************************
 * Equal Tests
 *****************************************************************************/
static void
ibis_tags_equal_null_null(void) {
	g_assert_true(ibis_tags_equal(NULL, NULL));
}

static void
ibis_tags_equal_empty_empty(void) {
	IbisTags *tags1 = NULL;
	IbisTags *tags2 = NULL;

	tags1 = ibis_tags_new();
	tags2 = ibis_tags_new();

	g_assert_true(ibis_tags_equal(tags1, tags2));

	g_assert_finalize_object(tags1);
	g_assert_finalize_object(tags2);
}

static void
ibis_tags_equal_null_empty(void) {
	IbisTags *tags = NULL;

	tags = ibis_tags_new();

	g_assert_false(ibis_tags_equal(NULL, tags));

	g_assert_finalize_object(tags);
}

static void
ibis_tags_equal_empty_null(void) {
	IbisTags *tags = NULL;

	tags = ibis_tags_new();

	g_assert_false(ibis_tags_equal(tags, NULL));

	g_assert_finalize_object(tags);
}

static void
ibis_tags_equal_single(void) {
	IbisTags *tags1 = NULL;
	IbisTags *tags2 = NULL;

	tags1 = ibis_tags_new();
	ibis_tags_add(tags1, "foo", "bar");

	tags2 = ibis_tags_new();
	ibis_tags_add(tags2, "foo", "bar");

	g_assert_true(ibis_tags_equal(tags1, tags2));

	g_assert_finalize_object(tags1);
	g_assert_finalize_object(tags2);
}

static void
ibis_tags_equal_multiple(void) {
	IbisTags *tags1 = NULL;
	IbisTags *tags2 = NULL;

	tags1 = ibis_tags_new();
	ibis_tags_add(tags1, "foo", "bar");
	ibis_tags_add(tags1, "baz", "quux");

	tags2 = ibis_tags_new();
	ibis_tags_add(tags2, "foo", "bar");
	ibis_tags_add(tags2, "baz", "quux");

	g_assert_true(ibis_tags_equal(tags1, tags2));

	g_assert_finalize_object(tags1);
	g_assert_finalize_object(tags2);
}

static void
ibis_tags_equal_mixed(void) {
	IbisTags *tags1 = NULL;
	IbisTags *tags2 = NULL;

	tags1 = ibis_tags_new();
	ibis_tags_add(tags1, "foo", "bar");

	tags2 = ibis_tags_new();
	ibis_tags_add(tags2, "foo", "bar");
	ibis_tags_add(tags2, "baz", "quux");

	g_assert_false(ibis_tags_equal(tags1, tags2));

	g_assert_finalize_object(tags1);
	g_assert_finalize_object(tags2);
}

/******************************************************************************
 * Contains Tests
 *****************************************************************************/
static void
ibis_tags_contains_empty(void) {
	IbisTags *tags1 = NULL;
	IbisTags *tags2 = NULL;

	tags1 = ibis_tags_new();
	ibis_tags_add(tags1, "foo", "bar");

	tags2 = ibis_tags_new();

	g_assert_true(ibis_tags_contains(tags1, tags2));

	g_assert_finalize_object(tags1);
	g_assert_finalize_object(tags2);
}

static void
ibis_tags_contains_single(void) {
	IbisTags *tags1 = NULL;
	IbisTags *tags2 = NULL;

	tags1 = ibis_tags_new();
	ibis_tags_add(tags1, "foo", "bar");
	ibis_tags_add(tags1, "baz", "quux");

	tags2 = ibis_tags_new();
	ibis_tags_add(tags1, "foo", "bar");

	g_assert_true(ibis_tags_contains(tags1, tags2));

	g_assert_finalize_object(tags1);
	g_assert_finalize_object(tags2);
}

static void
ibis_tags_contains_multiple(void) {
	IbisTags *tags1 = NULL;
	IbisTags *tags2 = NULL;

	tags1 = ibis_tags_new();
	ibis_tags_add(tags1, "foo", "bar");
	ibis_tags_add(tags1, "baz", "quux");
	ibis_tags_add(tags1, "qux", "corge");

	tags2 = ibis_tags_new();
	ibis_tags_add(tags2, "foo", "bar");
	ibis_tags_add(tags2, "qux", "corge");

	g_assert_true(ibis_tags_contains(tags1, tags2));

	g_assert_finalize_object(tags1);
	g_assert_finalize_object(tags2);
}

static void
ibis_tags_contains_too_many(void) {
	IbisTags *tags1 = NULL;
	IbisTags *tags2 = NULL;

	tags1 = ibis_tags_new();
	ibis_tags_add(tags1, "foo", "bar");
	ibis_tags_add(tags1, "baz", "quux");

	tags2 = ibis_tags_new();
	ibis_tags_add(tags2, "foo", "bar");
	ibis_tags_add(tags1, "baz", "quux");
	ibis_tags_add(tags2, "qux", "corge");

	g_assert_false(ibis_tags_contains(tags1, tags2));

	g_assert_finalize_object(tags1);
	g_assert_finalize_object(tags2);
}

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

	g_test_add_func("/ibis/tags/new", test_ibis_tags_new);

	/* Serialize Tests */
	g_test_add_func("/ibis/tags/serialize/empty",
	                test_ibis_tags_serialize_empty);
	g_test_add_func("/ibis/tags/serialize/single",
	                test_ibis_tags_serialize_single);
	g_test_add_func("/ibis/tags/serialize/single-missing-value",
	                test_ibis_tags_serialize_single_missing_value);
	g_test_add_func("/ibis/tags/serialize/single-empty-value",
	                test_ibis_tags_serialize_single_empty_value);
	g_test_add_func("/ibis/tags/serialize/mixed-values",
	                test_ibis_tags_serialize_mixed_values);
	g_test_add_func("/ibis/tags/serialize/messed-with",
	                test_ibis_tags_serialize_messed_with);
	g_test_add_func("/ibis/tags/serialize/escapes",
	                test_ibis_tags_serialize_escapes);
	g_test_add_func("/ibis/tags/serialize/mixed-values-with-escapes",
	                test_ibis_tags_serialize_mixed_values_with_escapes);
	g_test_add_func("/ibis/tags/serialize/duplicates",
	                test_ibis_tags_serialize_duplicates);

	/* Parser Tests */
	g_test_add_func("/ibis/tags/parse/empty", test_ibis_tags_parse_empty);
	g_test_add_func("/ibis/tags/parse/single", test_ibis_tags_parse_single);
	g_test_add_func("/ibis/tags/parse/single-missing-value",
	                test_ibis_tags_parse_single_missing_value);
	g_test_add_func("/ibis/tags/parse/single-empty-value",
	                test_ibis_tags_parse_single_empty_value);
	g_test_add_func("/ibis/tags/parse/mixed-values",
	                test_ibis_tags_parse_mixed_values);
	g_test_add_func("/ibis/tags/parse/escapes", test_ibis_tags_parse_escapes);
	g_test_add_func("/ibis/tags/parse/mixed-values-with-escape",
	                test_ibis_tags_parse_mixed_values_with_escapes);
	g_test_add_func("/ibis/tags/parse/duplicates",
	                test_ibis_tags_parse_duplicates);

	/* Spec Tests */
	g_test_add_func("/ibis/tags/spec/trailing-slash",
	                test_ibis_tags_spec_trailing_slash);
	g_test_add_func("/ibis/tags/spec/invalid-escape",
	                test_ibis_tags_spec_invalid_escape);

	/* Equal Tests */
	g_test_add_func("/ibis/tags/equal/null-null", ibis_tags_equal_null_null);
	g_test_add_func("/ibis/tags/equal/empty-empty",
	                ibis_tags_equal_empty_empty);
	g_test_add_func("/ibis/tags/equal/null-empty", ibis_tags_equal_null_empty);
	g_test_add_func("/ibis/tags/equal/empty-null", ibis_tags_equal_empty_null);
	g_test_add_func("/ibis/tags/equal/single", ibis_tags_equal_single);
	g_test_add_func("/ibis/tags/equal/multiple", ibis_tags_equal_multiple);
	g_test_add_func("/ibis/tags/equal/mixed", ibis_tags_equal_mixed);

	/* Contains Tests */
	g_test_add_func("/ibis/tags/contains/empty", ibis_tags_contains_empty);
	g_test_add_func("/ibis/tags/contains/single", ibis_tags_contains_single);
	g_test_add_func("/ibis/tags/contains/multiple",
	                ibis_tags_contains_multiple);
	g_test_add_func("/ibis/tags/contains/too-many",
	                ibis_tags_contains_too_many);

	return g_test_run();
}
