/*
 * 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 "roccat_update_assistant.h"
#include "roccat_warning_dialog.h"

#define ROCCAT_UPDATE_ASSISTANT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ROCCAT_UPDATE_ASSISTANT_TYPE, RoccatUpdateAssistantPrivate))

struct _RoccatUpdateAssistantPrivate {
	gint file_page;
	GtkLabel *file_page_label;
	GtkFileChooserButton *file_chooser;

	gint check_page;
	GtkLabel *check_page_device_label;
	GtkLabel *actual_firmware_info;
	GtkLabel *new_firmware_info;

	gint action_page;
	GtkProgressBar *progress_bar;

	gint success_page;

	gint failure_page;
	GtkLabel *failure_label;

	gchar *device_name;
	gchar *actual_firmware_version;
};

G_DEFINE_TYPE(RoccatUpdateAssistant, roccat_update_assistant, GTK_TYPE_ASSISTANT);

enum {
	FILE_SELECTED,
	PREPARE_ACTION,
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

enum {
	PROP_0,
	PROP_DEVICE_NAME,
	PROP_ACTUAL_FIRMWARE_VERSION,
};

gchar *roccat_update_assistant_get_filename(RoccatUpdateAssistant *assistant) {
	return gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(assistant->priv->file_chooser));
}

void roccat_update_assistant_set_new_firmware_info(RoccatUpdateAssistant *assistant, gchar const *info) {
	gtk_label_set_text(assistant->priv->new_firmware_info, info);
}

void roccat_update_assistant_set_ok(RoccatUpdateAssistant *assistant) {
	gtk_assistant_set_page_complete(GTK_ASSISTANT(assistant),
			gtk_assistant_get_nth_page(GTK_ASSISTANT(assistant), assistant->priv->file_page), TRUE);
}

void roccat_update_assistant_set_progress_fraction(RoccatUpdateAssistant *assistant, gdouble fraction) {
	gtk_progress_bar_set_fraction(assistant->priv->progress_bar, fraction);
}

static void file_set_cb(GtkFileChooserButton *button, gpointer user_data) {
	g_signal_emit((gpointer)ROCCAT_UPDATE_ASSISTANT(user_data), signals[FILE_SELECTED], 0);
}

static void add_file_filter(GtkFileChooser *chooser) {
	GtkFileFilter *filter;
	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, "Roccat firmware images");
	gtk_file_filter_add_pattern(filter, "*.bin");
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);

	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, "All files");
	gtk_file_filter_add_pattern(filter, "*");
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
}

static void append_file_page(RoccatUpdateAssistant *assistant) {
	RoccatUpdateAssistantPrivate *priv = assistant->priv;
	GtkWidget *page;
	GtkWidget *label;

	page = gtk_vbox_new(FALSE, 0);

	priv->file_page_label = GTK_LABEL(gtk_label_new(NULL));
	gtk_label_set_justify(priv->file_page_label, GTK_JUSTIFY_CENTER);
	gtk_box_pack_start(GTK_BOX(page), GTK_WIDGET(priv->file_page_label), TRUE, TRUE, 0);

	label = gtk_label_new("Please select a firmware image that fits your device.");
	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
	gtk_box_pack_start(GTK_BOX(page), label, TRUE, TRUE, 10);

	priv->file_chooser = GTK_FILE_CHOOSER_BUTTON(gtk_file_chooser_button_new("Select firmware image", GTK_FILE_CHOOSER_ACTION_OPEN));
	add_file_filter(GTK_FILE_CHOOSER(priv->file_chooser));
	gtk_box_pack_start(GTK_BOX(page), GTK_WIDGET(priv->file_chooser), TRUE, FALSE, 0);
	g_signal_connect(G_OBJECT(priv->file_chooser), "file-set", G_CALLBACK(file_set_cb), assistant);

	priv->file_page = gtk_assistant_append_page(GTK_ASSISTANT(assistant), page);
	gtk_assistant_set_page_title(GTK_ASSISTANT(assistant), page, "Select firmware");
	gtk_assistant_set_page_type(GTK_ASSISTANT(assistant), page, GTK_ASSISTANT_PAGE_CONTENT);
	gtk_assistant_set_page_complete(GTK_ASSISTANT(assistant), page, FALSE);

	gtk_widget_show_all(page);
}

static void append_check_page(RoccatUpdateAssistant *assistant) {
	RoccatUpdateAssistantPrivate *priv = assistant->priv;
	GtkWidget *page;
	GtkWidget *label;
	GtkTable *table;

	page = gtk_vbox_new(FALSE, 0);

	table = GTK_TABLE(gtk_table_new(3, 2, FALSE));
	gtk_box_pack_start(GTK_BOX(page), GTK_WIDGET(table), FALSE, FALSE, 0);

	label = gtk_label_new("Device:");
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_table_attach(table, label, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	priv->check_page_device_label = GTK_LABEL(gtk_label_new(NULL));
	gtk_misc_set_alignment(GTK_MISC(priv->check_page_device_label), 0.0, 0.5);
	gtk_table_attach(table, GTK_WIDGET(priv->check_page_device_label), 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);

	label = gtk_label_new("Actual firmware:");
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_table_attach(table,label, 0, 1, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	priv->actual_firmware_info = GTK_LABEL(gtk_label_new(NULL));
	gtk_misc_set_alignment(GTK_MISC(priv->actual_firmware_info), 0.0, 0.5);
	gtk_table_attach(table, GTK_WIDGET(priv->actual_firmware_info), 1, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);

	label = gtk_label_new("New firmware:");
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_table_attach(table, label, 0, 1, 2, 3, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	priv->new_firmware_info = GTK_LABEL(gtk_label_new(NULL));
	gtk_misc_set_alignment(GTK_MISC(priv->new_firmware_info), 0.0, 0.5);
	gtk_table_attach(table, GTK_WIDGET(priv->new_firmware_info), 1, 2, 2, 3, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);

	label = gtk_label_new(NULL);
	gtk_label_set_markup(GTK_LABEL(label), "<b>Please do not unplug the device\n"
			"or power down your computer while the update\nprocess is running.</b>");
	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
	gtk_box_pack_start(GTK_BOX(page), label, TRUE, TRUE, 10);

	label = gtk_label_new("Please continue to update the firmware now.");
	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
	gtk_box_pack_start(GTK_BOX(page), label, TRUE, TRUE, 10);

	priv->check_page = gtk_assistant_append_page(GTK_ASSISTANT(assistant), page);
	gtk_assistant_set_page_title(GTK_ASSISTANT(assistant), page, "Initiate update");
	gtk_assistant_set_page_type(GTK_ASSISTANT(assistant), page, GTK_ASSISTANT_PAGE_CONFIRM);
	gtk_assistant_set_page_complete(GTK_ASSISTANT(assistant), page, TRUE);
	gtk_widget_show_all(page);
}

static void append_action_page(RoccatUpdateAssistant *assistant) {
	RoccatUpdateAssistantPrivate *priv = assistant->priv;
	GtkWidget *label;
	GtkWidget *page;

	page = gtk_vbox_new(FALSE, 0);

	label = gtk_label_new("Please wait while the update process is running.");
	priv->progress_bar = GTK_PROGRESS_BAR(gtk_progress_bar_new());

	gtk_box_pack_start(GTK_BOX(page), label, TRUE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(page), GTK_WIDGET(priv->progress_bar), TRUE, FALSE, 0);

	priv->action_page = gtk_assistant_append_page(GTK_ASSISTANT(assistant), page);
	gtk_assistant_set_page_title(GTK_ASSISTANT(assistant), page, "Updating...");
	gtk_assistant_set_page_type(GTK_ASSISTANT(assistant), page, GTK_ASSISTANT_PAGE_PROGRESS);
	gtk_assistant_set_page_complete(GTK_ASSISTANT(assistant), page, FALSE);
	gtk_widget_show_all(page);
}

void roccat_update_assistant_set_success(RoccatUpdateAssistant *assistant) {
	RoccatUpdateAssistantPrivate *priv = assistant->priv;
	gtk_assistant_set_current_page(GTK_ASSISTANT(assistant), priv->success_page);
}

static void append_success_page(RoccatUpdateAssistant *assistant) {
	RoccatUpdateAssistantPrivate *priv = assistant->priv;
	GtkWidget *page;
	GtkWidget *label;

	page = gtk_vbox_new(FALSE, 0);

	label = gtk_label_new("Firmware update successful!");
	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
	gtk_box_pack_start(GTK_BOX(page), label, TRUE, FALSE, 0);

	label = gtk_label_new("Please unplug and replug your device now.");
	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
	gtk_box_pack_start(GTK_BOX(page), label, TRUE, FALSE, 0);

	priv->success_page = gtk_assistant_append_page(GTK_ASSISTANT(assistant), page);
	gtk_assistant_set_page_title(GTK_ASSISTANT(assistant), page, "Success");
	gtk_assistant_set_page_type(GTK_ASSISTANT(assistant), page, GTK_ASSISTANT_PAGE_SUMMARY);
	gtk_assistant_set_page_complete(GTK_ASSISTANT(assistant), page, TRUE);
	gtk_widget_show_all(page);
}

void roccat_update_assistant_set_failed(RoccatUpdateAssistant *assistant, gchar const *text) {
	RoccatUpdateAssistantPrivate *priv = assistant->priv;
	gtk_label_set_text(priv->failure_label, text);
	gtk_assistant_set_current_page(GTK_ASSISTANT(assistant), priv->failure_page);
}

/* This page is inserted after success page and not reachable in normal flow */
static void append_failure_page(RoccatUpdateAssistant *assistant) {
	RoccatUpdateAssistantPrivate *priv = assistant->priv;
	GtkWidget *page;
	GtkWidget *label;

	page = gtk_vbox_new(FALSE, 0);

	label = gtk_label_new("Firmware update failed!");
	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
	gtk_box_pack_start(GTK_BOX(page), label, TRUE, FALSE, 0);

	priv->failure_label = GTK_LABEL(gtk_label_new(NULL));
	gtk_label_set_justify(priv->failure_label, GTK_JUSTIFY_CENTER);
	gtk_box_pack_start(GTK_BOX(page), GTK_WIDGET(priv->failure_label), TRUE, FALSE, 0);

	label = gtk_label_new("Please try again.");
	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
	gtk_box_pack_start(GTK_BOX(page), label, TRUE, FALSE, 0);

	priv->failure_page = gtk_assistant_append_page(GTK_ASSISTANT(assistant), page);
	gtk_assistant_set_page_title(GTK_ASSISTANT(assistant), page, "Failure");
	gtk_assistant_set_page_type(GTK_ASSISTANT(assistant), page, GTK_ASSISTANT_PAGE_SUMMARY);
	gtk_assistant_set_page_complete(GTK_ASSISTANT(assistant), page, TRUE);
	gtk_widget_show_all(page);
}

