/*
 * Pidgin - Internet Messenger
 * Copyright (C) Pidgin Developers <devel@pidgin.im>
 *
 * Pidgin 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 program 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 program 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 program; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib.h>
#include <glib/gi18n.h>

#define G_SETTINGS_ENABLE_BACKEND
#include <gio/gsettingsbackend.h>

#include <birb.h>

#include <adwaita.h>

#include <purpleconfig.h>

#include "pidginui.h"

#include "gtkwhiteboard.h"
#include "pidginapplication.h"
#include "pidgincore.h"
#include "pidgindebug.h"
#include "pidginqrcodedialog.h"
#include "pidginrequestpage.h"

struct _PidginUi {
	PurpleUi parent;
};

G_DEFINE_FINAL_TYPE(PidginUi, pidgin_ui, PURPLE_TYPE_UI)

/******************************************************************************
 * Helpers
 *****************************************************************************/
static void
pidgin_ui_add_protocol_theme_paths(PurpleProtocol *protocol) {
	GdkDisplay *display = NULL;
	GtkIconTheme *theme = NULL;
	const char *path = NULL;

	display = gdk_display_get_default();

	theme = gtk_icon_theme_get_for_display(display);

	path = purple_protocol_get_icon_search_path(protocol);
	if(path != NULL) {
		gtk_icon_theme_add_search_path(theme, path);
	}

	path = purple_protocol_get_icon_resource_path(protocol);
	if(path != NULL) {
		gtk_icon_theme_add_resource_path(theme, path);
	}
}

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
pidgin_ui_protocol_added_cb(G_GNUC_UNUSED PurpleProtocolManager *manager,
                            PurpleProtocol *protocol)
{
	pidgin_ui_add_protocol_theme_paths(protocol);
}

/******************************************************************************
 * PurpleUi Implementation
 *****************************************************************************/
static gboolean
pidgin_ui_start(G_GNUC_UNUSED PurpleUi *ui, G_GNUC_UNUSED GError **error) {
	PurpleCore *core = NULL;
	PurpleProtocolManager *protocol_manager = NULL;
	AdwStyleManager *style_manager = NULL;
	GdkDisplay *display = NULL;
	GtkIconTheme *theme = NULL;
	GListModel *protocol_model = NULL;
	GSettings *settings = NULL;
	char *path;

	pidgin_debug_init();

	core = purple_core_get_default();
	settings = purple_core_new_settings(core, "im.pidgin.Pidgin3.Appearance");
	style_manager = adw_style_manager_get_default();
	g_settings_bind(settings, "color-scheme", style_manager, "color-scheme",
	                G_SETTINGS_BIND_GET);
	g_clear_object(&settings);

	display = gdk_display_get_default();
	theme = gtk_icon_theme_get_for_display(display);

	path = g_build_filename(PURPLE_DATADIR, "pidgin", "icons", NULL);
	gtk_icon_theme_add_search_path(theme, path);
	g_free(path);

	/* Add a callback for when a protocol is registered to add its icon paths
	 * if it was found after initial startup.
	 */
	protocol_manager = purple_core_get_protocol_manager(core);
	protocol_model = G_LIST_MODEL(protocol_manager);
	g_signal_connect(protocol_manager, "added",
	                 G_CALLBACK(pidgin_ui_protocol_added_cb), NULL);

	/* Add the icon paths for all the protocols that libpurple found during
	 * start up.
	 */
	for(guint i = 0; i < g_list_model_get_n_items(protocol_model); i++) {
		PurpleProtocol *protocol = NULL;

		protocol = g_list_model_get_item(protocol_model, i);
		pidgin_ui_add_protocol_theme_paths(protocol);
		g_clear_object(&protocol);
	}

	/* Set the UI operation structures. */
	purple_whiteboard_set_ui_ops(pidgin_whiteboard_get_ui_ops());

	return TRUE;
}

static void
pidgin_ui_stop(G_GNUC_UNUSED PurpleUi *ui) {
	/* Be sure to close all windows that are not attached to anything
	 * (e.g., the debug window), or they may access things after they are
	 * shut down. */
	pidgin_debug_window_hide();
	pidgin_debug_uninit();

	/* and end it all... */
	g_application_quit(g_application_get_default());
}

static PurpleContactManagerBackend *
pidgin_ui_get_contact_manager_backend(G_GNUC_UNUSED PurpleUi *ui,
                                      GError **error)
{
	PurpleContactManagerBackend *backend = NULL;
	char *filename = NULL;

	filename = g_build_filename(purple_config_dir(), "contacts.db", NULL);
	backend = purple_contact_manager_seagull_backend_new(filename, error);
	g_free(filename);

	return backend;
}

