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

#include <birb.h>

#include <adwaita.h>

#include "pidgin/pidginaccountmanagerrow.h"

struct _PidginAccountManagerRow {
	GtkListBoxRow parent;

	PurpleAccount *account;

	GtkSwitch *enabled;
	AdwAvatar *avatar;
	GtkLabel *name;

	GBindingGroup *bindings;
	GSignalGroup *signals;

	GtkWidget *status_label;
	char *status;
};

enum {
	PROP_0,
	PROP_ACCOUNT,
	PROP_STATUS,
	N_PROPERTIES,
};
static GParamSpec *properties[N_PROPERTIES] = {NULL, };

/******************************************************************************
 * Helpers
 *****************************************************************************/
static void
pidgin_account_manager_row_set_status(PidginAccountManagerRow *row,
                                      const char *status)
{
	g_return_if_fail(PIDGIN_IS_ACCOUNT_MANAGER_ROW(row));

	if(g_set_str(&row->status, status)) {
		g_object_notify_by_pspec(G_OBJECT(row), properties[PROP_STATUS]);
	}
}

static void
pidgin_account_manager_row_refresh_status(PidginAccountManagerRow *row,
                                          PurpleAccount *account)
{
	PurpleConnectionState state = PURPLE_CONNECTION_STATE_DISCONNECTED;
	GError *error = NULL;

	g_return_if_fail(PIDGIN_IS_ACCOUNT_MANAGER_ROW(row));

	/* If account is null clean up the status label. */
	if(!PURPLE_IS_ACCOUNT(account)) {
		pidgin_account_manager_row_set_status(row, NULL);
		gtk_widget_remove_css_class(row->status_label, "error");

		return;
	}

	/* If the account has an error use the message as the status and turn on
	 * the error css class.
	 */
	error = purple_account_get_error(account);
	if(error != NULL) {
		pidgin_account_manager_row_set_status(row, error->message);
		gtk_widget_add_css_class(row->status_label, "error");

		return;
	}

	/* At this point we won't have an error so we can remove the css class. */
	gtk_widget_remove_css_class(row->status_label, "error");

	/* If the account is disabled and doesn't have an error, set the status to
	 * disabled so it's a bit more obvious than the switch.
	 */
	if(!purple_account_get_enabled(account)) {
		pidgin_account_manager_row_set_status(row, _("Disabled"));

		return;
	}

	/* Now we're just down to the account connection state. */
	state = purple_account_get_connection_state(account);
	switch(state) {
	case PURPLE_CONNECTION_STATE_CONNECTED:
		pidgin_account_manager_row_set_status(row, _("Connected"));
		break;
	case PURPLE_CONNECTION_STATE_CONNECTING:
		pidgin_account_manager_row_set_status(row, _("Connecting..."));
		break;
	case PURPLE_CONNECTION_STATE_DISCONNECTED:
		/* If the account has an error, we let the error notify handler set
		 * the status message.
		 */
		if(purple_account_get_error(account) == NULL) {
			pidgin_account_manager_row_set_status(row, _("Disconnected"));
		}

		break;
	case PURPLE_CONNECTION_STATE_DISCONNECTING:
		pidgin_account_manager_row_set_status(row, _("Disconnecting..."));
		break;
	}
}

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
pidgin_account_manager_remove_account_cb(G_GNUC_UNUSED AdwMessageDialog *self,
                                         char *response, gpointer data)
{
	PurpleAccount *account = data;
	PurpleCore *core = NULL;
	PurpleNotificationManager *notification_manager = NULL;

	if(!birb_str_equal(response, "remove")) {
		return;
	}

	/* Remove all notifications including connection errors for the account. */
	core = purple_core_get_default();
	notification_manager = purple_core_get_notification_manager(core);
	purple_notification_manager_remove_with_account(notification_manager,
	                                                account, TRUE);

	/* Delete the account. */
	purple_account_delete(account);

	g_clear_object(&account);
}

