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

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
test_purple_conversation_manager_counter_cb(PurpleConversationManager *manager,
                                            PurpleConversation *conversation,
                                            gpointer data)
{
	guint *counter = data;

	birb_assert_type(manager, PURPLE_TYPE_CONVERSATION_MANAGER);
	birb_assert_type(conversation, PURPLE_TYPE_CONVERSATION);

	*counter = *counter + 1;
}

/******************************************************************************
 * Tests
 *****************************************************************************/
static void
test_purple_conversation_manager_new_null(void) {
	PurpleConversationManager *manager = NULL;
	PurpleConversationManagerBackend *backend = NULL;

	manager = purple_conversation_manager_new(NULL);

	g_assert_true(PURPLE_IS_CONVERSATION_MANAGER(manager));
	g_assert_true(G_IS_LIST_MODEL(manager));

	backend = purple_conversation_manager_get_backend(manager);
	g_assert_null(backend);

	g_assert_finalize_object(manager);
}

static void
test_purple_conversation_manager_add_remove(void) {
	PurpleAccount *account = NULL;
	PurpleConversationManager *manager = NULL;
	PurpleConversation *conversation = NULL;
	PurpleConversation *conversation1 = NULL;
	GListModel *conversations = NULL;
	guint added_counter = 0;
	guint removed_counter = 0;
	guint changed_counter = 0;
	gboolean result = FALSE;

	manager = g_object_new(PURPLE_TYPE_CONVERSATION_MANAGER, NULL);

	g_assert_true(PURPLE_IS_CONVERSATION_MANAGER(manager));

	/* Wire up our signals. */
	g_signal_connect(manager, "added",
	                 G_CALLBACK(test_purple_conversation_manager_counter_cb),
	                 &added_counter);
	g_signal_connect(manager, "removed",
	                 G_CALLBACK(test_purple_conversation_manager_counter_cb),
	                 &removed_counter);
	birb_count_list_model_items_changed(G_LIST_MODEL(manager),
	                                    &changed_counter);

	/* Create our account. */
	account = purple_account_new("test", "test");

	/* Create the conversation. */
	conversation = g_object_new(
		PURPLE_TYPE_CONVERSATION,
		"account", account,
		"id", "test-conversation",
		NULL);

	/* Add the conversation to the manager. */
	added_counter = 0;
	removed_counter = 0;
	changed_counter = 0;
	result = purple_conversation_manager_add(manager, conversation);
	g_assert_true(result);
	g_assert_cmpuint(added_counter, ==, 1);
	g_assert_cmpuint(removed_counter, ==, 0);
	g_assert_cmpuint(changed_counter, ==, 1);
	g_assert_cmpuint(g_list_model_get_n_items(G_LIST_MODEL(manager)), ==, 1);

	/* Verify that get_all_for_account works. */
	conversations = purple_conversation_manager_get_all_for_account(manager,
	                                                                account);
	g_assert_true(G_IS_LIST_MODEL(conversations));
	birb_assert_list_model_n_items(conversations, 1);

	conversation1 = g_list_model_get_item(G_LIST_MODEL(conversations), 0);
	birb_assert_type(conversation1, PURPLE_TYPE_CONVERSATION);
	g_assert_true(conversation1 == conversation);
	g_clear_object(&conversation1);

	g_assert_finalize_object(conversations);

	/* Remove the conversation. */
	added_counter = 0;
	removed_counter = 0;
	changed_counter = 0;
	result = purple_conversation_manager_remove(manager, conversation);
	g_assert_true(result);
	g_assert_cmpuint(added_counter, ==, 0);
	g_assert_cmpuint(removed_counter, ==, 1);
	g_assert_cmpuint(changed_counter, ==, 1);
	g_assert_cmpuint(g_list_model_get_n_items(G_LIST_MODEL(manager)), ==, 0);

	/* Clean up.*/
	g_assert_finalize_object(conversation);
	g_assert_finalize_object(account);
	g_assert_finalize_object(manager);
}

/******************************************************************************
 * Signal Tests
 *****************************************************************************/
static void
test_purple_conversation_manager_signal_conversation_changed_cb(PurpleConversationManager *manager,
                                                                PurpleConversation *conversation,
                                                                GParamSpec *pspec,
                                                                gpointer data)
{
	guint *counter = data;

	g_assert_true(PURPLE_IS_CONVERSATION_MANAGER(manager));
	g_assert_true(PURPLE_IS_CONVERSATION(conversation));
	g_assert_true(G_IS_PARAM_SPEC(pspec));

	*counter = *counter + 1;
}