static PurpleConversationManagerBackend *
pidgin_ui_get_conversation_manager_backend(G_GNUC_UNUSED PurpleUi *ui,
                                           GError **error)
{
	PurpleConversationManagerBackend *backend = NULL;
	char *filename = NULL;

	filename = g_build_filename(purple_config_dir(), "conversations.db", NULL);
	backend = purple_conversation_manager_seagull_backend_new(filename, error);
	g_free(filename);

	return backend;
}

static gpointer
pidgin_ui_get_settings_backend(G_GNUC_UNUSED PurpleUi *ui) {
	GSettingsBackend *backend = NULL;
	char *config = NULL;

	config = g_build_filename(purple_config_dir(), "pidgin3.ini", NULL);
	backend = g_keyfile_settings_backend_new(config, "/", NULL);

	g_free(config);

	return backend;
}

static PurplePresenceManagerBackend *
pidgin_ui_get_presence_manager_backend(G_GNUC_UNUSED PurpleUi *ui,
                                       GError **error)
{
	PurplePresenceManagerBackend *backend = NULL;
	char *path = NULL;

	path = g_build_filename(purple_config_dir(), "presences.db", NULL);
	backend = purple_presence_manager_seagull_backend_new(path, error);
	g_free(path);

	return backend;
}

static PurpleAccountManagerBackend *
pidgin_ui_get_account_manager_backend(G_GNUC_UNUSED PurpleUi *ui,
                                      GError **error)
{
	PurpleAccountManagerBackend *backend = NULL;
	char *path = NULL;

	g_mkdir_with_parents(purple_config_dir(), 0700);

	path = g_build_filename(purple_config_dir(), "accounts.db", NULL);
	backend = purple_account_manager_seagull_backend_new(path, error);
	g_free(path);

	return backend;
}

/******************************************************************************
 * OpenURI Implementation
 *****************************************************************************/
static void
pidgin_ui_open_uri_cb(GObject *source, GAsyncResult *result, gpointer data) {
	GError *error = NULL;
	GTask *task = data;
	gboolean success = FALSE;

	success = gtk_uri_launcher_launch_finish(GTK_URI_LAUNCHER(source), result,
	                                         &error);

	if(!success) {
		g_task_return_error(task, error);
	} else {
		g_task_return_boolean(task, success);
	}

	g_clear_object(&source);
	g_clear_object(&task);
}

static void
pidgin_ui_open_uri(PurpleUi *ui, const char *uri, GCancellable *cancellable,
                   GAsyncReadyCallback callback, gpointer data)
{
	GTask *task = NULL;
	GtkUriLauncher *launcher = NULL;

	if(birb_str_is_empty(uri)) {
		g_task_report_new_error(ui, callback, data, pidgin_ui_open_uri,
		                        PIDGIN_UI_ERROR, 0, _("No URI provided"));

		return;
	}

	task = g_task_new(ui, cancellable, callback, data);
	g_task_set_source_tag(task, pidgin_ui_open_uri);

	launcher = gtk_uri_launcher_new(uri);

	gtk_uri_launcher_launch(launcher, NULL, cancellable, pidgin_ui_open_uri_cb,
	                        task);
}

