/*
 * Purple - Internet Messaging Library
 * Copyright (C) Pidgin Developers <devel@pidgin.im>
 *
 * 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 <birb.h>

#include <purple.h>

/******************************************************************************
 * Basic Tests
 *****************************************************************************/
static void
test_purple_account_settings_new(void) {
	PurpleAccountSettings *settings = NULL;

	settings = purple_account_settings_new();
	birb_assert_type(settings, PURPLE_TYPE_ACCOUNT_SETTINGS);

	g_assert_finalize_object(settings);
}

static void
test_purple_account_settings_properties(void) {
	PurpleAccountSettings *settings = NULL;
	GType item_type = G_TYPE_INVALID;
	guint n_items = 0;

	settings = g_object_new(
		PURPLE_TYPE_ACCOUNT_SETTINGS,
		NULL);

	birb_assert_type(settings, PURPLE_TYPE_ACCOUNT_SETTINGS);

	g_object_get(
		G_OBJECT(settings),
		"item-type", &item_type,
		"n-items", &n_items,
		NULL);

	g_assert_cmpuint(item_type, ==, PURPLE_TYPE_ACCOUNT_SETTING);

	g_assert_cmpuint(n_items, ==, 0);

	g_assert_finalize_object(settings);
}

/******************************************************************************
 * Add/Remove Tests
 *****************************************************************************/
static void
test_purple_account_settings_add_remove(void) {
	PurpleAccountSetting *setting = NULL;
	PurpleAccountSettings *settings = NULL;
	gboolean result = FALSE;
	guint items_changed_counter = 0;

	settings = purple_account_settings_new();
	birb_count_list_model_items_changed(G_LIST_MODEL(settings),
	                                    &items_changed_counter);

	/* Create the setting. */
	setting = purple_account_setting_boolean_new("test-foo", "Foo", TRUE);

	/* Add a setting. */
	items_changed_counter = 0;
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);

	g_assert_cmpuint(items_changed_counter, ==, 1);
	birb_assert_list_model_n_items(settings, 1);

	/* And then remove it. */
	items_changed_counter = 0;
	result = purple_account_settings_remove_setting(settings, "test-foo");
	g_assert_true(result);

	g_assert_cmpuint(items_changed_counter, ==, 1);
	birb_assert_list_model_n_items(settings, 0);

	/* Try to remove it again. */
	items_changed_counter = 0;
	result = purple_account_settings_remove_setting(settings, "test-foo");
	g_assert_false(result);

	g_assert_cmpuint(items_changed_counter, ==, 0);
	birb_assert_list_model_n_items(settings, 0);

	/* Cleanup. */
	g_assert_finalize_object(settings);
}

static void
test_purple_account_settings_double_add(void) {
	if(g_test_subprocess()) {
		PurpleAccountSetting *setting = NULL;
		PurpleAccountSettings *settings = NULL;
		gboolean result = FALSE;
		guint items_changed_counter = 0;

		settings = purple_account_settings_new();
		birb_count_list_model_items_changed(G_LIST_MODEL(settings),
		                                    &items_changed_counter);

		/* Create the setting. */
		setting = purple_account_setting_boolean_new("test-foo", "Foo", TRUE);

		/* Add another reference for the double add to sink. */
		g_object_ref(setting);

		/* Add a setting. */
		items_changed_counter = 0;
		result = purple_account_settings_add_setting(settings, setting);
		g_assert_true(result);

		g_assert_cmpuint(items_changed_counter, ==, 1);
		birb_assert_list_model_n_items(settings, 1);

		/* Add the settings again. */
		items_changed_counter = 0;
		result = purple_account_settings_add_setting(settings, setting);
		g_assert_false(result);
		g_assert_not_reached();
	}

	g_test_trap_subprocess(NULL, 0, 0);
	g_test_trap_assert_stderr("*PurpleAccountSettings-CRITICAL*'existing == NULL'*failed*");
}

