/*
 * This file is part of roccat-tools.
 *
 * roccat-tools 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.
 *
 * roccat-tools 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 roccat-tools. If not, see <http://www.gnu.org/licenses/>.
 */

#include "koneconfig_application.h"
#include "roccat_config_window.h"
#include "koneconfig_configuration_dialog.h"
#include "koneconfig_profile_page.h"
#include "koneconfig_settings_page.h"
#include "roccat_warning_dialog.h"
#include "roccat_save_dialog.h"
#include "roccat_text_dialog.h"
#include "dbus_roccat_helper.h"

struct _KoneconfigApplication {
	RoccatDevice *kone;
	RoccatDeviceScanner *device_scanner;
	RoccatConfigWindow *window;
	KoneconfigProfilePage *profile_pages[KONE_PROFILE_NUM];
	KoneconfigSettingsPage *settings_page;
	RoccatKeyFile *config;
	DBusGProxy *dbus_proxy;
	guint actual_profile_index;
};

static gboolean warn_if_no_device(KoneconfigApplication *app) {
	return roccat_no_device_warning_dialog(GTK_WINDOW(app->window), app->kone, KONE_DEVICE_NAME);
}

static void settings_page_set_weight(KoneconfigApplication *app) {
	GError *local_error = NULL;
	guint weight;

	if (!warn_if_no_device(app))
		return;

	weight = kone_weight_read(app->kone, &local_error);
	if (!roccat_handle_error_dialog(GTK_WINDOW(app->window), "Error reading weight", &local_error))
		koneconfig_settings_page_clear_weight(app->settings_page);
	else
		koneconfig_settings_page_set_weight(app->settings_page, weight);
}

static void settings_page_update_weight_cb(KoneconfigSettingsPage *settings_page, gpointer user_data) {
	KoneconfigApplication *app = (KoneconfigApplication *)user_data;
	if (warn_if_no_device(app))
		settings_page_set_weight(app);
}

static void settings_page_actual_profile_changed_cb(KoneconfigSettingsPage *settings_page, gpointer user_data) {
	KoneconfigApplication *app = (KoneconfigApplication *)user_data;
	guint profile_number = koneconfig_settings_page_get_actual_profile(app->settings_page);
	guint profile_index = profile_number - 1;
	GError *local_error = NULL;

	if (!warn_if_no_device(app))
		return;

	if (app->actual_profile_index != profile_index) {
		app->actual_profile_index = profile_index;
		kone_profile_activate(app->kone, profile_number, &local_error);
		if (!roccat_handle_error_dialog(GTK_WINDOW(app->window), "Error activating profile", &local_error))
			return;
		kone_dbus_emit_profile_changed_outside(app->dbus_proxy, profile_number);
	}
}

static void actual_profile_changed_from_device_cb(DBusGProxy *proxy, guchar profile_number, gpointer user_data) {
	KoneconfigApplication *app = (KoneconfigApplication *)user_data;
	guint profile_index = profile_number - 1;

	if (app->actual_profile_index != profile_index) {
		app->actual_profile_index = profile_index;
		koneconfig_settings_page_set_actual_profile(app->settings_page, profile_number);
	}
}

static gboolean settings_page_set_info(KoneconfigApplication *app) {
	GError *local_error = NULL;
	guint firmware_version;

	firmware_version = kone_firmware_version_read(app->kone, &local_error);
	if (!roccat_handle_error_dialog(GTK_WINDOW(app->window), "Can't read info:", &local_error))
		return FALSE;
	koneconfig_settings_page_set_info(app->settings_page, firmware_version);
	return TRUE;
}

static gboolean add_cb(RoccatDevice *device, gpointer user_data) {
	KoneconfigApplication *app = (KoneconfigApplication *)user_data;
	GError *error = NULL;
	guint profile_number;
	guint i;
	KoneRMP *rmp;

	if (app->kone) {
		roccat_multiple_device_warning_dialog(GTK_WINDOW(app->window), KONE_DEVICE_NAME);
		return TRUE;
	}

	app->kone = device;

	profile_number = kone_actual_profile_read(app->kone, &error);
	if (!roccat_handle_error_dialog(GTK_WINDOW(app->window), "Can't read actual profile:", &error))
		goto error;
	app->actual_profile_index = profile_number - 1;
	koneconfig_settings_page_set_actual_profile(app->settings_page, profile_number);

	if (!settings_page_set_info(app))
		goto error;

	settings_page_set_weight(app);

	for (i = 0; i < KONE_PROFILE_NUM; ++i) {
		rmp = koneconfig_profile_page_get_rmp(app->profile_pages[i]);
		kone_rmp_update_from_device(rmp, app->kone, i + 1);
		koneconfig_profile_page_set_rmp(app->profile_pages[i], rmp);
		kone_rmp_free(rmp);
	}

	dbus_g_proxy_connect_signal(app->dbus_proxy, "ProfileChanged", G_CALLBACK(actual_profile_changed_from_device_cb), app, NULL);

	roccat_config_window_set_title_connected(app->window, KONE_DEVICE_NAME);

	return FALSE;
error:
	app->kone = FALSE;
	return TRUE;
}

