/*
 * 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_thread.h"
#include "g_roccat_helper.h"
#include "g_thread_roccat_helper.c"

#define ROCCAT_MACRO_THREAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ROCCAT_MACRO_THREAD_TYPE, RoccatMacroThreadClass))
#define IS_ROCCAT_MACRO_THREAD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ROCCAT_MACRO_THREAD_TYPE))
#define ROCCAT_MACRO_THREAD_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ROCCAT_MACRO_THREAD_TYPE, RoccatMacroThreadPrivate))

typedef struct _RoccatMacroThreadClass RoccatMacroThreadClass;

struct _RoccatMacroThreadClass {
	GObjectClass parent_class;
};

struct _RoccatMacroThreadPrivate {
	GThread *thread;

	GRoccatMutex mutex;
	GRoccatCond condition;
	gboolean cancelled;
	gboolean paused;

	gint running;

	int kbd_file;
	int mouse_file;
	RoccatMacro *macro;
};

G_DEFINE_TYPE(RoccatMacroThread, roccat_macro_thread, G_TYPE_OBJECT);

void roccat_macro_thread_set_paused(RoccatMacroThread *roccat_macro_thread, gboolean value) {
	RoccatMacroThreadPrivate *priv = roccat_macro_thread->priv;
	g_roccat_mutex_lock(&priv->mutex);
	priv->paused = value;
	if (!value)
		g_roccat_cond_signal(&priv->condition);
	g_roccat_mutex_unlock(&priv->mutex);
}

void roccat_macro_thread_toggle_paused(RoccatMacroThread *roccat_macro_thread) {
	RoccatMacroThreadPrivate *priv = roccat_macro_thread->priv;
	g_roccat_mutex_lock(&priv->mutex);
	priv->paused = priv->paused ? FALSE : TRUE;
	if (!priv->paused)
		g_roccat_cond_signal(&priv->condition);
	g_roccat_mutex_unlock(&priv->mutex);
}

gboolean roccat_macro_thread_get_paused(RoccatMacroThread *roccat_macro_thread) {
	RoccatMacroThreadPrivate *priv = roccat_macro_thread->priv;
	gboolean retval;
	g_roccat_mutex_lock(&priv->mutex);
	retval = priv->paused;
	g_roccat_mutex_unlock(&priv->mutex);
	return retval;
}

static void roccat_macro_thread_set_running(RoccatMacroThread *roccat_macro_thread, gboolean value) {
	g_atomic_int_set(&roccat_macro_thread->priv->running, value);
}

gboolean roccat_macro_thread_get_running(RoccatMacroThread *roccat_macro_thread) {
	return g_atomic_int_get(&roccat_macro_thread->priv->running);
}

void roccat_macro_thread_set_cancelled(RoccatMacroThread *roccat_macro_thread) {
	RoccatMacroThreadPrivate *priv = roccat_macro_thread->priv;
	g_roccat_mutex_lock(&priv->mutex);
	priv->cancelled = TRUE;
	g_roccat_cond_signal(&priv->condition);
	g_roccat_mutex_unlock(&priv->mutex);
}

gboolean roccat_macro_thread_get_cancelled(RoccatMacroThread *roccat_macro_thread) {
	RoccatMacroThreadPrivate *priv = roccat_macro_thread->priv;
	gboolean retval;
	g_roccat_mutex_lock(&priv->mutex);
	retval = priv->cancelled;
	g_roccat_mutex_unlock(&priv->mutex);
	return retval;
}

static guint8 roccat_keystroke_action_to_event_action(guint8 action) {
	return (action == ROCCAT_MACRO_KEYSTROKE_ACTION_PRESS) ? ROCCAT_INPUT_EVENT_VALUE_PRESS : ROCCAT_INPUT_EVENT_VALUE_RELEASE;
}

/* IDEA invent save macro option
 * keep track of pressed buttons/keys and release on cancel
 */
