/*
 * 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_advanced_list_store.h"
#include "roccat_dscale.h"
#include "roccat_gdk_key_translations.h"
#include "gtk_roccat_helper.h"
#include "g_roccat_helper.h"
#include <math.h>

// TODO bookkeeping of max for speedup

#define ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_TYPE, RoccatMacroEditorAdvancedListStoreClass))
#define IS_ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_TYPE))
#define ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_TYPE, RoccatMacroEditorAdvancedListStorePrivate))

typedef struct _RoccatMacroEditorAdvancedListStoreClass RoccatMacroEditorAdvancedListStoreClass;

struct _RoccatMacroEditorAdvancedListStoreClass {
	GtkListStoreClass parent_class;
	void (*max_changed)(RoccatMacroEditorAdvancedListStore *roccat_macro_editor_advanced_list_store);
	void (*empty_changed)(RoccatMacroEditorAdvancedListStore *roccat_macro_editor_advanced_list_store);
	void (*modified)(RoccatMacroEditorAdvancedListStore *roccat_macro_editor_advanced_list_store);
	void (*keypress_added)(RoccatMacroEditorAdvancedListStore *roccat_macro_editor_advanced_list_store, GtkTreeIter *iter);
};

struct _RoccatMacroEditorAdvancedListStorePrivate {
	gboolean modified;
};

G_DEFINE_TYPE(RoccatMacroEditorAdvancedListStore, roccat_macro_editor_advanced_list_store, GTK_TYPE_LIST_STORE);

enum {
	MAX_CHANGED,
	EMPTY_CHANGED,
	MODIFIED,
	KEYPRESS_ADDED,
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

// TODO thats ugly (see also spin_button_set_value_without_upper_bound)
static gdouble spin_button_initial_max = 10.0;

enum {
	ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_KEY_COLUMN,
	ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_ACTION_COLUMN,
	ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_LABEL_COLUMN,
	ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_LOWER_COLUMN,
	ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_UPPER_COLUMN,
	ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_SCALE_COLUMN,
	ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_N_COLUMNS
};

static glong sec_to_msec(gdouble sec) {
	return (glong)round(sec * (gdouble)G_ROCCAT_MSEC_PER_SEC);
}

static gdouble msec_to_sec(glong msec) {
	return (gdouble)msec / (gdouble)G_ROCCAT_MSEC_PER_SEC;
}

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

static void keypress_added(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, GtkTreeIter *iter) {
	g_signal_emit((gpointer)macro_editor_advanced_list_store, signals[KEYPRESS_ADDED], 0, iter);
}

static void max_changed(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store) {
	g_signal_emit((gpointer)macro_editor_advanced_list_store, signals[MAX_CHANGED], 0);
}

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

void roccat_macro_editor_advanced_list_store_set_modified(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, gboolean state) {
	macro_editor_advanced_list_store->priv->modified = state;
	if (state)
		modified(macro_editor_advanced_list_store);
}

gboolean roccat_macro_editor_advanced_list_store_get_modified(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store) {
	return macro_editor_advanced_list_store->priv->modified;
}

RoccatMacroEditorAdvancedListStore *roccat_macro_editor_advanced_list_store_new(void) {
	RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store;
	GType types[ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_N_COLUMNS] = {
		G_TYPE_UINT,
		G_TYPE_UINT,
		G_TYPE_OBJECT,
		G_TYPE_OBJECT,
		G_TYPE_OBJECT,
		G_TYPE_OBJECT
	};

	macro_editor_advanced_list_store = g_object_new(ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_TYPE,
			NULL);

	gtk_list_store_set_column_types(GTK_LIST_STORE(macro_editor_advanced_list_store),
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_N_COLUMNS, types);

	return macro_editor_advanced_list_store;
}

static void roccat_macro_editor_advanced_list_store_init(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store) {
	RoccatMacroEditorAdvancedListStorePrivate *priv = ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_GET_PRIVATE(macro_editor_advanced_list_store);

	macro_editor_advanced_list_store->priv = priv;

	priv->modified = FALSE;
}

static void roccat_macro_editor_advanced_list_store_finalize(GObject *object) {
	G_OBJECT_CLASS(roccat_macro_editor_advanced_list_store_parent_class)->finalize(object);
}

static void roccat_macro_editor_advanced_list_store_class_init(RoccatMacroEditorAdvancedListStoreClass *klass) {
	GObjectClass *gobject_class;

	gobject_class = G_OBJECT_CLASS(klass);

	gobject_class->finalize = roccat_macro_editor_advanced_list_store_finalize;

	g_type_class_add_private(klass, sizeof(RoccatMacroEditorAdvancedListStorePrivate));

	signals[MAX_CHANGED] = g_signal_new("max-changed",
			G_TYPE_FROM_CLASS(klass),
			G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
			G_STRUCT_OFFSET(RoccatMacroEditorAdvancedListStoreClass, max_changed),
			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(RoccatMacroEditorAdvancedListStoreClass, empty_changed),
			NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

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

	signals[KEYPRESS_ADDED] = g_signal_new("keypress-added",
			G_TYPE_FROM_CLASS(klass),
			G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
			G_STRUCT_OFFSET(RoccatMacroEditorAdvancedListStoreClass, keypress_added),
			NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
}

/*
 * Settings max to G_MAXDOUBLE makes the spinbutton bigger than needed.
 * TODO how to disable upper bound a better way?
 */
