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

#include <glib/gi18n-lib.h>

#include <json-glib/json-glib.h>

#include <adwaita.h>

#include "pidginabout.h"

#ifdef HAVE_MESON_CONFIG
#include "meson-config.h"
#endif

#include "package_revision.h"
#include "pidgincore.h"
#include "pidginresources.h"

struct _PidginAboutDialog {
	AdwWindow parent;

	GtkWidget *application_name;

	AdwPreferencesPage *developers_page;

	AdwPreferencesPage *translators_page;

	AdwPreferencesGroup *build_info_group;
	AdwPreferencesGroup *runtime_info_group;
	AdwPreferencesGroup *gtk_settings_group;
	AdwPreferencesGroup *plugin_search_paths_group;
	AdwPreferencesGroup *conf_path_info_group;
	AdwPreferencesGroup *build_args_group;
};

/******************************************************************************
 * Helpers
 *****************************************************************************/
static void
pidgin_about_dialog_load_application_name(PidginAboutDialog *about) {
	gchar *label = g_strdup_printf("%s %s", PIDGIN_NAME, VERSION);

	gtk_label_set_text(GTK_LABEL(about->application_name), label);

	g_free(label);
}

static void
pidgin_about_dialog_group_add_row(AdwPreferencesGroup *group,
                                  const char *title, const char *value)
{
	GtkWidget *row = adw_action_row_new();

	adw_preferences_row_set_title(ADW_PREFERENCES_ROW(row), title);

	if(value != NULL) {
		adw_action_row_set_subtitle(ADW_ACTION_ROW(row), value);
	}

	adw_preferences_group_add(group, row);
}

static void
pidgin_about_dialog_load_json(AdwPreferencesPage *page,
                              const char *json_section)
{
	GInputStream *istream = NULL;
	GList *l = NULL, *sections = NULL;
	GError *error = NULL;
	JsonParser *parser = NULL;
	JsonNode *root_node = NULL;
	JsonObject *root_object = NULL;
	JsonArray *sections_array = NULL;

	/* get a stream to the credits resource */
	istream = g_resource_open_stream(pidgin_get_resource(),
	                                 "/im/pidgin/Pidgin3/About/credits.json",
	                                 G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);

	/* create our parser */
	parser = json_parser_new();

	if(!json_parser_load_from_stream(parser, istream, NULL, &error)) {
		g_critical("%s", error->message);
		g_clear_error(&error);
		return;
	}

	root_node = json_parser_get_root(parser);
	root_object = json_node_get_object(root_node);

	sections_array = json_object_get_array_member(root_object, json_section);
	sections = json_array_get_elements(sections_array);

	for(l = sections; l; l = l->next) {
		JsonObject *section = json_node_get_object(l->data);
		JsonArray *people = NULL;
		const gchar *title = NULL;
		AdwPreferencesGroup *group = NULL;
		guint n_people = 0;

		title = json_object_get_string_member(section, "title");
		group = ADW_PREFERENCES_GROUP(adw_preferences_group_new());
		adw_preferences_group_set_title(group, title);
		adw_preferences_page_add(page, group);

		people = json_object_get_array_member(section, "people");
		n_people = json_array_get_length(people);

		for(guint idx = 0; idx < n_people; idx++) {
			const char *name = json_array_get_string_element(people, idx);
			pidgin_about_dialog_group_add_row(group, name, NULL);
		}
	}

	g_list_free(sections);

	/* clean up */
	g_object_unref(parser);

	g_input_stream_close(istream, NULL, NULL);
}

static void
pidgin_about_dialog_load_developers(PidginAboutDialog *about) {
	pidgin_about_dialog_load_json(about->developers_page, "developers");
}

static void
pidgin_about_dialog_load_translators(PidginAboutDialog *about) {
	pidgin_about_dialog_load_json(about->translators_page, "languages");
}

static void
pidgin_about_dialog_build_info_add_version(AdwPreferencesGroup *group,
                                           const gchar *title,
                                           guint major,
                                           guint minor,
                                           guint micro)
{
	gchar *version = g_strdup_printf("%u.%u.%u", major, minor, micro);

	pidgin_about_dialog_group_add_row(group, title, version);

	g_free(version);
}