static void
pidgin_account_manager_row_remove_cb(G_GNUC_UNUSED GtkButton *self,
                                     gpointer data)
{
	PidginAccountManagerRow *row = data;
	PurpleProtocol *protocol = NULL;
	GtkRoot *root = NULL;
	AdwAlertDialog *dialog = NULL;
	const char *name = NULL;
	const char *protocol_name = NULL;

	name = purple_account_get_name(row->account);
	protocol = purple_account_get_protocol(row->account);
	protocol_name = purple_protocol_get_name(protocol);

	root = gtk_widget_get_root(GTK_WIDGET(row));
	dialog = ADW_ALERT_DIALOG(adw_alert_dialog_new(_("Remove account?"), NULL));
	adw_alert_dialog_format_body(dialog,
	                             _("Do you want to remove the %s (%s) "
	                               "account from Pidgin?"),
	                             name, protocol_name);
	adw_alert_dialog_add_responses(dialog, "cancel", _("Cancel"),
	                               "remove", _("Remove"), NULL);
	adw_alert_dialog_set_response_appearance(dialog, "remove",
	                                         ADW_RESPONSE_DESTRUCTIVE);
	adw_alert_dialog_set_default_response(dialog, "cancel");
	adw_alert_dialog_set_close_response(dialog, "cancel");

	g_signal_connect_object(dialog, "response",
	                        G_CALLBACK(pidgin_account_manager_remove_account_cb),
	                        row->account, 0);

	adw_dialog_present(ADW_DIALOG(dialog), GTK_WIDGET(root));
}

static void
pidgin_account_manager_row_connection_state_changed_cb(GObject *self,
                                                       G_GNUC_UNUSED GParamSpec *pspec,
                                                       gpointer data)
{
	pidgin_account_manager_row_refresh_status(PIDGIN_ACCOUNT_MANAGER_ROW(data),
	                                          PURPLE_ACCOUNT(self));
}

