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

#define TEST_PURPLE_ACCOUNT_ERROR (g_quark_from_static_string("test-account"))

/******************************************************************************
 * Test Protocol
 *****************************************************************************/
G_DECLARE_FINAL_TYPE(TestPurpleAccountProtocol, test_purple_account_protocol,
                     TEST_PURPLE, ACCOUNT_PROTOCOL, PurpleProtocol)

struct _TestPurpleAccountProtocol {
	PurpleProtocol parent;
};

G_DEFINE_FINAL_TYPE(TestPurpleAccountProtocol, test_purple_account_protocol,
                    PURPLE_TYPE_PROTOCOL);

static void
test_purple_account_protocol_init(G_GNUC_UNUSED TestPurpleAccountProtocol *protocol)
{
}

static void
test_purple_account_protocol_class_init(G_GNUC_UNUSED TestPurpleAccountProtocolClass *klass)
{
}

static PurpleProtocol *
test_purple_account_protocol_new(void) {
	return g_object_new(
		test_purple_account_protocol_get_type(),
		"id", "account-test",
		"name", "account-test",
		NULL);
}

/******************************************************************************
 * Tests
 *****************************************************************************/
static void
test_purple_account_new(void) {
	PurpleAccount *account = NULL;
	PurpleProtocol *protocol = NULL;

	account = purple_account_new("username", "protocol-id");
	g_assert_true(PURPLE_IS_ACCOUNT(account));

	protocol = purple_account_get_protocol(account);
	g_assert_null(protocol);

	g_assert_finalize_object(account);
}

static void
test_purple_account_new_with_protocol(void) {
	PurpleAccount *account = NULL;
	PurpleProtocol *protocol = NULL;
	PurpleProtocol *protocol1 = NULL;
	char *protocol_id = NULL;
	char *username = NULL;

	protocol = test_purple_account_protocol_new();

	account = g_object_new(
		PURPLE_TYPE_ACCOUNT,
		"protocol", protocol,
		"username", "username1",
		NULL);

	g_assert_true(PURPLE_IS_ACCOUNT(account));

	g_object_get(
		G_OBJECT(account),
		"protocol-id", &protocol_id,
		"protocol", &protocol1,
		"username", &username,
		NULL);

	g_assert_cmpstr(protocol_id, ==, "account-test");
	g_clear_pointer(&protocol_id, g_free);

	g_assert_true(protocol1 == protocol);
	g_clear_object(&protocol1);

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

	g_assert_finalize_object(account);
	g_assert_finalize_object(protocol);
}

static void
test_purple_account_properties(void) {
	PurpleAccount *account = NULL;
	PurpleConnectionState connection_state = PURPLE_CONNECTION_STATE_DISCONNECTED;
	PurpleContactInfo *contact_info = NULL;
	char *id = NULL;
	char *name = NULL;
	char *username = NULL;
	char *user_info;
	char *buddy_icon_path;
	char *protocol_id;
	gboolean connected = FALSE;
	gboolean disconnected = FALSE;
	gboolean enabled;
	gboolean remember_password;
	gboolean require_password;

	account = g_object_new(
		PURPLE_TYPE_ACCOUNT,
		"buddy-icon-path", "buddy-icon-path1",
		"enabled", TRUE,
		"id", "id1",
		"name", "Test Account 1",
		"protocol-id", "protocol-id1",
		"remember-password", TRUE,
		"require-password", TRUE,
		"user-info", "user-info1",
		"username", "username1",
		NULL);

	g_assert_true(PURPLE_IS_ACCOUNT(account));

	g_object_get(
		G_OBJECT(account),
		"buddy-icon-path", &buddy_icon_path,
		"connected", &connected,
		"connection-state", &connection_state,
		"contact-info", &contact_info,
		"disconnected", &disconnected,
		"enabled", &enabled,
		"id", &id,
		"name", &name,
		"protocol-id", &protocol_id,
		"remember-password", &remember_password,
		"require-password", &require_password,
		"user-info", &user_info,
		"username", &username,
		NULL);

	g_assert_cmpstr(buddy_icon_path, ==, "buddy-icon-path1");
	g_clear_pointer(&buddy_icon_path, g_free);

	g_assert_false(connected);

	g_assert_cmpuint(connection_state, ==, PURPLE_CONNECTION_STATE_DISCONNECTED);

	g_assert_true(PURPLE_IS_CONTACT_INFO(contact_info));
	g_clear_object(&contact_info);

	g_assert_true(disconnected);

	g_assert_true(enabled);

	g_assert_cmpstr(id, ==, "id1");
	g_clear_pointer(&id, g_free);

	g_assert_cmpstr(name, ==, "Test Account 1");
	g_clear_pointer(&name, g_free);

	g_assert_cmpstr(protocol_id, ==, "protocol-id1");
	g_clear_pointer(&protocol_id, g_free);

	g_assert_true(remember_password);

	g_assert_true(require_password);

	g_assert_cmpstr(user_info, ==, "user-info1");
	g_clear_pointer(&user_info, g_free);

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

	g_assert_finalize_object(account);
}