static gboolean remove_cb(RoccatDevice *device, gpointer user_data) {
	KoneconfigApplication *app = (KoneconfigApplication *)user_data;

	dbus_g_proxy_disconnect_signal(app->dbus_proxy, "ProfileChanged", G_CALLBACK(actual_profile_changed_from_device_cb), app);

	roccat_device_free(app->kone);
	app->kone = NULL;

	koneconfig_settings_page_clear_info(app->settings_page);
	koneconfig_settings_page_clear_weight(app->settings_page);
	roccat_config_window_set_title_disconnected(app->window);

	return TRUE;
}

static gboolean save_unsaved(KoneconfigApplication *app, KoneRMP *rmp, guint profile_index, GError **error) {
	guint profile_number = profile_index + 1;

	if (app->kone)
		kone_rmp_save(app->kone, rmp, profile_number, error);
	else
		kone_rmp_save_actual(rmp, profile_number, error);

	if (*error)
		return FALSE;
	else {
		kone_dbus_emit_profile_data_changed_outside(app->dbus_proxy, profile_number);
		koneconfig_profile_page_set_rmp(app->profile_pages[profile_index], rmp);
		return TRUE;
	}
}

static gboolean display_quit_dialog(KoneconfigApplication *app) {
	guint i;
	gboolean modified;
	KoneRMP *rmps[KONE_PROFILE_NUM];
	gboolean retval;
	GError *error = NULL;

	modified = FALSE;
	for (i = 0; i < KONE_PROFILE_NUM; ++i) {
		rmps[i] = koneconfig_profile_page_get_rmp(app->profile_pages[i]);
		if (kone_rmp_get_modified(rmps[i]))
			modified = TRUE;
	}
	if (!modified) {
		for (i = 0; i < KONE_PROFILE_NUM; ++i)
			kone_rmp_free(rmps[i]);
		return TRUE;
	}

	switch (roccat_save_unsaved_dialog(GTK_WINDOW(app->window), TRUE)) {
	case GTK_RESPONSE_CANCEL:
		retval = FALSE;
		break;
	case GTK_RESPONSE_ACCEPT:
		retval = TRUE;
		warn_if_no_device(app);
		for (i = 0; i < KONE_PROFILE_NUM; ++i) {
			if (!save_unsaved(app, rmps[i], i, &error)) {
				retval = FALSE;
				break;
			}
		}
		roccat_handle_error_dialog(GTK_WINDOW(app->window), "Error storing unsaved data", &error);
		break;
	default:
		retval = TRUE;
		break;
	}

	for (i = 0; i < KONE_PROFILE_NUM; ++i)
		kone_rmp_free(rmps[i]);

	return retval;
}

static gboolean delete_event_cb(GtkWidget *window, GdkEvent *event, gpointer user_data) {
	KoneconfigApplication *app = (KoneconfigApplication *)user_data;

	return !display_quit_dialog(app);
}

static void destroy_event_cb(GtkWidget *window, gpointer user_data) {
	KoneconfigApplication *app = (KoneconfigApplication *)user_data;
	gtk_main_quit();
	app->window = NULL;
}

static void save_profile_to_device_cb(KoneconfigProfilePage *profile_page, gpointer user_data) {
	KoneconfigApplication *app = (KoneconfigApplication *)user_data;
	KoneRMP *rmp = koneconfig_profile_page_get_rmp(profile_page);
	guint profile_index = koneconfig_profile_page_get_index(profile_page);
	GError *error = NULL;

	warn_if_no_device(app);
	save_unsaved(app, rmp, profile_index, &error);
	roccat_handle_error_dialog(GTK_WINDOW(app->window), "Error storing unsaved data", &error);

	kone_rmp_free(rmp);
}