static void spin_button_set_value_without_upper_bound(GtkSpinButton *spin, gdouble new_value) {
	gdouble max;
	gtk_spin_button_get_range(spin, NULL, &max);
	/* + 1 lets me simulate variable max for spin button */
	gtk_spin_button_set_range(spin, 0, MAX(max, new_value + 0.001));
	gtk_spin_button_set_value(spin, new_value);
}

static void dscale_max_changed_cb(RoccatDScale *dscale, gpointer user_data) {
	RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store = ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE(user_data);
	gdouble max;
	max = roccat_dscale_get_max(dscale);
	roccat_macro_editor_advanced_list_store_set_all_max(macro_editor_advanced_list_store, max);
	max_changed(macro_editor_advanced_list_store);
}

static void dscale_lower_changed_cb(RoccatDScale *dscale, gpointer user_data) {
	GtkTreeRowReference *reference = (GtkTreeRowReference *)user_data;
	RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store;
	GtkWidget *spin;
	GtkTreeIter iter;

	macro_editor_advanced_list_store = ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE(gtk_tree_row_reference_get_model(reference));
	gtk_roccat_tree_row_reference_get_iter(reference, &iter);

	spin = roccat_macro_editor_advanced_list_store_get_lower_widget(macro_editor_advanced_list_store, &iter);
	spin_button_set_value_without_upper_bound(GTK_SPIN_BUTTON(spin), roccat_dscale_get_lower(dscale));
	g_object_unref(spin);

	roccat_macro_editor_advanced_list_store_set_modified(macro_editor_advanced_list_store, TRUE);
}

static void dscale_upper_changed_cb(RoccatDScale *dscale, gpointer user_data) {
	GtkTreeRowReference *reference = (GtkTreeRowReference *)user_data;
	RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store;
	GtkWidget *spin;
	GtkTreeIter iter;

	macro_editor_advanced_list_store = ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE(gtk_tree_row_reference_get_model(reference));
	gtk_roccat_tree_row_reference_get_iter(reference, &iter);

	spin = roccat_macro_editor_advanced_list_store_get_upper_widget(macro_editor_advanced_list_store, &iter);
	spin_button_set_value_without_upper_bound(GTK_SPIN_BUTTON(spin), roccat_dscale_get_upper(dscale));
	g_object_unref(spin);

	roccat_macro_editor_advanced_list_store_set_modified(macro_editor_advanced_list_store, TRUE);
}

