/*
 * 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 <purple.h>

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

	g_assert_true(PURPLE_IS_CONVERSATION_MEMBER(self));

	*counter = *counter + 1;
}

static void
test_purple_conversation_member_badges_changed_counter(PurpleConversationMember *member,
                                                       gpointer data)
{
	guint *counter = data;

	g_assert_true(PURPLE_IS_CONVERSATION_MEMBER(member));

	*counter = *counter + 1;
}

/******************************************************************************
 * Tests
 *****************************************************************************/
static void
test_purple_conversation_member_new(void) {
	PurpleContactInfo *info = NULL;
	PurpleConversationMember *member = NULL;

	info = purple_contact_info_new(NULL);
	g_assert_true(PURPLE_IS_CONTACT_INFO(info));

	member = purple_conversation_member_new(info);
	g_assert_true(PURPLE_IS_CONVERSATION_MEMBER(member));

	g_clear_object(&info);
	g_clear_object(&member);
}

static void
test_purple_conversation_member_properties(void) {
	PurpleBadges *badges = NULL;
	PurpleContactInfo *info = NULL;
	PurpleContactInfo *info1 = NULL;
	PurpleConversationMember *member = NULL;
	PurpleTags *tags = NULL;
	PurpleTypingState typing_state = PURPLE_TYPING_STATE_NONE;
	char *alias = NULL;
	char *color = NULL;
	char *name_for_display = NULL;
	char *nickname = NULL;

	info = purple_contact_info_new("abc123");

	/* 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.
	 */
	member = g_object_new(
		PURPLE_TYPE_CONVERSATION_MEMBER,
		"alias", "pidgy1",
		"color", "#55aaff",
		"contact-info", info,
		"nickname", "pidgy",
		"typing-state", PURPLE_TYPING_STATE_TYPING,
		NULL);

	/* Now use g_object_get to read all of the properties. */
	g_object_get(
		member,
		"alias", &alias,
		"badges", &badges,
		"color", &color,
		"contact-info", &info1,
		"name-for-display", &name_for_display,
		"nickname", &nickname,
		"tags", &tags,
		"typing-state", &typing_state,
		NULL);

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

	g_assert_true(PURPLE_IS_BADGES(badges));
	g_clear_object(&badges);

	g_assert_cmpstr(color, ==, "#55aaff");
	g_clear_pointer(&color, g_free);

	g_assert_true(info1 == info);
	g_clear_object(&info1);

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

	g_assert_cmpstr(nickname, ==, "pidgy");
	g_clear_pointer(&nickname, g_free);

	g_assert_true(PURPLE_IS_TAGS(tags));
	g_clear_object(&tags);

	g_assert_cmpint(typing_state, ==, PURPLE_TYPING_STATE_TYPING);

	g_assert_finalize_object(member);
	g_assert_finalize_object(info);
}

static void
test_purple_conversation_member_name_for_display(void) {
	PurpleContactInfo *info = NULL;
	PurpleConversationMember *member = NULL;
	const char *name_for_display = NULL;
	guint counter = 0;

	info = purple_contact_info_new("abc123");
	member = purple_conversation_member_new(info);
	g_signal_connect(member, "notify::name-for-display",
	                 G_CALLBACK(test_purple_conversation_member_notify_counter),
	                 &counter);

	/* Make sure our counter is still 0. */
	g_assert_cmpuint(counter, ==, 0);

	/* Make sure the default falls back to the contact info's id. */
	name_for_display = purple_conversation_member_get_name_for_display(member);
	g_assert_cmpstr(name_for_display, ==, "abc123");

	/* Set the username on the contact info and make sure it propagates up. */
	counter = 0;
	purple_contact_info_set_username(info, "tron");
	name_for_display = purple_conversation_member_get_name_for_display(member);
	g_assert_cmpstr(name_for_display, ==, "tron");
	g_assert_cmpuint(counter, ==, 1);

	/* Set the nickname on the conversation member and make sure that takes
	 * precedence.
	 */
	counter = 0;
	purple_conversation_member_set_nickname(member, "rinzler");
	name_for_display = purple_conversation_member_get_name_for_display(member);
	g_assert_cmpstr(name_for_display, ==, "rinzler");
	g_assert_cmpuint(counter, ==, 1);

	/* Set the alias on the conversation member and make sure that takes
	 * precedence.
	 */
	counter = 0;
	purple_conversation_member_set_alias(member, "Alan");
	name_for_display = purple_conversation_member_get_name_for_display(member);
	g_assert_cmpstr(name_for_display, ==, "Alan");
	g_assert_cmpuint(counter, ==, 1);

	/* Remove the alias and verify we fall back to the nickname. */
	counter = 0;
	purple_conversation_member_set_alias(member, NULL);
	name_for_display = purple_conversation_member_get_name_for_display(member);
	g_assert_cmpstr(name_for_display, ==, "rinzler");
	g_assert_cmpuint(counter, ==, 1);

	/* Remove the nickname and verify it falls back to the value from the
	 * contact info.
	 */
	counter = 0;
	purple_conversation_member_set_nickname(member, NULL);
	name_for_display = purple_conversation_member_get_name_for_display(member);
	g_assert_cmpstr(name_for_display, ==, "tron");
	g_assert_cmpuint(counter, ==, 1);

	g_assert_finalize_object(member);
	g_assert_finalize_object(info);
}