static void
pidgin_account_manager_row_error_changed_cb(GObject *self,
                                            G_GNUC_UNUSED GParamSpec *pspec,
                                            gpointer data)
{
	pidgin_account_manager_row_refresh_status(PIDGIN_ACCOUNT_MANAGER_ROW(data),
	                                          PURPLE_ACCOUNT(self));
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
G_DEFINE_FINAL_TYPE(PidginAccountManagerRow, pidgin_account_manager_row,
                    GTK_TYPE_LIST_BOX_ROW)

static void
pidgin_account_manager_row_finalize(GObject *obj) {
	PidginAccountManagerRow *row = PIDGIN_ACCOUNT_MANAGER_ROW(obj);

	g_clear_object(&row->bindings);
	g_clear_object(&row->signals);

	g_clear_object(&row->account);
	g_clear_pointer(&row->status, g_free);

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

static void
pidgin_account_manager_row_get_property(GObject *obj, guint param_id,
                                        GValue *value, GParamSpec *pspec)
{
	PidginAccountManagerRow *row = PIDGIN_ACCOUNT_MANAGER_ROW(obj);

	switch(param_id) {
	case PROP_ACCOUNT:
		g_value_set_object(value,
		                   pidgin_account_manager_row_get_account(row));
		break;
	case PROP_STATUS:
		g_value_set_string(value,
		                   pidgin_account_manager_row_get_status(row));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
		break;
	}
}

static void
pidgin_account_manager_row_set_property(GObject *obj, guint param_id,
                                const GValue *value, GParamSpec *pspec)
{
	PidginAccountManagerRow *row = PIDGIN_ACCOUNT_MANAGER_ROW(obj);

	switch(param_id) {
		case PROP_ACCOUNT:
			pidgin_account_manager_row_set_account(row,
			                                       g_value_get_object(value));
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
			break;
	}
}

static void
pidgin_account_manager_row_init(PidginAccountManagerRow *row) {
	gtk_widget_init_template(GTK_WIDGET(row));

	row->bindings = g_binding_group_new();
	g_binding_group_bind(row->bindings, "enabled",
	                     row->enabled, "active",
	                     G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
	g_binding_group_bind(row->bindings, "connected",
	                     row->enabled, "state",
	                     G_BINDING_SYNC_CREATE);

	row->signals = g_signal_group_new(PURPLE_TYPE_ACCOUNT);
	g_signal_group_connect_object(row->signals, "notify::connection-state",
	                              G_CALLBACK(pidgin_account_manager_row_connection_state_changed_cb),
	                              row, G_CONNECT_DEFAULT);
	g_signal_group_connect_object(row->signals, "notify::error",
			                      G_CALLBACK(pidgin_account_manager_row_error_changed_cb),
			                      row, G_CONNECT_DEFAULT);
}

static void
pidgin_account_manager_row_class_init(PidginAccountManagerRowClass *klass) {
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);

	obj_class->finalize = pidgin_account_manager_row_finalize;
	obj_class->get_property = pidgin_account_manager_row_get_property;
	obj_class->set_property = pidgin_account_manager_row_set_property;

	/* properties */

	/**
	 * PidginAccountManagerRow:account:
	 *
	 * The account that this row will be representing.
	 *
	 * Since: 3.0
	 */
	properties[PROP_ACCOUNT] = g_param_spec_object(
		"account", NULL, NULL,
		PURPLE_TYPE_ACCOUNT,
		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	/**
	 * PidginAccountManagerRow:status:
	 *
	 * A status message for the account.
	 *
	 * This can be the connection state or an error.
	 *
	 * Since: 3.0
	 */
	properties[PROP_STATUS] = g_param_spec_string(
		"status", NULL, NULL,
		NULL,
		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);

	gtk_widget_class_set_template_from_resource(
	    widget_class,
	    "/im/pidgin/Pidgin3/Accounts/manager-row.ui"
	);

	gtk_widget_class_bind_template_child(widget_class, PidginAccountManagerRow,
	                                     enabled);
	gtk_widget_class_bind_template_child(widget_class, PidginAccountManagerRow,
	                                     avatar);
	gtk_widget_class_bind_template_child(widget_class, PidginAccountManagerRow,
	                                     name);
	gtk_widget_class_bind_template_child(widget_class, PidginAccountManagerRow,
	                                     status_label);

	gtk_widget_class_bind_template_callback(widget_class,
	                                        pidgin_account_manager_row_remove_cb);
}

/******************************************************************************
 * Public API
 *****************************************************************************/
PurpleAccount *
pidgin_account_manager_row_get_account(PidginAccountManagerRow *row) {
	g_return_val_if_fail(PIDGIN_IS_ACCOUNT_MANAGER_ROW(row), NULL);

	return row->account;
}

const char *
pidgin_account_manager_row_get_status(PidginAccountManagerRow *row) {
	g_return_val_if_fail(PIDGIN_IS_ACCOUNT_MANAGER_ROW(row), NULL);

	return row->status;
}

GtkWidget *
pidgin_account_manager_row_new(PurpleAccount *account) {
	return g_object_new(
		PIDGIN_TYPE_ACCOUNT_MANAGER_ROW,
		"account", account,
		NULL);
}

void
pidgin_account_manager_row_set_account(PidginAccountManagerRow *row,
                                       PurpleAccount *account)
{
	g_return_if_fail(PIDGIN_IS_ACCOUNT_MANAGER_ROW(row));

	if(g_set_object(&row->account, account)) {
		GObject *obj = G_OBJECT(row);

		g_binding_group_set_source(row->bindings, account);
		g_signal_group_set_target(row->signals, account);

		pidgin_account_manager_row_refresh_status(row, account);

		g_object_freeze_notify(obj);
		g_object_notify_by_pspec(obj, properties[PROP_ACCOUNT]);
		g_object_notify_by_pspec(obj, properties[PROP_STATUS]);
		g_object_thaw_notify(obj);
	}
}