static void spin_button_lower_changed_cb(GtkSpinButton *spin_button, gpointer user_data) {
	GtkTreeRowReference *reference = (GtkTreeRowReference *)user_data;
	RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store;
	GtkWidget *scale;
	GtkTreeIter iter;

	macro_editor_advanced_list_store = ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE(gtk_tree_row_reference_get_model(reference));
	gtk_roccat_tree_row_reference_get_iter(reference, &iter);

	scale = roccat_macro_editor_advanced_list_store_get_scale_widget(macro_editor_advanced_list_store, &iter);
	roccat_dscale_set_lower(ROCCAT_DSCALE(scale), gtk_spin_button_get_value(spin_button));
	g_object_unref(scale);

	roccat_macro_editor_advanced_list_store_set_modified(macro_editor_advanced_list_store, TRUE);
}

static void spin_button_upper_changed_cb(GtkSpinButton *spin_button, gpointer user_data) {
	GtkTreeRowReference *reference = (GtkTreeRowReference *)user_data;
	RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store;
	GtkWidget *scale;
	GtkTreeIter iter;

	macro_editor_advanced_list_store = ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE(gtk_tree_row_reference_get_model(reference));
	gtk_roccat_tree_row_reference_get_iter(reference, &iter);

	scale = roccat_macro_editor_advanced_list_store_get_scale_widget(macro_editor_advanced_list_store, &iter);
	roccat_dscale_set_upper(ROCCAT_DSCALE(scale), gtk_spin_button_get_value(spin_button));
	g_object_unref(scale);

	roccat_macro_editor_advanced_list_store_set_modified(macro_editor_advanced_list_store, TRUE);
}

static void set_label(GtkLabel *label, guint usage_id) {
	gchar *keyname;
	keyname = roccat_hid_to_keyname(usage_id);
	gtk_label_set_text(label, keyname);
	g_free(keyname);
}

static void add_press(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store,
		GtkTreeIter *iter, guint key, guint action, gdouble seconds) {
	GtkLabel *label;
	GtkSpinButton *lower;
	GtkSpinButton *upper;
	RoccatDScale *scale;
	GtkTreeRowReference *reference;
	gboolean was_empty;

	was_empty = roccat_macro_editor_advanced_list_store_empty(macro_editor_advanced_list_store);

	gtk_list_store_append(GTK_LIST_STORE(macro_editor_advanced_list_store), iter);

	// FIXME Store reference in store (as boxed?) and make sure gtk_tree_row_reference_free is called
	reference = gtk_roccat_tree_row_reference_new_from_iter(GTK_TREE_MODEL(macro_editor_advanced_list_store), iter);

	label = GTK_LABEL(gtk_label_new(NULL));

	lower = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(0.0, spin_button_initial_max, 0.001));
	gtk_spin_button_set_digits(lower, 3);
	gtk_spin_button_set_update_policy(lower, GTK_UPDATE_IF_VALID);

	upper = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(0.0, spin_button_initial_max, 0.001));
	gtk_spin_button_set_digits(upper, 3);
	gtk_spin_button_set_update_policy(upper, GTK_UPDATE_IF_VALID);

	scale = ROCCAT_DSCALE(roccat_dscale_new());
	roccat_dscale_set_min(scale, (gdouble)0.0);
	roccat_dscale_set_fix_min(scale, TRUE);
	roccat_dscale_set_fix_max(scale, FALSE);

	gtk_list_store_set(GTK_LIST_STORE(macro_editor_advanced_list_store), iter,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_KEY_COLUMN, key,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_ACTION_COLUMN, action,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_LABEL_COLUMN, label,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_LOWER_COLUMN, lower,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_UPPER_COLUMN, upper,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_SCALE_COLUMN, scale,
			-1);

	set_label(label, key);
	spin_button_set_value_without_upper_bound(lower, seconds);
	roccat_dscale_set_lower(scale, seconds);

	g_signal_connect(G_OBJECT(lower), "value-changed", G_CALLBACK(spin_button_lower_changed_cb), reference);
	g_signal_connect(G_OBJECT(upper), "value-changed", G_CALLBACK(spin_button_upper_changed_cb), reference);
	g_signal_connect(G_OBJECT(scale), "max-changed", G_CALLBACK(dscale_max_changed_cb), macro_editor_advanced_list_store);
	g_signal_connect(G_OBJECT(scale), "lower-changed", G_CALLBACK(dscale_lower_changed_cb), reference);
	g_signal_connect(G_OBJECT(scale), "upper-changed", G_CALLBACK(dscale_upper_changed_cb), reference);

	roccat_macro_editor_advanced_list_store_set_modified(macro_editor_advanced_list_store, TRUE);
	keypress_added(macro_editor_advanced_list_store, iter);
	if (was_empty)
		empty_changed(macro_editor_advanced_list_store);
}