/******************************************************************************
 * Typing State Timeout
 *****************************************************************************/
static void
test_purple_conversation_manager_timeout_notify(GObject *obj,
                                                G_GNUC_UNUSED GParamSpec *pspec,
                                                gpointer data)
{
	PurpleConversationMember *member = NULL;
	PurpleTypingState state = PURPLE_TYPING_STATE_NONE;
	gboolean *done = data;
	static guint count = 0;

	g_assert_true(PURPLE_IS_CONVERSATION_MEMBER(obj));
	member = PURPLE_CONVERSATION_MEMBER(obj);

	state = purple_conversation_member_get_typing_state(member);
	if(count == 0) {
		g_assert_cmpuint(state, ==, PURPLE_TYPING_STATE_TYPING);
	} else if(count == 1) {
		g_assert_cmpuint(state, ==, PURPLE_TYPING_STATE_NONE);
	} else {
		g_assert_not_reached();
	}

	/* Increment count each time we're called. */
	count++;
	if(count >= 2) {
		*done = TRUE;
	}
}

static void
test_purple_conversation_member_typing_state_fail_safe(gpointer data) {
	gboolean *done = data;

	g_assert_not_reached();

	*done = TRUE;
}

static void
test_purple_conversation_member_typing_state_timeout(void) {
	PurpleContactInfo *info = NULL;
	PurpleConversationMember *member = NULL;
	PurpleTypingState typing_state = PURPLE_TYPING_STATE_TYPING;
	gboolean done = FALSE;

	/* Create the member and add a notify callback on the typing-state property
	 * so we can check it and exit the main loop.
	 */
	info = purple_contact_info_new(NULL);
	member = purple_conversation_member_new(info);
	g_signal_connect(member, "notify::typing-state",
	                 G_CALLBACK(test_purple_conversation_manager_timeout_notify),
	                 &done);

	/* Set the state to typing with a timeout of 1 second. */
	purple_conversation_member_set_typing_state(member,
	                                            PURPLE_TYPING_STATE_TYPING, 1);

	/* We add a fail safe to set done after 2 seconds. */
	g_timeout_add_seconds_once(2,
	                           test_purple_conversation_member_typing_state_fail_safe,
	                           &done);

	while(!done) {
		g_main_context_iteration(NULL, FALSE);
	}

	/* Verify that our state got reset back to PURPLE_TYPING_STATE_NONE. */
	typing_state = purple_conversation_member_get_typing_state(member);
	g_assert_cmpint(typing_state, ==, PURPLE_TYPING_STATE_NONE);

	/* Clean everything up. */
	g_clear_object(&info);
	g_clear_object(&member);
}

static void
test_purple_conversation_member_badges_changed_signal(void) {
	PurpleBadge *badge = NULL;
	PurpleBadges *badges = NULL;
	PurpleContactInfo *info = NULL;
	PurpleConversationMember *member = NULL;
	guint counter = 0;

	/* Create the member and connect to the badges-changed signal for verify
	 * it was emitted when expected.
	 */
	info = purple_contact_info_new(NULL);
	member = purple_conversation_member_new(info);
	g_signal_connect(member, "badges-changed",
	                 G_CALLBACK(test_purple_conversation_member_badges_changed_counter),
	                 &counter);

	badges = purple_conversation_member_get_badges(member);

	/* Add a badge and verify badges-changed only got emitted once. */
	counter = 0;
	badge = purple_badge_new("test", 0, "test-icon", "t");
	purple_badges_add_badge(badges, badge);
	g_assert_cmpuint(counter, ==, 1);
	g_clear_object(&badge);

	/* Remove the badge and verify badges-changed only got emitted once. */
	counter = 0;
	purple_badges_remove_badge(badges, "test");
	g_assert_cmpuint(counter, ==, 1);

	/* Clean everything up. */
	g_clear_object(&info);
	g_clear_object(&member);
}

