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

/******************************************************************************
 * Tests
 *****************************************************************************/
static void
test_purple_person_new(void) {
	PurplePerson *person = NULL;

	person = purple_person_new();

	g_assert_true(PURPLE_IS_PERSON(person));

	g_assert_finalize_object(person);
}

static void
test_purple_person_properties(void) {
	PurpleAvatar *avatar = NULL;
	PurpleAvatar *avatar1 = NULL;
	PurpleAvatar *avatar_for_display = NULL;
	PurpleContact *person = NULL;
	PurpleTags *tags = NULL;
	char *id = NULL;
	char *alias = NULL;
	char *color = NULL;
	char *color_for_display = NULL;
	char *name_for_display = NULL;

	/* Create our avatar for testing. */
	avatar = g_object_new(PURPLE_TYPE_AVATAR, NULL);

	/* Use g_object_new so we can test setting properties by name. All of them
	 * call the setter methods, so by doing it this way we exercise more of the
	 * code.
	 */
	person = g_object_new(
		PURPLE_TYPE_PERSON,
		"alias", "alias",
		"avatar", avatar,
		"color", "#794a85",
		NULL);

	/* Now use g_object_get to read all of the properties. */
	g_object_get(person,
		"id", &id,
		"alias", &alias,
		"avatar", &avatar1,
		"avatar-for-display", &avatar_for_display,
		"color", &color,
		"color-for-display", &color_for_display,
		"name-for-display", &name_for_display,
		"tags", &tags,
		NULL);

	/* Test all the things. */
	g_assert_nonnull(id);
	g_clear_pointer(&id, g_free);

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

	g_assert_true(avatar1 == avatar);
	g_clear_object(&avatar1);

	g_assert_true(avatar_for_display == avatar);
	g_clear_object(&avatar_for_display);

	g_assert_cmpstr(color, ==, "#794a85");
	g_clear_pointer(&color, g_free);

	g_assert_cmpstr(color_for_display, ==, "#794a85");
	g_clear_pointer(&color_for_display, g_free);

	g_assert_cmpstr(name_for_display, ==, "alias");
	g_clear_pointer(&name_for_display, g_free);

	g_assert_nonnull(tags);
	g_clear_object(&tags);

	/* Additional cleanup. */
	g_assert_finalize_object(person);
	g_assert_finalize_object(avatar);
}

static void
test_purple_person_avatar_for_display_person(void) {
	PurpleAvatar *avatar = NULL;
	PurpleContactInfo *info = NULL;
	PurplePerson *person = NULL;
	guint avatar_counter = 0;
	guint avatar_for_display_counter = 0;

	person = purple_person_new();
	birb_count_property_changed(G_OBJECT(person), "avatar", &avatar_counter);
	birb_count_property_changed(G_OBJECT(person), "avatar-for-display",
	                            &avatar_for_display_counter);
	avatar = g_object_new(PURPLE_TYPE_AVATAR, NULL);
	purple_person_set_avatar(person, avatar);
	g_assert_cmpuint(avatar_counter, ==, 1);
	g_assert_cmpuint(avatar_for_display_counter, ==, 1);

	info = purple_contact_info_new("id");
	purple_person_add_contact_info(person, info);

	/* Make sure the person's avatar is overriding the contact info. */
	g_assert_true(purple_person_get_avatar_for_display(person) == avatar);

	/* Contact info's have a reference on the person, so the easiest way to
	 * remove that is to remove them from the person.
	 */
	purple_person_remove_contact_info(person, info);

	g_assert_finalize_object(person);
	g_assert_finalize_object(info);
	g_assert_finalize_object(avatar);
}

static void
test_purple_person_avatar_for_display_contact(void) {
	PurpleAvatar *avatar = NULL;
	PurpleContactInfo *info = NULL;
	PurplePerson *person = NULL;
	guint counter = 0;

	person = purple_person_new();
	birb_count_property_changed(G_OBJECT(person), "avatar-for-display",
	                            &counter);

	info = purple_contact_info_new("id");
	avatar = g_object_new(PURPLE_TYPE_AVATAR, NULL);
	purple_contact_info_set_avatar(info, avatar);
	purple_person_add_contact_info(person, info);

	g_assert_cmpuint(counter, ==, 1);

	/* Make sure the person's avatar is overriding the contact info. */
	g_assert_true(purple_person_get_avatar_for_display(person) == avatar);
	g_clear_object(&avatar);

	/* Now change the avatar on the contact info an verify that we not notified
	 * of the property changing.
	 */
	counter = 0;
	avatar = g_object_new(PURPLE_TYPE_AVATAR, NULL);
	purple_contact_info_set_avatar(info, avatar);
	g_assert_cmpuint(counter, ==, 1);

	/* Contact info's have a reference on the person, so the easiest way to
	 * remove that is to remove them from the person.
	 */
	purple_person_remove_contact_info(person, info);

	g_assert_finalize_object(person);
	g_assert_finalize_object(info);
	g_assert_finalize_object(avatar);
}