static void
pidgin_about_dialog_load_build_info(PidginAboutDialog *about) {
	/* add the cpu */
	pidgin_about_dialog_group_add_row(about->build_info_group,
	                                  _("CPU"),
	                                  PURPLE_BUILD_CPU);

	/* add the architecture */
	pidgin_about_dialog_group_add_row(about->build_info_group,
	                                  _("Architecture"),
	                                  PURPLE_BUILD_ARCHITECTURE);

	/* add the endianness */
	pidgin_about_dialog_group_add_row(about->build_info_group,
	                                  _("Endianness"),
	                                  PURPLE_BUILD_ENDIANNESS);

	/* add the commit hash */
	pidgin_about_dialog_group_add_row(about->build_info_group,
	                                  _("Commit Hash"), REVISION);

	/* add the purple version */
	pidgin_about_dialog_build_info_add_version(about->build_info_group,
	                                           _("Purple Version"),
	                                           PURPLE_MAJOR_VERSION,
	                                           PURPLE_MINOR_VERSION,
	                                           PURPLE_MICRO_VERSION);

	/* add the glib version */
	pidgin_about_dialog_build_info_add_version(about->build_info_group,
	                                           _("GLib Version"),
	                                           GLIB_MAJOR_VERSION,
	                                           GLIB_MINOR_VERSION,
	                                           GLIB_MICRO_VERSION);

	/* add the gtk version */
	pidgin_about_dialog_build_info_add_version(about->build_info_group,
	                                           _("GTK Version"),
	                                           GTK_MAJOR_VERSION,
	                                           GTK_MINOR_VERSION,
	                                           GTK_MICRO_VERSION);

	/* add the adwaita version */
	pidgin_about_dialog_build_info_add_version(about->build_info_group,
	                                           _("Adwaita Version"),
	                                           ADW_MAJOR_VERSION,
	                                           ADW_MINOR_VERSION,
	                                           ADW_MICRO_VERSION);
}

static char *
pidgin_about_dialog_copy_build_info(void) {
	char *info = NULL;

	info = g_strdup_printf(
		"Build Information\n"
		"=================\n"
		"CPU: %s\n"
		"Architecture: %s\n"
		"Endianness: %s\n"
		"Commit Hash: %s\n"
		"Purple Version: %u.%u.%u\n"
		"GLib Version: %u.%u.%u\n"
		"GTK Version: %u.%u.%u\n"
		"Adwaita Version: %u.%u.%u\n",
		PURPLE_BUILD_CPU, PURPLE_BUILD_ARCHITECTURE, PURPLE_BUILD_ENDIANNESS,
		REVISION,
		PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_MICRO_VERSION,
		GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION,
		GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
		ADW_MAJOR_VERSION, ADW_MINOR_VERSION, ADW_MICRO_VERSION);

	return info;
}

static void
pidgin_about_dialog_load_runtime_info(PidginAboutDialog *about) {
	char *value = NULL;

	/* add the operating system */
	value = g_get_os_info(G_OS_INFO_KEY_PRETTY_NAME);
	pidgin_about_dialog_group_add_row(about->runtime_info_group,
	                                  _("Operating System"),
	                                  value);
	g_clear_pointer(&value, g_free);

	/* add the purple version */
	pidgin_about_dialog_build_info_add_version(about->runtime_info_group,
	                                           _("Purple Version"),
	                                           purple_major_version,
	                                           purple_minor_version,
	                                           purple_micro_version);

	/* add the glib version */
	pidgin_about_dialog_build_info_add_version(about->runtime_info_group,
	                                           _("GLib Version"),
	                                           glib_major_version,
	                                           glib_minor_version,
	                                           glib_micro_version);

	/* add the gtk version */
	pidgin_about_dialog_build_info_add_version(about->runtime_info_group,
	                                           _("GTK Version"),
	                                           gtk_get_major_version(),
	                                           gtk_get_minor_version(),
	                                           gtk_get_micro_version());

	/* add the adwaita version */
	pidgin_about_dialog_build_info_add_version(about->runtime_info_group,
	                                           _("Adwaita Version"),
	                                           adw_get_major_version(),
	                                           adw_get_minor_version(),
	                                           adw_get_micro_version());
}

static char *
pidgin_about_dialog_copy_runtime_info(void) {
	char *info = NULL;
	char *os_name = NULL;

	os_name = g_get_os_info(G_OS_INFO_KEY_PRETTY_NAME);
	info = g_strdup_printf(
		"Runtime Information\n"
		"===================\n"
		"Operating System: %s\n"
		"Purple Version: %u.%u.%u\n"
		"GLib Version: %u.%u.%u\n"
		"GTK Version: %u.%u.%u\n"
		"Adwaita Version: %u.%u.%u\n",
		os_name,
		purple_major_version, purple_minor_version, purple_micro_version,
		glib_major_version, glib_minor_version, glib_micro_version,
		gtk_get_major_version(), gtk_get_minor_version(), gtk_get_micro_version(),
		adw_get_major_version(), adw_get_minor_version(), adw_get_micro_version());

	g_free(os_name);

	return info;
}

