/*
 * 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 "config.h"
#include "roccat_macro_editor.h"
#include "roccat_macro_editor_macros_frame.h"
#include "roccat_macro_editor_macro_options_frame.h"
#include "roccat_macro_editor_record_options_frame.h"
#include "roccat_macro_editor_key_sequence_frame.h"
#include "roccat_macro_tree_store.h"
#include "roccat/roccat.h"
#include "g_roccat_helper.h"
#include "gtk_roccat_helper.h"
#include "roccat_gdk_key_translations.h"
#include "hid_uid.h"
#include <time.h>

#define ROCCAT_MACRO_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ROCCAT_MACRO_EDITOR_TYPE, RoccatMacroEditorClass))
#define IS_ROCCAT_MACRO_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ROCCAT_MACRO_EDITOR_TYPE))
#define ROCCAT_MACRO_EDITOR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ROCCAT_MACRO_EDITOR_TYPE, RoccatMacroEditorPrivate))

typedef struct _RoccatMacroEditorClass RoccatMacroEditorClass;

enum {
	NUM_VIEWS = 2
};

struct _RoccatMacroEditorClass {
	GtkHPanedClass parent_class;
};

struct _RoccatMacroEditorPrivate {
	RoccatMacroEditorMacrosFrame *macros_frame;
	RoccatMacroEditorRecordOptionsFrame *record_options_frame;
	RoccatMacroEditorMacroOptionsFrame *macro_options_frame;
	RoccatMacroEditorKeySequenceFrame *key_sequence_frame;
	GtkButton *save_button;
	GtkTreeRowReference *reference;
	struct timespec record_start_time;
	struct timespec record_last_event_time;
	gboolean edited_modified;
	gboolean modified;
};

G_DEFINE_TYPE(RoccatMacroEditor, roccat_macro_editor, GTK_TYPE_HPANED);

/*
 * left - right
 * Returns msec
 */
static glong time_diff(struct timespec const *left, struct timespec const *right) {
	return ((glong)left->tv_sec - (glong)right->tv_sec) * G_ROCCAT_MSEC_PER_SEC + ((glong)left->tv_nsec - (glong)right->tv_nsec) / G_ROCCAT_NSEC_PER_MSEC;
}

/* subtracts @difference msecs from @timespec */
static void time_minus(struct timespec *timespec, glong difference) {
	glong nsec = (glong)timespec->tv_nsec - difference * (glong)G_ROCCAT_NSEC_PER_MSEC;
	glong sec_diff = nsec / (glong)G_ROCCAT_NSEC_PER_SEC;
	timespec->tv_nsec = nsec - sec_diff * (glong)G_ROCCAT_NSEC_PER_SEC;
	timespec->tv_sec += sec_diff;
}

void roccat_macro_editor_set_modified(RoccatMacroEditor *macro_editor, gboolean state) {
	macro_editor->priv->modified = state;
}

gboolean roccat_macro_editor_get_modified(RoccatMacroEditor *macro_editor) {
	RoccatMacroEditorPrivate *priv = macro_editor->priv;
	return priv->edited_modified || priv->modified;
}

static gboolean is_record_on(RoccatMacroEditor *macro_editor) {
	return roccat_macro_editor_record_options_frame_is_record_on(macro_editor->priv->record_options_frame);
}

static gboolean should_record_delays(RoccatMacroEditor *macro_editor) {
	return roccat_macro_editor_record_options_frame_should_record_delays(macro_editor->priv->record_options_frame);
}

static void buttons_set_sensitive(RoccatMacroEditor *macro_editor) {
	RoccatMacroEditorPrivate *priv = macro_editor->priv;
	gboolean valid = gtk_tree_row_reference_valid(priv->reference);
	gboolean modified = priv->edited_modified;
	gboolean empty = roccat_macro_editor_key_sequence_frame_empty(priv->key_sequence_frame);

	gtk_widget_set_sensitive(GTK_WIDGET(priv->save_button), valid && !empty && modified);
	gtk_widget_set_sensitive(GTK_WIDGET(priv->record_options_frame), valid);
	gtk_widget_set_sensitive(GTK_WIDGET(priv->macro_options_frame), valid);
	gtk_widget_set_sensitive(GTK_WIDGET(priv->key_sequence_frame), valid);
}