static void
test_purple_person_color_for_display_person(void) {
	PurpleContactInfo *info = NULL;
	PurplePerson *person = NULL;
	const char *color = NULL;
	guint color_counter = 0;
	guint color_for_display_counter = 0;

	person = purple_person_new();
	birb_count_property_changed(G_OBJECT(person), "color", &color_counter);
	birb_count_property_changed(G_OBJECT(person), "color-for-display",
	                            &color_for_display_counter);
	purple_person_set_color(person, "#abcdef");

	g_assert_cmpuint(color_counter, ==, 1);
	g_assert_cmpuint(color_for_display_counter, ==, 1);

	/* Make sure the person's color is overriding the contact info. */
	color_counter = 0;
	color_for_display_counter = 0;
	info = purple_contact_info_new("id");
	purple_person_add_contact_info(person, info);

	color = purple_person_get_color_for_display(person);
	g_assert_cmpstr(color, ==, "#abcdef");

	g_assert_cmpuint(color_counter, ==, 0);
	g_assert_cmpuint(color_for_display_counter, ==, 0);

	/* Contact info's have a reference on the person, so the easiest way to
	 * remove that is to remove them from the person.
	 */
	purple_person_remove_contact_info(person, info);

	/* Cleanup. */
	g_assert_finalize_object(person);
	g_assert_finalize_object(info);
}

static void
test_purple_person_color_for_display_contact(void) {
	PurpleContactInfo *info = NULL;
	PurplePerson *person = NULL;
	const char *color = NULL;
	guint counter = 0;

	person = purple_person_new();
	birb_count_property_changed(G_OBJECT(person), "color-for-display",
	                            &counter);

	info = purple_contact_info_new("id");
	purple_contact_info_set_color(info, "#012345");
	purple_person_add_contact_info(person, info);

	g_assert_cmpuint(counter, ==, 1);

	/* Make sure the person's color is overriding the contact info. */
	color = purple_person_get_color_for_display(person);
	g_assert_cmpstr(color, ==, "#012345");

	/* Now change the color on the contact info and verify that we are notified
	 * of the property changing.
	 */
	counter = 0;
	purple_contact_info_set_color(info, "#6789ab");
	g_assert_cmpuint(counter, ==, 1);

	/* Contact info's have a reference on the person, so the easiest way to
	 * remove that is to remove them from the person.
	 */
	purple_person_remove_contact_info(person, info);

	g_assert_finalize_object(person);
	g_assert_finalize_object(info);
}

static void
test_purple_person_name_for_display_person(void) {
	PurpleContactInfo *info = NULL;
	PurplePerson *person = NULL;

	person = purple_person_new();
	purple_person_set_alias(person, "person-alias");

	info = purple_contact_info_new("id");
	purple_person_add_contact_info(person, info);

	/* Make sure the person's alias is overriding the contact info. */
	g_assert_cmpstr(purple_person_get_name_for_display(person), ==,
	                "person-alias");

	/* Contact info's have a reference on the person, so the easiest way to
	 * remove that is to remove them from the person.
	 */
	purple_person_remove_contact_info(person, info);

	g_assert_finalize_object(person);
	g_assert_finalize_object(info);
}

static void
test_purple_person_name_for_display_contact(void) {
	PurpleContactInfo *info = NULL;
	PurplePerson *person = NULL;
	guint counter = 0;

	person = purple_person_new();
	birb_count_property_changed(G_OBJECT(person), "name-for-display",
	                            &counter);

	info = purple_contact_info_new("id");
	purple_person_add_contact_info(person, info);
	g_assert_cmpuint(counter, ==, 1);

	/* Make sure the name for display matches the id of the contact. */
	g_assert_cmpstr(purple_person_get_name_for_display(person), ==, "id");

	/* Now set a username on the contact and verify that the name for display
	 * matches and that the notify signal was emitted for the property.
	 */
	counter = 0;
	purple_contact_info_set_username(info, "clu");
	g_assert_cmpstr(purple_person_get_name_for_display(person), ==, "clu");
	g_assert_cmpuint(counter, ==, 1);

	/* Contact info's have a reference on the person, so the easiest way to
	 * remove that is to remove them from the person.
	 */
	purple_person_remove_contact_info(person, info);

	g_assert_finalize_object(person);
	g_assert_finalize_object(info);
}