static void
test_purple_account_settings_add_again(void) {
	if(g_test_subprocess()) {
		PurpleAccountSetting *setting = NULL;
		PurpleAccountSettings *settings = NULL;
		gboolean result = FALSE;
		guint items_changed_counter = 0;

		settings = purple_account_settings_new();
		birb_count_list_model_items_changed(G_LIST_MODEL(settings),
		                                    &items_changed_counter);

		/* Create the setting. */
		setting = purple_account_setting_boolean_new("test-foo", "Foo", TRUE);


		/* Add a setting. */
		items_changed_counter = 0;
		result = purple_account_settings_add_setting(settings, setting);
		g_assert_true(result);

		g_assert_cmpuint(items_changed_counter, ==, 1);
		birb_assert_list_model_n_items(settings, 1);

		/* Create another setting with the same id. This should emit a
		 * g_warning and cause the test to fail so we don't do any other
		 * verification.
		 */
		setting = purple_account_setting_string_new("test-foo", "Foobar",
		                                            "a string!");
		purple_account_settings_add_setting(settings, setting);
		g_assert_not_reached();
	}

	g_test_trap_subprocess(NULL, 0, 0);
	g_test_trap_assert_stderr("*PurpleAccountSettings-CRITICAL*'existing == NULL'*failed*");
}

/******************************************************************************
 * Propagate Changed Signal Tests
 *****************************************************************************/
static void
test_purple_account_settings_propagate_changed_signal_cb(PurpleAccountSettings *settings,
                                                         PurpleAccountSetting *setting,
                                                         gpointer data)
{
	guint *counter = data;

	birb_assert_type(settings, PURPLE_TYPE_ACCOUNT_SETTINGS);
	birb_assert_type(setting, PURPLE_TYPE_ACCOUNT_SETTING_BOOLEAN);

	*counter = *counter + 1;
}

static void
test_purple_account_settings_propagate_changed_signal(void) {
	PurpleAccountSettings *settings = NULL;
	PurpleAccountSetting *setting = NULL;
	guint changed_counter = 0;
	gboolean result = FALSE;

	settings = purple_account_settings_new();
	g_signal_connect(settings, "setting-changed",
	                 G_CALLBACK(test_purple_account_settings_propagate_changed_signal_cb),
	                 &changed_counter);

	/* Create the setting and add a reference as we need to use it but the
	 * account settings object takes an existing reference.
	 */
	setting = purple_account_setting_boolean_new("test", "Test", FALSE);
	g_object_ref(setting);

	/* Add it to the settings object. */
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);

	/* Change the value of the setting. */
	changed_counter = 0;
	purple_account_setting_boolean_set_value(PURPLE_ACCOUNT_SETTING_BOOLEAN(setting),
	                                         TRUE);
	g_assert_cmpuint(changed_counter, ==, 1);

	g_assert_finalize_object(settings);
	g_assert_finalize_object(setting);
}

/******************************************************************************
 * Get/Set Tests
 *****************************************************************************/
static void
test_purple_account_settings_get_set_boolean(void) {
	PurpleAccountSetting *setting = NULL;
	PurpleAccountSettings *settings = NULL;
	gboolean result = FALSE;
	gboolean value = FALSE;

	settings = purple_account_settings_new();


	/* Make sure we get the fallback if the setting doesn't exist. */
	value = purple_account_settings_get_boolean(settings, "test", FALSE);
	g_assert_false(value);

	/* Add the setting and make sure we get its value. */
	setting = purple_account_setting_boolean_new("test", "Test", TRUE);
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);

	value = purple_account_settings_get_boolean(settings, "test", FALSE);
	g_assert_true(value);

	/* Change the setting and verify its value. */
	purple_account_settings_set_boolean(settings, "test", FALSE);
	value = purple_account_settings_get_boolean(settings, "test", TRUE);
	g_assert_false(value);

	/* Add a setting with the wrong type and make sure we get the fallback
	 * value.
	 */
	setting = purple_account_setting_int_new("wrong", "Wrong", 42);
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);

	value = purple_account_settings_get_boolean(settings, "wrong", FALSE);
	g_assert_false(value);

	/* Cleanup. */
	g_assert_finalize_object(settings);
}

