/*
 * 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>

#define TEST_IBIS_MESSAGE_ERROR (g_quark_from_static_string("test-ibis-message"))

/******************************************************************************
 * Tests
 *****************************************************************************/
static void
test_ibis_message_new(void) {
	IbisMessage *message = NULL;

	message = ibis_message_new(IBIS_MSG_PRIVMSG);

	g_assert_true(IBIS_IS_MESSAGE(message));

	g_assert_finalize_object(message);
}

static void
test_ibis_message_properties(void) {
	IbisMessage *message = NULL;
	IbisTags *tags1 = NULL;
	IbisTags *tags2 = NULL;
	GError *error1 = NULL;
	GError *error2 = NULL;
	GStrv params1 = NULL;
	GStrv params2 = NULL;
	GStrvBuilder *builder = NULL;
	char *raw_message = NULL;
	char *source = NULL;
	char *command = NULL;

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

	builder = g_strv_builder_new();
	g_strv_builder_add_many(builder, "foo", "bar", "baz", NULL);
	params1 = g_strv_builder_end(builder);
	g_strv_builder_unref(builder);

	error1 = g_error_new(TEST_IBIS_MESSAGE_ERROR, 0, "weeeee");

	message = g_object_new(
		IBIS_TYPE_MESSAGE,
		"command", "command",
		"params", params1,
		"raw-message", ":source command param1",
		"source", "source",
		"tags", tags1,
		"error", error1,
		NULL);

	g_assert_true(IBIS_IS_MESSAGE(message));

	g_object_get(
		message,
		"command", &command,
		"params", &params2,
		"raw-message", &raw_message,
		"source", &source,
		"tags", &tags2,
		"error", &error2,
		NULL);

	g_assert_cmpstr(command, ==, "COMMAND");
	g_clear_pointer(&command, g_free);

	g_assert_cmpstrv(params2, params1);
	g_clear_pointer(&params2, g_strfreev);

	g_assert_cmpstr(raw_message, ==, ":source command param1");
	g_clear_pointer(&raw_message, g_free);

	g_assert_cmpstr(source, ==, "source");
	g_clear_pointer(&source, g_free);

	g_assert_nonnull(tags2);
	g_assert_true(ibis_tags_equal(tags1, tags2));
	g_clear_object(&tags2);

	g_assert_error(error2, TEST_IBIS_MESSAGE_ERROR, 0);
	g_clear_error(&error2);

	g_clear_pointer(&params1, g_strfreev);
	g_clear_object(&tags1);
	g_clear_error(&error1);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_take_error(void) {
	IbisMessage *message = NULL;
	GError *error1 = NULL;
	GError *error2 = NULL;

	message = ibis_message_new(IBIS_MSG_PRIVMSG);

	error1 = g_error_new(TEST_IBIS_MESSAGE_ERROR, 0, "weeeee");
	ibis_message_take_error(message, error1);

	error2 = ibis_message_get_error(message);
	g_assert_error(error2, TEST_IBIS_MESSAGE_ERROR, 0);

	g_assert_finalize_object(message);
}

static void
test_ibis_message_command_quark(void) {
	IbisMessage *message = NULL;
	GQuark privmsg = g_quark_from_static_string(IBIS_MSG_PRIVMSG);
	GQuark mixed_case = g_quark_from_static_string("PrIvMsg");
	GQuark notice = g_quark_from_static_string(IBIS_MSG_NOTICE);
	GQuark quark = 0;

	message = ibis_message_new(IBIS_MSG_PRIVMSG);
	quark = ibis_message_get_command_quark(message);

	g_assert_cmpuint(quark, ==, privmsg);
	g_assert_cmpuint(quark, !=, mixed_case);
	g_assert_cmpuint(quark, !=, notice);

	g_assert_finalize_object(message);
}

static void
test_ibis_message_is_command_null(void) {
	IbisMessage *message = NULL;

	message = ibis_message_new(IBIS_MSG_PRIVMSG);
	g_assert_false(ibis_message_is_command(message, NULL));

	g_assert_finalize_object(message);
}

static void
test_ibis_message_is_command_true(void) {
	IbisMessage *message = NULL;

	message = ibis_message_new(IBIS_MSG_PRIVMSG);
	g_assert_true(ibis_message_is_command(message, IBIS_MSG_PRIVMSG));

	g_assert_finalize_object(message);
}

static void
test_ibis_message_is_command_false(void) {
	IbisMessage *message = NULL;

	message = ibis_message_new(IBIS_MSG_PRIVMSG);
	g_assert_false(ibis_message_is_command(message, IBIS_MSG_NOTICE));

	g_assert_finalize_object(message);
}

static void
test_ibis_message_set_ctcp_privmsg(void) {
	IbisMessage *message = NULL;

	message = ibis_message_new(IBIS_MSG_PRIVMSG);

	g_assert_false(ibis_message_get_ctcp(message));
	G_GNUC_BEGIN_IGNORE_DEPRECATIONS
	ibis_message_set_ctcp(message, TRUE);
	G_GNUC_END_IGNORE_DEPRECATIONS
	g_assert_true(ibis_message_get_ctcp(message));

	g_assert_finalize_object(message);
}

static void
test_ibis_message_set_ctcp_notice(void) {
	IbisMessage *message = NULL;

	message = ibis_message_new(IBIS_MSG_NOTICE);

	g_assert_false(ibis_message_get_ctcp(message));
	G_GNUC_BEGIN_IGNORE_DEPRECATIONS
	ibis_message_set_ctcp(message, TRUE);
	G_GNUC_END_IGNORE_DEPRECATIONS
	g_assert_true(ibis_message_get_ctcp(message));

	g_assert_finalize_object(message);
}

static void
test_ibis_message_set_ctcp_other(void) {
	IbisMessage *message = NULL;

	message = ibis_message_new("OTHER");

	g_assert_false(ibis_message_get_ctcp(message));
	G_GNUC_BEGIN_IGNORE_DEPRECATIONS
	ibis_message_set_ctcp(message, TRUE);
	G_GNUC_END_IGNORE_DEPRECATIONS
	g_assert_false(ibis_message_get_ctcp(message));

	g_assert_finalize_object(message);
}

static void
test_ibis_message_set_params_variadic(void) {
	IbisMessage *message = NULL;
	GStrv params = NULL;
	const char * const expected[] = {"foo", "bar", "baz", NULL};

	message = ibis_message_new("command");

	params = ibis_message_get_params(message);
	g_assert_null(params);

	ibis_message_set_params(message, "foo", "bar", "baz", NULL);

	params = ibis_message_get_params(message);
	g_assert_cmpstrv(params, (GStrv)expected);

	g_assert_finalize_object(message);
}

/******************************************************************************
 * get_timestamp Tests
 *****************************************************************************/
static void
test_ibis_message_get_timestamp_no_tags(void) {
	IbisMessage *message = NULL;
	GDateTime *dt = NULL;
	GError *error = NULL;

	message = ibis_message_parse("COMMAND", &error);
	g_assert_no_error(error);

	dt = ibis_message_get_timestamp(message);

	g_assert_null(dt);

	g_assert_finalize_object(message);
}

static void
test_ibis_message_get_timestamp_no_time(void) {
	IbisMessage *message = NULL;
	GDateTime *dt = NULL;
	GError *error = NULL;

	message = ibis_message_parse("@msgid=abc123 COMMAND", &error);
	g_assert_no_error(error);

	dt = ibis_message_get_timestamp(message);

	g_assert_null(dt);

	g_assert_finalize_object(message);
}

static void
test_ibis_message_get_timestamp_empty_time(void) {
	IbisMessage *message = NULL;
	GDateTime *dt = NULL;
	GError *error = NULL;

	message = ibis_message_parse("@time COMMAND", &error);
	g_assert_no_error(error);

	dt = ibis_message_get_timestamp(message);

	g_assert_null(dt);

	g_assert_finalize_object(message);
}

static void
test_ibis_message_get_timestamp_normal(void) {
	IbisMessage *message = NULL;
	GDateTime *dt = NULL;
	GError *error = NULL;
	gint64 timestamp = 0;

	message = ibis_message_parse("@time=2011-10-19T16:40:51.620Z COMMAND",
	                             &error);
	g_assert_no_error(error);

	dt = ibis_message_get_timestamp(message);

	/* Once we require glib 2.80 we should use g_date_time_to_unix_usec. */
	timestamp = g_date_time_to_unix(dt);

	g_assert_true(G_GINT64_CONSTANT(1319042451) == timestamp);

	g_clear_pointer(&dt, g_date_time_unref);
	g_assert_finalize_object(message);
}

/******************************************************************************
 * Serialization Tests
 *****************************************************************************/
static void
test_ibis_message_serialize_empty(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = g_object_new(IBIS_TYPE_MESSAGE, NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, "");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_serialize_source(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = g_object_new(IBIS_TYPE_MESSAGE, "source", "source", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, ":source\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_serialize_command(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new("command");

	serialized = ibis_message_serialize_to_string(message, TRUE);
	g_assert_cmpstr(serialized, ==, "COMMAND\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_serialize_with_tags(void) {
	IbisMessage *message = NULL;
	IbisTags *tags = NULL;
	char *serialized = NULL;

	message = ibis_message_new("command");

	tags = ibis_message_get_tags(message);
	ibis_tags_add(tags, "foo", "bar");

	serialized = ibis_message_serialize_to_string(message, TRUE);
	g_assert_cmpstr(serialized, ==, "@foo=bar COMMAND\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}


static void
test_ibis_message_serialize_no_tags(void) {
	IbisMessage *message = NULL;
	IbisTags *tags = NULL;
	char *serialized = NULL;

	message = ibis_message_new("command");

	tags = ibis_message_get_tags(message);
	ibis_tags_add(tags, "foo", "bar");

	serialized = ibis_message_serialize_to_string(message, FALSE);
	g_assert_cmpstr(serialized, ==, "COMMAND\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_serialize_parameters_normal(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = g_object_new(IBIS_TYPE_MESSAGE, NULL);
	ibis_message_set_params(message, "foo", "bar", "baz", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, "foo bar baz\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_serialize_parameters_last_empty(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = g_object_new(IBIS_TYPE_MESSAGE, NULL);
	ibis_message_set_params(message, "", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, ":\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_serialize_parameters_last_starts_with_colon(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = g_object_new(IBIS_TYPE_MESSAGE, NULL);
	ibis_message_set_params(message, ":foo", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, "::foo\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_serialize_parameters_last_contains_space(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = g_object_new(IBIS_TYPE_MESSAGE, NULL);
	ibis_message_set_params(message, "I'm in space!!!!", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, ":I'm in space!!!!\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_serialize_ctcp_no_params(void) {
	IbisCTCPMessage *ctcp_message = NULL;
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new(IBIS_MSG_PRIVMSG);
	ibis_message_set_params(message, "irene", NULL);

	ctcp_message = ibis_ctcp_message_new("VERSION");
	ibis_message_set_ctcp_message(message, ctcp_message);
	g_clear_object(&ctcp_message);

	serialized = ibis_message_serialize_to_string(message, TRUE);
	g_assert_cmpstr(serialized, ==, "PRIVMSG irene :\001VERSION\001\r\n");
	g_clear_pointer(&serialized, g_free);

	g_assert_finalize_object(message);
}

static void
test_ibis_message_serialize_ctcp_single_param(void) {
	IbisCTCPMessage *ctcp_message = NULL;
	IbisMessage *message = NULL;
	char *serialized = NULL;
	const char *params[] = {"slaps", "grim", "with", "a", "large", "trout",
	                        NULL};

	message = ibis_message_new(IBIS_MSG_PRIVMSG);
	ibis_message_set_params(message, "#ibis", NULL);

	ctcp_message = ibis_ctcp_message_new("ACTION");
	ibis_ctcp_message_set_paramsv(ctcp_message, (GStrv)params);
	ibis_message_set_ctcp_message(message, ctcp_message);
	g_clear_object(&ctcp_message);

	serialized = ibis_message_serialize_to_string(message, TRUE);
	g_assert_cmpstr(serialized, ==,
	                "PRIVMSG #ibis :\001ACTION slaps grim with a large trout\001\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_serialize_ctcp_multiple_params(void) {
	IbisCTCPMessage *ctcp_message = NULL;
	IbisMessage *message = NULL;
	char *serialized = NULL;
	const char *ctcp_params[] = {"SEND", "pidgin.ico", "::1", "0", NULL};

	message = ibis_message_new(IBIS_MSG_PRIVMSG);
	ibis_message_set_params(message, "irene", NULL);

	ctcp_message = ibis_ctcp_message_new(IBIS_CTCP_DCC);
	ibis_ctcp_message_set_paramsv(ctcp_message, (GStrv)ctcp_params);
	ibis_message_set_ctcp_message(message, ctcp_message);
	g_clear_object(&ctcp_message);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==,
	                "PRIVMSG irene :\001DCC SEND pidgin.ico ::1 0\001\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_serialize_ctcp_mixed_params(void) {
	IbisCTCPMessage *ctcp_message = NULL;
	IbisMessage *message = NULL;
	char *serialized = NULL;
	const char *ctcp_params[] = {"SEND", "pidgin.ico ::1 0", NULL};

	message = ibis_message_new(IBIS_MSG_PRIVMSG);
	ibis_message_set_params(message, "irene", NULL);

	ctcp_message = ibis_ctcp_message_new(IBIS_CTCP_DCC);
	ibis_ctcp_message_set_paramsv(ctcp_message, (GStrv)ctcp_params);
	ibis_message_set_ctcp_message(message, ctcp_message);
	g_clear_object(&ctcp_message);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==,
	                "PRIVMSG irene :\001DCC SEND pidgin.ico ::1 0\001\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_serialize_ctcp_version(void) {
	IbisCTCPMessage *ctcp_message = NULL;
	IbisMessage *message = NULL;
	char *serialized = NULL;
	const char *ctcp_params[] = {"ibis 0.8.1", NULL};

	message = ibis_message_new(IBIS_MSG_NOTICE);
	ibis_message_set_params(message, "irene", NULL);

	ctcp_message = ibis_ctcp_message_new(IBIS_CTCP_VERSION);
	ibis_ctcp_message_set_paramsv(ctcp_message, (GStrv)ctcp_params);
	ibis_message_set_ctcp_message(message, ctcp_message);
	g_clear_object(&ctcp_message);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, "NOTICE irene :\001VERSION ibis 0.8.1\001\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

/******************************************************************************
 * irc-docs tests
 *****************************************************************************/
static void
test_ibis_message_ircdocs_verb_and_params(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");
	ibis_message_set_params(message, "bar", "baz", "asdf", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, "FOO bar baz asdf\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_source_and_command(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new(IBIS_MSG_AWAY);
	ibis_message_set_source(message, "src");

	serialized = ibis_message_serialize_to_string(message, TRUE);
	g_assert_cmpstr(serialized, ==, ":src AWAY\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_source_command_and_trailing_param(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new(IBIS_MSG_AWAY);
	ibis_message_set_source(message, "src");
	ibis_message_set_params(message, "", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);
	g_assert_cmpstr(serialized, ==, ":src AWAY :\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_simple_with_source(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");
	ibis_message_set_source(message, "coolguy");
	ibis_message_set_params(message, "bar", "baz", "asdf", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, ":coolguy FOO bar baz asdf\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_simple_with_trailing_param(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");
	ibis_message_set_params(message, "bar", "baz", "asdf quux", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, "FOO bar baz :asdf quux\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_simple_with_empty_trailing_param(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");
	ibis_message_set_params(message, "bar", "baz", "", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, "FOO bar baz :\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_simple_with_empty_trailing_param_with_colon(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");
	ibis_message_set_params(message, "bar", "baz", ":asdf", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, "FOO bar baz ::asdf\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_simple_with_source_and_trailing_param(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");
	ibis_message_set_source(message, "coolguy");
	ibis_message_set_params(message, "bar", "baz", "asdf quux", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, ":coolguy FOO bar baz :asdf quux\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_simple_with_source_and_trailing_param_with_whitespace(void)
{
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");
	ibis_message_set_source(message, "coolguy");
	ibis_message_set_params(message, "bar", "baz", "  asdf quux ", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, ":coolguy FOO bar baz :  asdf quux \r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_trailing_containing_another(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new(IBIS_MSG_PRIVMSG);
	ibis_message_set_source(message, "coolguy");
	ibis_message_set_params(message, "bar", "lol :)", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, ":coolguy PRIVMSG bar :lol :)\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_simple_with_source_and_empty_trailing_param(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");
	ibis_message_set_source(message, "coolguy");
	ibis_message_set_params(message, "bar", "baz", "", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, ":coolguy FOO bar baz :\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_simple_with_source_and_trailing_param_only_whitespace(void)
{
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");
	ibis_message_set_source(message, "coolguy");
	ibis_message_set_params(message, "bar", "baz", "  ", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, ":coolguy FOO bar baz :  \r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_param_contains_tab(void) {
	IbisMessage *message = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");
	ibis_message_set_source(message, "coolguy");
	ibis_message_set_params(message, "b\tar", "baz", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, ":coolguy FOO b\tar baz\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_empty_tag_and_space_trailing(void) {
	IbisMessage *message = NULL;
	IbisTags *tags = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");
	tags = ibis_message_get_tags(message);
	ibis_tags_add(tags, "asd", "");

	ibis_message_set_source(message, "coolguy");
	ibis_message_set_params(message, "bar", "baz", "  ", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, "@asd :coolguy FOO bar baz :  \r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_missing_tag_and_space_trailing(void) {
	IbisMessage *message = NULL;
	IbisTags *tags = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");

	tags = ibis_message_get_tags(message);
	ibis_tags_add(tags, "asd", NULL);

	ibis_message_set_source(message, "coolguy");
	ibis_message_set_params(message, "bar", "baz", "  ", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, "@asd :coolguy FOO bar baz :  \r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_tags_with_escaped_values(void) {
	IbisMessage *message = NULL;
	IbisTags *tags = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");

	tags = ibis_message_get_tags(message);
	ibis_tags_add(tags, "a", "b\\and\nk");
	ibis_tags_add(tags, "d", "gh;764");

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==, "@a=b\\\\and\\nk;d=gh\\:764 FOO\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_tags_with_escaped_values_and_params(void) {
	IbisMessage *message = NULL;
	IbisTags *tags = NULL;
	char *serialized = NULL;

	message = ibis_message_new("foo");

	tags = ibis_message_get_tags(message);
	ibis_tags_add(tags, "a", "b\\and\nk");
	ibis_tags_add(tags, "d", "gh;764");

	ibis_message_set_params(message, "par1", "par2", NULL);

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==,
	                "@a=b\\\\and\\nk;d=gh\\:764 FOO par1 par2\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

static void
test_ibis_message_ircdocs_tags_with_long_strange_values(void) {
	IbisMessage *message = NULL;
	IbisTags *tags = NULL;
	char *serialized = NULL;


	message = ibis_message_new("COMMAND");
	tags = ibis_message_get_tags(message);
	ibis_tags_add(tags, "foo", "\\\\;\\s \r\n");

	serialized = ibis_message_serialize_to_string(message, TRUE);

	g_assert_cmpstr(serialized, ==,
	                "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND\r\n");

	g_clear_pointer(&serialized, g_free);
	g_assert_finalize_object(message);
}

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

	g_test_add_func("/ibis/message/new", test_ibis_message_new);
	g_test_add_func("/ibis/message/properties", test_ibis_message_properties);
	g_test_add_func("/ibis/message/take-error", test_ibis_message_take_error);
	g_test_add_func("/ibis/message/command-quark",
	                test_ibis_message_command_quark);

	g_test_add_func("/ibis/message/is_command/null",
	                test_ibis_message_is_command_null);
	g_test_add_func("/ibis/message/is_command/true",
	                test_ibis_message_is_command_true);
	g_test_add_func("/ibis/message/is_command/false",
	                test_ibis_message_is_command_false);

	g_test_add_func("/ibis/message/set_ctcp/privmsg",
	                test_ibis_message_set_ctcp_privmsg);
	g_test_add_func("/ibis/message/set_ctcp/notice",
	                test_ibis_message_set_ctcp_notice);
	g_test_add_func("/ibis/message/set_ctcp/other",
	                test_ibis_message_set_ctcp_other);

	g_test_add_func("/ibis/message/set-params-variadic",
	                test_ibis_message_set_params_variadic);

	g_test_add_func("/ibis/message/get-timestamp/no_tags",
	                test_ibis_message_get_timestamp_no_tags);
	g_test_add_func("/ibis/message/get-timestamp/no_time",
	                test_ibis_message_get_timestamp_no_time);
	g_test_add_func("/ibis/message/get-timestamp/empty_time",
	                test_ibis_message_get_timestamp_empty_time);
	g_test_add_func("/ibis/message/get-timestamp/normal",
	                test_ibis_message_get_timestamp_normal);

	g_test_add_func("/ibis/message/serialize/empty",
	                test_ibis_message_serialize_empty);
	g_test_add_func("/ibis/message/serialize/source",
	                test_ibis_message_serialize_source);
	g_test_add_func("/ibis/message/serialize/command",
	                test_ibis_message_serialize_command);
	g_test_add_func("/ibis/message/serialize/tags",
	                test_ibis_message_serialize_with_tags);
	g_test_add_func("/ibis/message/serialize/no-tags",
	                test_ibis_message_serialize_no_tags);
	g_test_add_func("/ibis/message/serialize/parameters/normal",
	                test_ibis_message_serialize_parameters_normal);
	g_test_add_func("/ibis/message/serialize/parameters/last_empty",
	                test_ibis_message_serialize_parameters_last_empty);
	g_test_add_func("/ibis/message/serialize/parameters/last_starts_with_colon",
	                test_ibis_message_serialize_parameters_last_starts_with_colon);
	g_test_add_func("/ibis/message/serialize/parameters/last_contains_space",
	                test_ibis_message_serialize_parameters_last_contains_space);
	g_test_add_func("/ibis/message/serialize/ctcp/no-params",
	                test_ibis_message_serialize_ctcp_no_params);
	g_test_add_func("/ibis/message/serialize/ctcp/single-param",
	                test_ibis_message_serialize_ctcp_single_param);
	g_test_add_func("/ibis/message/serialize/ctcp/multiple-params",
	                test_ibis_message_serialize_ctcp_multiple_params);
	g_test_add_func("/ibis/message/serialize/ctcp/mixed-params",
	                test_ibis_message_serialize_ctcp_mixed_params);
	g_test_add_func("/ibis/message/serialize/ctcp-version",
	                test_ibis_message_serialize_ctcp_version);

	/* These tests are based on the msg-join tests from
	 * https://github.com/ircdocs/parser-tests/blob/master/tests/msg-join.yaml
	 */
	g_test_add_func("/ibis/message/ircdocs/verb-and-params",
	                test_ibis_message_ircdocs_verb_and_params);
	g_test_add_func("/ibis/message/ircdocs/source-and-command",
	                test_ibis_message_ircdocs_source_and_command);
	g_test_add_func("/ibis/message/ircdocs/source-command-and-trailing-param",
	                test_ibis_message_ircdocs_source_command_and_trailing_param);
	g_test_add_func("/ibis/message/ircdocs/simple-with-source",
	                test_ibis_message_ircdocs_simple_with_source);
	g_test_add_func("/ibis/message/ircdocs/simple-with-trailing-param",
	                test_ibis_message_ircdocs_simple_with_trailing_param);
	g_test_add_func("/ibis/message/ircdocs/simple-with-empty-trailing-param",
	                test_ibis_message_ircdocs_simple_with_empty_trailing_param);
	g_test_add_func("/ibis/message/ircdocs/simple-with-empty-trailing-param-with-colon",
	                test_ibis_message_ircdocs_simple_with_empty_trailing_param_with_colon);
	g_test_add_func("/ibis/message/ircdocs/simple-with-source-and-trailing-param",
	                test_ibis_message_ircdocs_simple_with_source_and_trailing_param);
	g_test_add_func("/ibis/message/ircdocs/simple-with-source-and-trailing-param-with-whitespace",
	                test_ibis_message_ircdocs_simple_with_source_and_trailing_param_with_whitespace);
	g_test_add_func("/ibis/message/ircdocs/trailing-containing-another",
	                test_ibis_message_ircdocs_trailing_containing_another);
	g_test_add_func("/ibis/message/ircdocs/simple-with-source-and-empty-trailing-param",
	                test_ibis_message_ircdocs_simple_with_source_and_empty_trailing_param);
	g_test_add_func("/ibis/message/ircdocs/simple-with-source-and-trailing-param-only-whitespace",
	                test_ibis_message_ircdocs_simple_with_source_and_trailing_param_only_whitespace);
	g_test_add_func("/ibis/message/ircdocs/param-contains-tab",
	                test_ibis_message_ircdocs_param_contains_tab);
	g_test_add_func("/ibis/message/ircdocs/empty_tag_and_space_trailing",
	                test_ibis_message_ircdocs_empty_tag_and_space_trailing);
	g_test_add_func("/ibis/message/ircdocs/missing_tag_and_space_trailing",
	                test_ibis_message_ircdocs_missing_tag_and_space_trailing);
	g_test_add_func("/ibis/message/ircdocs/tags_with_escaped_values",
	                test_ibis_message_ircdocs_tags_with_escaped_values);
	g_test_add_func("/ibis/message/ircdocs/tags_with_escaped_values_and_params",
	                test_ibis_message_ircdocs_tags_with_escaped_values_and_params);
	g_test_add_func("/ibis/message/ircdocs/tag_with_long_strange_values",
	                test_ibis_message_ircdocs_tags_with_long_strange_values);

	return g_test_run();
}