static void prepare_cb(GtkAssistant *assistant, GtkWidget *page, gpointer user_data) {
	RoccatUpdateAssistantPrivate *priv = ROCCAT_UPDATE_ASSISTANT(assistant)->priv;

	if (page == gtk_assistant_get_nth_page(GTK_ASSISTANT(assistant), priv->action_page)) {
#if (GTK_CHECK_VERSION(2,22,0))
		gtk_assistant_commit(assistant);
#endif
		g_signal_emit((gpointer)ROCCAT_UPDATE_ASSISTANT(assistant), signals[PREPARE_ACTION], 0);
	}
}

static void close_cb(GtkAssistant *assistant, gpointer user_data) {
	// FIXME deactivate while updating
	gtk_widget_destroy(GTK_WIDGET(assistant));
}

static void cancel_cb(GtkAssistant *assistant, gpointer user_data) {
	// FIXME deactivate while updating
	gtk_widget_destroy(GTK_WIDGET(assistant));
}

static void apply_cb(GtkAssistant *assistant, gpointer user_data) {
}

static void roccat_update_assistant_init(RoccatUpdateAssistant *assistant) {
	RoccatUpdateAssistantPrivate *priv = ROCCAT_UPDATE_ASSISTANT_GET_PRIVATE(assistant);
	assistant->priv = priv;

	append_file_page(assistant);
	append_check_page(assistant);
	append_action_page(assistant);
	append_success_page(assistant);
	append_failure_page(assistant);

	g_signal_connect(G_OBJECT(assistant), "prepare", G_CALLBACK(prepare_cb), NULL);
	g_signal_connect(G_OBJECT(assistant), "close", G_CALLBACK(close_cb), NULL);
	g_signal_connect(G_OBJECT(assistant), "cancel", G_CALLBACK(cancel_cb), NULL);
	g_signal_connect(G_OBJECT(assistant), "apply", G_CALLBACK(apply_cb), NULL);
}

