/*
 * Purple - Internet Messaging Library
 * Copyright (C) Pidgin Developers <devel@pidgin.im>
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib/gi18n-lib.h>

#include "purpleircv3protocol.h"

#include "purpleircv3commands.h"
#include "purpleircv3connection.h"
#include "purpleircv3core.h"
#include "purpleircv3protocolconversation.h"

struct _PurpleIRCv3Protocol {
	PurpleProtocol parent;
};

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
purple_ircv3_protocol_can_reach_cb(GObject *self, GAsyncResult *result,
                                   gpointer data)
{
	GError *error = NULL;
	GTask *task = data;
	gboolean can_reach = FALSE;

	/* task and result share a cancellable, so either can be used here. */
	if(g_task_return_error_if_cancelled(task)) {
		g_clear_object(&task);

		return;
	}

	can_reach = g_network_monitor_can_reach_finish(G_NETWORK_MONITOR(self), result,
	                                               &error);

	if(error != NULL) {
		g_task_return_error(task, error);
	} else if(!can_reach) {
		g_task_return_new_error(task, PURPLE_IRCV3_DOMAIN, 0,
		                        _("Unknown network error."));
	} else {
		g_task_return_boolean(task, TRUE);
	}

	g_clear_object(&task);
}

/******************************************************************************
 * PurpleProtocol Implementation
 *****************************************************************************/
static GList *
purple_ircv3_protocol_get_user_splits(G_GNUC_UNUSED PurpleProtocol *protocol) {
	PurpleAccountUserSplit *split = NULL;
	GList *splits = NULL;

	split = purple_account_user_split_new(_("Server"),
	                                      PURPLE_IRCV3_DEFAULT_SERVER,
	                                      '@');
	splits = g_list_append(splits, split);

	return splits;
}

static GList *
purple_ircv3_protocol_get_account_options(G_GNUC_UNUSED PurpleProtocol *protocol)
{
	PurpleAccountOption *option;
	GList *options = NULL;

	option = purple_account_option_int_new(_("Port"), "port",
	                                       PURPLE_IRCV3_DEFAULT_TLS_PORT);
	options = g_list_append(options, option);

	option = purple_account_option_bool_new(_("Use TLS"), "use-tls", TRUE);
	options = g_list_append(options, option);

	option = purple_account_option_string_new(_("Server password"),
	                                          "server-password", "");
	purple_account_option_string_set_masked(option, TRUE);
	options = g_list_append(options, option);

	option = purple_account_option_string_new(_("Ident name"), "ident", "");
	options = g_list_append(options, option);

	option = purple_account_option_string_new(_("Real name"), "real-name", "");
	options = g_list_append(options, option);

	option = purple_account_option_string_new(_("SASL login name"),
	                                          "sasl-login-name", "");
	options = g_list_append(options, option);

	option = purple_account_option_string_new(_("SASL mechanisms"),
	                                          "sasl-mechanisms", "");
	options = g_list_append(options, option);

	option = purple_account_option_bool_new(_("Allow plaintext SASL auth over "
	                                          "unencrypted connection"),
	                                        "plain-sasl-in-clear", FALSE);
	options = g_list_append(options, option);

	option = purple_account_option_int_new(_("Seconds between sending "
	                                         "messages"),
	                                       "rate-limit-interval", 2);
	options = g_list_append(options, option);

	option = purple_account_option_int_new(_("Maximum messages to send at "
	                                         "once"),
	                                       "rate-limit-burst", 5);
	options = g_list_append(options, option);

	return options;
}

static PurpleConnection *
purple_ircv3_protocol_create_connection(PurpleProtocol *protocol,
                                        PurpleAccount *account,
                                        const char *password,
                                        GError **error)
{
	const char *username = NULL;

	g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), NULL);
	g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);

	/* Make sure the username (which includes the servername via usersplits),
	 * does not contain any whitespace.
	 */
	username = purple_account_get_username(account);
	if(strpbrk(username, " \t\v\r\n") != NULL) {
		g_set_error(error,
		            PURPLE_CONNECTION_ERROR,
		            PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
		            _("IRC nick and server may not contain whitespace"));

		return NULL;
	}

	return g_object_new(
		PURPLE_IRCV3_TYPE_CONNECTION,
		"protocol", protocol,
		"account", account,
		"password", password,
		NULL);
}