/******************************************************************************
 * Matches Tests
 *****************************************************************************/
static void
test_purple_conversation_member_matches_accepts_null(void) {
	PurpleContactInfo *info = NULL;
	PurpleConversationMember *member = NULL;

	info = purple_contact_info_new(NULL);
	member = purple_conversation_member_new(info);

	g_assert_true(purple_conversation_member_matches(member, NULL));

	g_assert_finalize_object(member);
	g_assert_finalize_object(info);
}

static void
test_purple_conversation_member_matches_empty_string(void) {
	PurpleContactInfo *info = NULL;
	PurpleConversationMember *member = NULL;

	info = purple_contact_info_new(NULL);
	member = purple_conversation_member_new(info);

	g_assert_true(purple_conversation_member_matches(member, ""));

	g_assert_finalize_object(member);
	g_assert_finalize_object(info);
}

static void
test_purple_conversation_member_matches_alias(void) {
	PurpleContactInfo *info = NULL;
	PurpleConversationMember *member = NULL;

	info = purple_contact_info_new(NULL);
	member = purple_conversation_member_new(info);
	purple_conversation_member_set_alias(member, "this is the alias");

	g_assert_true(purple_conversation_member_matches(member, "the"));
	g_assert_false(purple_conversation_member_matches(member, "what"));

	g_assert_finalize_object(member);
	g_assert_finalize_object(info);
}

static void
test_purple_conversation_member_matches_nickname(void) {
	PurpleContactInfo *info = NULL;
	PurpleConversationMember *member = NULL;

	info = purple_contact_info_new(NULL);
	member = purple_conversation_member_new(info);
	purple_conversation_member_set_alias(member, "nickosaurus");

	g_assert_true(purple_conversation_member_matches(member, "nick"));
	g_assert_false(purple_conversation_member_matches(member, "dinosaur"));

	g_assert_finalize_object(member);
	g_assert_finalize_object(info);
}

static void
test_purple_conversation_member_matches_contact_info(void) {
	PurpleContactInfo *info = NULL;
	PurpleConversationMember *member = NULL;

	info = purple_contact_info_new(NULL);
	purple_contact_info_set_alias(info, "something");
	member = purple_conversation_member_new(info);

	g_assert_true(purple_conversation_member_matches(member, "some"));
	g_assert_false(purple_conversation_member_matches(member, "any"));

	g_assert_finalize_object(member);
	g_assert_finalize_object(info);
}

/******************************************************************************
 * Compare Tests
 *****************************************************************************/
static void
test_purple_conversation_member_compare_not_null__null(void) {
	PurpleContactInfo *info = NULL;
	PurpleConversationMember *member = NULL;

	info = purple_contact_info_new(NULL);
	member = purple_conversation_member_new(info);

	g_assert_cmpint(purple_conversation_member_compare(member, NULL), <, 0);

	g_assert_finalize_object(member);
	g_assert_finalize_object(info);
}

static void
test_purple_conversation_member_compare_null__not_null(void) {
	PurpleContactInfo *info = NULL;
	PurpleConversationMember *member = NULL;

	info = purple_contact_info_new(NULL);
	member = purple_conversation_member_new(info);

	g_assert_cmpint(purple_conversation_member_compare(NULL, member), >, 0);

	g_assert_finalize_object(member);
	g_assert_finalize_object(info);
}

static void
test_purple_conversation_member_compare_null__null(void) {
	g_assert_cmpint(purple_conversation_member_compare(NULL, NULL), ==, 0);
}

static void
test_purple_conversation_member_compare_same(void) {
	PurpleContactInfo *info = NULL;
	PurpleConversationMember *member = NULL;

	info = purple_contact_info_new(NULL);
	member = purple_conversation_member_new(info);

	g_assert_cmpint(purple_conversation_member_compare(member, member), ==, 0);

	g_assert_finalize_object(member);
	g_assert_finalize_object(info);
}