static void add_release(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store,
		GtkTreeIter *iter, GtkTreeIter *press, guint key, guint action, gdouble seconds) {
	GtkWidget *label;
	GtkWidget *lower;
	GtkWidget *upper;
	GtkWidget *scale;
	gboolean was_empty;

	was_empty = roccat_macro_editor_advanced_list_store_empty(macro_editor_advanced_list_store);

	gtk_list_store_append(GTK_LIST_STORE(macro_editor_advanced_list_store), iter);

	gtk_tree_model_get(GTK_TREE_MODEL(macro_editor_advanced_list_store), press,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_LABEL_COLUMN, &label,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_LOWER_COLUMN, &lower,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_UPPER_COLUMN, &upper,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_SCALE_COLUMN, &scale,
			-1);

	gtk_list_store_set(GTK_LIST_STORE(macro_editor_advanced_list_store), iter,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_KEY_COLUMN, key,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_ACTION_COLUMN, action,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_LABEL_COLUMN, label,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_LOWER_COLUMN, lower,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_UPPER_COLUMN, upper,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_SCALE_COLUMN, scale,
			-1);

	roccat_dscale_set_upper(ROCCAT_DSCALE(scale), seconds);
	spin_button_set_value_without_upper_bound(GTK_SPIN_BUTTON(upper), seconds);

	roccat_macro_editor_advanced_list_store_set_modified(macro_editor_advanced_list_store, TRUE);
	if (was_empty)
		empty_changed(macro_editor_advanced_list_store);
}

static guint get_key(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, GtkTreeIter *iter) {
	guint key;
	gtk_tree_model_get(GTK_TREE_MODEL(macro_editor_advanced_list_store), iter,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_KEY_COLUMN, &key,
			-1);
	return key;
}

static guint get_action(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, GtkTreeIter *iter) {
	guint action;
	gtk_tree_model_get(GTK_TREE_MODEL(macro_editor_advanced_list_store), iter,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_ACTION_COLUMN, &action,
			-1);
	return action;
}

static gdouble get_max(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, GtkTreeIter *iter) {
	GtkWidget *dscale;
	gdouble value;
	dscale = roccat_macro_editor_advanced_list_store_get_scale_widget(macro_editor_advanced_list_store, iter);
	value = roccat_dscale_get_max(ROCCAT_DSCALE(dscale));
	g_object_unref(dscale);
	return value;
}

static gboolean find_backwards(RoccatMacroEditorAdvancedListStore *store, GtkTreeIter *iter, guint key) {
	guint other_key;
	GtkTreeIter temp_iter;

	if (!gtk_roccat_tree_model_get_iter_last(GTK_TREE_MODEL(store), &temp_iter, NULL))
		return FALSE;

	do {
		other_key = get_key(store, &temp_iter);
		if (other_key == key) {
			*iter = temp_iter;
			return TRUE;
		}
	} while (gtk_roccat_tree_model_get_iter_previous(GTK_TREE_MODEL(store), &temp_iter));

	return FALSE;
}