static void
pidgin_about_dialog_load_gtk_settings(PidginAboutDialog *about) {
	gchar *cursor_theme_name = NULL, *theme_name = NULL;
	gchar *icon_theme_name = NULL;
	gchar *im_module = NULL;
	gchar *sound_theme_name = NULL;
	gboolean enable_animations = FALSE;
	gboolean shell_shows_app_menu = FALSE, shell_shows_menubar = FALSE;

	/* get the settings we're interested in */
	g_object_get(
		gtk_settings_get_default(),
		"gtk-cursor-theme-name", &cursor_theme_name,
		"gtk-enable-animations", &enable_animations,
		"gtk-icon-theme-name", &icon_theme_name,
		"gtk-im-module", &im_module,
		"gtk-shell-shows-app-menu", &shell_shows_app_menu,
		"gtk-shell-shows-menubar", &shell_shows_menubar,
		"gtk-sound-theme-name", &sound_theme_name,
		"gtk-theme-name", &theme_name,
		NULL);

	pidgin_about_dialog_group_add_row(
		about->gtk_settings_group,
		"gtk-cursor-theme-name",
		(cursor_theme_name != NULL) ? cursor_theme_name : _("(not set)"));

	pidgin_about_dialog_group_add_row(
		about->gtk_settings_group,
		"gtk-enable-animations",
		enable_animations ? _("yes") : _("no"));

	pidgin_about_dialog_group_add_row(
		about->gtk_settings_group,
		"gtk-icon-theme-name",
		(icon_theme_name != NULL) ? icon_theme_name : _("(not set)"));

	pidgin_about_dialog_group_add_row(
		about->gtk_settings_group,
		"gtk-im-module",
		(im_module != NULL) ? im_module : _("(not set)"));

	pidgin_about_dialog_group_add_row(
		about->gtk_settings_group,
		"gtk-shell-shows-app-menu",
		shell_shows_app_menu ? _("yes") : _("no"));

	pidgin_about_dialog_group_add_row(
		about->gtk_settings_group,
		"gtk-shell-shows-menubar",
		shell_shows_menubar ? _("yes") : _("no"));

	pidgin_about_dialog_group_add_row(
		about->gtk_settings_group,
		"gtk-sound-theme-name",
		(sound_theme_name != NULL) ? sound_theme_name : _("(not set)"));

	pidgin_about_dialog_group_add_row(
		about->gtk_settings_group,
		"gtk-theme-name",
		(theme_name != NULL) ? theme_name : _("(not set)"));

	g_free(cursor_theme_name);
	g_free(icon_theme_name);
	g_free(im_module);
	g_free(sound_theme_name);
	g_free(theme_name);
}

static char *
pidgin_about_dialog_copy_gtk_settings(void) {
	char *cursor_theme_name = NULL, *theme_name = NULL;
	char *icon_theme_name = NULL;
	char *im_module = NULL;
	char *sound_theme_name = NULL;
	gboolean enable_animations = FALSE;
	gboolean shell_shows_app_menu = FALSE, shell_shows_menubar = FALSE;
	GString *info = NULL;

	/* get the settings we're interested in */
	g_object_get(
		gtk_settings_get_default(),
		"gtk-cursor-theme-name", &cursor_theme_name,
		"gtk-enable-animations", &enable_animations,
		"gtk-icon-theme-name", &icon_theme_name,
		"gtk-im-module", &im_module,
		"gtk-shell-shows-app-menu", &shell_shows_app_menu,
		"gtk-shell-shows-menubar", &shell_shows_menubar,
		"gtk-sound-theme-name", &sound_theme_name,
		"gtk-theme-name", &theme_name,
		NULL);

	info = g_string_new(
		"GTK Settings\n"
		"============\n");

	g_string_append_printf(
		info, "gtk-cursor-theme-name: %s\n",
		(cursor_theme_name != NULL) ? cursor_theme_name : _("(not set)"));

	g_string_append_printf(info, "gtk-enable-animations: %s\n",
	                       enable_animations ? _("yes") : _("no"));

	g_string_append_printf(
		info, "gtk-icon-theme-name: %s\n",
		(icon_theme_name != NULL) ? icon_theme_name : _("(not set)"));

	g_string_append_printf(info, "gtk-im-module: %s\n",
	                       (im_module != NULL) ? im_module : _("(not set)"));

	g_string_append_printf(info, "gtk-shell-shows-app-menu: %s\n",
	                       shell_shows_app_menu ? _("yes") : _("no"));

	g_string_append_printf(info, "gtk-shell-shows-menubar: %s\n",
	                       shell_shows_menubar ? _("yes") : _("no"));

	g_string_append_printf(
		info, "gtk-sound-theme-name: %s\n",
		(sound_theme_name != NULL) ? sound_theme_name : _("(not set)"));

	g_string_append_printf(info, "gtk-theme-name: %s\n",
	                       (theme_name != NULL) ? theme_name : _("(not set)"));

	g_free(cursor_theme_name);
	g_free(icon_theme_name);
	g_free(im_module);
	g_free(sound_theme_name);
	g_free(theme_name);

	return g_string_free_and_steal(info);
}