static void unset(RoccatMacroEditor *macro_editor) {
	RoccatMacroEditorPrivate *priv = macro_editor->priv;
	roccat_macro_editor_key_sequence_frame_clear(priv->key_sequence_frame);

	gtk_tree_row_reference_free(priv->reference);
	priv->reference = NULL;

	priv->edited_modified = FALSE;
	buttons_set_sensitive(macro_editor);
}

static void unset_if(RoccatMacroEditor *macro_editor) {
	if (!gtk_tree_row_reference_valid(macro_editor->priv->reference))
		unset(macro_editor);
}

/* moves keystrokes from key_sequence view back to macros_tree_view */
static void store(RoccatMacroEditor *macro_editor) {
	RoccatMacroEditorPrivate *priv = macro_editor->priv;
	GtkTreeModel *model;
	GtkTreeIter iter;
	RoccatMacroKeystrokes *keystrokes;

	if (!gtk_tree_row_reference_valid(priv->reference))
		return;

	keystrokes = roccat_macro_editor_key_sequence_frame_get_keystrokes(priv->key_sequence_frame);
	keystrokes->loop = roccat_macro_editor_macro_options_frame_get_loop(priv->macro_options_frame);

	model = gtk_tree_row_reference_get_model(priv->reference);
	gtk_roccat_tree_row_reference_get_iter(priv->reference, &iter);

	roccat_macro_tree_store_set_keystrokes(ROCCAT_MACRO_TREE_STORE(model), &iter, keystrokes);
	g_free(keystrokes);

	priv->edited_modified = FALSE;
	roccat_macro_editor_set_modified(macro_editor, TRUE);
	buttons_set_sensitive(macro_editor);
}

static void store_if_needed(RoccatMacroEditor *macro_editor) {
	RoccatMacroEditorPrivate *priv = macro_editor->priv;
	GtkWidget *dialog;

	if (!priv->edited_modified ||
			roccat_macro_editor_key_sequence_frame_empty(priv->key_sequence_frame) ||
			!gtk_tree_row_reference_valid(priv->reference))
		return;

	dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "Would you like to store actual modifications?");
	gtk_window_set_title(GTK_WINDOW(dialog), "Store modifications");

	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES)
		store(macro_editor);

	gtk_widget_destroy(dialog);
}

static void set(RoccatMacroEditor *macro_editor, GtkTreeRowReference *reference) {
	RoccatMacroEditorPrivate *priv = macro_editor->priv;
	RoccatMacroKeystrokes *keystrokes;
	GtkTreeIter iter;
	GtkTreeModel *model;

	store_if_needed(macro_editor);
	unset(macro_editor);

	gtk_tree_row_reference_free(priv->reference);
	priv->reference = gtk_tree_row_reference_copy(reference);

	model = gtk_tree_row_reference_get_model(reference);
	gtk_roccat_tree_row_reference_get_iter(reference, &iter);

	keystrokes = roccat_macro_tree_store_get_keystrokes(ROCCAT_MACRO_TREE_STORE(model), &iter);

	roccat_macro_editor_macro_options_frame_set_loop(priv->macro_options_frame, keystrokes->loop);
	roccat_macro_editor_key_sequence_frame_set_keystrokes(priv->key_sequence_frame, keystrokes);
	priv->edited_modified = FALSE;

	g_free(keystrokes);
	buttons_set_sensitive(macro_editor);
}

static void edited_modified_cb(GtkWidget *widget, gpointer user_data) {
	RoccatMacroEditor *macro_editor = ROCCAT_MACRO_EDITOR(user_data);
	macro_editor->priv->edited_modified = TRUE;
	buttons_set_sensitive(macro_editor);
}

static void save_button_clicked_cb(GtkButton *button, gpointer user_data) {
	RoccatMacroEditor *macro_editor = ROCCAT_MACRO_EDITOR(user_data);
	store(macro_editor);
}

static void record_button_clicked_cb(RoccatMacroEditorRecordOptionsFrame *frame, gpointer user_data) {
	RoccatMacroEditorPrivate *priv = ROCCAT_MACRO_EDITOR(user_data)->priv;
	glong abs_time;

	/*
	 * Always set times on beginning, so record_delays can be toggled while
	 * recording.
	 * abs_time is subtracted from actual time, so amending keystrokes in
	 * advanced editor continues at end of last element.
	 */
	abs_time = roccat_macro_editor_key_sequence_frame_abs_time(priv->key_sequence_frame);
	clock_gettime(CLOCK_REALTIME, &priv->record_start_time);
	time_minus(&priv->record_start_time, abs_time);
	priv->record_last_event_time = priv->record_start_time;
}