static gboolean
pidgin_ui_open_uri_finish(PurpleUi *ui, GAsyncResult *result, GError **error) {
	g_return_val_if_fail(PIDGIN_IS_UI(ui), FALSE);

	g_return_val_if_fail(g_async_result_is_tagged(result, pidgin_ui_open_uri),
	                     FALSE);

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

/******************************************************************************
 * Present QR Code Implementation
 *****************************************************************************/
static void
pidgin_ui_present_qr_code_cancelled_cb(G_GNUC_UNUSED GCancellable *cancellable,
                                       gpointer data)
{
	gtk_window_close(data);
}

static void
pidgin_ui_presence_qr_code_destroy_cb(G_GNUC_UNUSED GtkWidget *self,
                                      gpointer data)
{
	GCancellable *cancellable = data;

	if(!g_cancellable_is_cancelled(cancellable)) {
		g_cancellable_cancel(cancellable);
	}
}

static gboolean
pidgin_ui_present_qr_code(G_GNUC_UNUSED PurpleUi *ui,
                          PurpleQrCode *qr_code,
                          GCancellable *cancellable,
                          GError **error)
{
	GtkWidget *dialog = NULL;
	GApplication *application = NULL;

	application = g_application_get_default();
	if(!PIDGIN_IS_APPLICATION(application)) {
		g_set_error(error, PIDGIN_UI_ERROR, 0, "application is not pidgin?");

		return FALSE;
	}

	dialog = pidgin_qr_code_dialog_new(qr_code);
	if(G_IS_CANCELLABLE(cancellable)) {
		g_cancellable_connect(cancellable,
		                      G_CALLBACK(pidgin_ui_present_qr_code_cancelled_cb),
		                      dialog,
		                      NULL);
		g_signal_connect_object(dialog,
		                        "destroy",
		                        G_CALLBACK(pidgin_ui_presence_qr_code_destroy_cb),
		                        cancellable,
		                        G_CONNECT_DEFAULT);
	}
	pidgin_application_present_transient_window(PIDGIN_APPLICATION(application),
	                                            GTK_WINDOW(dialog));

	return TRUE;
}

/******************************************************************************
 * RequestPage Implementation
 *****************************************************************************/
static void
pidgin_ui_request_page_accepted_cb(PidginRequestPage *pidgin_request_page,
                                   gpointer data)
{
	PurpleRequestPage *purple_request_page = NULL;
	GTask *task = data;

	purple_request_page = pidgin_request_page_get_request_page(pidgin_request_page);

	g_task_return_pointer(task, g_object_ref(purple_request_page),
	                      g_object_unref);

	gtk_window_close(GTK_WINDOW(pidgin_request_page));

	g_clear_object(&task);
}

static void
pidgin_ui_request_page_cancelled_cb(PidginRequestPage *request_page,
                                    gpointer data)
{
	GTask *task = data;

	/* This will get called when ever the window is closed including when the
	 * accept signal is emitted. So we need to check if the task is already
	 * completed before completing it.
	 *
	 * We don't do a global unref because we only own a reference when we are
	 * completing the task for the first time as the other references are from
	 * g_signal_connect_object and not managed by us.
	 */
	if(!g_task_get_completed(task)) {
		g_task_return_new_error(task, PIDGIN_UI_ERROR, 0,
		                        "user closed the window");

		gtk_window_close(GTK_WINDOW(request_page));

		g_clear_object(&task);
	}
}


static void
pidgin_ui_request_page_async(PurpleUi *ui, PurpleRequestPage *request_page,
                             GCancellable *cancellable,
                             GAsyncReadyCallback callback,
                             gpointer data)
{
	GtkWidget *widget = NULL;
	GApplication *application = NULL;
	GTask *task = NULL;

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

	application = g_application_get_default();

	widget = pidgin_request_page_new(GTK_APPLICATION(application),
	                                 request_page);
	g_signal_connect_object(widget, "accepted",
	                        G_CALLBACK(pidgin_ui_request_page_accepted_cb),
	                        task, G_CONNECT_DEFAULT);
	g_signal_connect_object(widget, "cancelled",
	                        G_CALLBACK(pidgin_ui_request_page_cancelled_cb),
	                        task, G_CONNECT_DEFAULT);
	gtk_window_present(GTK_WINDOW(widget));
}

static PurpleRequestPage *
pidgin_ui_request_page_finish(PurpleUi *ui, GAsyncResult *result,
                              GError **error)
{
	g_return_val_if_fail(PIDGIN_IS_UI(ui), NULL);
	g_return_val_if_fail(g_task_is_valid(result, ui), NULL);

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

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
static void
pidgin_ui_init(G_GNUC_UNUSED PidginUi *ui) {
}

static void
pidgin_ui_class_init(PidginUiClass *klass) {
	PurpleUiClass *ui_class = PURPLE_UI_CLASS(klass);

	ui_class->start = pidgin_ui_start;
	ui_class->stop = pidgin_ui_stop;

	ui_class->get_settings_backend = pidgin_ui_get_settings_backend;

	ui_class->get_account_manager_backend =
		pidgin_ui_get_account_manager_backend;
	ui_class->get_contact_manager_backend =
		pidgin_ui_get_contact_manager_backend;
	ui_class->get_conversation_manager_backend =
		pidgin_ui_get_conversation_manager_backend;
	ui_class->get_presence_manager_backend =
		pidgin_ui_get_presence_manager_backend;

	ui_class->open_uri = pidgin_ui_open_uri;
	ui_class->open_uri_finish = pidgin_ui_open_uri_finish;

	ui_class->present_qr_code = pidgin_ui_present_qr_code;

	ui_class->request_page_async = pidgin_ui_request_page_async;
	ui_class->request_page_finish = pidgin_ui_request_page_finish;
}

/******************************************************************************
 * Public UI
 *****************************************************************************/
PurpleUi *
pidgin_ui_new(void) {
	return g_object_new(
		PIDGIN_TYPE_UI,
		"id", "pidgin3",
		"name", PIDGIN_NAME,
		"version", VERSION,
		"website", "https://pidgin.im",
		"support-website", "https://pidgin.im/contact/",
		"client-type", "pc",
		NULL);
}