static void
test_purple_account_settings_get_set_int(void) {
	PurpleAccountSetting *setting = NULL;
	PurpleAccountSettings *settings = NULL;
	gint value = 0;
	gboolean result = FALSE;

	settings = purple_account_settings_new();

	/* Make sure we get the fallback if the setting doesn't exist. */
	value = purple_account_settings_get_int(settings, "test", 42);
	g_assert_cmpint(value, ==, 42);

	/* Add the setting and make sure we get its value. */
	setting = purple_account_setting_int_new("test", "Test", 1337);
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);

	value = purple_account_settings_get_int(settings, "test", 42);
	g_assert_cmpint(value, ==, 1337);

	/* Change the setting and verify its value. */
	purple_account_settings_set_int(settings, "test", -1);
	value = purple_account_settings_get_int(settings, "test", 42);
	g_assert_cmpint(value, ==, -1);

	/* Add a setting with the wrong type and make sure we get the fallback
	 * value.
	 */
	setting = purple_account_setting_string_new("wrong", "Wrong", "bad-wrong");
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);

	value = purple_account_settings_get_int(settings, "wrong", 42);
	g_assert_cmpint(value, ==, 42);

	/* Cleanup. */
	g_assert_finalize_object(settings);
}

static void
test_purple_account_settings_get_set_string(void) {
	PurpleAccountSetting *setting = NULL;
	PurpleAccountSettings *settings = NULL;
	const char *value = NULL;
	gboolean result = FALSE;

	settings = purple_account_settings_new();

	/* Make sure we get the fallback if the setting doesn't exist. */
	value = purple_account_settings_get_string(settings, "test", "fallback");
	g_assert_cmpstr(value, ==, "fallback");

	/* Add the setting and make sure we get its value. */
	setting = purple_account_setting_string_new("test", "Test", "the value");
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);

	value = purple_account_settings_get_string(settings, "test", "fallback");
	g_assert_cmpstr(value, ==, "the value");

	/* Change the setting and verify its value. */
	purple_account_settings_set_string(settings, "test", "the other value");
	value = purple_account_settings_get_string(settings, "test", "the value");
	g_assert_cmpstr(value, ==, "the other value");

	/* Add a setting with the wrong type and make sure we get the fallback
	 * value.
	 */
	setting = purple_account_setting_boolean_new("wrong", "Wrong", TRUE);
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);

	value = purple_account_settings_get_string(settings, "wrong", "fallback");
	g_assert_cmpstr(value, ==, "fallback");

	/* Cleanup. */
	g_assert_finalize_object(settings);
}

static void
test_purple_account_settings_get_set_string_list(void) {
	PurpleAccountSetting *setting = NULL;
	PurpleAccountSettingStringList *list = NULL;
	PurpleAccountSettings *settings = NULL;
	const char *item_id = NULL;
	gboolean result = FALSE;

	settings = purple_account_settings_new();

	/* Make sure we get the fallback if the setting doesn't exist. */
	item_id = purple_account_settings_get_string_list(settings, "test",
	                                                  "fallback");
	g_assert_cmpstr(item_id, ==, "fallback");

	/* Create the setting. */
	setting = purple_account_setting_string_list_new("test", "Test");
	list = PURPLE_ACCOUNT_SETTING_STRING_LIST(setting);
	purple_account_setting_string_list_add_item(list, "foo", "Foo");
	purple_account_setting_string_list_add_item(list, "bar", "Bar");
	purple_account_setting_string_list_add_item(list, "baz", "baz");

	/* Add the setting. */
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);

	/* Make sure we get the fallback since nothing was set as active. */
	item_id = purple_account_settings_get_string_list(settings, "test",
	                                                  "fallback");
	g_assert_cmpstr(item_id, ==, "fallback");

	/* Set the active item to foo and verify it is returned. */
	purple_account_settings_set_string_list(settings, "test", "foo");
	item_id = purple_account_settings_get_string_list(settings, "test", NULL);
	g_assert_cmpstr(item_id, ==, "foo");

	/* Set the active item to bar and verify it was set. */
	purple_account_settings_set_string_list(settings, "test", "bar");
	item_id = purple_account_settings_get_string_list(settings, "test", NULL);
	g_assert_cmpstr(item_id, ==, "bar");

	/* Set the active item to NULL. */
	purple_account_settings_set_string_list(settings, "test", NULL);
	item_id = purple_account_settings_get_string_list(settings, "test",
	                                                  "fallback");
	g_assert_cmpstr(item_id, ==, "fallback");

	/* Add a setting with the wrong type and make sure we get the fallback
	 * value.
	 */
	setting = purple_account_setting_boolean_new("wrong", "Wrong", TRUE);
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);

	item_id = purple_account_settings_get_string_list(settings, "wrong",
	                                                  "fallback");
	g_assert_cmpstr(item_id, ==, "fallback");

	/* Cleanup. */
	g_assert_finalize_object(settings);
}