static void
test_purple_conversation_manager_signal_conversation_changed(void) {
	PurpleAccount *account = NULL;
	PurpleConversationManager *manager = NULL;
	PurpleConversation *conversation = NULL;
	guint counter = 0;
	guint detailed_counter = 0;
	gboolean result = FALSE;

	manager = g_object_new(PURPLE_TYPE_CONVERSATION_MANAGER, NULL);
	g_signal_connect(manager, "conversation-changed",
	                 G_CALLBACK(test_purple_conversation_manager_signal_conversation_changed_cb),
	                 &counter);
	g_signal_connect(manager, "conversation-changed::title",
	                 G_CALLBACK(test_purple_conversation_manager_signal_conversation_changed_cb),
	                 &detailed_counter);

	account = purple_account_new("test", "test");

	conversation = g_object_new(
		PURPLE_TYPE_CONVERSATION,
		"account", account,
		"id", "test-conversation",
		NULL);
	purple_conversation_manager_add(manager, conversation);

	/* Set the features and title of the conversation, and verify our counters. */
	purple_conversation_set_title(conversation, "A place for kool kats");

	/* The title also notifies for the title-for-display property which is why
	 * we get 2 callbacks called instead of just 1.
	 */
	g_assert_cmpuint(counter, ==, 2);
	g_assert_cmpuint(detailed_counter, ==, 1);

	/* Unregister the conversation, to make sure signals are no longer
	 * propagated.
	 */
	result = purple_conversation_manager_remove(manager, conversation);
	g_assert_true(result);

	counter = 0;
	detailed_counter = 0;
	purple_conversation_set_title(conversation,
	                              "no longer a place for kool kats");
	g_assert_cmpuint(counter, ==, 0);
	g_assert_cmpuint(detailed_counter, ==, 0);

	/* Clean up. */
	g_clear_object(&conversation);
	g_clear_object(&account);
	g_clear_object(&manager);
}

static void
test_purple_conversation_manager_signal_present_conversation(void) {
	PurpleAccount *account = NULL;
	PurpleConversation *conversation = NULL;
	PurpleConversationManager *manager = NULL;
	guint counter = 0;
	gboolean ret = FALSE;

	account = purple_account_new("test", "test");

	manager = g_object_new(PURPLE_TYPE_CONVERSATION_MANAGER, NULL);
	g_signal_connect(manager, "present-conversation",
	                 G_CALLBACK(test_purple_conversation_manager_counter_cb),
	                 &counter);

	conversation = g_object_new(
		PURPLE_TYPE_CONVERSATION,
		"account", account,
		"id", "test-conversation",
		"type", PURPLE_CONVERSATION_TYPE_DM,
		NULL);

	ret = purple_conversation_manager_add(manager, conversation);
	g_assert_true(ret);

	g_assert_cmpuint(counter, ==, 0);
	purple_conversation_present(conversation);
	g_assert_cmpuint(counter, ==, 1);

	ret = purple_conversation_manager_remove(manager, conversation);
	g_assert_true(ret);

	g_assert_finalize_object(manager);
	g_assert_finalize_object(conversation);
	g_clear_object(&account);
}

static void
test_purple_conversation_manager_signal_conversation_displayed(void) {
	PurpleAccount *account = NULL;
	PurpleConversation *conversation = NULL;
	PurpleConversationManager *manager = NULL;
	guint counter = 0;
	gboolean ret = FALSE;

	account = purple_account_new("test", "test");

	manager = g_object_new(PURPLE_TYPE_CONVERSATION_MANAGER, NULL);
	g_signal_connect(manager, "conversation-displayed",
	                 G_CALLBACK(test_purple_conversation_manager_counter_cb),
	                 &counter);

	conversation = g_object_new(
		PURPLE_TYPE_CONVERSATION,
		"account", account,
		"id", "test-conversation",
		"type", PURPLE_CONVERSATION_TYPE_DM,
		NULL);

	ret = purple_conversation_manager_add(manager, conversation);
	g_assert_true(ret);

	g_assert_cmpuint(counter, ==, 0);
	purple_conversation_displayed(conversation);
	g_assert_cmpuint(counter, ==, 1);

	ret = purple_conversation_manager_remove(manager, conversation);
	g_assert_true(ret);

	g_assert_finalize_object(manager);
	g_assert_finalize_object(conversation);
	g_clear_object(&account);
}

/******************************************************************************
 * find-dm tests
 *****************************************************************************/
static void
test_purple_conversation_manager_find_dm_empty(void) {
	PurpleAccount *account = NULL;
	PurpleContact *contact = NULL;
	PurpleConversation *conversation = NULL;
	PurpleConversationManager *manager = NULL;

	manager = g_object_new(PURPLE_TYPE_CONVERSATION_MANAGER, NULL);

	account = purple_account_new("test", "test");
	contact = purple_contact_new(account, NULL);

	conversation = purple_conversation_manager_find_dm(manager, contact);
	g_assert_null(conversation);

	g_assert_finalize_object(contact);
	g_assert_finalize_object(manager);

	g_clear_object(&account);
}

