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

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
test_ibis_features_changed_counter(G_GNUC_UNUSED GObject *self,
                                   G_GNUC_UNUSED GParamSpec *pspec,
                                   gpointer data)
{
	guint *counter = data;

	*counter = *counter + 1;
}

/******************************************************************************
 * Tests
 *****************************************************************************/
static void
test_ibis_features_new(void) {
	IbisFeatures *features = NULL;

	features = ibis_features_new();
	g_assert_true(IBIS_IS_FEATURES(features));

	g_assert_finalize_object(features);
}

static void
test_ibis_features_parse_fail(void) {
	IbisMessage *message = NULL;
	IbisFeatures *features = NULL;

	message = ibis_message_new(IBIS_MSG_PRIVMSG);
	features = ibis_features_new();

	g_assert_false(ibis_features_parse(features, message));

	g_assert_finalize_object(message);
	g_assert_finalize_object(features);
}

static void
test_ibis_features_parse_all(void) {
	IbisMessage *message = NULL;
	IbisFeatures *features = NULL;
	GStrvBuilder *builder = NULL;
	GStrv msg_params = NULL;
	guint notify_counter = 0;

	builder = g_strv_builder_new();
	g_strv_builder_add_many(builder,
	                        "AWAYLEN=390",
	                        "CASEMAPPING=rfc1459-strict",
	                        "CHANLIMIT=#:100",
	                        "CHANMODES=Ibe,k,fl,CEMRUimnstu",
	                        "CHANNELLEN=64",
	                        "CHANTYPES=&#",
	                        "ELIST=U",
	                        "EXCEPTS",
	                        "EXTBAN=,m",
	                        "HOSTLEN=63",
	                        "INVEX",
	                        "KICKLEN=390",
	                        "MAXLIST=beI:60",
	                        "MAXTARGETS=4",
	                        "MODES",
	                        "NETWORK=ErgoTest",
	                        "NICKLEN=32",
	                        "PREFIX=(qaohv)~&@%+",
	                        "SAFELIST",
	                        "SILENCE=15",
	                        "STATUSMSG=~&@%+",
	                        "TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,"
	                        "PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100",
	                        "TOPICLEN=390",
	                        "USERLEN=18",
	                        NULL);
	msg_params = g_strv_builder_end(builder);
	g_strv_builder_unref(builder);

	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_paramsv(message, msg_params);

	features = ibis_features_new();
	g_signal_connect(features, "changed",
	                 G_CALLBACK(test_ibis_features_changed_counter),
	                 &notify_counter);
	g_assert_cmpuint(notify_counter, ==, 0);

	g_assert_true(ibis_features_parse(features, message));

	/* We have 2 synthetic features we need to account for. */
	g_assert_cmpuint(notify_counter, ==, g_strv_length(msg_params) + 2);

	g_assert_cmpuint(ibis_features_get_awaylen(features), ==, 390);
	g_assert_cmpstr(ibis_features_get_casemapping(features), ==,
	                "rfc1459-strict");
	g_assert_cmpstr(ibis_features_get_chanlimit(features), ==, "#:100");
	g_assert_cmpstr(ibis_features_get_chanmodes(features), ==,
	                "Ibe,k,fl,CEMRUimnstu");
	g_assert_cmpstr(ibis_features_get_chantypes(features), ==, "&#");
	g_assert_cmpuint(ibis_features_get_channellen(features), ==, 64);
	g_assert_cmpstr(ibis_features_get_elist(features), ==, "U");
	g_assert_cmpint(ibis_features_get_excepts(features), ==, 'e');
	g_assert_cmpstr(ibis_features_get_extban(features), ==, ",m");
	g_assert_cmpuint(ibis_features_get_hostlen(features), ==, 63);
	g_assert_cmpint(ibis_features_get_invex(features), ==, 'I');
	g_assert_cmpuint(ibis_features_get_kicklen(features), ==, 390);
	g_assert_cmpstr(ibis_features_get_maxlist(features), ==, "beI:60");
	g_assert_cmpuint(ibis_features_get_maxtargets(features), ==, 4);
	g_assert_cmpuint(ibis_features_get_modes(features), ==, G_MAXUINT32);
	g_assert_cmpstr(ibis_features_get_network(features), ==, "ErgoTest");
	g_assert_cmpuint(ibis_features_get_nicklen(features), ==, 32);
	g_assert_cmpstr(ibis_features_get_prefix(features), ==, "(qaohv)~&@%+");
	g_assert_cmpstr(ibis_features_get_prefix_modes(features), ==, "qaohv");
	g_assert_cmpstr(ibis_features_get_prefix_prefixes(features), ==, "~&@%+");
	g_assert_true(ibis_features_get_safelist(features));
	g_assert_cmpuint(ibis_features_get_silence(features), ==, 15);
	g_assert_cmpstr(ibis_features_get_statusmsg(features), ==, "~&@%+");
	g_assert_cmpstr(ibis_features_get_targmax(features), ==,
	                "NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,"
	                "NOTICE:4,MONITOR:100");
	g_assert_cmpuint(ibis_features_get_topiclen(features), ==, 390);
	g_assert_cmpuint(ibis_features_get_userlen(features), ==, 18);

	g_strfreev(msg_params);
	g_assert_finalize_object(message);
	g_assert_finalize_object(features);
}