static GtkFileFilter *windows_file_filter_new(void) {
	GtkFileFilter *filter;
	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, "Kone Windows profiles");
	gtk_file_filter_add_pattern(filter, "*.rmp");
	return filter;
}

static GtkFileFilter *linux_file_filter_new(void) {
	GtkFileFilter *filter;
	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, "Kone Linux profiles");
	gtk_file_filter_add_pattern(filter, "*.rmp");
	return filter;
}

static GtkFileFilter *all_file_filter_new(void) {
	GtkFileFilter *filter;
	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, "All files");
	gtk_file_filter_add_pattern(filter, "*");
	return filter;
}

static void save_profile_to_file_cb(KoneconfigProfilePage *profile_page, gpointer user_data) {
	KoneconfigApplication *app = (KoneconfigApplication *)user_data;
	GtkWidget *dialog;
	gchar *filename, *path;
	GError *error = NULL;
	GtkFileFilter *windows_filter;
	GtkFileFilter *linux_filter;
	GtkFileFilter *all_filter;
	GtkFileFilter *filter;
	guint profile_index = koneconfig_profile_page_get_index(profile_page);
	KoneRMP *rmp;

	dialog = gtk_file_chooser_dialog_new("Save profile",
			GTK_WINDOW(app->window),
			GTK_FILE_CHOOSER_ACTION_SAVE,
			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
			NULL);

	windows_filter = windows_file_filter_new();
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), windows_filter);
	linux_filter = linux_file_filter_new();
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), linux_filter);
	all_filter = all_file_filter_new();
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), all_filter);

	path = kone_configuration_get_rmp_save_path(app->config);
	gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path);
	g_free(path);

	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
		filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
		path = g_path_get_dirname(filename);
		kone_configuration_set_rmp_save_path(app->config, path);
		g_free(path);

		rmp = koneconfig_profile_page_get_rmp(app->profile_pages[profile_index]);

		if (filter != linux_filter)
			kone_linux_rmp_to_windows(rmp);
		
		kone_rmp_write_with_path(filename, rmp, &error);
		kone_rmp_free(rmp);
		if (error) {
			roccat_warning_dialog(GTK_WINDOW(dialog), "Error saving rmp", error->message);
			g_error_free(error);
		}

		g_free(filename);
	}
	gtk_widget_destroy(dialog);
}

static void load_profile_from_file_cb(KoneconfigProfilePage *profile_page, gpointer user_data) {
	KoneconfigApplication *app = (KoneconfigApplication *)user_data;
	GtkWidget *dialog;
	gchar *filename, *path;
	GError *error = NULL;
	GtkFileFilter *windows_filter;
	GtkFileFilter *linux_filter;
	GtkFileFilter *all_filter;
	GtkFileFilter *filter;
	KoneRMP *rmp;
	guint profile_index = koneconfig_profile_page_get_index(profile_page);

	dialog = gtk_file_chooser_dialog_new("Load profile",
			GTK_WINDOW(app->window),
			GTK_FILE_CHOOSER_ACTION_OPEN,
			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
			NULL);

	windows_filter = windows_file_filter_new();
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), windows_filter);
	linux_filter = linux_file_filter_new();
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), linux_filter);
	all_filter = all_file_filter_new();
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), all_filter);

	path = kone_configuration_get_rmp_save_path(app->config);
	gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path);
	g_free(path);

	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
		filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
		path = g_path_get_dirname(filename);
		kone_configuration_set_rmp_save_path(app->config, path);
		g_free(path);

		rmp = kone_rmp_read_with_path(filename, &error);
		
		if (filter != linux_filter)
			kone_windows_rmp_to_linux(rmp);

		if (error) {
			roccat_warning_dialog(GTK_WINDOW(dialog), "Error loading rmp", error->message);
			g_error_free(error);
		} else {
			kone_rmp_set_modified(rmp);
			koneconfig_profile_page_set_rmp(app->profile_pages[profile_index], rmp);
			kone_rmp_free(rmp);
		}

		g_free(filename);
	}

	gtk_widget_destroy(dialog);
}