static GdkKeymapKey *get_key_with_group_0(GdkKeymapKey *keys, gint n_keys) {
	gint i;

	for (i = 0; i < n_keys; ++i)
		if (keys[i].group == 0)
			return &keys[i];

	/* fallback: return first element */
	g_warning("can't find keycode for group 0");
	return keys;
}

static void paste_button_clicked_cb(RoccatMacroEditorRecordOptionsFrame *frame, gpointer user_data) {
	RoccatMacroEditorPrivate *priv = ROCCAT_MACRO_EDITOR(user_data)->priv;
	GtkClipboard *clipboard;
	gchar *text;
	gchar const *iter;
	guint keyval;
	gboolean shift = FALSE;
	gboolean alt = FALSE;
	gunichar character;
	GdkKeymapKey *keys;
	GdkKeymapKey *key;
	gint n_keys;
	GdkKeymap *keymap;

	clipboard = gtk_clipboard_get_for_display(gdk_display_get_default(), GDK_SELECTION_PRIMARY);
	text = gtk_clipboard_wait_for_text(clipboard);
	if (text) {
		iter = text;

		keymap = gdk_keymap_get_default();

		while(TRUE) {
			character = g_utf8_get_char_validated(iter, -1);
			if (character == 0)
				break;

			keyval = gdk_unicode_to_keyval(character);

			if (!gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) {
				g_warning("keyval 0x%04x has no keycode!", keyval);
				break;
			}

			key = get_key_with_group_0(keys, n_keys);

			/* First release keys before pressing others */

			if (key->level != 1 && shift) {
				roccat_macro_editor_key_sequence_frame_add_keystroke(priv->key_sequence_frame,
						HID_UID_KB_LEFT_SHIFT, ROCCAT_MACRO_KEYSTROKE_ACTION_RELEASE, 0, 0);
				shift = FALSE;
			}

			if (key->level != 2 && alt) {
				roccat_macro_editor_key_sequence_frame_add_keystroke(priv->key_sequence_frame,
						HID_UID_KB_RIGHT_ALT, ROCCAT_MACRO_KEYSTROKE_ACTION_RELEASE, 0, 0);
				alt = FALSE;
			}

			if (key->level == 1 && !shift) {
				roccat_macro_editor_key_sequence_frame_add_keystroke(priv->key_sequence_frame,
						HID_UID_KB_LEFT_SHIFT, ROCCAT_MACRO_KEYSTROKE_ACTION_PRESS, 0, 0);
				shift = TRUE;
			}

			if (key->level == 2 && !alt) {
				roccat_macro_editor_key_sequence_frame_add_keystroke(priv->key_sequence_frame,
						HID_UID_KB_RIGHT_ALT, ROCCAT_MACRO_KEYSTROKE_ACTION_PRESS, 0, 0);
				alt = TRUE;
			}

			roccat_macro_editor_key_sequence_frame_add_keystroke(priv->key_sequence_frame,
					roccat_xkeycode_to_hid(key->keycode), ROCCAT_MACRO_KEYSTROKE_ACTION_PRESS, 0, 0);
			roccat_macro_editor_key_sequence_frame_add_keystroke(priv->key_sequence_frame,
					roccat_xkeycode_to_hid(key->keycode), ROCCAT_MACRO_KEYSTROKE_ACTION_RELEASE, 0, 0);

			g_free(keys);

			iter = g_utf8_find_next_char(iter, NULL);
		}

		if (shift)
			roccat_macro_editor_key_sequence_frame_add_keystroke(priv->key_sequence_frame,
					HID_UID_KB_LEFT_SHIFT, ROCCAT_MACRO_KEYSTROKE_ACTION_RELEASE, 0, 0);

		if (alt)
			roccat_macro_editor_key_sequence_frame_add_keystroke(priv->key_sequence_frame,
					HID_UID_KB_RIGHT_ALT, ROCCAT_MACRO_KEYSTROKE_ACTION_RELEASE, 0, 0);

		g_free(text);
	}
}