static void
test_purple_conversation_manager_find_dm_exists(void) {
	PurpleAccount *account = NULL;
	PurpleContact *contact = NULL;
	PurpleConversation *conversation1 = NULL;
	PurpleConversation *conversation2 = NULL;
	PurpleConversationManager *manager = NULL;
	PurpleConversationMembers *members = NULL;

	manager = g_object_new(PURPLE_TYPE_CONVERSATION_MANAGER, NULL);

	account = purple_account_new("test", "test");
	contact = purple_contact_new(account,
	                             "a9780f2a-eeb5-4d6b-89cb-52e5dad3973f");

	conversation1 = g_object_new(
		PURPLE_TYPE_CONVERSATION,
		"account", account,
		"id", "test-conversation",
		"type", PURPLE_CONVERSATION_TYPE_DM,
		NULL);
	purple_conversation_manager_add(manager, conversation1);
	members = purple_conversation_get_members(conversation1);

	purple_conversation_members_add_member(members,
	                                       PURPLE_CONTACT_INFO(contact),
	                                       FALSE, NULL);

	conversation2 = purple_conversation_manager_find_dm(manager, contact);
	g_assert_nonnull(conversation2);
	g_assert_true(conversation1 == conversation2);

	purple_conversation_manager_remove(manager, conversation1);

	g_assert_finalize_object(conversation1);
	g_assert_finalize_object(contact);
	g_assert_finalize_object(manager);

	g_clear_object(&account);
}

/* This test makes sure we hit all of the conditional code. */
static void
test_purple_conversation_manager_find_dm_does_not_exist(void) {
	PurpleAccount *account1 = NULL;
	PurpleAccount *account2 = NULL;
	PurpleContact *contact = NULL;
	PurpleContactInfo *info = NULL;
	PurpleConversation *conversation1 = NULL;
	PurpleConversation *conversation2 = NULL;
	PurpleConversation *conversation3 = NULL;
	PurpleConversation *conversation4 = NULL;
	PurpleConversationManager *manager = NULL;

	manager = g_object_new(PURPLE_TYPE_CONVERSATION_MANAGER, NULL);

	/* Create the accounts and set the id for their contact infos as there's no
	 * protocol backing them.
	 */
	account1 = purple_account_new("test1", "test1");
	info = purple_account_get_contact_info(account1);
	purple_contact_info_set_id(info, "test1");

	account2 = purple_account_new("test2", "test2");
	info = purple_account_get_contact_info(account2);
	purple_contact_info_set_id(info, "test2");

	contact = purple_contact_new(account1, NULL);

	/* Create a conversation with the contact's account that's not a dm. */
	conversation1 = g_object_new(
		PURPLE_TYPE_CONVERSATION,
		"account", account1,
		"id", "test-conversation1",
		"type", PURPLE_CONVERSATION_TYPE_CHANNEL,
		NULL);
	purple_conversation_manager_add(manager, conversation1);

	/* Create a conversation with the contact's account that is a dm but not
	 * with the contact.
	 */
	conversation2 = g_object_new(
		PURPLE_TYPE_CONVERSATION,
		"account", account1,
		"id", "test-conversation2",
		"type", PURPLE_CONVERSATION_TYPE_DM,
		NULL);
	purple_conversation_manager_add(manager, conversation2);

	/* Create a conversation with a different account. */
	conversation3 = g_object_new(
		PURPLE_TYPE_CONVERSATION,
		"account", account2,
		"id", "test-conversation3",
		"type", PURPLE_CONVERSATION_TYPE_CHANNEL,
		NULL);
	purple_conversation_manager_add(manager, conversation3);

	conversation4 = purple_conversation_manager_find_dm(manager, contact);
	g_assert_null(conversation4);

	purple_conversation_manager_remove(manager, conversation1);
	purple_conversation_manager_remove(manager, conversation2);
	purple_conversation_manager_remove(manager, conversation3);

	g_assert_finalize_object(conversation1);
	g_assert_finalize_object(conversation2);
	g_assert_finalize_object(conversation3);
	g_assert_finalize_object(contact);
	g_assert_finalize_object(manager);

	g_clear_object(&account1);
	g_clear_object(&account2);
}

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

	g_test_add_func("/conversation-manager/new/null",
	                test_purple_conversation_manager_new_null);

	g_test_add_func("/conversation-manager/add-remove",
	                test_purple_conversation_manager_add_remove);

	g_test_add_func("/conversation-manager/signals/conversation-changed",
	                test_purple_conversation_manager_signal_conversation_changed);
	g_test_add_func("/conversation-manager/signals/present-conversation",
	                test_purple_conversation_manager_signal_present_conversation);
	g_test_add_func("/conversation-manager/signals/conversation-displayed",
	                test_purple_conversation_manager_signal_conversation_displayed);

	g_test_add_func("/conversation-manager/find-dm/empty",
	                test_purple_conversation_manager_find_dm_empty);
	g_test_add_func("/conversation-manager/find-dm/exists",
	                test_purple_conversation_manager_find_dm_exists);
	g_test_add_func("/conversation-manager/find-dm/does-not-exist",
	                test_purple_conversation_manager_find_dm_does_not_exist);

	return g_test_run();
}