void roccat_macro_editor_advanced_list_store_add_keystroke(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, guint key, guint action, glong abs_time) {
	gdouble seconds;
	GtkTreeIter press_iter;
	GtkTreeIter iter;
	gboolean iter_is_valid;

	seconds = msec_to_sec(abs_time);

	if (action == ROCCAT_MACRO_KEYSTROKE_ACTION_PRESS)
		add_press(macro_editor_advanced_list_store, &iter, key, action, seconds);
	else {
		iter_is_valid = find_backwards(macro_editor_advanced_list_store, &press_iter, key);
		if (iter_is_valid)
			add_release(macro_editor_advanced_list_store, &iter, &press_iter, key, action, seconds);
		else
			g_warning("no up for %x", key);
	}

}

GtkWidget *roccat_macro_editor_advanced_list_store_get_label_widget(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, GtkTreeIter *iter) {
	GtkWidget *widget;
	gtk_tree_model_get(GTK_TREE_MODEL(macro_editor_advanced_list_store), iter,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_LABEL_COLUMN, &widget,
			-1);
	return widget;
}

GtkWidget *roccat_macro_editor_advanced_list_store_get_lower_widget(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, GtkTreeIter *iter) {
	GtkWidget *widget;
	gtk_tree_model_get(GTK_TREE_MODEL(macro_editor_advanced_list_store), iter,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_LOWER_COLUMN, &widget,
			-1);
	return widget;
}

GtkWidget *roccat_macro_editor_advanced_list_store_get_upper_widget(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, GtkTreeIter *iter) {
	GtkWidget *widget;
	gtk_tree_model_get(GTK_TREE_MODEL(macro_editor_advanced_list_store), iter,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_UPPER_COLUMN, &widget,
			-1);
	return widget;
}

GtkWidget *roccat_macro_editor_advanced_list_store_get_scale_widget(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, GtkTreeIter *iter) {
	GtkWidget *widget;
	gtk_tree_model_get(GTK_TREE_MODEL(macro_editor_advanced_list_store), iter,
			ROCCAT_MACRO_EDITOR_ADVANCED_LIST_STORE_SCALE_COLUMN, &widget,
			-1);
	return widget;
}

glong roccat_macro_editor_advanced_list_store_get_abs_time(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store) {
	return sec_to_msec(roccat_macro_editor_advanced_list_store_get_all_max(macro_editor_advanced_list_store));
}

gdouble roccat_macro_editor_advanced_list_store_get_all_max(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store) {
	gdouble max = 0.0;
	gdouble value;
	GtkTreeIter iter;

	if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(macro_editor_advanced_list_store), &iter))
		return 0.0;

	do {
		value = get_max(macro_editor_advanced_list_store, &iter);
		if (value > max)
			max = value;
	} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(macro_editor_advanced_list_store), &iter));

	return max;
}

/* TODO disadvantage: most scales are set twice */
void roccat_macro_editor_advanced_list_store_set_all_max(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, gdouble value) {
	GtkWidget *dscale;
	GtkTreeIter iter;

	if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(macro_editor_advanced_list_store), &iter))
		return;

	do {
		dscale = roccat_macro_editor_advanced_list_store_get_scale_widget(macro_editor_advanced_list_store, &iter);
		roccat_dscale_set_max(ROCCAT_DSCALE(dscale), value);
		g_object_unref(dscale);
	} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(macro_editor_advanced_list_store), &iter));
}

static gdouble get_seconds(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, GtkTreeIter *iter) {
	GtkWidget *dscale;
	gdouble value;

	dscale = roccat_macro_editor_advanced_list_store_get_scale_widget(macro_editor_advanced_list_store, iter);

	if (get_action(macro_editor_advanced_list_store, iter) == ROCCAT_MACRO_KEYSTROKE_ACTION_PRESS)
		value = roccat_dscale_get_lower(ROCCAT_DSCALE(dscale));
	else
		value = roccat_dscale_get_upper(ROCCAT_DSCALE(dscale));

	g_object_unref(dscale);

	return value;
}

/*
 * Needs a stable sort algorithm. Current implementation is bubble sort.
 * TODO replace current sort implemenation with faster alternative.
 */