static void set_property(GObject *object, guint prop_id, GValue const *value, GParamSpec *pspec) {
	RoccatUpdateAssistantPrivate *priv = ROCCAT_UPDATE_ASSISTANT(object)->priv;
	gchar *text;

	switch(prop_id) {
	case PROP_DEVICE_NAME:
		g_free(priv->device_name);
		priv->device_name = g_strdup(g_value_get_string(value));

		text = g_strdup_printf("%s firmware update", priv->device_name);
		g_object_set(G_OBJECT(object), "title", text, NULL);
		g_free(text);

		text = g_strdup_printf("You are about to update a %s.", priv->device_name);
		gtk_label_set_text(priv->file_page_label, text);
		g_free(text);

		gtk_label_set_text(priv->check_page_device_label, priv->device_name);

		break;
	case PROP_ACTUAL_FIRMWARE_VERSION:
		g_free(priv->actual_firmware_version);
		priv->actual_firmware_version = g_strdup(g_value_get_string(value));

		gtk_label_set_text(priv->actual_firmware_info, priv->actual_firmware_version);

		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
	}
}

static void get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
	switch(prop_id) {
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
	}
}

static void finalize(GObject *object) {
	RoccatUpdateAssistantPrivate *priv = ROCCAT_UPDATE_ASSISTANT(object)->priv;
	g_free(priv->device_name);
	g_free(priv->actual_firmware_version);
	G_OBJECT_CLASS(roccat_update_assistant_parent_class)->finalize(object);
}