static void
pidgin_about_dialog_load_plugin_search_paths(PidginAboutDialog *about) {
	GList *paths = NULL;
	GPluginManager *manager = gplugin_manager_get_default();

	/* add the search paths */
	paths = gplugin_manager_get_paths(manager);
	while(paths != NULL) {
		pidgin_about_dialog_group_add_row(about->plugin_search_paths_group,
		                                  paths->data, NULL);

		paths = paths->next;
	}
}

static char *
pidgin_about_dialog_copy_plugin_search_paths(void) {
	GList *paths = NULL;
	GPluginManager *manager = gplugin_manager_get_default();
	GString *info = NULL;

	info = g_string_new(
		"Plugin Search Paths\n"
		"===================\n");

	/* add the search paths */
	paths = gplugin_manager_get_paths(manager);
	while(paths != NULL) {
		g_string_append_printf(info, "- %s\n", (char *)paths->data);

		paths = paths->next;
	}

	return g_string_free_and_steal(info);
}

static void
pidgin_about_dialog_load_conf_path_info(PidginAboutDialog *about) {
	/* add the cache directory path */
	pidgin_about_dialog_group_add_row(about->conf_path_info_group, _("Cache"),
	                                  purple_cache_dir());

	/* add the config directory path */
	pidgin_about_dialog_group_add_row(about->conf_path_info_group,
	                                  _("Configuration"), purple_config_dir());

	/* add the data directory path */
	pidgin_about_dialog_group_add_row(about->conf_path_info_group, _("Data"),
	                                  purple_data_dir());

	/* add the locale directory path */
	pidgin_about_dialog_group_add_row(about->conf_path_info_group, _("Locale"),
	                                  purple_get_locale_dir());
}

static char *
pidgin_about_dialog_copy_conf_path_info(void) {
	char *info = NULL;

	info = g_strdup_printf(
		"Runtime Directories\n"
		"===================\n"
		"Cache: %s\n"
		"Configuration: %s\n"
		"Data: %s\n"
		"Locale: %s\n",
		purple_cache_dir(),
		purple_config_dir(),
		purple_data_dir(),
		purple_get_locale_dir());

	return info;
}

static void
pidgin_about_dialog_add_build_args(PidginAboutDialog *about,
                                   const PurpleKeyValuePair build_args[])
{
	/* Walk through the arguments and add them */
	for(gint idx = 0; build_args[idx].key != NULL; idx++) {
		pidgin_about_dialog_group_add_row(about->build_args_group,
		                                  build_args[idx].key,
		                                  build_args[idx].value);
	}
}

static char *
pidgin_about_dialog_copy_build_args(const PurpleKeyValuePair build_args[]) {
	GString *info = NULL;

	info = g_string_new(
		"Meson Arguments\n"
		"===============\n");

	/* Walk through the arguments and add them */
	for(gint idx = 0; build_args[idx].key != NULL; idx++) {
		char *value = NULL;

		if(build_args[idx].value != NULL) {
			value = purple_unescape_text(build_args[idx].value);
		} else {
			value = NULL;
		}

		g_string_append_printf(info, "%s: %s\n", build_args[idx].key, value);

		g_free(value);
	}

	return g_string_free_and_steal(info);
}