void sort(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store) {
	GtkTreeIter actual_iter;
	GtkTreeIter next_iter;
	gboolean swapped = TRUE;
	gdouble actual_value;
	gdouble next_value;

	while (swapped) {
		if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(macro_editor_advanced_list_store), &actual_iter))
			return;

		swapped = FALSE;

		while (TRUE) {
			next_iter = actual_iter;
			if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(macro_editor_advanced_list_store), &next_iter))
				break;

			actual_value = get_seconds(macro_editor_advanced_list_store, &actual_iter);
			next_value = get_seconds(macro_editor_advanced_list_store, &next_iter);

			if (next_value < actual_value) {
				gtk_list_store_swap(GTK_LIST_STORE(macro_editor_advanced_list_store), &actual_iter, &next_iter);
				swapped = TRUE;
			}

			actual_iter = next_iter;
		};
	}
}

RoccatMacroKeystrokes *roccat_macro_editor_advanced_list_store_get_keystrokes(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store) {
	RoccatMacroKeystrokes *keystrokes;
	RoccatMacroKeystroke keystroke;
	GtkTreeIter actual_iter;
	GtkTreeIter next_iter;
	gdouble actual_value;
	gdouble next_value;
	gboolean next_valid;

	sort(macro_editor_advanced_list_store);

	keystrokes = roccat_macro_keystrokes_new();

	if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(macro_editor_advanced_list_store), &actual_iter))
		return keystrokes;

	while (TRUE) {
		next_iter = actual_iter;
		next_valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(macro_editor_advanced_list_store), &next_iter);

		if (next_valid) {
			actual_value = get_seconds(macro_editor_advanced_list_store, &actual_iter);
			next_value = get_seconds(macro_editor_advanced_list_store, &next_iter);
			roccat_macro_keystroke_set_period(&keystroke, sec_to_msec(next_value - actual_value));
		} else
			roccat_macro_keystroke_set_period(&keystroke, 0);

		keystroke.key = get_key(macro_editor_advanced_list_store, &actual_iter);
		keystroke.action = get_action(macro_editor_advanced_list_store, &actual_iter);

		roccat_macro_keystrokes_add(keystrokes, &keystroke);

		if (next_valid)
			actual_iter = next_iter;
		else
			break;
	};

	return keystrokes;
}

static void equalize_max_time(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store) {
	gdouble max;
	max = roccat_macro_editor_advanced_list_store_get_all_max(macro_editor_advanced_list_store);
	roccat_macro_editor_advanced_list_store_set_all_max(macro_editor_advanced_list_store, MAX(1.001, max));
}

void roccat_macro_editor_advanced_list_store_set_keystrokes(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store, RoccatMacroKeystrokes const *macro_keystrokes) {
	RoccatMacroKeystroke const *keystroke;
	guint i;
	guint count;
	glong abs_time;

	roccat_macro_editor_advanced_list_store_clear(macro_editor_advanced_list_store);

	count = roccat_macro_keystrokes_get_count(macro_keystrokes);
	abs_time = 0;
	for (i = 0; i < count; ++i) {
		keystroke = &macro_keystrokes->keystrokes[i];
		roccat_macro_editor_advanced_list_store_add_keystroke(macro_editor_advanced_list_store, keystroke->key, keystroke->action, abs_time);
		abs_time += roccat_macro_keystroke_get_period(keystroke);
	}

	equalize_max_time(macro_editor_advanced_list_store);
}

void roccat_macro_editor_advanced_list_store_clear(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store) {
	gboolean was_empty;

	was_empty = roccat_macro_editor_advanced_list_store_empty(macro_editor_advanced_list_store);

	gtk_list_store_clear(GTK_LIST_STORE(macro_editor_advanced_list_store));
	modified(macro_editor_advanced_list_store);
	if (!was_empty)
		empty_changed(macro_editor_advanced_list_store);
}

gboolean roccat_macro_editor_advanced_list_store_empty(RoccatMacroEditorAdvancedListStore *macro_editor_advanced_list_store) {
	GtkTreeIter iter;
	return !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(macro_editor_advanced_list_store), &iter);
}