static void macro_selected_cb(RoccatMacroEditorMacrosFrame *frame, GtkTreeRowReference *reference, gpointer user_data) {
	RoccatMacroEditor *macro_editor = ROCCAT_MACRO_EDITOR(user_data);
	set(macro_editor, reference);
}

static void macro_removed_cb(RoccatMacroEditorMacrosFrame *frame, gpointer user_data) {
	RoccatMacroEditor *macro_editor = ROCCAT_MACRO_EDITOR(user_data);
	roccat_macro_editor_set_modified(macro_editor, TRUE);
	unset_if(macro_editor);
}

/* To distinguish between a pressed and released key and autorepeat GDK sends
 * press/press/press/release on autorepeat.
 * Has to see every event in order.
 * It's not reentrant, but doesn't matter here.
 */
static gboolean is_autorepeat_event(GdkEventKey *event) {
	static guint16 last_keycode;
	static guint last_type = GDK_NOTHING;

	if (event->type == GDK_KEY_PRESS && last_type == GDK_KEY_PRESS && event->hardware_keycode == last_keycode)
		return TRUE;

	last_type = event->type;
	last_keycode = event->hardware_keycode;

	return FALSE;
}

static gboolean key_event_cb(GtkWindow *window, GdkEventKey *event, gpointer user_data) {
	RoccatMacroEditor *macro_editor = ROCCAT_MACRO_EDITOR(user_data);
	RoccatMacroEditorPrivate *priv = macro_editor->priv;
	glong abs_time, rel_time;
	struct timespec actual_time;
	guint action;

	if (!is_record_on(macro_editor))
		return FALSE; /* propagate */

	if (is_autorepeat_event(event))
		return TRUE;

	if (should_record_delays(macro_editor)) {
		clock_gettime(CLOCK_REALTIME, &actual_time);
		abs_time = time_diff(&actual_time, &priv->record_start_time);
		rel_time = time_diff(&actual_time, &priv->record_last_event_time);
		priv->record_last_event_time = actual_time;
	} else {
		abs_time = 0;
		rel_time = 0;
	}

	action = (event->type == GDK_KEY_PRESS) ? ROCCAT_MACRO_KEYSTROKE_ACTION_PRESS : ROCCAT_MACRO_KEYSTROKE_ACTION_RELEASE;

	roccat_macro_editor_key_sequence_frame_add_keystroke(priv->key_sequence_frame,
			roccat_xkeycode_to_hid(event->hardware_keycode), action, abs_time, rel_time);

	return TRUE;
}

GtkWidget *roccat_macro_editor_new(void) {
	RoccatMacroEditor *macro_editor;

	macro_editor = g_object_new(ROCCAT_MACRO_EDITOR_TYPE, NULL);

	buttons_set_sensitive(macro_editor);

	return GTK_WIDGET(macro_editor);
}

