/*
 * Ibis - IRCv3 Library
 * Copyright (C) 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 program 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.1 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 Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib.h>
#include <glib/gi18n.h>

#include <ibis.h>

/******************************************************************************
 * Tests
 *****************************************************************************/
static void
test_ibis_client_properties(void) {
	IbisCapabilities *capabilities = NULL;
	IbisClient *client = NULL;
	IbisFeatures *features = NULL;
	GCancellable *cancellable = NULL;
	GError *error = NULL;
	char *primary_nick = NULL;
	char *realname = NULL;
	char *secondary_nick = NULL;
	char *tertiary_nick = NULL;
	char *username = NULL;
	gboolean connected = FALSE;
	guint client_ping_timeout = 0;

	client = g_object_new(
		IBIS_TYPE_CLIENT,
		"client-ping-timeout", 1000,
		"primary-nick", "nick",
		"realname", "realname",
		"secondary-nick", "nick1",
		"tertiary-nick", "nick2",
		"username", "username",
		NULL);

	g_object_get(
		G_OBJECT(client),
		"cancellable", &cancellable,
		"capabilities", &capabilities,
		"client-ping-timeout", &client_ping_timeout,
		"connected", &connected,
		"error", &error,
		"features", &features,
		"primary_nick", &primary_nick,
		"realname", &realname,
		"secondary-nick", &secondary_nick,
		"tertiary-nick", &tertiary_nick,
		"username", &username,
		NULL);

	g_assert_null(cancellable);

	g_assert_true(IBIS_IS_CAPABILITIES(capabilities));
	g_clear_object(&capabilities);

	g_assert_cmpuint(client_ping_timeout, ==, 1000);

	g_assert_false(connected);

	g_assert_no_error(error);

	g_assert_true(IBIS_IS_FEATURES(features));
	g_clear_object(&features);

	g_assert_cmpstr(primary_nick, ==, "nick");
	g_clear_pointer(&primary_nick, g_free);

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

	g_assert_cmpstr(secondary_nick, ==, "nick1");
	g_clear_pointer(&secondary_nick, g_free);

	g_assert_cmpstr(tertiary_nick, ==, "nick2");
	g_clear_pointer(&tertiary_nick, g_free);

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

	g_assert_finalize_object(client);
}

static void
test_ibis_client_secondary_nick(void) {
	IbisClient *client = NULL;
	const char *secondary_nick = NULL;

	client = ibis_client_new();

	ibis_client_set_secondary_nick(client, "secondary");
	secondary_nick = ibis_client_get_secondary_nick(client);
	g_assert_cmpstr(secondary_nick, ==, "secondary");

	ibis_client_set_secondary_nick(client, NULL);
	secondary_nick = ibis_client_get_secondary_nick(client);
	g_assert_cmpstr(secondary_nick, ==, NULL);

	ibis_client_set_secondary_nick(client, "");
	secondary_nick = ibis_client_get_secondary_nick(client);
	g_assert_cmpstr(secondary_nick, ==, "");

	g_assert_finalize_object(client);
}

static void
test_ibis_client_tertiary_nick(void) {
	IbisClient *client = NULL;
	const char *tertiary_nick = NULL;

	client = ibis_client_new();

	ibis_client_set_tertiary_nick(client, "tertiary");
	tertiary_nick = ibis_client_get_tertiary_nick(client);
	g_assert_cmpstr(tertiary_nick, ==, "tertiary");

	ibis_client_set_tertiary_nick(client, NULL);
	tertiary_nick = ibis_client_get_tertiary_nick(client);
	g_assert_cmpstr(tertiary_nick, ==, NULL);

	ibis_client_set_tertiary_nick(client, "");
	tertiary_nick = ibis_client_get_tertiary_nick(client);
	g_assert_cmpstr(tertiary_nick, ==, "");

	g_assert_finalize_object(client);
}