static void
test_purple_account_error(void) {
	PurpleAccount *account = NULL;
	GError *error = NULL;
	GError *error1 = NULL;

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

	error1 = purple_account_get_error(account);
	g_assert_no_error(error1);

	error = g_error_new_literal(PURPLE_CONNECTION_ERROR, 0, "failed");
	purple_account_set_error(account, error);

	error1 = purple_account_get_error(account);
	g_assert_error(error1, PURPLE_CONNECTION_ERROR, 0);

	/* Accounts create a PurpleNotification when their error property is set
	 * which has a reference cycle to the account. So we need to clear that
	 * error, to make sure the account gets destroyed properly.
	 */
	purple_account_set_error(account, NULL);

	error1 = purple_account_get_error(account);
	g_assert_no_error(error1);

	g_assert_finalize_object(account);
}

static void
test_purple_account_compare_not_null__null(void) {
	PurpleAccount *account = NULL;

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

	g_assert_cmpint(purple_account_compare(account, NULL), <, 0);

	g_assert_finalize_object(account);
}

static void
test_purple_account_compare_null__not_null(void) {
	PurpleAccount *account = NULL;

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

	g_assert_cmpint(purple_account_compare(NULL, account), >, 0);

	g_assert_finalize_object(account);
}

static void
test_purple_account_compare_null__null(void) {
	g_assert_cmpint(purple_account_compare(NULL, NULL), ==, 0);
}

static void
test_purple_account_compare_not_null__not_null(void) {
	PurpleAccount *account_a = NULL;
	PurpleAccount *account_b = NULL;

	account_a = g_object_new(
		PURPLE_TYPE_ACCOUNT,
		"id", "id_a",
		"username", "test_a",
		"protocol-id", "test",
		"enabled", FALSE,
		NULL);
	account_b = g_object_new(
		PURPLE_TYPE_ACCOUNT,
		"id", "id_b",
		"username", "test_b",
		"protocol-id", "test",
		"enabled", FALSE,
		NULL);

	g_assert_cmpint(purple_account_compare(account_a, account_b), <, 0);
	g_assert_cmpint(purple_account_compare(account_b, account_a), >, 0);
	g_assert_cmpint(purple_account_compare(account_a, account_a), ==, 0);

	g_assert_finalize_object(account_a);
	g_assert_finalize_object(account_b);
}

static void
test_purple_account_equal_not_null__null(void) {
	PurpleAccount *account = NULL;

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

	g_assert_false(purple_account_equal(account, NULL));

	g_assert_finalize_object(account);
}

static void
test_purple_account_equal_null__not_null(void) {
	PurpleAccount *account = NULL;

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

	g_assert_false(purple_account_equal(NULL, account));

	g_assert_finalize_object(account);
}

static void
test_purple_account_equal_null__null(void) {
	g_assert_true(purple_account_equal(NULL, NULL));
}

static void
test_purple_account_equal_not_null__not_null(void) {
	PurpleAccount *account_a = NULL;
	PurpleAccount *account_b = NULL;

	account_a = g_object_new(
		PURPLE_TYPE_ACCOUNT,
		"id", "id_a",
		"username", "test_a",
		"protocol-id", "test",
		"enabled", FALSE,
		NULL);
	account_b = g_object_new(
		PURPLE_TYPE_ACCOUNT,
		"id", "id_b",
		"username", "test_b",
		"protocol-id", "test",
		"enabled", FALSE,
		NULL);

	g_assert_false(purple_account_equal(account_a, account_b));
	g_assert_true(purple_account_equal(account_a, account_a));

	g_assert_finalize_object(account_a);
	g_assert_finalize_object(account_b);
}

/******************************************************************************
 * Disconnect tests
 *****************************************************************************/
static void
test_purple_account_disconnect_normal(void) {
	PurpleAccount *account = NULL;
	PurpleConnectionState state = PURPLE_CONNECTION_STATE_DISCONNECTED;

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

	state = purple_account_get_connection_state(account);
	g_assert_cmpuint(state, ==, PURPLE_CONNECTION_STATE_DISCONNECTED);

	/* Tell the account it's online. */
	purple_account_ready(account);

	state = purple_account_get_connection_state(account);
	g_assert_cmpuint(state, ==, PURPLE_CONNECTION_STATE_CONNECTED);

	/* Disconnect the account. */
	purple_account_disconnect(account, NULL);

	state = purple_account_get_connection_state(account);
	g_assert_cmpuint(state, ==, PURPLE_CONNECTION_STATE_DISCONNECTED);

	g_assert_finalize_object(account);
}

