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

#include "purpleircv3protocolconversation.h"

#include "purpleircv3connection.h"
#include "purpleircv3core.h"

/******************************************************************************
 * PurpleProtocolConversation Implementation
 *****************************************************************************/
static void
purple_ircv3_protocol_conversation_send_message_async(PurpleProtocolConversation *protocol,
                                                      PurpleConversation *conversation,
                                                      PurpleMessage *message,
                                                      GCancellable *cancellable,
                                                      GAsyncReadyCallback callback,
                                                      gpointer data)
{
	PurpleIRCv3Connection *v3_connection = NULL;
	PurpleAccount *account = NULL;
	PurpleConnection *connection = NULL;
	PurpleConversation *status_conversation = NULL;
	IbisClient *client = NULL;
	IbisMessage *ibis_message = NULL;
	GTask *task = NULL;
	const char *contents = NULL;
	const char *id = NULL;

	account = purple_conversation_get_account(conversation);
	connection = purple_account_get_connection(account);
	v3_connection = PURPLE_IRCV3_CONNECTION(connection);

	/* We ignore non commands to the status conversation. */
	status_conversation = purple_ircv3_connection_get_status_conversation(v3_connection);
	if(conversation == status_conversation) {
		task = g_task_new(protocol, cancellable, callback, data);
		g_task_return_boolean(task, TRUE);
		g_clear_object(&task);

		return;
	}

	id = purple_conversation_get_id(conversation);
	contents = purple_message_get_contents(message);

	ibis_message = ibis_message_new(IBIS_MSG_PRIVMSG);
	if(purple_message_get_action(message)) {
		IbisCTCPMessage *ctcp_message = NULL;

		ibis_message_set_params(ibis_message, id, NULL);

		ctcp_message = ibis_ctcp_message_new(IBIS_CTCP_ACTION);
		ibis_ctcp_message_set_params(ctcp_message, contents, NULL);
		ibis_message_set_ctcp_message(ibis_message, ctcp_message);
	} else {
		ibis_message_set_params(ibis_message, id, contents, NULL);
	}

	client = purple_ircv3_connection_get_client(v3_connection);
	ibis_client_write(client, ibis_message);

	task = g_task_new(protocol, cancellable, callback, data);
	g_task_return_boolean(task, TRUE);
	g_clear_object(&task);

	/* This will be made conditional when we add echo-message support.
	 * https://ircv3.net/specs/extensions/echo-message
	 */
	purple_conversation_write_message(conversation, message);
}

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

static PurpleChannelJoinDetails *
purple_ircv3_protocol_conversation_get_channel_join_details(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
                                                            G_GNUC_UNUSED PurpleAccount *account)
{
	return purple_channel_join_details_new(0, FALSE, 0, TRUE, 0);
}

static void
purple_ircv3_protocol_conversation_join_channel_async(PurpleProtocolConversation *protocol,
                                                      PurpleAccount *account,
                                                      PurpleChannelJoinDetails* details,
                                                      GCancellable* cancellable,
                                                      GAsyncReadyCallback callback,
                                                      gpointer data)
{
	PurpleIRCv3Connection *v3_connection = NULL;
	PurpleConnection *connection = NULL;
	PurpleConversation *conversation = NULL;
	PurpleConversationManager *manager = NULL;
	IbisClient *client = NULL;
	IbisMessage *message = NULL;
	GTask *task = NULL;
	char *normalized_name = NULL;
	const char *name = NULL;
	const char *password = NULL;

	connection = purple_account_get_connection(account);
	v3_connection = PURPLE_IRCV3_CONNECTION(connection);

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

	/* Validate that the name isn't empty. */
	/* TODO: check that name match the ISUPPORT channel prefixes. */
	name = purple_channel_join_details_get_name(details);
	if(purple_strempty(name)) {
		g_task_return_new_error_literal(task, PURPLE_IRCV3_DOMAIN, 0,
		                                _("channel name is empty"));
		g_clear_object(&task);

		return;
	}

	if(strchr(name, ',') != NULL) {
		g_task_return_new_error_literal(task, PURPLE_IRCV3_DOMAIN, 0,
		                                _("only one channel may be joined at a time"));
		g_clear_object(&task);

		return;
	}

	client = purple_ircv3_connection_get_client(v3_connection);
	normalized_name = ibis_client_normalize(client, name);

	manager = purple_conversation_manager_get_default();
	conversation = purple_conversation_manager_find_with_id(manager, account,
	                                                        normalized_name);

	/* If the conversation already exists, just return TRUE. */
	if(PURPLE_IS_CONVERSATION(conversation)) {
		g_free(normalized_name);

		g_task_return_boolean(task, TRUE);
		g_clear_object(&task);

		return;
	}

	message = ibis_message_new(IBIS_MSG_JOIN);

	password = purple_channel_join_details_get_password(details);
	if(!purple_strempty(password)) {
		ibis_message_set_params(message, normalized_name, password, NULL);
	} else {
		ibis_message_set_params(message, normalized_name, NULL);
	}

	conversation = g_object_new(
		PURPLE_TYPE_CONVERSATION,
		"account", account,
		"type", PURPLE_CONVERSATION_TYPE_CHANNEL,
		"id", normalized_name,
		"title", name,
		"online", TRUE,
		NULL);
	purple_conversation_manager_add(manager, conversation);

	ibis_client_write(client, message);

	g_free(normalized_name);

	g_task_return_pointer(task, conversation, g_object_unref);
	g_clear_object(&task);
}