static void roccat_update_assistant_class_init(RoccatUpdateAssistantClass *klass) {
	GObjectClass *gobject_class = (GObjectClass*)klass;

	gobject_class->set_property = set_property;
	gobject_class->get_property = get_property;
	gobject_class->finalize = finalize;

	g_type_class_add_private(klass, sizeof(RoccatUpdateAssistantPrivate));

	signals[FILE_SELECTED] = g_signal_new("file-selected", G_TYPE_FROM_CLASS(klass),
			G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
			0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
			0);

	signals[PREPARE_ACTION] = g_signal_new("prepare-action", G_TYPE_FROM_CLASS(klass),
			G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
			0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
			0);

	g_object_class_install_property(gobject_class, PROP_DEVICE_NAME,
			g_param_spec_string("device-name",
					"device-name",
					"Device name",
					"unknown",
					G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));

	g_object_class_install_property(gobject_class, PROP_ACTUAL_FIRMWARE_VERSION,
			g_param_spec_string("actual-firmware-version",
					"actual-firmware-version",
					"Actual firmware version",
					"unknown",
					G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
}

guint8 *roccat_update_assistant_firmware_read(gchar const *path, gsize totalsize, gsize chunks, gsize chunk_size) {
	gchar *result;
	gsize bytes;
	GError *error = NULL;
	guint i;
	gchar *error_text;

	if (!g_file_get_contents(path, &result, &bytes, &error)) {
		roccat_handle_error_dialog(NULL, "Error reading firmware", &error);
		return NULL;
	}

	if (bytes != totalsize) {
		error_text = g_strdup_printf("%zu bytes instead of %zu", bytes, totalsize);
		roccat_warning_dialog(NULL, "Firmware has wrong size", error_text);
		g_free(error_text);
		return NULL;
	}

	result = g_realloc(result, chunk_size * chunks);

	for (i = totalsize; i < chunk_size * chunks; ++i)
		result[i] = 0;

	return (guint8 *)result;
}