static void
test_ibis_features_parse_unknown(void) {
	IbisMessage *message = NULL;
	IbisFeatures *features = NULL;
	GStrvBuilder *builder = NULL;
	GStrv msg_params = NULL;

	builder = g_strv_builder_new();
	g_strv_builder_add_many(builder,
	                        "MONITOR=100",
	                        "UTF8MAPPING=rfc8265",
	                        "UTF8ONLY",
	                        NULL);
	msg_params = g_strv_builder_end(builder);
	g_strv_builder_unref(builder);

	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_paramsv(message, msg_params);

	features = ibis_features_new();

	g_assert_true(ibis_features_parse(features, message));
	g_assert_cmpuint(ibis_features_get_uint(features, "MONITOR"), ==, 100);
	g_assert_cmpstr(ibis_features_get_string(features, "UTF8MAPPING"), ==, "rfc8265");
	g_assert_true(ibis_features_get_boolean(features, "UTF8ONLY"));

	/* try using the wrong types */
	g_assert_cmpstr(ibis_features_get_string(features, "MONITOR"), ==, NULL);
	g_assert_false(ibis_features_get_boolean(features, "MONITOR"));
	g_assert_cmpuint(ibis_features_get_uint(features, "UTF8MAPPING"), ==, 0);
	g_assert_false(ibis_features_get_boolean(features, "UTF8MAPPING"));
	g_assert_cmpuint(ibis_features_get_uint(features, "UTF8ONLY"), ==, 0);
	g_assert_cmpstr(ibis_features_get_string(features, "UTF8ONLY"), ==, NULL);

	/* try some really unknown parameters */
	g_assert_cmpuint(ibis_features_get_uint(features, "MOONITOR"), ==, 0);
	g_assert_cmpstr(ibis_features_get_string(features, "UTF8MAPING"), ==, NULL);
	g_assert_false(ibis_features_get_boolean(features, "UTF9ONLY"));

	g_strfreev(msg_params);
	g_assert_finalize_object(message);
	g_assert_finalize_object(features);
}