static void
test_ibis_client_realname_null(void) {
	IbisClient *client = NULL;
	const char *realname = NULL;

	client = ibis_client_new();

	ibis_client_set_realname(client, "realname");
	realname = ibis_client_get_realname(client);
	g_assert_cmpstr(realname, ==, "realname");

	ibis_client_set_realname(client, NULL);
	realname = ibis_client_get_realname(client);
	g_assert_cmpstr(realname, ==, NULL);

	g_assert_finalize_object(client);
}

static void
test_ibis_client_realname_empty(void) {
	if(g_test_subprocess()) {
		IbisClient *client = NULL;

		client = ibis_client_new();
		ibis_client_set_realname(client, "");

		g_assert_finalize_object(client);
	}

	g_test_trap_subprocess(NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
	g_test_trap_assert_stderr("*realname[0] != '\\0'*");
}

static void
test_ibis_client_username_null(void) {
	IbisClient *client = NULL;
	const char *username = NULL;

	client = ibis_client_new();

	ibis_client_set_username(client, "username");
	username = ibis_client_get_username(client);
	g_assert_cmpstr(username, ==, "username");

	ibis_client_set_username(client, NULL);
	username = ibis_client_get_username(client);
	g_assert_cmpstr(username, ==, NULL);

	g_assert_finalize_object(client);
}

static void
test_ibis_client_username_empty(void) {
	if(g_test_subprocess()) {
		IbisClient *client = NULL;

		client = ibis_client_new();
		ibis_client_set_username(client, "");

		g_assert_finalize_object(client);
	}

	g_test_trap_subprocess(NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
	g_test_trap_assert_stderr("*username[0] != '\\0'*");
}

static void
test_ibis_client_is_channel_default(void) {
	IbisClient *client = NULL;

	client = ibis_client_new();

	g_assert_true(ibis_client_is_channel(client, "#foo"));
	g_assert_false(ibis_client_is_channel(client, "&bar"));
	g_assert_false(ibis_client_is_channel(client, "baz"));

	g_assert_finalize_object(client);
}

static void
test_ibis_client_is_channel_custom(void) {
	IbisClient *client = NULL;
	IbisFeatures *features = NULL;
	IbisMessage *message = NULL;

	client = ibis_client_new();
	features = ibis_client_get_features(client);

	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_params(message, IBIS_FEATURE_CHANTYPES "=#&", NULL);
	ibis_features_parse(features, message);
	g_assert_finalize_object(message);

	g_assert_true(ibis_client_is_channel(client, "#foo"));
	g_assert_true(ibis_client_is_channel(client, "&bar"));
	g_assert_false(ibis_client_is_channel(client, "baz"));

	g_assert_finalize_object(client);
}

static void
test_ibis_client_network_default(void) {
	IbisClient *client = NULL;
	const char *network = NULL;

	client = ibis_client_new();

	network = ibis_client_get_network(client);
	g_assert_cmpstr(network, ==, NULL);

	g_assert_finalize_object(client);
}

static void
test_ibis_client_network_custom(void) {
	IbisClient *client = NULL;
	IbisFeatures *features = NULL;
	IbisMessage *message = NULL;
	const char *network = NULL;

	client = ibis_client_new();
	features = ibis_client_get_features(client);

	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_params(message, IBIS_FEATURE_NETWORK "=unit-test", NULL);
	ibis_features_parse(features, message);
	g_assert_finalize_object(message);

	network = ibis_client_get_network(client);
	g_assert_cmpstr(network, ==, "unit-test");

	g_assert_finalize_object(client);
}

static void
test_ibis_client_normalize_null(void) {
	IbisClient *client = NULL;
	char *normalized = NULL;

	client = ibis_client_new();

	normalized = ibis_client_normalize(client, "Foo[]🐻\\Baz~");
	g_assert_cmpstr(normalized, ==, "Foo[]🐻\\Baz~");
	g_free(normalized);

	g_assert_finalize_object(client);
}

static void
test_ibis_client_normalize_ascii(void) {
	IbisClient *client = NULL;
	IbisFeatures *features = NULL;
	IbisMessage *message = NULL;
	char *normalized = NULL;

	client = ibis_client_new();
	features = ibis_client_get_features(client);

	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_params(message, IBIS_FEATURE_CASEMAPPING "=ascii", NULL);
	ibis_features_parse(features, message);
	g_assert_finalize_object(message);

	normalized = ibis_client_normalize(client, "Foo[]🐻\\Baz~");
	g_assert_cmpstr(normalized, ==, "foo[]🐻\\baz~");
	g_free(normalized);

	g_assert_finalize_object(client);
}

static void
test_ibis_client_normalize_rfc1459(void) {
	IbisClient *client = NULL;
	IbisFeatures *features = NULL;
	IbisMessage *message = NULL;
	char *normalized = NULL;

	client = ibis_client_new();
	features = ibis_client_get_features(client);

	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_params(message, IBIS_FEATURE_CASEMAPPING "=rfc1459", NULL);
	ibis_features_parse(features, message);
	g_assert_finalize_object(message);

	normalized = ibis_client_normalize(client, "Foo[]🐻\\Baz~");
	g_assert_cmpstr(normalized, ==, "foo{}🐻|baz^");
	g_free(normalized);

	g_assert_finalize_object(client);
}

static void
test_ibis_client_normalize_rfc1459_strict(void) {
	IbisClient *client = NULL;
	IbisFeatures *features = NULL;
	IbisMessage *message = NULL;
	char *normalized = NULL;

	client = ibis_client_new();
	features = ibis_client_get_features(client);

	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_params(message, IBIS_FEATURE_CASEMAPPING "=rfc1459-strict", NULL);
	ibis_features_parse(features, message);
	g_assert_finalize_object(message);

	normalized = ibis_client_normalize(client, "Foo[]🐻\\Baz~");
	g_assert_cmpstr(normalized, ==, "foo{}🐻|baz~");
	g_free(normalized);

	g_assert_finalize_object(client);
}

static void
test_ibis_client_parse_mode_string_with_chanmodes(void) {
	IbisClient *client = NULL;
	IbisFeatures *features = NULL;
	IbisMessage *message = NULL;
	IbisModeChange *mode_changes = NULL;
	GError *error = NULL;
	const char * const params[] = {"irene", "pidgy", NULL};
	guint n_changes = 0;

	client = ibis_client_new();
	features = ibis_client_get_features(client);

	message = ibis_message_new(IBIS_RPL_ISUPPORT);
	ibis_message_set_params(message,
	                        IBIS_FEATURE_CHANMODES "=Ibe,k,fl,CEMRUimnstu",
	                        IBIS_FEATURE_PREFIX "=(qaohv)~&@%+",
	                        NULL);
	ibis_features_parse(features, message);
	g_assert_finalize_object(message);

	mode_changes = ibis_client_parse_mode_string(client, "+o-o",
	                                             (GStrv)params, &n_changes,
	                                             &error);
	g_assert_no_error(error);
	g_assert_cmpuint(n_changes, ==, 2);
	g_assert_nonnull(mode_changes);

	/* Check the changes. */
	g_assert_cmpint(mode_changes[0].mode, ==, 'o');
	g_assert_true(mode_changes[0].add);
	g_assert_cmpstr(mode_changes[0].parameter, ==, "irene");

	g_assert_cmpint(mode_changes[1].mode, ==, 'o');
	g_assert_false(mode_changes[1].add);
	g_assert_cmpstr(mode_changes[1].parameter, ==, "pidgy");

	g_assert_finalize_object(client);
	g_free(mode_changes);
}

static void
test_ibis_client_parse_mode_string_without_chanmodes(void) {
	IbisClient *client = NULL;
	IbisModeChange *mode_changes = NULL;
	GError *error = NULL;
	const char * const params[] = {"irene", "pidgy", NULL};
	guint n_changes = 0;

	client = ibis_client_new();

	mode_changes = ibis_client_parse_mode_string(client, "+o-o",
	                                             (GStrv)params, &n_changes,
	                                             &error);
	g_assert_no_error(error);
	g_assert_cmpuint(n_changes, ==, 2);
	g_assert_nonnull(mode_changes);

	/* Check the changes. */
	g_assert_cmpint(mode_changes[0].mode, ==, 'o');
	g_assert_true(mode_changes[0].add);
	g_assert_cmpstr(mode_changes[0].parameter, ==, "irene");

	g_assert_cmpint(mode_changes[1].mode, ==, 'o');
	g_assert_false(mode_changes[1].add);
	g_assert_cmpstr(mode_changes[1].parameter, ==, "pidgy");

	g_assert_finalize_object(client);
	g_free(mode_changes);
}

static void
test_ibis_client_parse_mode_string_missing_parameters(void) {
	IbisClient *client = NULL;
	IbisModeChange *mode_changes = NULL;
	GError *error = NULL;
	const char * const params[] = {"irene", NULL};
	guint n_changes = 1;

	client = ibis_client_new();

	mode_changes = ibis_client_parse_mode_string(client, "+o-o",
	                                             (GStrv)params, &n_changes,
	                                             &error);
	g_assert_error(error,
	               IBIS_CLIENT_ERROR,
	               IBIS_CLIENT_ERROR_PARSE_MODE_STRING_NOT_ENOUGH_PARAMETERS);
	g_clear_error(&error);

	g_assert_nonnull(mode_changes);
	g_assert_cmpuint(n_changes, ==, 0);

	g_assert_finalize_object(client);
	g_free(mode_changes);
}

static void
test_ibis_client_parse_mode_string_extra_parameters(void) {
	IbisClient *client = NULL;
	IbisModeChange *mode_changes = NULL;
	GError *error = NULL;
	const char * const params[] = {"irene", "pidgy", NULL};
	guint n_changes = 1;

	client = ibis_client_new();

	mode_changes = ibis_client_parse_mode_string(client, "+o",
	                                             (GStrv)params, &n_changes,
	                                             &error);
	g_assert_error(error,
	               IBIS_CLIENT_ERROR,
	               IBIS_CLIENT_ERROR_PARSE_MODE_STRING_TOO_MANY_PARAMETERS);
	g_clear_error(&error);

	g_assert_nonnull(mode_changes);
	g_assert_cmpuint(n_changes, ==, 0);

	g_assert_finalize_object(client);
	g_free(mode_changes);
}

static void
test_ibis_client_parse_mode_string_spec_example_command_1(void) {
	IbisClient *client = NULL;
	IbisModeChange *mode_changes = NULL;
	GError *error = NULL;
	const char * const params[] = {"*@127.0.0.1", NULL};
	guint n_changes = 0;

	client = ibis_client_new();

	mode_changes = ibis_client_parse_mode_string(client, "+mb",
	                                             (GStrv)params, &n_changes,
	                                             &error);
	g_assert_no_error(error);
	g_assert_cmpuint(n_changes, ==, 2);
	g_assert_nonnull(mode_changes);

	/* Check the changes. */
	g_assert_cmpint(mode_changes[0].mode, ==, 'm');
	g_assert_true(mode_changes[0].add);
	g_assert_cmpstr(mode_changes[0].parameter, ==, NULL);

	g_assert_cmpint(mode_changes[1].mode, ==, 'b');
	g_assert_true(mode_changes[1].add);
	g_assert_cmpstr(mode_changes[1].parameter, ==, "*@127.0.0.1");

	g_assert_finalize_object(client);
	g_free(mode_changes);
}

static void
test_ibis_client_parse_mode_string_spec_example_message_1(void) {
	IbisClient *client = NULL;
	IbisModeChange *mode_changes = NULL;
	GError *error = NULL;
	const char * const params[] = {"*@192.168.0.1", NULL};
	guint n_changes = 0;

	client = ibis_client_new();

	mode_changes = ibis_client_parse_mode_string(client, "-bl+i",
	                                             (GStrv)params, &n_changes,
	                                             &error);
	g_assert_no_error(error);
	g_assert_cmpuint(n_changes, ==, 3);
	g_assert_nonnull(mode_changes);

	/* Check the changes. */
	g_assert_cmpint(mode_changes[0].mode, ==, 'b');
	g_assert_false(mode_changes[0].add);
	g_assert_cmpstr(mode_changes[0].parameter, ==, "*@192.168.0.1");

	g_assert_cmpint(mode_changes[1].mode, ==, 'l');
	g_assert_false(mode_changes[1].add);
	g_assert_cmpstr(mode_changes[1].parameter, ==, NULL);

	g_assert_cmpint(mode_changes[2].mode, ==, 'i');
	g_assert_true(mode_changes[2].add);
	g_assert_cmpstr(mode_changes[2].parameter, ==, NULL);

	g_assert_finalize_object(client);
	g_free(mode_changes);
}

static void
test_ibis_client_parse_mode_string_spec_example_message_2(void) {
	IbisClient *client = NULL;
	IbisModeChange *mode_changes = NULL;
	GError *error = NULL;
	const char * const params[] = {"bunny", NULL};
	guint n_changes = 0;

	client = ibis_client_new();

	mode_changes = ibis_client_parse_mode_string(client, "+o",
	                                             (GStrv)params, &n_changes,
	                                             &error);
	g_assert_no_error(error);
	g_assert_cmpuint(n_changes, ==, 1);
	g_assert_nonnull(mode_changes);

	/* Check the changes. */
	g_assert_cmpint(mode_changes[0].mode, ==, 'o');
	g_assert_true(mode_changes[0].add);
	g_assert_cmpstr(mode_changes[0].parameter, ==, "bunny");

	g_assert_finalize_object(client);
	g_free(mode_changes);
}

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

	g_test_set_nonfatal_assertions();

	g_test_add_func("/ibis/client/properties", test_ibis_client_properties);

	g_test_add_func("/ibis/client/secondary-nick",
	                test_ibis_client_secondary_nick);
	g_test_add_func("/ibis/client/tertiary-nick",
	                test_ibis_client_tertiary_nick);

	g_test_add_func("/ibis/client/realname/null",
	                test_ibis_client_realname_null);
	g_test_add_func("/ibis/client/realname/empty",
	                test_ibis_client_realname_empty);

	g_test_add_func("/ibis/client/username/null",
	                test_ibis_client_username_null);
	g_test_add_func("/ibis/client/username/empty",
	                test_ibis_client_username_empty);

	g_test_add_func("/ibis/client/is-channel/default",
	                test_ibis_client_is_channel_default);
	g_test_add_func("/ibis/client/is-channel/custom",
	                test_ibis_client_is_channel_custom);

	g_test_add_func("/ibis/client/network/default",
	                test_ibis_client_network_default);
	g_test_add_func("/ibis/client/network/custom",
	                test_ibis_client_network_custom);

	g_test_add_func("/ibis/client/normalize/null",
	                test_ibis_client_normalize_null);
	g_test_add_func("/ibis/client/normalize/ascii",
	                test_ibis_client_normalize_ascii);
	g_test_add_func("/ibis/client/normalize/rfc1459",
	                test_ibis_client_normalize_rfc1459);
	g_test_add_func("/ibis/client/normalize/rfc1459-strict",
	                test_ibis_client_normalize_rfc1459_strict);

	g_test_add_func("/ibis/client/parse-mode-string/with-chanmodes",
	                test_ibis_client_parse_mode_string_with_chanmodes);
	g_test_add_func("/ibis/client/parse-mode-string/without-chanmodes",
	                test_ibis_client_parse_mode_string_without_chanmodes);
	g_test_add_func("/ibis/client/parse-mode-string/missing-parameters",
	                test_ibis_client_parse_mode_string_missing_parameters);
	g_test_add_func("/ibis/client/parse-mode-string/too-extra-parameters",
	                test_ibis_client_parse_mode_string_extra_parameters);
	g_test_add_func("/ibis/client/parse-mode-string/spec-example-command-1",
	                test_ibis_client_parse_mode_string_spec_example_command_1);
	g_test_add_func("/ibis/client/parse-mode-string/spec-example-message-1",
	                test_ibis_client_parse_mode_string_spec_example_message_1);
	g_test_add_func("/ibis/client/parse-mode-string/spec-example-message-2",
	                test_ibis_client_parse_mode_string_spec_example_message_2);

	return g_test_run();
}
