/*
 * 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_macro_editor_basic_list_store.h"
#include "roccat_gdk_key_translations.h"
#include "gtk_roccat_helper.h"
#include <stdlib.h>

#define ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_TYPE, RoccatMacroEditorBasicListStoreClass))
#define IS_ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_TYPE))
#define ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_TYPE, RoccatMacroEditorBasicListStorePrivate))

typedef struct _RoccatMacroEditorBasicListStoreClass RoccatMacroEditorBasicListStoreClass;

struct _RoccatMacroEditorBasicListStoreClass {
	GtkListStoreClass parent_class;
	void (*modified)(RoccatMacroEditorBasicListStore *roccat_macro_editor_basic_list_store);
	void (*empty_changed)(RoccatMacroEditorBasicListStore *roccat_macro_editor_basic_list_store);
};

struct _RoccatMacroEditorBasicListStorePrivate {
	gboolean modified;
};

G_DEFINE_TYPE(RoccatMacroEditorBasicListStore, roccat_macro_editor_basic_list_store, GTK_TYPE_LIST_STORE);

enum {
	MODIFIED,
	EMPTY_CHANGED,
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

enum {
	ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_ICON_COLUMN,
	ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_TEXT_COLUMN,
	ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_IS_KEY_COLUMN,
	ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_IS_NOT_KEY_COLUMN,
	ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_KEY_COLUMN,
	ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_ACTION_COLUMN,
	ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_PERIOD_COLUMN,
	ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_N_COLUMNS
};

static void modified(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store) {
	g_signal_emit((gpointer)macro_editor_basic_list_store, signals[MODIFIED], 0);
}

static void empty_changed(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store) {
	g_signal_emit((gpointer)macro_editor_basic_list_store, signals[EMPTY_CHANGED], 0);
}

void roccat_macro_editor_basic_list_store_set_modified(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, gboolean state) {
	macro_editor_basic_list_store->priv->modified = state;
	if (state)
		modified(macro_editor_basic_list_store);
}

gboolean roccat_macro_editor_basic_list_store_get_modified(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store) {
	return macro_editor_basic_list_store->priv->modified;
}

RoccatMacroEditorBasicListStore *roccat_macro_editor_basic_list_store_new(void) {
	RoccatMacroEditorBasicListStore *macro_editor_basic_list_store;
	GType types[ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_N_COLUMNS] = {
		G_TYPE_STRING,
		G_TYPE_STRING,
		G_TYPE_BOOLEAN,
		G_TYPE_BOOLEAN,
		G_TYPE_UINT,
		G_TYPE_UINT,
		G_TYPE_UINT
	};

	macro_editor_basic_list_store = g_object_new(ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_TYPE,
			NULL);

	gtk_list_store_set_column_types(GTK_LIST_STORE(macro_editor_basic_list_store),
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_N_COLUMNS, types);

	return macro_editor_basic_list_store;
}

static void roccat_macro_editor_basic_list_store_init(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store) {
	RoccatMacroEditorBasicListStorePrivate *priv = ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_GET_PRIVATE(macro_editor_basic_list_store);

	macro_editor_basic_list_store->priv = priv;

	priv->modified = FALSE;
}

static void roccat_macro_editor_basic_list_store_class_init(RoccatMacroEditorBasicListStoreClass *klass) {
	g_type_class_add_private(klass, sizeof(RoccatMacroEditorBasicListStorePrivate));

	signals[MODIFIED] = g_signal_new("modified",
			G_TYPE_FROM_CLASS(klass),
			G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
			G_STRUCT_OFFSET(RoccatMacroEditorBasicListStoreClass, modified),
			NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

	signals[EMPTY_CHANGED] = g_signal_new("empty-changed",
			G_TYPE_FROM_CLASS(klass),
			G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
			G_STRUCT_OFFSET(RoccatMacroEditorBasicListStoreClass, empty_changed),
			NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
}

static void insert_single_action_key_before(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, GtkTreeIter *sibling, guint key, guint action) {
	GtkListStore *store;
	GtkTreeIter iter;
	gchar *title;
	gchar const *icon;

	store = GTK_LIST_STORE(macro_editor_basic_list_store);

	title = roccat_hid_to_keyname(key);
	icon = (action == ROCCAT_MACRO_KEYSTROKE_ACTION_PRESS) ? "roccat-press" : "roccat-release";

	gtk_list_store_insert_before(store, &iter, sibling);
	gtk_list_store_set(store, &iter,
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_ICON_COLUMN, icon,
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_TEXT_COLUMN, title,
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_IS_KEY_COLUMN, TRUE,
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_IS_NOT_KEY_COLUMN, FALSE,
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_KEY_COLUMN, key,
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_ACTION_COLUMN, action,
			-1);
	g_free(title);
}

void roccat_macro_editor_basic_list_store_insert_single_action_key_before(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, GtkTreeIter *sibling, guint key, guint action) {
	gboolean was_empty = roccat_macro_editor_basic_list_store_empty(macro_editor_basic_list_store);
	insert_single_action_key_before(macro_editor_basic_list_store, sibling, key, action);
	roccat_macro_editor_basic_list_store_set_modified(macro_editor_basic_list_store, TRUE);
	if (was_empty)
		empty_changed(macro_editor_basic_list_store);
}

static gboolean is_key(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, GtkTreeIter *iter) {
	gboolean is_key;
	gtk_tree_model_get(GTK_TREE_MODEL(macro_editor_basic_list_store), iter, ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_IS_KEY_COLUMN, &is_key, -1);
	return is_key;
}

static gboolean previous_is_key(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, GtkTreeIter *iter) {
	gboolean valid;
	GtkTreeIter test_iter;

	if (iter) {
		test_iter = *iter;
		valid = gtk_roccat_tree_model_get_iter_previous(GTK_TREE_MODEL(macro_editor_basic_list_store), &test_iter);
	} else
		valid = gtk_roccat_tree_model_get_iter_last(GTK_TREE_MODEL(macro_editor_basic_list_store), &test_iter, NULL);

	if (valid)
		return is_key(macro_editor_basic_list_store, &test_iter);
	else
		return FALSE;
}

gboolean insert_period_before(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, GtkTreeIter *sibling, glong rel_time) {
	GtkListStore *store;
	GtkTreeIter iter;
	gchar *title;

	if (rel_time == 0)
		return FALSE;

	if (!previous_is_key(macro_editor_basic_list_store, sibling))
		return FALSE;

	store = GTK_LIST_STORE(macro_editor_basic_list_store);

	title = g_strdup_printf("%li", rel_time);

	gtk_list_store_insert_before(store, &iter, sibling);
	gtk_list_store_set(store, &iter,
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_ICON_COLUMN, "roccat-wait",
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_TEXT_COLUMN, title,
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_IS_KEY_COLUMN, FALSE,
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_IS_NOT_KEY_COLUMN, TRUE,
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_PERIOD_COLUMN, rel_time,
			-1);
	g_free(title);
	return TRUE;
}

gboolean roccat_macro_editor_basic_list_store_insert_period_before(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, GtkTreeIter *sibling, glong rel_time) {
	gboolean retval;

	retval = insert_period_before(macro_editor_basic_list_store, sibling, rel_time);
	if (retval)
		roccat_macro_editor_basic_list_store_set_modified(macro_editor_basic_list_store, TRUE);

	return retval;
}

void roccat_macro_editor_basic_list_store_insert_dual_action_key_before(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, GtkTreeIter *sibling, guint key) {
	gboolean was_empty = roccat_macro_editor_basic_list_store_empty(macro_editor_basic_list_store);
	insert_single_action_key_before(macro_editor_basic_list_store, sibling, key, ROCCAT_MACRO_KEYSTROKE_ACTION_PRESS);
	insert_single_action_key_before(macro_editor_basic_list_store, sibling, key, ROCCAT_MACRO_KEYSTROKE_ACTION_RELEASE);
	roccat_macro_editor_basic_list_store_set_modified(macro_editor_basic_list_store, TRUE);
	if (was_empty)
		empty_changed(macro_editor_basic_list_store);
}

void roccat_macro_editor_basic_list_store_insert_keystroke_before(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, GtkTreeIter *sibling, guint key, guint action, glong rel_time) {
	gboolean was_empty = roccat_macro_editor_basic_list_store_empty(macro_editor_basic_list_store);
	insert_single_action_key_before(macro_editor_basic_list_store, sibling, key, action);
	insert_period_before(macro_editor_basic_list_store, sibling, rel_time);
	roccat_macro_editor_basic_list_store_set_modified(macro_editor_basic_list_store, TRUE);
	if (was_empty)
		empty_changed(macro_editor_basic_list_store);
}

void roccat_macro_editor_basic_list_store_insert_roccat_macro_keystroke_before(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, GtkTreeIter *sibling, RoccatMacroKeystroke const *keystroke) {
	roccat_macro_editor_basic_list_store_insert_keystroke_before(macro_editor_basic_list_store, sibling, keystroke->key, keystroke->action, roccat_macro_keystroke_get_period(keystroke));
}

gboolean roccat_macro_editor_basic_list_store_remove(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, GtkTreeIter *iter) {
	gboolean result;
	gboolean is_key;
	gboolean was_empty = roccat_macro_editor_basic_list_store_empty(macro_editor_basic_list_store);

	result = gtk_list_store_remove(GTK_LIST_STORE(macro_editor_basic_list_store), iter);
	if (result) {
		/* iter points now to following element
		 * remove it if its a period */
		gtk_tree_model_get(GTK_TREE_MODEL(macro_editor_basic_list_store), iter, ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_IS_KEY_COLUMN, &is_key, -1);
		if (!is_key)
			gtk_list_store_remove(GTK_LIST_STORE(macro_editor_basic_list_store), iter);
	}
	roccat_macro_editor_basic_list_store_set_modified(macro_editor_basic_list_store, TRUE);
	if (!was_empty)
		empty_changed(macro_editor_basic_list_store);
	return result;
}