static void
test_purple_person_contacts_single(void) {
	PurpleContactInfo *info = NULL;
	PurplePerson *person = NULL;
	PurplePerson *person1 = NULL;
	gboolean removed = FALSE;
	guint counter = 0;

	info = purple_contact_info_new("id");
	person = purple_person_new();
	birb_count_list_model_items_changed(G_LIST_MODEL(person), &counter);

	birb_assert_list_model_n_items(person, 0);
	purple_person_add_contact_info(person, info);
	birb_assert_list_model_n_items(person, 1);
	g_assert_cmpuint(counter, ==, 1);

	person1 = purple_contact_info_get_person(info);
	g_assert_true(person1 == person);

	counter = 0;
	removed = purple_person_remove_contact_info(person, info);
	g_assert_true(removed);
	g_assert_cmpuint(counter, ==, 1);
	birb_assert_list_model_n_items(person, 0);

	person1 = purple_contact_info_get_person(info);
	g_assert_null(person1);

	g_assert_finalize_object(person);
	g_assert_finalize_object(info);
}

static void
test_purple_person_contacts_multiple(void) {
	PurplePerson *person = NULL;
	GPtrArray *infos = NULL;
	const int n_infos = 5;
	guint counter = 0;

	person = purple_person_new();
	birb_count_list_model_items_changed(G_LIST_MODEL(person), &counter);

	infos = g_ptr_array_new_full(n_infos, g_object_unref);
	for(int i = 0; i < n_infos; i++) {
		PurpleContactInfo *info = NULL;
		char *username = NULL;

		counter = 0;

		birb_assert_list_model_n_items(person, i);

		username = g_strdup_printf("username%d", i);
		info = purple_contact_info_new(NULL);
		purple_contact_info_set_username(info, username);
		g_free(username);

		/* Add the contact info to the ptr array so we can remove it below. */
		g_ptr_array_add(infos, info);

		/* Add the contact info to the person and make sure that all the magic
		 * happened.
		 */
		purple_person_add_contact_info(person, info);
		birb_assert_list_model_n_items(person, i + 1);
		g_assert_cmpuint(counter, ==, 1);
	}

	for(int i = 0; i < n_infos; i++) {
		PurpleContactInfo *info = g_ptr_array_index(infos, i);
		gboolean removed = FALSE;

		counter = 0;

		birb_assert_list_model_n_items(person, n_infos - i);

		removed = purple_person_remove_contact_info(person, info);
		g_assert_true(removed);

		birb_assert_list_model_n_items(person, n_infos - (i + 1));
		g_assert_cmpuint(counter, ==, 1);
	}

	/* Final sanity check that the person has no more contacts. */
	birb_assert_list_model_n_items(person, 0);

	g_ptr_array_free(infos, TRUE);

	g_assert_finalize_object(person);
}

static void
test_purple_person_priority_single(void) {
	PurpleContactInfo *info = NULL;
	PurpleContactInfo *priority = NULL;
	PurplePerson *person = NULL;
	PurplePresence *presence = NULL;
	guint counter = 0;

	person = purple_person_new();
	birb_count_property_changed(G_OBJECT(person), "priority-contact-info",
	                            &counter);
	priority = purple_person_get_priority_contact_info(person);
	g_assert_null(priority);

	/* Now create a real contact. */
	info = purple_contact_info_new(NULL);
	purple_person_add_contact_info(person, info);

	/* Set the presence of the contact. */
	presence = purple_contact_info_get_presence(info);
	purple_presence_set_primitive(presence,
	                              PURPLE_PRESENCE_PRIMITIVE_AVAILABLE);

	g_assert_cmpuint(counter, ==, 1);

	priority = purple_person_get_priority_contact_info(person);
	g_assert_true(priority == info);

	/* Contact info's have a reference on the person, so the easiest way to
	 * remove that is to remove them from the person.
	 */
	purple_person_remove_contact_info(person, info);

	g_assert_finalize_object(person);
	g_assert_finalize_object(info);
}