static void
test_purple_account_disconnect_with_error(void) {
	PurpleAccount *account = NULL;
	PurpleConnectionState state = PURPLE_CONNECTION_STATE_DISCONNECTED;
	GError *error = NULL;

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

	state = purple_account_get_connection_state(account);
	g_assert_cmpuint(state, ==, PURPLE_CONNECTION_STATE_DISCONNECTED);

	error = purple_account_get_error(account);
	g_assert_no_error(error);

	/* Tell the account it's online. */
	purple_account_ready(account);

	state = purple_account_get_connection_state(account);
	g_assert_cmpuint(state, ==, PURPLE_CONNECTION_STATE_CONNECTED);

	error = purple_account_get_error(account);
	g_assert_no_error(error);

	/* Now disconnect with a new error. */
	error = g_error_new(TEST_PURPLE_ACCOUNT_ERROR, 1337, "foo %s baz", "bar");
	purple_account_disconnect_with_error(account, NULL, error);
	error = NULL;

	state = purple_account_get_connection_state(account);
	g_assert_cmpuint(state, ==, PURPLE_CONNECTION_STATE_DISCONNECTED);

	error = purple_account_get_error(account);
	g_assert_error(error, TEST_PURPLE_ACCOUNT_ERROR, 1337);

	/* Set the error to NULL to clear the notification which is holding an
	 * additional reference to the account.
	 */
	purple_account_set_error(account, NULL);

	g_assert_finalize_object(account);
}

static void
test_purple_account_disconnect_with_new_error(void) {
	PurpleAccount *account = NULL;
	PurpleConnectionState state = PURPLE_CONNECTION_STATE_DISCONNECTED;
	GError *error = NULL;

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

	state = purple_account_get_connection_state(account);
	g_assert_cmpuint(state, ==, PURPLE_CONNECTION_STATE_DISCONNECTED);

	error = purple_account_get_error(account);
	g_assert_no_error(error);

	/* Tell the account it's online. */
	purple_account_ready(account);

	state = purple_account_get_connection_state(account);
	g_assert_cmpuint(state, ==, PURPLE_CONNECTION_STATE_CONNECTED);

	error = purple_account_get_error(account);
	g_assert_no_error(error);

	/* Now disconnect with a new error. */
	purple_account_disconnect_with_new_error(account,
	                                         NULL,
	                                         TEST_PURPLE_ACCOUNT_ERROR,
	                                         1337,
	                                         "foo %s baz",
	                                         "bar");

	state = purple_account_get_connection_state(account);
	g_assert_cmpuint(state, ==, PURPLE_CONNECTION_STATE_DISCONNECTED);

	error = purple_account_get_error(account);
	g_assert_error(error, TEST_PURPLE_ACCOUNT_ERROR, 1337);

	/* Set the error to NULL to clear the notification which is holding an
	 * additional reference to the account.
	 */
	purple_account_set_error(account, NULL);

	g_assert_finalize_object(account);
}

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

	g_test_add_func("/account/new", test_purple_account_new);
	g_test_add_func("/account/new-with-protocol",
	                test_purple_account_new_with_protocol);
	g_test_add_func("/account/properties", test_purple_account_properties);
	g_test_add_func("/account/error", test_purple_account_error);

	g_test_add_func("/account/compare/not_null__null",
	                test_purple_account_compare_not_null__null);
	g_test_add_func("/account/compare/null__not_null",
	                test_purple_account_compare_null__not_null);
	g_test_add_func("/account/compare/null__null",
	                test_purple_account_compare_null__null);
	g_test_add_func("/account/compare/not_null__not_null",
	                test_purple_account_compare_not_null__not_null);

	g_test_add_func("/account/equal/not_null__null",
	                test_purple_account_equal_not_null__null);
	g_test_add_func("/account/equal/null__not_null",
	                test_purple_account_equal_null__not_null);
	g_test_add_func("/account/equal/null__null",
	                test_purple_account_equal_null__null);
	g_test_add_func("/account/equal/not_null__not_null",
	                test_purple_account_equal_not_null__not_null);

	g_test_add_func("/account/disconnect/normal",
	                test_purple_account_disconnect_normal);
	g_test_add_func("/account/disconnect/with-error",
	                test_purple_account_disconnect_with_error);
	g_test_add_func("/account/disconnect/with-new-error",
	                test_purple_account_disconnect_with_new_error);

	return g_test_run();
}