void roccat_macro_editor_basic_list_store_set_keystrokes(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, RoccatMacroKeystrokes const *keystrokes) {
	guint count;
	guint i;

	roccat_macro_editor_basic_list_store_clear(macro_editor_basic_list_store);

	count = roccat_macro_keystrokes_get_count(keystrokes);
	for (i = 0; i < count; ++i)
		roccat_macro_editor_basic_list_store_insert_roccat_macro_keystroke_before(macro_editor_basic_list_store, NULL, &keystrokes->keystrokes[i]);
}

RoccatMacroKeystrokes *roccat_macro_editor_basic_list_store_get_keystrokes(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store) {
	GtkTreeModel *model;
	GtkTreeIter iter;
	RoccatMacroKeystrokes *keystrokes;
	RoccatMacroKeystroke keystroke;
	gboolean added;
	gboolean key_type;
	guint key;
	guint action;
	guint period;

	keystrokes = roccat_macro_keystrokes_new();

	model = GTK_TREE_MODEL(macro_editor_basic_list_store);
	if (!gtk_tree_model_get_iter_first(model, &iter))
		return keystrokes;

	added = TRUE;

	do {
		gtk_tree_model_get(model, &iter,
				ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_IS_KEY_COLUMN, &key_type,
				ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_KEY_COLUMN, &key,
				ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_ACTION_COLUMN, &action,
				ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_PERIOD_COLUMN, &period,
				-1);

		if (key_type) {
			if (!added)
				roccat_macro_keystrokes_add(keystrokes, &keystroke);

			keystroke.action = action;
			keystroke.key = key;
			roccat_macro_keystroke_set_period(&keystroke, 0);
			added = FALSE;
		} else {
			if (added)
				g_critical("dangling period");
			roccat_macro_keystroke_set_period(&keystroke, period);
		}

	} while (gtk_tree_model_iter_next(model, &iter));

	if (!added)
		roccat_macro_keystrokes_add(keystrokes, &keystroke);

	return keystrokes;
}