static void roccat_macro_editor_init(RoccatMacroEditor *macro_editor) {
	RoccatMacroEditorPrivate *priv = ROCCAT_MACRO_EDITOR_GET_PRIVATE(macro_editor);
	GtkWidget *hbox;
	GtkWidget *vbox;
	GtkBox *action_area;

	macro_editor->priv = priv;

	priv->reference = NULL;
	priv->modified = FALSE;
	priv->edited_modified = FALSE;

	vbox = gtk_vbox_new(FALSE, 0);
	hbox = gtk_hbox_new(FALSE, 0);

	priv->save_button = GTK_BUTTON(gtk_button_new_with_label("Save"));
	g_object_ref_sink(priv->save_button);
	priv->macros_frame = ROCCAT_MACRO_EDITOR_MACROS_FRAME(roccat_macro_editor_macros_frame_new());
	g_object_ref_sink(priv->macros_frame);
	priv->macro_options_frame = ROCCAT_MACRO_EDITOR_MACRO_OPTIONS_FRAME(roccat_macro_editor_macro_options_frame_new());
	g_object_ref_sink(priv->macro_options_frame);
	priv->record_options_frame = ROCCAT_MACRO_EDITOR_RECORD_OPTIONS_FRAME(roccat_macro_editor_record_options_frame_new());
	g_object_ref_sink(priv->record_options_frame);
	priv->key_sequence_frame = ROCCAT_MACRO_EDITOR_KEY_SEQUENCE_FRAME(roccat_macro_editor_key_sequence_frame_new());
	g_object_ref_sink(priv->key_sequence_frame);

	gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(priv->record_options_frame), TRUE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(priv->macro_options_frame), TRUE, TRUE, 0);

	gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(priv->key_sequence_frame), TRUE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);

	gtk_paned_pack1(GTK_PANED(macro_editor), GTK_WIDGET(priv->macros_frame), FALSE, FALSE);
	gtk_paned_pack2(GTK_PANED(macro_editor), hbox, TRUE, FALSE);

	action_area = GTK_BOX(roccat_macro_editor_key_sequence_get_action_area(priv->key_sequence_frame));
	gtk_box_pack_start(action_area, GTK_WIDGET(priv->save_button), FALSE, FALSE, 0);
	gtk_box_reorder_child(action_area, GTK_WIDGET(priv->save_button), 0);

	g_signal_connect(G_OBJECT(priv->key_sequence_frame), "modified", G_CALLBACK(edited_modified_cb), macro_editor);
	g_signal_connect(G_OBJECT(priv->macro_options_frame), "modified", G_CALLBACK(edited_modified_cb), macro_editor);
	g_signal_connect(G_OBJECT(priv->record_options_frame), "started", G_CALLBACK(record_button_clicked_cb), macro_editor);
	g_signal_connect(G_OBJECT(priv->record_options_frame), "paste", G_CALLBACK(paste_button_clicked_cb), macro_editor);
	g_signal_connect(G_OBJECT(macro_editor), "key-press-event", G_CALLBACK(key_event_cb), macro_editor);
	g_signal_connect(G_OBJECT(macro_editor), "key-release-event", G_CALLBACK(key_event_cb), macro_editor);
	g_signal_connect(G_OBJECT(priv->macros_frame), "removed", G_CALLBACK(macro_removed_cb), macro_editor);
	g_signal_connect(G_OBJECT(priv->macros_frame), "selected", G_CALLBACK(macro_selected_cb), macro_editor);
	g_signal_connect(G_OBJECT(priv->save_button), "clicked", G_CALLBACK(save_button_clicked_cb), macro_editor);

	gtk_widget_show(GTK_WIDGET(priv->macros_frame));
	gtk_widget_show(GTK_WIDGET(priv->macro_options_frame));
	gtk_widget_show(GTK_WIDGET(priv->record_options_frame));
	gtk_widget_show(GTK_WIDGET(priv->key_sequence_frame));
	gtk_widget_show(GTK_WIDGET(priv->save_button));
	gtk_widget_show(hbox);
	gtk_widget_show(vbox);
}

static void roccat_macro_editor_finalize(GObject *object) {
	RoccatMacroEditorPrivate *priv = ROCCAT_MACRO_EDITOR(object)->priv;
	g_object_unref(priv->macros_frame);
	g_object_unref(priv->macro_options_frame);
	g_object_unref(priv->record_options_frame);
	g_object_unref(priv->key_sequence_frame);
	g_object_unref(priv->save_button);
	G_OBJECT_CLASS(roccat_macro_editor_parent_class)->finalize(object);
}

static void roccat_macro_editor_class_init(RoccatMacroEditorClass *klass) {
	GObjectClass *gobject_class;

	gobject_class = (GObjectClass*)klass;

	gobject_class->finalize = roccat_macro_editor_finalize;

	gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), INSTALL_PREFIX "/share/roccat/icons");

	g_type_class_add_private(klass, sizeof(RoccatMacroEditorPrivate));
}

void roccat_macro_editor_clear(RoccatMacroEditor *macro_editor) {
	roccat_macro_editor_macros_frame_clear(macro_editor->priv->macros_frame);
}

void roccat_macro_editor_add_macros(RoccatMacroEditor *macro_editor, RoccatMacros *macros) {
	if (roccat_macro_editor_macros_frame_add_macros(macro_editor->priv->macros_frame, macros))
		roccat_macro_editor_set_modified(macro_editor, TRUE);
}

RoccatMacros *roccat_macro_editor_get_macros(RoccatMacroEditor *macro_editor) {
	store_if_needed(macro_editor);
	return roccat_macro_editor_macros_frame_get_macros(macro_editor->priv->macros_frame);
}