static PurpleConversation *
purple_ircv3_protocol_conversation_join_channel_finish(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
                                                       GAsyncResult *result,
                                                       GError **error)
{
	return g_task_propagate_pointer(G_TASK(result), error);
}

static void
purple_ircv3_protocol_conversation_leave_conversation_async(PurpleProtocolConversation *protocol,
                                                            PurpleConversation *conversation,
                                                            GCancellable *cancellable,
                                                            GAsyncReadyCallback callback,
                                                            gpointer data)
{
	GTask *task = NULL;

	if(purple_conversation_is_channel(conversation)) {
		PurpleIRCv3Connection *v3_connection = NULL;
		PurpleAccount *account = NULL;
		PurpleConnection *connection = NULL;
		IbisClient *client = NULL;
		IbisMessage *message = NULL;

		account = purple_conversation_get_account(conversation);
		connection = purple_account_get_connection(account);
		v3_connection = PURPLE_IRCV3_CONNECTION(connection);
		client = purple_ircv3_connection_get_client(v3_connection);

		message = ibis_message_new(IBIS_MSG_PART);
		ibis_message_set_params(message,
		                        purple_conversation_get_id(conversation),
		                        NULL);
		ibis_client_write(client, message);
	}

	task = g_task_new(protocol, cancellable, callback, data);
	g_task_set_source_tag(task,
	                      purple_ircv3_protocol_conversation_leave_conversation_async);
	g_task_return_boolean(task, TRUE);
	g_clear_object(&task);
}

static gboolean
purple_ircv3_protocol_conversation_leave_conversation_finish(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
                                                             GAsyncResult *result,
                                                             GError **error)
{
	g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);

	if(!g_async_result_is_tagged(result,
	                             purple_ircv3_protocol_conversation_leave_conversation_async))
	{
		g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
		                    "result has unexpected tag");
		return FALSE;
	}

	return g_task_propagate_boolean(G_TASK(result), error);
}

static void
purple_ircv3_protocol_conversation_send_typing(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
                                               PurpleConversation *conversation,
                                               PurpleTypingState state)
{
	PurpleIRCv3Connection *connection;
	PurpleAccount *account = NULL;
	PurpleConnection *purple_connection = NULL;
	PurpleConversation *status_conversation = NULL;
	IbisClient *client = NULL;
	IbisMessage *message = NULL;
	IbisTags *tags = NULL;
	const char *value = NULL;

	if(state == PURPLE_TYPING_STATE_TYPING) {
		value = IBIS_TYPING_ACTIVE;
	} else if(state == PURPLE_TYPING_STATE_PAUSED) {
		value = IBIS_TYPING_PAUSED;
	} else if(state == PURPLE_TYPING_STATE_NONE) {
		value = IBIS_TYPING_DONE;
	}

	if(value == NULL) {
		return;
	}

	account = purple_conversation_get_account(conversation);
	purple_connection = purple_account_get_connection(account);
	connection = PURPLE_IRCV3_CONNECTION(purple_connection);

	status_conversation = purple_ircv3_connection_get_status_conversation(connection);
	if(conversation == status_conversation) {
		return;
	}

	message = ibis_message_new(IBIS_MSG_TAGMSG);
	ibis_message_set_params(message, purple_conversation_get_id(conversation),
	                        NULL);

	tags = ibis_message_get_tags(message);
	ibis_tags_add(tags, IBIS_TAG_TYPING, value);

	client = purple_ircv3_connection_get_client(connection);
	ibis_client_write(client, message);
}

static void
purple_ircv3_protocol_conversation_refresh(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
                                           PurpleConversation *conversation)
{
	PurpleIRCv3Connection *connection;
	PurpleAccount *account = NULL;
	PurpleConnection *purple_connection = NULL;
	IbisClient *client = NULL;
	IbisMessage *message = NULL;

	if(purple_conversation_get_online(conversation)) {
		return;
	}

	if(!purple_conversation_is_channel(conversation)) {
		/* We need to mark DM's as online again. */
		purple_conversation_set_online(conversation, TRUE);

		return;
	}

	account = purple_conversation_get_account(conversation);
	purple_connection = purple_account_get_connection(account);
	connection = PURPLE_IRCV3_CONNECTION(purple_connection);

	message = ibis_message_new(IBIS_MSG_JOIN);
	ibis_message_set_params(message, purple_conversation_get_id(conversation),
	                        NULL);

	client = purple_ircv3_connection_get_client(connection);
	ibis_client_write(client, message);

	/* TODO: We need to validate the JOIN by handling the join from the server,
	 * but right now we just assume it was successful.
	 */
	purple_conversation_set_online(conversation, TRUE);
}

void
purple_ircv3_protocol_conversation_init(PurpleProtocolConversationInterface *iface) {
	iface->send_message_async =
		purple_ircv3_protocol_conversation_send_message_async;
	iface->send_message_finish =
		purple_ircv3_protocol_conversation_send_message_finish;

	iface->get_channel_join_details =
		purple_ircv3_protocol_conversation_get_channel_join_details;
	iface->join_channel_async =
		purple_ircv3_protocol_conversation_join_channel_async;
	iface->join_channel_finish =
		purple_ircv3_protocol_conversation_join_channel_finish;

	iface->leave_conversation_async =
		purple_ircv3_protocol_conversation_leave_conversation_async;
	iface->leave_conversation_finish =
		purple_ircv3_protocol_conversation_leave_conversation_finish;

	iface->send_typing  =
		purple_ircv3_protocol_conversation_send_typing;
	iface->refresh =
		purple_ircv3_protocol_conversation_refresh;
}