static gpointer thread(gpointer user_data) {
	RoccatMacroThread *macro_thread = ROCCAT_MACRO_THREAD(user_data);
	RoccatMacroThreadPrivate *priv = macro_thread->priv;
	guint key_count, key;
	guint loop_count, loop;
	RoccatMacroKeystroke const *keystroke;

	g_debug("Playing macro %s/%s", priv->macro->macro_set, priv->macro->macro);

	key_count = roccat_macro_keystrokes_get_count(&priv->macro->keystrokes);
	loop_count = priv->macro->keystrokes.loop;
	
	for (loop = 0; loop < loop_count; ++loop) {
		for (key = 0; key < key_count; ++key) {
			
			g_roccat_mutex_lock(&priv->mutex);

			if (priv->paused && !priv->cancelled)
				g_debug("Pausing macro %s/%s", priv->macro->macro_set, priv->macro->macro);
			
			while (priv->paused && !priv->cancelled) {
				g_roccat_cond_wait(&priv->condition, &priv->mutex);
				if (!priv->paused && !priv->cancelled)
					g_debug("Continuing macro %s/%s", priv->macro->macro_set, priv->macro->macro);
			}
			
			if (priv->cancelled) {
				g_debug("Cancelling macro %s/%s", priv->macro->macro_set, priv->macro->macro);
				g_roccat_mutex_unlock(&priv->mutex);
				goto exit;
			}

			g_roccat_mutex_unlock(&priv->mutex);

			keystroke = &priv->macro->keystrokes.keystrokes[key];
			if (roccat_hid_is_mouse_button(keystroke->key))
				roccat_input_event_write_with_sync(priv->mouse_file, roccat_hid_to_btn_keycode(keystroke->key), roccat_keystroke_action_to_event_action(keystroke->action));
			else
				roccat_input_event_write_with_sync(priv->kbd_file, roccat_hid_to_kbd_keycode(keystroke->key), roccat_keystroke_action_to_event_action(keystroke->action));
			
			/* Skip wait of last key in last loop to improve cancel/restart behaviour. */
			if (key != key_count - 1 || loop != loop_count - 1)
				g_usleep(roccat_macro_keystroke_get_period(keystroke) * G_ROCCAT_USEC_PER_MSEC);
		}
	}

	g_debug("Finished macro %s/%s", priv->macro->macro_set, priv->macro->macro);
exit:
	roccat_macro_thread_set_running(macro_thread, FALSE);
	return NULL;
}

RoccatMacroThread *roccat_macro_thread_new(int kbd_file, int mouse_file, RoccatMacro const *macro) {
	GObject *object;
	RoccatMacroThread *macro_thread;
	RoccatMacroThreadPrivate *priv;
	GError *error = NULL;
	
	object = g_object_new(ROCCAT_MACRO_THREAD_TYPE, NULL);
	macro_thread = ROCCAT_MACRO_THREAD(object);
	priv = macro_thread->priv;
	
	priv->kbd_file = kbd_file;
	priv->mouse_file = mouse_file;
	priv->macro = roccat_macro_dup(macro);
	priv->cancelled = FALSE;
	priv->paused = FALSE;
	roccat_macro_thread_set_running(macro_thread, TRUE);

	priv->thread = g_roccat_thread_try_new("RoccatMacro", thread, macro_thread, &error);

	if (error) {
		g_warning("Can't create macro thread: %s", error->message);
		g_clear_error(&error);
	}
	
	return macro_thread;
}

static void roccat_macro_thread_init(RoccatMacroThread *macro_thread_data) {
	RoccatMacroThreadPrivate *priv = ROCCAT_MACRO_THREAD_GET_PRIVATE(macro_thread_data);
	macro_thread_data->priv = priv;

	g_roccat_mutex_init(&priv->mutex);
	g_roccat_cond_init(&priv->condition);
}

static void roccat_macro_thread_finalize(GObject *object) {
	RoccatMacroThreadPrivate *priv = ROCCAT_MACRO_THREAD(object)->priv;
	
	if (priv->thread)
		g_thread_join(priv->thread);

	roccat_macro_free(priv->macro);

	g_roccat_mutex_clear(&priv->mutex);
	g_roccat_cond_clear(&priv->condition);
	
	G_OBJECT_CLASS(roccat_macro_thread_parent_class)->finalize(object);
}

static void roccat_macro_thread_class_init(RoccatMacroThreadClass *klass) {
	GObjectClass *gobject_class;

	gobject_class = (GObjectClass*)klass;

	gobject_class->finalize = roccat_macro_thread_finalize;

	g_type_class_add_private(klass, sizeof(RoccatMacroThreadPrivate));
}