static void
test_ibis_features_no_parse(void) {
	IbisFeatures *features = NULL;

	features = ibis_features_new();

	g_assert_cmpuint(ibis_features_get_awaylen(features), ==, 0);
	g_assert_cmpstr(ibis_features_get_casemapping(features), ==, NULL);
	g_assert_cmpstr(ibis_features_get_chanlimit(features), ==, NULL);
	g_assert_cmpstr(ibis_features_get_chanmodes(features), ==, NULL);
	g_assert_cmpuint(ibis_features_get_channellen(features), ==, 0);
	g_assert_cmpstr(ibis_features_get_chantypes(features), ==, "#");
	g_assert_cmpstr(ibis_features_get_elist(features), ==, NULL);
	g_assert_cmpint(ibis_features_get_excepts(features), ==, 0);
	g_assert_cmpstr(ibis_features_get_extban(features), ==, NULL);
	g_assert_cmpuint(ibis_features_get_hostlen(features), ==, 0);
	g_assert_cmpint(ibis_features_get_invex(features), ==, 0);
	g_assert_cmpuint(ibis_features_get_kicklen(features), ==, 0);
	g_assert_cmpstr(ibis_features_get_maxlist(features), ==, NULL);
	g_assert_cmpuint(ibis_features_get_maxtargets(features), ==, 0);
	g_assert_cmpuint(ibis_features_get_modes(features), ==, 0);
	g_assert_cmpstr(ibis_features_get_network(features), ==, NULL);
	g_assert_cmpuint(ibis_features_get_nicklen(features), ==, 0);
	g_assert_cmpstr(ibis_features_get_prefix(features), ==, "(ov)@+");
	g_assert_cmpstr(ibis_features_get_prefix_modes(features), ==, "ov");
	g_assert_cmpstr(ibis_features_get_prefix_prefixes(features), ==, "@+");
	g_assert_false(ibis_features_get_safelist(features));
	g_assert_cmpuint(ibis_features_get_silence(features), ==, 0);
	g_assert_cmpstr(ibis_features_get_statusmsg(features), ==, NULL);
	g_assert_cmpstr(ibis_features_get_targmax(features), ==, NULL);
	g_assert_cmpuint(ibis_features_get_topiclen(features), ==, 0);
	g_assert_cmpuint(ibis_features_get_userlen(features), ==, 0);

	g_assert_finalize_object(features);
}