void roccat_macro_editor_basic_list_store_set_value(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store, GtkTreeIter *iter, gchar const *text) {
	gint value;

	value = atoi(text);
	if (value < 1 || value > ROCCAT_MACRO_KEYSTROKE_PERIOD_MAX)
		return;

	gtk_list_store_set(GTK_LIST_STORE(macro_editor_basic_list_store), iter,
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_PERIOD_COLUMN, value,
			ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_TEXT_COLUMN, text,
			-1);

	roccat_macro_editor_basic_list_store_set_modified(macro_editor_basic_list_store, TRUE);
}

void roccat_macro_editor_basic_list_store_clear(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store) {
	gboolean was_empty = roccat_macro_editor_basic_list_store_empty(macro_editor_basic_list_store);

	gtk_list_store_clear(GTK_LIST_STORE(macro_editor_basic_list_store));
	roccat_macro_editor_basic_list_store_set_modified(macro_editor_basic_list_store, TRUE);
	if (!was_empty)
		empty_changed(macro_editor_basic_list_store);
}

gboolean roccat_macro_editor_basic_list_store_empty(RoccatMacroEditorBasicListStore *macro_editor_basic_list_store) {
	GtkTreeIter iter;
	return !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(macro_editor_basic_list_store), &iter);
}

guint roccat_macro_editor_basic_list_store_icon_column(void) {
	return ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_ICON_COLUMN;
}

guint roccat_macro_editor_basic_list_store_text_column(void) {
	return ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_TEXT_COLUMN;
}

guint roccat_macro_editor_basic_list_store_is_key_column(void) {
	return ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_IS_KEY_COLUMN;
}

guint roccat_macro_editor_basic_list_store_is_not_key_column(void) {
	return ROCCAT_MACRO_EDITOR_BASIC_LIST_STORE_IS_NOT_KEY_COLUMN;
}