static void
pidgin_about_dialog_load_build_configuration(PidginAboutDialog *about) {
	pidgin_about_dialog_load_build_info(about);
	pidgin_about_dialog_load_runtime_info(about);
	pidgin_about_dialog_load_gtk_settings(about);
	pidgin_about_dialog_load_plugin_search_paths(about);
	pidgin_about_dialog_load_conf_path_info(about);

#ifdef HAVE_MESON_CONFIG
	pidgin_about_dialog_add_build_args(about, MESON_ARGS);
	gtk_widget_set_visible(GTK_WIDGET(about->build_args_group), TRUE);
#endif /* HAVE_MESON_CONFIG */
}

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
pidgin_about_dialog_copy_button_cb(GtkButton *button,
                                   gpointer data)
{
	PidginAboutDialog *about = NULL;
	GdkClipboard *clipboard = NULL;
	char *info = NULL;

	about = PIDGIN_ABOUT_DIALOG(gtk_widget_get_root(GTK_WIDGET(button)));

	if(data == about->build_info_group) {
		info = pidgin_about_dialog_copy_build_info();
	} else if(data == about->runtime_info_group) {
		info = pidgin_about_dialog_copy_runtime_info();
	} else if(data == about->gtk_settings_group) {
		info = pidgin_about_dialog_copy_gtk_settings();
	} else if(data == about->plugin_search_paths_group) {
		info = pidgin_about_dialog_copy_plugin_search_paths();
	} else if(data == about->conf_path_info_group) {
		info = pidgin_about_dialog_copy_conf_path_info();
#ifdef HAVE_MESON_CONFIG
	} else if(data == about->build_args_group) {
		info = pidgin_about_dialog_copy_build_args(MESON_ARGS);
#endif
	} else {
		GString *everything = g_string_new(NULL);

		info = pidgin_about_dialog_copy_build_info();
		g_string_append(everything, info);
		g_string_append_c(everything, '\n');
		g_free(info);

		info = pidgin_about_dialog_copy_runtime_info();
		g_string_append(everything, info);
		g_string_append_c(everything, '\n');
		g_free(info);

		info = pidgin_about_dialog_copy_conf_path_info();
		g_string_append(everything, info);
		g_string_append_c(everything, '\n');
		g_free(info);

		info = pidgin_about_dialog_copy_gtk_settings();
		g_string_append(everything, info);
		g_string_append_c(everything, '\n');
		g_free(info);

		info = pidgin_about_dialog_copy_plugin_search_paths();
		g_string_append(everything, info);
		g_free(info);

#ifdef HAVE_MESON_CONFIG
		g_string_append_c(everything, '\n');
		info = pidgin_about_dialog_copy_build_args(MESON_ARGS);
		g_string_append(everything, info);
		g_free(info);
#endif

		info = g_string_free_and_steal(everything);
	}

	clipboard = gtk_widget_get_clipboard(GTK_WIDGET(about));
	gdk_clipboard_set_text(clipboard, info);

	g_free(info);
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
G_DEFINE_FINAL_TYPE(PidginAboutDialog, pidgin_about_dialog, ADW_TYPE_WINDOW)

static void
pidgin_about_dialog_class_init(PidginAboutDialogClass *klass) {
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);

	gtk_widget_class_set_template_from_resource(
		widget_class,
		"/im/pidgin/Pidgin3/About/about.ui"
	);

	gtk_widget_class_bind_template_child(widget_class, PidginAboutDialog,
	                                     application_name);

	gtk_widget_class_bind_template_child(widget_class, PidginAboutDialog,
	                                     developers_page);

	gtk_widget_class_bind_template_child(widget_class, PidginAboutDialog,
	                                     translators_page);

	gtk_widget_class_bind_template_child(widget_class, PidginAboutDialog,
	                                     build_info_group);
	gtk_widget_class_bind_template_child(widget_class, PidginAboutDialog,
	                                     runtime_info_group);
	gtk_widget_class_bind_template_child(widget_class, PidginAboutDialog,
	                                     gtk_settings_group);
	gtk_widget_class_bind_template_child(widget_class, PidginAboutDialog,
	                                     plugin_search_paths_group);
	gtk_widget_class_bind_template_child(widget_class, PidginAboutDialog,
	                                     conf_path_info_group);
	gtk_widget_class_bind_template_child(widget_class, PidginAboutDialog,
	                                     build_args_group);

	gtk_widget_class_bind_template_callback(widget_class,
	                                        pidgin_about_dialog_copy_button_cb);
}

static void
pidgin_about_dialog_init(PidginAboutDialog *about) {
	gtk_widget_init_template(GTK_WIDGET(about));

	/* setup the application name label */
	pidgin_about_dialog_load_application_name(about);

	/* setup the developers stuff */
	pidgin_about_dialog_load_developers(about);

	/* setup the translators stuff */
	pidgin_about_dialog_load_translators(about);

	/* setup the build info page */
	pidgin_about_dialog_load_build_configuration(about);
}

/******************************************************************************
 * Public API
 *****************************************************************************/
GtkWidget *
pidgin_about_dialog_new(GtkApplication *application) {
	return g_object_new(
		PIDGIN_TYPE_ABOUT_DIALOG,
		"title", "About Pidgin",
		"application", application,
		NULL
	);
}