static void save_all_cb(GtkMenuItem *item, gpointer user_data) {
	KoneconfigApplication *app = (KoneconfigApplication *)user_data;
	KoneRMP *rmp;
	guint i;
	GError *error = NULL;

	warn_if_no_device(app);
	for (i = 0; i < KONE_PROFILE_NUM; ++i) {
		rmp = koneconfig_profile_page_get_rmp(app->profile_pages[i]);
		save_unsaved(app, rmp, i, &error);
		roccat_handle_error_dialog(GTK_WINDOW(app->window), "Error storing unsaved data", &error);

		kone_rmp_free(rmp);
	}
}

static void edit_preferences_cb(GtkMenuItem *item, gpointer user_data) {
	KoneconfigApplication *app = (KoneconfigApplication *)user_data;
	koneconfig_configuration_dialog(GTK_WINDOW(app->window), app->config);
	kone_dbus_emit_configuration_changed_outside(app->dbus_proxy);
}

KoneconfigApplication *koneconfig_application_new(void) {
	KoneconfigApplication *app;
	KoneRMP *rmp;
	GtkWidget *tab_label;
	guint i;
	GtkMenuItem *preferences_menu;

	app = (KoneconfigApplication *)g_malloc0(sizeof(KoneconfigApplication));

	app->config = kone_configuration_load();

	app->window = ROCCAT_CONFIG_WINDOW(roccat_config_window_new(KONE_DEVICE_NAME));
	gtk_widget_show(GTK_WIDGET(app->window));

	for (i = 0; i < KONE_PROFILE_NUM; ++i) {
		app->profile_pages[i] = KONECONFIG_PROFILE_PAGE(koneconfig_profile_page_new(i));
		tab_label = koneconfig_profile_page_get_tab_label(app->profile_pages[i]);
		roccat_config_window_append_page(app->window, GTK_WIDGET(app->profile_pages[i]), tab_label);

		rmp = kone_rmp_load_actual(i + 1);
		koneconfig_profile_page_set_rmp(app->profile_pages[i], rmp);
		kone_rmp_free(rmp);

		g_signal_connect(G_OBJECT(app->profile_pages[i]), "load-from-file", G_CALLBACK(load_profile_from_file_cb), app);
		g_signal_connect(G_OBJECT(app->profile_pages[i]), "save-to-file", G_CALLBACK(save_profile_to_file_cb), app);
		g_signal_connect(G_OBJECT(app->profile_pages[i]), "save-to-device", G_CALLBACK(save_profile_to_device_cb), app);
	}

	app->settings_page = KONECONFIG_SETTINGS_PAGE(koneconfig_settings_page_new());
	roccat_config_window_append_page(app->window, GTK_WIDGET(app->settings_page), gtk_label_new("Settings"));

	preferences_menu = GTK_MENU_ITEM(gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL));
	g_signal_connect(G_OBJECT(preferences_menu), "activate", G_CALLBACK(edit_preferences_cb), app);
	roccat_config_window_edit_menu_append(app->window, preferences_menu);

	g_signal_connect(G_OBJECT(app->settings_page), "actual-profile-changed", G_CALLBACK(settings_page_actual_profile_changed_cb), app);
	g_signal_connect(G_OBJECT(app->settings_page), "update-weight", G_CALLBACK(settings_page_update_weight_cb), app);
	g_signal_connect(G_OBJECT(app->window), "destroy", G_CALLBACK(destroy_event_cb), app);
	g_signal_connect(G_OBJECT(app->window), "delete-event", G_CALLBACK(delete_event_cb), app);
	g_signal_connect(G_OBJECT(app->window), "save-all", G_CALLBACK(save_all_cb), app);

	/* keep this order */
	app->dbus_proxy = kone_dbus_proxy_new();
	app->device_scanner = kone_device_scanner_new(add_cb, remove_cb, app);

	/* if a device is already plugged in, add_cb has already been executed */
	warn_if_no_device(app);

	return app;
}

void koneconfig_application_free(KoneconfigApplication *app) {
	roccat_device_scanner_free(app->device_scanner);
	app->device_scanner = NULL;

	dbus_roccat_proxy_free(app->dbus_proxy);
	app->dbus_proxy = NULL;

	roccat_device_free(app->kone);
	app->kone = NULL;

	if (app->window) {
		gtk_widget_destroy(GTK_WIDGET(app->window));
		app->window = NULL;
	}

	koneconfig_configuration_save(GTK_WINDOW(app->window), app->config);
	kone_configuration_free(app->config);
	app->config = NULL;

	g_free(app);
}