static void
test_purple_person_priority_multiple_with_change(void) {
	PurpleContactInfo *priority = NULL;
	PurpleContactInfo *first = NULL;
	PurpleContactInfo *sorted_contact = NULL;
	PurplePerson *person = NULL;
	PurplePresence *sorted_presence = NULL;
	guint counter = 0;
	int n_infos = 5;

	/* This unit test is a bit complicated, but it adds 5 contact infos to a
	 * person all whose presences are set to offline. After adding all the
	 * contact infos, we verify that the first contact info we added is the
	 * priority contact info. Then we flip the active status of the n_infos - 2
	 * infos to available. This should make it the priority contact info which
	 * we then assert.
	 */

	/* Create the person and connected to the notify signal for the
	 * priority-contact property.
	 */
	person = purple_person_new();
	birb_count_property_changed(G_OBJECT(person), "priority-contact-info",
	                            &counter);
	priority = purple_person_get_priority_contact_info(person);
	g_assert_null(priority);

	/* Create and add all contact infos. */
	for(int i = 0; i < n_infos; i++) {
		PurpleContactInfo *info = NULL;
		char *username = NULL;

		/* Set counter to 0 as it shouldn't be called as the priority contact
		 * info shouldn't change except for the first index.
		 */
		counter = 0;

		/* Now create a real contact. */
		username = g_strdup_printf("username%d", i + 1);
		info = purple_contact_info_new(NULL);
		purple_contact_info_set_username(info, username);
		g_free(username);

		purple_person_add_contact_info(person, info);

		if(i == 0) {
			first = g_object_ref(info);
			g_assert_cmpuint(counter, ==, 1);
		} else {
			g_assert_cmpuint(counter, ==, 0);

			if(i == n_infos - 2) {
				PurplePresence *presence = NULL;

				/* Add a reference to the presence of this specific contact
				 * info, as we want to tweak it later.
				 */
				presence = purple_contact_info_get_presence(info);
				sorted_presence = g_object_ref(presence);

				sorted_contact = g_object_ref(info);
			}
		}

		g_clear_object(&info);
	}

	birb_assert_list_model_n_items(person, n_infos);

	priority = purple_person_get_priority_contact_info(person);
	g_assert_true(priority == first);
	g_clear_object(&first);

	/* Now set the second from the last contact info's status to available, and
	 * verify that that contact info is now the priority contact info.
	 */
	counter = 0;
	purple_presence_set_primitive(sorted_presence,
	                              PURPLE_PRESENCE_PRIMITIVE_AVAILABLE);
	priority = purple_person_get_priority_contact_info(person);
	g_assert_true(priority == sorted_contact);
	g_assert_cmpuint(counter, ==, 1);

	/* Cleanup. */
	purple_person_remove_all_contact_infos(person);

	g_clear_object(&sorted_contact);
	g_clear_object(&sorted_presence);

	g_assert_finalize_object(person);
}

/******************************************************************************
 * Matches tests
 *****************************************************************************/
static void
test_purple_person_matches_accepts_null(void) {
	PurplePerson *person = purple_person_new();

	g_assert_true(purple_person_matches(person, NULL));

	g_assert_finalize_object(person);
}

static void
test_purple_person_matches_empty_string(void) {
	PurplePerson *person = purple_person_new();

	g_assert_true(purple_person_matches(person, ""));

	g_assert_finalize_object(person);
}

static void
test_purple_person_matches_alias(void) {
	PurplePerson *person = purple_person_new();

	purple_person_set_alias(person, "this is the alias");

	g_assert_true(purple_person_matches(person, "the"));
	g_assert_false(purple_person_matches(person, "what"));

	g_assert_finalize_object(person);
}

static void
test_purple_person_matches_contact_info(void) {
	PurplePerson *person = purple_person_new();
	PurpleContactInfo *info = purple_contact_info_new(NULL);

	purple_contact_info_set_username(info, "user1");
	purple_person_add_contact_info(person, info);

	g_assert_true(purple_person_matches(person, "user1"));

	/* Contact info's have a reference on the person, so the easiest way to
	 * remove that is to remove them from the person.
	 */
	purple_person_remove_contact_info(person, info);

	g_assert_finalize_object(person);
}

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

	g_test_add_func("/person/new",
	                test_purple_person_new);
	g_test_add_func("/person/properties",
	                test_purple_person_properties);

	g_test_add_func("/person/avatar-for-display/person",
	                test_purple_person_avatar_for_display_person);
	g_test_add_func("/person/avatar-for-display/contact",
	                test_purple_person_avatar_for_display_contact);

	g_test_add_func("/person/color-for-display/person",
	                test_purple_person_color_for_display_person);
	g_test_add_func("/person/color-for-display/contact",
	                test_purple_person_color_for_display_contact);

	g_test_add_func("/person/name-for-display/person",
	                test_purple_person_name_for_display_person);
	g_test_add_func("/person/name-for-display/contact",
	                test_purple_person_name_for_display_contact);

	g_test_add_func("/person/contacts/single",
	                test_purple_person_contacts_single);
	g_test_add_func("/person/contacts/multiple",
	                test_purple_person_contacts_multiple);

	g_test_add_func("/person/priority/single",
	                test_purple_person_priority_single);
	g_test_add_func("/person/priority/multiple-with-change",
	                test_purple_person_priority_multiple_with_change);

	g_test_add_func("/person/matches/accepts_null",
	                test_purple_person_matches_accepts_null);
	g_test_add_func("/person/matches/empty_string",
	                test_purple_person_matches_empty_string);
	g_test_add_func("/person/matches/alias",
	                test_purple_person_matches_alias);
	g_test_add_func("/person/matches/contact_info",
	                test_purple_person_matches_contact_info);

	return g_test_run();
}