static void
test_ibis_features_parse_invalid_prefix(void) {
	if(g_test_subprocess()) {
		IbisMessage *message = NULL;
		IbisFeatures *features = NULL;

		message = ibis_message_new(IBIS_RPL_ISUPPORT);
		ibis_message_set_params(message, "PREFIX=(ov)@%+", NULL);

		features = ibis_features_new();

		g_assert_false(ibis_features_parse(features, message));

		g_clear_object(&features);

		g_assert_not_reached();
	}

	g_test_trap_subprocess(NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
	g_test_trap_assert_stderr("*format of prefix*is invalid*");
}

static void
test_ibis_features_clear(void) {
	IbisMessage *message = NULL;
	IbisFeatures *features = NULL;
	guint counter = 0;

	features = ibis_features_new();
	g_signal_connect(features, "changed",
	                 G_CALLBACK(test_ibis_features_changed_counter), &counter);

	/* There are 2 default and 2 synthetic features that should get removed and
	 * reset on a clear which means we should see 8 changes.
	 */
	ibis_features_clear(features);
	g_assert_cmpuint(counter, ==, 8);

	/* Build a simple RPL_ISUPPORT message and parse it. */
	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_params(message, "CASEMAPPING=rfc1459", "TOPICLEN=307",
	                        NULL);

	counter = 0;
	ibis_features_parse(features, message);
	g_assert_cmpuint(counter, ==, 2);

	/* Clear again. Make sure we got called 10 times, 6 removals, 4 adds. */
	counter = 0;
	ibis_features_clear(features);
	g_assert_cmpuint(counter, ==, 10);

	/* Cleanup. */
	g_assert_finalize_object(message);
	g_assert_finalize_object(features);
}

static void
test_ibis_features_client_tag_deny_none(void) {
	IbisMessage *message = NULL;
	IbisFeatures *features = NULL;

	features = ibis_features_new();
	message = ibis_message_new(IBIS_RPL_ISUPPORT);

	g_assert_false(ibis_features_client_tag_denied(features, "foo"));

	ibis_message_set_params(message, "CLIENTTAGDENY=", NULL);
	ibis_features_parse(features, message);

	g_assert_false(ibis_features_client_tag_denied(features, "foo"));

	g_assert_finalize_object(message);
	g_assert_finalize_object(features);
}

static void
test_ibis_features_client_tag_deny_single(void) {
	IbisMessage *message = NULL;
	IbisFeatures *features = NULL;

	features = ibis_features_new();
	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_params(message, "CLIENTTAGDENY=foo", NULL);
	ibis_features_parse(features, message);

	g_assert_true(ibis_features_client_tag_denied(features, "foo"));
	g_assert_false(ibis_features_client_tag_denied(features, "bar"));

	g_assert_finalize_object(message);
	g_assert_finalize_object(features);
}

static void
test_ibis_features_client_tag_deny_multiple(void) {
	IbisMessage *message = NULL;
	IbisFeatures *features = NULL;

	features = ibis_features_new();
	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_params(message, "CLIENTTAGDENY=foo,example/bar", NULL);
	ibis_features_parse(features, message);

	g_assert_true(ibis_features_client_tag_denied(features, "foo"));
	g_assert_true(ibis_features_client_tag_denied(features, "example/bar"));
	g_assert_false(ibis_features_client_tag_denied(features, "bar"));

	g_assert_finalize_object(message);
	g_assert_finalize_object(features);
}

static void
test_ibis_features_client_tag_deny_all(void) {
	IbisMessage *message = NULL;
	IbisFeatures *features = NULL;

	features = ibis_features_new();
	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_params(message, "CLIENTTAGDENY=*", NULL);
	ibis_features_parse(features, message);

	g_assert_true(ibis_features_client_tag_denied(features, "foo"));
	g_assert_true(ibis_features_client_tag_denied(features, "example/bar"));
	g_assert_true(ibis_features_client_tag_denied(features, "bar"));

	g_assert_finalize_object(message);
	g_assert_finalize_object(features);
}

static void
test_ibis_features_client_tag_deny_all_except_some(void) {
	IbisMessage *message = NULL;
	IbisFeatures *features = NULL;

	features = ibis_features_new();
	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_params(message, "CLIENTTAGDENY=*,-foo,-example/bar", NULL);
	ibis_features_parse(features, message);

	g_assert_false(ibis_features_client_tag_denied(features, "foo"));
	g_assert_false(ibis_features_client_tag_denied(features, "example/bar"));
	g_assert_true(ibis_features_client_tag_denied(features, "bar"));

	g_assert_finalize_object(message);
	g_assert_finalize_object(features);
}

static void
test_ibis_features_client_tag_denied_query_with_prefix(void) {
	IbisMessage *message = NULL;
	IbisFeatures *features = NULL;

	features = ibis_features_new();
	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_params(message, "CLIENTTAGDENY=draft/reply,typing", NULL);
	ibis_features_parse(features, message);

	g_assert_true(ibis_features_client_tag_denied(features, "+draft/reply"));
	g_assert_true(ibis_features_client_tag_denied(features, "+typing"));
	g_assert_false(ibis_features_client_tag_denied(features, "+typoing"));

	g_assert_finalize_object(message);
	g_assert_finalize_object(features);
}

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

	g_test_set_nonfatal_assertions();

	g_test_add_func("/ibis/features/new", test_ibis_features_new);
	g_test_add_func("/ibis/features/parse/fail", test_ibis_features_parse_fail);
	g_test_add_func("/ibis/features/parse/all", test_ibis_features_parse_all);
	g_test_add_func("/ibis/features/parse/unknown", test_ibis_features_parse_unknown);
	g_test_add_func("/ibis/features/parse/none", test_ibis_features_no_parse);
	g_test_add_func("/ibis/features/parse/invalid-prefix",
	                test_ibis_features_parse_invalid_prefix);
	g_test_add_func("/ibis/features/clear", test_ibis_features_clear);
	g_test_add_func("/ibis/features/client_tag_deny_none",
	                test_ibis_features_client_tag_deny_none);
	g_test_add_func("/ibis/features/client_tag_deny_single",
	                test_ibis_features_client_tag_deny_single);
	g_test_add_func("/ibis/features/client_tag_deny_multiple",
	                test_ibis_features_client_tag_deny_multiple);
	g_test_add_func("/ibis/features/client_tag_deny_all",
	                test_ibis_features_client_tag_deny_all);
	g_test_add_func("/ibis/features/client_tag_deny_all_except_some",
	                test_ibis_features_client_tag_deny_all_except_some);
	g_test_add_func("/ibis/features/client_tag_denied_query_with_prefix",
	                test_ibis_features_client_tag_denied_query_with_prefix);

	return g_test_run();
}