static void
purple_ircv3_protocol_can_connect_async(PurpleProtocol *protocol,
                                        PurpleAccount *account,
                                        GCancellable *cancellable,
                                        GAsyncReadyCallback callback,
                                        gpointer data)
{
	GNetworkMonitor *monitor = NULL;
	GSocketConnectable *connectable = NULL;
	GStrv parts = NULL;
	GTask *task = NULL;
	const char *username = NULL;
	int port = 0;

	task = g_task_new(protocol, cancellable, callback, data);

	monitor = g_network_monitor_get_default();

	username = purple_account_get_username(account);
	parts = g_strsplit(username, "@", 2);
	port = purple_account_get_int(account, "port",
	                              PURPLE_IRCV3_DEFAULT_TLS_PORT);

	connectable = g_network_address_new(parts[1], (guint16)port);
	g_strfreev(parts);

	g_network_monitor_can_reach_async(monitor, connectable, cancellable,
	                                  purple_ircv3_protocol_can_reach_cb,
	                                  task);
	g_clear_object(&connectable);
}

static gboolean
purple_ircv3_protocol_can_connect_finish(G_GNUC_UNUSED PurpleProtocol *protocol,
                                         GAsyncResult *result,
                                         GError **error)
{
	return g_task_propagate_boolean(G_TASK(result), error);
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
G_DEFINE_DYNAMIC_TYPE_EXTENDED(
	PurpleIRCv3Protocol,
	purple_ircv3_protocol,
	PURPLE_TYPE_PROTOCOL,
	G_TYPE_FLAG_FINAL,
	G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CONVERSATION,
	                              purple_ircv3_protocol_conversation_init))

static void
purple_ircv3_protocol_finalize(GObject *obj) {
	PurpleCommandManager *manager = NULL;

	manager = purple_command_manager_get_default();
	purple_command_manager_remove_all_with_source(manager,
	                                              PURPLE_IRCV3_PROTOCOL_ID);

	G_OBJECT_CLASS(purple_ircv3_protocol_parent_class)->finalize(obj);
}

static void
purple_ircv3_protocol_init(PurpleIRCv3Protocol *protocol) {
	PurpleCommand *command = NULL;
	PurpleCommandManager *manager = NULL;
	PurpleTags *tags = NULL;

	manager = purple_command_manager_get_default();

	command = purple_command_new("query", "IRCv3", 1000);
	purple_command_set_summary(command,
	                           _("Open a conversation with the given nick"));
	tags = purple_command_get_tags(command);
	purple_tags_add(tags, "protocol-id:" PURPLE_IRCV3_PROTOCOL_ID);
	g_signal_connect_object(command, "executed",
	                        G_CALLBACK(purple_ircv3_command_query_cb),
	                        protocol, G_CONNECT_DEFAULT);
	purple_command_manager_add(manager, command);

	command = purple_command_new("quote", "IRCv3", 1000);
	purple_command_set_summary(command,
	                           _("Sends the command directly to the server"));
	tags = purple_command_get_tags(command);
	purple_tags_add(tags, "protocol-id:" PURPLE_IRCV3_PROTOCOL_ID);
	g_signal_connect_object(command, "executed",
	                        G_CALLBACK(purple_ircv3_command_quote_cb),
	                        protocol, G_CONNECT_DEFAULT);
	purple_command_manager_add(manager, command);
}

static void
purple_ircv3_protocol_class_finalize(G_GNUC_UNUSED PurpleIRCv3ProtocolClass *klass) {
}

static void
purple_ircv3_protocol_class_init(PurpleIRCv3ProtocolClass *klass) {
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
	PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass);

	obj_class->finalize = purple_ircv3_protocol_finalize;

	protocol_class->get_user_splits = purple_ircv3_protocol_get_user_splits;
	protocol_class->get_account_options =
		purple_ircv3_protocol_get_account_options;
	protocol_class->create_connection =
		purple_ircv3_protocol_create_connection;
	protocol_class->can_connect_async =
		purple_ircv3_protocol_can_connect_async;
	protocol_class->can_connect_finish =
		purple_ircv3_protocol_can_connect_finish;
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
void
purple_ircv3_protocol_register(GPluginNativePlugin *plugin) {
	purple_ircv3_protocol_register_type(G_TYPE_MODULE(plugin));
}

PurpleProtocol *
purple_ircv3_protocol_new(void) {
	return g_object_new(
		PURPLE_IRCV3_TYPE_PROTOCOL,
		"id", PURPLE_IRCV3_PROTOCOL_ID,
		"name", "IRCv3",
		"description", _("Version 3 of Internet Relay Chat (IRC)."),
		"icon-name", PURPLE_IRCV3_ICON_NAME,
		"icon-resource-path", "/im/pidgin/libpurple/protocols/ircv3/icons",
		"options", OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL |
		           OPT_PROTO_SLASH_COMMANDS_NATIVE,
		NULL);
}