/******************************************************************************
 * Remove All tests
 *****************************************************************************/
static void
test_purple_account_settings_remove_all(void) {
	PurpleAccountSetting *setting = NULL;
	PurpleAccountSettings *settings = NULL;
	guint counter = 0;
	gboolean result = FALSE;

	settings = purple_account_settings_new();
	birb_count_list_model_items_changed(G_LIST_MODEL(settings), &counter);

	/* Make sure remove all does nothing on an empty settings. */
	counter = 0;
	purple_account_settings_remove_all_settings(settings);
	g_assert_cmpuint(counter, ==, 0);
	birb_assert_list_model_n_items(settings, 0);

	/* Add a few settings. */
	counter = 0;
	setting = purple_account_setting_string_new("test1", "Test 1", NULL);
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);
	g_assert_cmpuint(counter, ==, 1);
	birb_assert_list_model_n_items(settings, 1);

	counter = 0;
	setting = purple_account_setting_string_new("test2", "Test 2", NULL);
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);
	g_assert_cmpuint(counter, ==, 1);
	birb_assert_list_model_n_items(settings, 2);

	counter = 0;
	setting = purple_account_setting_string_new("test3", "Test 3", NULL);
	result = purple_account_settings_add_setting(settings, setting);
	g_assert_true(result);
	g_assert_cmpuint(counter, ==, 1);
	birb_assert_list_model_n_items(settings, 3);

	/* Now remove everything. */
	counter = 0;
	purple_account_settings_remove_all_settings(settings);
	g_assert_cmpuint(counter, ==, 1);
	birb_assert_list_model_n_items(settings, 0);

	/* Clean up. */
	g_assert_finalize_object(settings);
}

/******************************************************************************
 * Update Tests
 *****************************************************************************/
static void
test_purple_account_settings_settings_updated_cb(PurpleAccountSettings *settings,
                                                 gpointer data)
{
	guint *counter = data;

	birb_assert_type(settings, PURPLE_TYPE_ACCOUNT_SETTINGS);

	*counter = *counter + 1;
}