static void
test_purple_conversation_member_compare_nickname__nickname(void) {
	PurpleContactInfo *info1 = NULL;
	PurpleContactInfo *info2 = NULL;
	PurpleConversationMember *member1 = NULL;
	PurpleConversationMember *member2 = NULL;
	int result = 0;

	info1 = purple_contact_info_new(NULL);
	member1 = purple_conversation_member_new(info1);
	purple_conversation_member_set_nickname(member1, "aaa");

	info2 = purple_contact_info_new(NULL);
	member2 = purple_conversation_member_new(info2);
	purple_conversation_member_set_nickname(member2, "zzz");

	result = purple_conversation_member_compare(member1, member2);
	g_assert_cmpint(result, <, 0);

	result = purple_conversation_member_compare(member2, member1);
	g_assert_cmpint(result, >, 0);

	purple_conversation_member_set_nickname(member2, "aaa");
	result = purple_conversation_member_compare(member1, member2);
	g_assert_cmpint(result, ==, 0);

	g_assert_finalize_object(member1);
	g_assert_finalize_object(member2);
	g_assert_finalize_object(info1);
	g_assert_finalize_object(info2);
}

static void
test_purple_conversation_member_compare_badges__nickname(void) {
	PurpleBadge *badge = NULL;
	PurpleBadges *badges = NULL;
	PurpleContactInfo *info1 = NULL;
	PurpleContactInfo *info2 = NULL;
	PurpleConversationMember *member1 = NULL;
	PurpleConversationMember *member2 = NULL;
	gboolean removed = FALSE;
	int result = 0;

	info1 = purple_contact_info_new(NULL);
	member1 = purple_conversation_member_new(info1);
	purple_conversation_member_set_nickname(member1, "zzz");
	badges = purple_conversation_member_get_badges(member1);
	badge = purple_badge_new("id", 1000, "icon", "I");
	purple_badges_add_badge(badges, badge);
	g_clear_object(&badge);

	info2 = purple_contact_info_new(NULL);
	member2 = purple_conversation_member_new(info2);
	purple_conversation_member_set_nickname(member2, "aaa");

	result = purple_conversation_member_compare(member1, member2);
	g_assert_cmpint(result, <, 0);

	result = purple_conversation_member_compare(member2, member1);
	g_assert_cmpint(result, >, 0);

	/* Remove the badge and make sure member2 is sorted first because its
	 * nickname sorts first.
	 */
	removed = purple_badges_remove_badge(badges, "id");
	g_assert_true(removed);

	result = purple_conversation_member_compare(member1, member2);
	g_assert_cmpint(result, >, 0);

	g_assert_finalize_object(member1);
	g_assert_finalize_object(member2);
	g_assert_finalize_object(info1);
	g_assert_finalize_object(info2);
}

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

	g_test_add_func("/conversation-member/new",
	                test_purple_conversation_member_new);
	g_test_add_func("/conversation-member/properties",
	                test_purple_conversation_member_properties);
	g_test_add_func("/conversation-member/name-for-display",
	                test_purple_conversation_member_name_for_display);

	g_test_add_func("/conversation-member/typing-state/timeout",
	                test_purple_conversation_member_typing_state_timeout);

	g_test_add_func("/conversation-member/badges-changed-signal",
	                test_purple_conversation_member_badges_changed_signal);

	g_test_add_func("/conversation-member/matches/accepts_null",
	                test_purple_conversation_member_matches_accepts_null);
	g_test_add_func("/conversation-member/matches/empty_string",
	                test_purple_conversation_member_matches_empty_string);
	g_test_add_func("/conversation-member/matches/alias",
	                test_purple_conversation_member_matches_alias);
	g_test_add_func("/conversation-member/matches/nickname",
	                test_purple_conversation_member_matches_nickname);
	g_test_add_func("/conversation-member/matches/contact_info",
	                test_purple_conversation_member_matches_contact_info);

	g_test_add_func("/conversation-member/compare/not_null__null",
	                test_purple_conversation_member_compare_not_null__null);
	g_test_add_func("/conversation-member/compare/null__not_null",
	                test_purple_conversation_member_compare_null__not_null);
	g_test_add_func("/conversation-member/compare/null__null",
	                test_purple_conversation_member_compare_null__null);
	g_test_add_func("/conversation-member/compare/same",
	                test_purple_conversation_member_compare_same);
	g_test_add_func("/conversation-member/compare/nickname__nickname",
	                test_purple_conversation_member_compare_nickname__nickname);
	g_test_add_func("/conversation-member/compare/badges__nickname",
	                test_purple_conversation_member_compare_badges__nickname);

	return g_test_run();
}