static void
test_purple_account_settings_update(void) {
	PurpleAccountSetting *setting = NULL;
	PurpleAccountSettingStringList *list = NULL;
	PurpleAccountSettings *settings = NULL;
	PurpleAccountSettings *updates = NULL;
	guint items_changed_counter = 0;
	guint settings_changed_counter = 0;
	const char *str_value = NULL;

	/* Create our base settings. */
	settings = purple_account_settings_new();
	birb_count_list_model_items_changed(G_LIST_MODEL(settings),
	                                    &items_changed_counter);
	g_signal_connect(settings, "settings-updated",
	                 G_CALLBACK(test_purple_account_settings_settings_updated_cb),
	                 &settings_changed_counter);

	/* Add a setting that won't be touched, another that will be updated, and
	 * one that will remain the same as the update has a different type.
	 */
	setting = purple_account_setting_boolean_new("foo", "Foo", TRUE);
	purple_account_settings_add_setting(settings, setting);

	setting = purple_account_setting_int_new("bar", "Bar", 42);
	purple_account_settings_add_setting(settings, setting);

	setting = purple_account_setting_string_new("baz", "Baz",
	                                            "You can't touch this");
	purple_account_settings_add_setting(settings, setting);

	setting = purple_account_setting_string_list_new("quux", "Quux");
	list = PURPLE_ACCOUNT_SETTING_STRING_LIST(setting);
	purple_account_setting_string_list_add_item(list, "abc", "123");
	purple_account_settings_add_setting(settings, setting);

	/* Create our update settings. */
	updates = purple_account_settings_new();

	/* Add a valid update, an invalid update, a new setting, and an active
	 * string list item.
	 */
	setting = purple_account_setting_int_new("bar", "Bar", 1337);
	purple_account_settings_add_setting(updates, setting);

	setting = purple_account_setting_boolean_new("baz", "Baz", FALSE);
	purple_account_settings_add_setting(updates, setting);

	setting = purple_account_setting_string_new("qux", "Qux", "woo");
	purple_account_settings_add_setting(updates, setting);

	setting = purple_account_setting_string_list_new("quux", "Quux");
	list = PURPLE_ACCOUNT_SETTING_STRING_LIST(setting);
	purple_account_setting_string_list_add_item(list, "abc", "123");
	purple_account_setting_string_list_set_active_item(list, "abc");
	purple_account_settings_add_setting(updates, setting);

	/* Run the update. */
	items_changed_counter = 0;
	settings_changed_counter = 0;
	purple_account_settings_update_settings(settings, updates);
	birb_assert_list_model_n_items(settings, 5);
	g_assert_cmpuint(items_changed_counter, ==, 1);
	g_assert_cmpuint(settings_changed_counter, ==, 1);

	/* Verify all the things. */
	g_assert_true(purple_account_settings_get_boolean(settings, "foo", FALSE));

	g_assert_cmpint(purple_account_settings_get_int(settings, "bar", 0), ==,
	                1337);

	str_value = purple_account_settings_get_string(settings, "baz", NULL);
	g_assert_cmpstr(str_value, ==, "You can't touch this");

	str_value = purple_account_settings_get_string(settings, "qux", NULL);
	g_assert_cmpstr(str_value, ==, "woo");

	str_value = purple_account_settings_get_string_list(settings, "quux",
	                                                    NULL);
	g_assert_cmpstr(str_value, ==, "abc");

	/* Clean up. */
	g_assert_finalize_object(settings);
	g_assert_finalize_object(updates);
}

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

	g_test_add_func("/account-settings/new", test_purple_account_settings_new);
	g_test_add_func("/account-settings/properties",
	                test_purple_account_settings_properties);

	g_test_add_func("/account-settings/add-remove",
	                test_purple_account_settings_add_remove);
	g_test_add_func("/account-settings/double-add",
	                test_purple_account_settings_double_add);
	g_test_add_func("/account-settings/add-again",
	                test_purple_account_settings_add_again);

	g_test_add_func("/account-settings/propagate-changed-signal",
	                test_purple_account_settings_propagate_changed_signal);

	g_test_add_func("/account-settings/get-set-boolean",
	                test_purple_account_settings_get_set_boolean);
	g_test_add_func("/account-settings/get-set-int",
	                test_purple_account_settings_get_set_int);
	g_test_add_func("/account-settings/get-set-string",
	                test_purple_account_settings_get_set_string);
	g_test_add_func("/account-settings/get-set-string-list",
	                test_purple_account_settings_get_set_string_list);

	g_test_add_func("/account-settings/remove-all",
	                test_purple_account_settings_remove_all);

	g_test_add_func("/account-settings/update",
	                test_purple_account_settings_update);

	return g_test_run();
}
