/*
 * 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 "ryos_eventhandler.h"
#include "ryos_eventhandler_channel.h"
#include "ryos_dbus_server.h"
#include "ryos_ripple_thread.h"
#include "roccat/ryos.h"
#include "config.h"
#include "hid_uid.h"
#include <glib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define RYOS_EVENTHANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), RYOS_EVENTHANDLER_TYPE, RyosEventhandlerClass))
#define IS_RYOS_EVENTHANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), RYOS_EVENTHANDLER_TYPE))
#define RYOS_EVENTHANDLER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), RYOS_EVENTHANDLER_TYPE, RyosEventhandlerPrivate))

typedef struct _RyosEventhandlerClass RyosEventhandlerClass;

struct _RyosEventhandlerClass {
	GObjectClass parent_class;
};

struct _RyosEventhandlerPrivate {
	RoccatEventhandlerHost *host;
	RyosDBusServer *dbus_server;
	RoccatDeviceScanner *device_scanner;
	RoccatDevice *device;
	gboolean device_set_up;
	RoccatKeyFile *config;
	guint actual_profile_index;
	RyosRkp *rkp[RYOS_PROFILE_NUM];
	RyosEventhandlerChannel *channel;

	RoccatNotificationProfile *profile_note;
	RoccatNotificationTimer *timer_note;
	RoccatNotificationLiveRecording *live_recording_note;

	RyosRippleThread *ripple_thread;
};

enum {
	PROP_0,
	PROP_HOST,
};

static void profile_changed(RyosEventhandler *eventhandler, guint profile_index) {
	RyosEventhandlerPrivate *priv = eventhandler->priv;

	priv->actual_profile_index = profile_index;

	roccat_notification_profile_update(priv->profile_note, ryos_configuration_get_profile_notification_type(priv->config),
			profile_index + 1, ryos_rkp_get_profile_name(priv->rkp[profile_index]));

	ryos_dbus_server_emit_profile_changed(priv->dbus_server, profile_index);
}

static gboolean set_profile(RyosEventhandler *eventhandler, guint profile_index) {
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	GError *local_error = NULL;

	if (priv->actual_profile_index != profile_index) {
		if (!ryos_profile_write(priv->device, profile_index, &local_error)) {
			// FIXME error message
			g_clear_error(&local_error);
			return FALSE;
		}
		profile_changed(eventhandler, profile_index);
	}
	return TRUE;
}

static gboolean talk_easyshift(RyosEventhandler *eventhandler, gboolean state) {
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	GError *error = NULL;
	gboolean retval = FALSE;

	if (priv->device_set_up) {
		retval = ryos_talk_easyshift(priv->device,
				(state != FALSE) ? RYOS_TALK_EASYSHIFT_ON : RYOS_TALK_EASYSHIFT_OFF,
				&error);
		if (error) {
			g_warning("Error issuing easyshift: %s", error->message);
			g_clear_error(&error);
		}
	}

	return retval;
}

static gboolean talk_easyshift_lock(RyosEventhandler *eventhandler, gboolean state) {
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	GError *error = NULL;
	gboolean retval = FALSE;

	if (priv->device_set_up) {
		retval = ryos_talk_easyshift_lock(priv->device,
				(state != FALSE) ? RYOS_TALK_EASYSHIFT_ON : RYOS_TALK_EASYSHIFT_OFF,
				&error);
		if (error) {
			g_warning("Error issuing easyshift_lock: %s", error->message);
			g_clear_error(&error);
		}
	}

	return retval;
}

static guint ryos_eventhandler_get_talk_device(RoccatEventhandlerPlugin *self) {
	RyosEventhandlerPrivate *priv = RYOS_EVENTHANDLER(self)->priv;

	if (!priv->device_set_up)
		return 0;

	return roccat_device_get_product_id(priv->device);
}

static void ryos_eventhandler_talk_easyshift(RoccatEventhandlerPlugin *self, gboolean state) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(self);
	talk_easyshift(eventhandler, state);
}

static void ryos_eventhandler_talk_easyshift_lock(RoccatEventhandlerPlugin *self, gboolean state) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(self);
	talk_easyshift_lock(eventhandler, state);
}

static void ryos_roccat_eventhandler_interface_init(RoccatEventhandlerPluginInterface *iface) {
	iface->get_talk_device = ryos_eventhandler_get_talk_device;
	iface->talk_easyshift = ryos_eventhandler_talk_easyshift;
	iface->talk_easyshift_lock = ryos_eventhandler_talk_easyshift_lock;
	iface->talk_easyaim = NULL;
	iface->set_led_rgb = NULL;
	iface->restore_led_rgb = NULL;
}

G_DEFINE_TYPE_WITH_CODE(RyosEventhandler, ryos_eventhandler, G_TYPE_OBJECT,
		G_IMPLEMENT_INTERFACE(ROCCAT_EVENTHANDLER_PLUGIN_TYPE, ryos_roccat_eventhandler_interface_init));

GObject *ryos_eventhandler_new(RoccatEventhandlerHost *host) {
	return g_object_new(RYOS_EVENTHANDLER_TYPE,
			"host", host,
			NULL);
}

static void talk_easyshift_cb(RyosDBusServer *server, guchar state, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	talk_easyshift(eventhandler, state);
}

static void talk_easyshift_lock_cb(RyosDBusServer *server, guchar state, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	talk_easyshift_lock(eventhandler, state);
}

static void open_gui_cb(gpointer source, gpointer user_data) {
	gchar *path;
	path = g_build_path("/", INSTALL_PREFIX, "bin", "ryosconfig", (gchar const *)NULL);
	roccat_double_fork(path);
	g_free(path);
}

static void profile_data_changed_outside_cb(RyosDBusServer *server, guchar profile_index, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	RyosEventhandlerPrivate *priv = eventhandler->priv;

	ryos_rkp_free(priv->rkp[profile_index]);
	priv->rkp[profile_index] = ryos_rkp_load_actual(profile_index);
}

static void configuration_reload(RyosEventhandler *eventhandler) {
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	if (priv->config)
		ryos_configuration_free(priv->config);
	priv->config = ryos_configuration_load();
}

static void configuration_changed_outside_cb(RyosDBusServer *server, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	configuration_reload(eventhandler);
}

static void set_kb_sdk_mode_cb(RyosDBusServer *server, gboolean state, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	ryos_sdk_set_kb_sdk_mode(eventhandler->priv->device, state, NULL);
}

static void set_all_leds_cb(RyosDBusServer *server, GArray const *data, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	ryos_sdk_set_all_leds(eventhandler->priv->device, data, NULL);
}

static void turn_on_all_leds_cb(RyosDBusServer *server, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	ryos_sdk_turn_on_all_leds(eventhandler->priv->device, NULL);
}

static void turn_off_all_leds_cb(RyosDBusServer *server, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	ryos_sdk_turn_off_all_leds(eventhandler->priv->device, NULL);
}

static void set_led_on_cb(RyosDBusServer *server, guchar index, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	ryos_sdk_set_led_on(eventhandler->priv->device, index, NULL);
}

static void set_led_off_cb(RyosDBusServer *server, guchar index, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	ryos_sdk_set_led_off(eventhandler->priv->device, index, NULL);
}

static void all_key_blinking_cb(RyosDBusServer *server, guint interval, guint count, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	ryos_sdk_all_key_blinking(eventhandler->priv->device, interval, count, NULL);
}

static void profile_changed_cb(gpointer source, guchar profile_index, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	profile_changed(eventhandler, profile_index);
}

static void quicklaunch_cb(RyosEventhandlerChannel *channel, guchar key_index, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	RyosRkpQuicklaunch *quicklaunch;
	gchar *path;

	quicklaunch = ryos_rkp_get_quicklaunch(priv->rkp[priv->actual_profile_index], key_index);
	path = ryos_rkp_quicklaunch_get_path(quicklaunch);

	roccat_double_fork(path);

	g_free(path);
	g_free(quicklaunch);
}

static void ripple_cb(RyosEventhandlerChannel *channel, guchar hid, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	RyosEventhandlerPrivate *priv = eventhandler->priv;

	hid = ryos_keys_primary_correct_remapped(&priv->rkp[priv->actual_profile_index]->data.keys_primary, hid);

	if (priv->ripple_thread && ryos_ripple_thread_get_running(priv->ripple_thread)) {
		ryos_ripple_thread_set_cancelled(priv->ripple_thread);
		g_object_unref(priv->ripple_thread);
		priv->ripple_thread = NULL;
	}
	priv->ripple_thread = ryos_ripple_thread_new(priv->device, hid);
}

static void talk_cb(RyosEventhandlerChannel *channel, guchar hid, guchar event, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	RyosRkpTalk *talk;
	static gboolean state = FALSE;
	gint talk_index;

	talk_index = ryos_hid_to_rkp_talk_index(hid);
	if (talk_index == -1) {
		g_warning("couldn't find talk info for hid 0x%02x", hid);
		return;
	}

	talk = &priv->rkp[priv->actual_profile_index]->data.talks[talk_index];

	switch(talk->type) {
	case RYOS_KEY_TYPE_TALK_EASYSHIFT:
		roccat_eventhandler_plugin_emit_talk_easyshift(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ryos_rkp_talk_get_product_id(talk),
				(event == RYOS_CHRDEV_EVENT_ACTION_PRESS) ? TRUE : FALSE);
		break;
	case RYOS_KEY_TYPE_TALK_EASYSHIFT_LOCK:
		if (event == RYOS_CHRDEV_EVENT_ACTION_PRESS) {
			state = !state;
			roccat_eventhandler_plugin_emit_talk_easyshift_lock(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
					ryos_rkp_talk_get_product_id(talk), state);
		}
		break;
	case RYOS_KEY_TYPE_TALK_EASYAIM_1:
		roccat_eventhandler_plugin_emit_talk_easyaim(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ryos_rkp_talk_get_product_id(talk),
				(event == RYOS_CHRDEV_EVENT_ACTION_PRESS) ? ROCCAT_TALK_EASYAIM_1 : ROCCAT_TALK_EASYAIM_OFF);
		break;
	case RYOS_KEY_TYPE_TALK_EASYAIM_2:
		roccat_eventhandler_plugin_emit_talk_easyaim(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ryos_rkp_talk_get_product_id(talk),
				(event == RYOS_CHRDEV_EVENT_ACTION_PRESS) ? ROCCAT_TALK_EASYAIM_2 : ROCCAT_TALK_EASYAIM_OFF);
		break;
	case RYOS_KEY_TYPE_TALK_EASYAIM_3:
		roccat_eventhandler_plugin_emit_talk_easyaim(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ryos_rkp_talk_get_product_id(talk),
				(event == RYOS_CHRDEV_EVENT_ACTION_PRESS) ? ROCCAT_TALK_EASYAIM_3 : ROCCAT_TALK_EASYAIM_OFF);
		break;
	case RYOS_KEY_TYPE_TALK_EASYAIM_4:
		roccat_eventhandler_plugin_emit_talk_easyaim(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ryos_rkp_talk_get_product_id(talk),
				(event == RYOS_CHRDEV_EVENT_ACTION_PRESS) ? ROCCAT_TALK_EASYAIM_4 : ROCCAT_TALK_EASYAIM_OFF);
		break;
	case RYOS_KEY_TYPE_TALK_EASYAIM_5:
		roccat_eventhandler_plugin_emit_talk_easyaim(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ryos_rkp_talk_get_product_id(talk),
				(event == RYOS_CHRDEV_EVENT_ACTION_PRESS) ? ROCCAT_TALK_EASYAIM_5 : ROCCAT_TALK_EASYAIM_OFF);
		break;
	default:
		g_message("unknown talk_type 0x%02x", talk->type);
		break;
	}
}

static void light_changed_cb(RyosEventhandlerChannel *channel, guchar brightness_index, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	guint profile_index;
	GError *error = NULL;

	profile_index = priv->actual_profile_index;

	ryos_rkp_set_light_brightness(priv->rkp[profile_index], brightness_index);
	ryos_rkp_save_actual(priv->rkp[profile_index], profile_index, &error);
	if (error) {
		g_clear_error(&error);
		return;
	}
	ryos_rkp_set_unmodified(priv->rkp[profile_index]);

	ryos_dbus_server_emit_brightness_changed(priv->dbus_server, profile_index, brightness_index);
}

static gboolean recording_event_store_roccat_macro(RyosMacro const *macro, GError **error) {
	RoccatMacros *roccat_macros;
	RoccatMacro *roccat_macro;
	gboolean retval;

	roccat_macro = ryos_macro_to_roccat_macro(macro);

	/*
	 * Using device specific macroset name instead of default "MLR"
	 * because macro file is shared between devices.
	 * Macroset name stored in device is still "MLR" to reduce writes.
	 */
	roccat_macro_set_macro_set_name(roccat_macro, RYOS_DEVICE_NAME " MLR");

	roccat_macros = roccat_macros_load();
	roccat_macros_set(roccat_macros, roccat_macro);
	retval = roccat_macros_save(roccat_macros, error);
	roccat_macros_free(roccat_macros);
	roccat_macro_free(roccat_macro);

	return retval;
}

static RyosMacro *recording_event_get_data(RoccatDevice *device, guint profile_index, guint key_index, GError **error) {
	return ryos_macro_read(device, profile_index, key_index, error);
}

static gboolean handle_recording_event(RyosEventhandler *eventhandler, guint profile_index, guint key_index) {
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	RyosMacro *macro;
	GError *error = NULL;

	macro = recording_event_get_data(priv->device, profile_index, key_index, &error);
	if (error) {
		g_clear_error(&error);
		return FALSE;
	}

	ryos_rkp_set_key_to_macro(priv->rkp[profile_index], key_index);
	ryos_rkp_set_macro(priv->rkp[profile_index], key_index, macro);

	ryos_rkp_save_actual(priv->rkp[profile_index], profile_index, &error);
	if (error) {
		g_clear_error(&error);
		g_free(macro);
		return FALSE;
	}
	ryos_rkp_set_unmodified(priv->rkp[profile_index]);

	recording_event_store_roccat_macro(macro, &error);
	if (error) {
		g_clear_error(&error);
		g_free(macro);
		return FALSE;
	}

	/* rkp has to be stored before this */
	ryos_dbus_server_emit_macro_changed(priv->dbus_server, profile_index, key_index, macro);

	g_free(macro);
	return TRUE;
}

static void live_recording_cb(RyosEventhandlerChannel *channel, guchar key_index, guchar event, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	RyosEventhandlerPrivate *priv = eventhandler->priv;

	switch(event) {
	case RYOS_CHRDEV_EVENT_LIVE_RECORDING_ACTION_START:
		roccat_notification_live_recording_select_macro_key(priv->live_recording_note,
				ryos_configuration_get_live_recording_notification_type(priv->config));
		break;
	case RYOS_CHRDEV_EVENT_LIVE_RECORDING_ACTION_MACRO_KEY_SELECTED:
		roccat_notification_live_recording_record_macro(priv->live_recording_note,
				ryos_configuration_get_live_recording_notification_type(priv->config));
		break;
	case RYOS_CHRDEV_EVENT_LIVE_RECORDING_ACTION_END_SUCCESS:
		roccat_notification_live_recording_end_success(priv->live_recording_note,
				ryos_configuration_get_live_recording_notification_type(priv->config));
		handle_recording_event(eventhandler, priv->actual_profile_index, key_index);
		break;
	case RYOS_CHRDEV_EVENT_LIVE_RECORDING_ACTION_END_ABORT:
		roccat_notification_live_recording_end_abort(priv->live_recording_note,
				ryos_configuration_get_live_recording_notification_type(priv->config));
		break;
	case RYOS_CHRDEV_EVENT_LIVE_RECORDING_ACTION_INVALID_KEY:
		roccat_notification_live_recording_invalid_key(priv->live_recording_note,
				ryos_configuration_get_live_recording_notification_type(priv->config));
		break;
	default:
		g_debug("unknown live recording event of type 0x%2x", event);
	}
}

static void timer_start_cb(RyosEventhandlerChannel *channel, guchar key_index, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	RyosRkpTimer *timer;

	timer = ryos_rkp_get_timer(priv->rkp[priv->actual_profile_index], key_index);

	roccat_notification_timer_start(priv->timer_note,
			ryos_configuration_get_timer_notification_type(priv->config),
			(gchar const *)timer->name, timer->duration);
	g_free(timer);
}

static void timer_stop_cb(RyosEventhandlerChannel *channel, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	RyosEventhandlerPrivate *priv = eventhandler->priv;

	roccat_notification_timer_stop(priv->timer_note);
}

static gboolean profile_data_load(RyosEventhandler *eventhandler, GError **error) {
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	GError *local_error = NULL;
	guint i;

	for (i = 0; i < RYOS_PROFILE_NUM; ++i) {
		priv->rkp[i] = ryos_rkp_load(priv->device, i, &local_error);
		if (local_error) {
			g_propagate_error(error, local_error);
			return FALSE;
		}
	}
	return TRUE;
}

static void window_changed_cb(RoccatEventhandlerHost *host, gchar const *title, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	guint profile_index;
	guint gamefile_index;
	gchar *pattern;

	for (profile_index = 0; profile_index < RYOS_PROFILE_NUM; ++profile_index) {
		for (gamefile_index = 0; gamefile_index < RYOS_RKP_PROFILE_GAMEFILE_NUM; ++gamefile_index) {
			pattern = ryos_rkp_get_gamefile_name(priv->rkp[profile_index], gamefile_index);
			if (strcmp(pattern, "") == 0) {
				g_free(pattern);
				continue;
			}

			if (g_regex_match_simple(pattern, title, 0, 0)) {
				set_profile(eventhandler, profile_index);
				g_free(pattern);
				return;
			}
			g_free(pattern);
		}
	}

	set_profile(eventhandler, ryos_configuration_get_default_profile_number(priv->config) - 1);
	return;
}

static gboolean add_cb(RoccatDevice *device, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	GError *error = NULL;

	if (priv->device) {
		g_critical("Multiple devices present.");
		return TRUE;
	}

	priv->device = device;

	configuration_reload(eventhandler);

	/* Notificators gets initialized only once if a device is found.
	 * Get deinited once in finalizer.
	 */
	if (!priv->profile_note)
		priv->profile_note = roccat_notification_profile_new(roccat_eventhandler_host_get_notificator(priv->host), RYOS_DEVICE_NAME);
	if (!priv->timer_note)
		priv->timer_note = roccat_notification_timer_new(roccat_eventhandler_host_get_notificator(priv->host), RYOS_DEVICE_NAME);
	if (!priv->live_recording_note)
		priv->live_recording_note = roccat_notification_live_recording_new(roccat_eventhandler_host_get_notificator(priv->host), RYOS_DEVICE_NAME);

	priv->actual_profile_index = ryos_profile_read(priv->device, &error);
	if (error) {
		g_warning("Error reading actual profile: %s", error->message);
		g_clear_error(&error);
	}

	profile_data_load(eventhandler, &error);
	if (error) {
		g_warning("Error reading profile data: %s", error->message);
		g_clear_error(&error);
	}

	priv->dbus_server = ryos_dbus_server_new();
	ryos_dbus_server_connect(priv->dbus_server);
	g_signal_connect(G_OBJECT(priv->dbus_server), "easyshift", G_CALLBACK(talk_easyshift_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "easyshift-lock", G_CALLBACK(talk_easyshift_lock_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "open-gui", G_CALLBACK(open_gui_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "profile-changed-outside", G_CALLBACK(profile_changed_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "profile-data-changed-outside", G_CALLBACK(profile_data_changed_outside_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "configuration-changed-outside", G_CALLBACK(configuration_changed_outside_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "set-kb-sdk-mode", G_CALLBACK(set_kb_sdk_mode_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "set-all-leds", G_CALLBACK(set_all_leds_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "turn-on-all-leds", G_CALLBACK(turn_on_all_leds_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "turn-off-all-leds", G_CALLBACK(turn_off_all_leds_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "set-led-on", G_CALLBACK(set_led_on_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "set-led-off", G_CALLBACK(set_led_off_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "all-key-blinking", G_CALLBACK(all_key_blinking_cb), eventhandler);

	g_signal_connect(G_OBJECT(priv->host), "active-window-changed", G_CALLBACK(window_changed_cb), eventhandler);

	ryos_eventhandler_channel_start(priv->channel, roccat_device_get_roccat_dev(priv->device), &error);
	if (error) {
		g_warning("Error starting chardev eventhandler: %s", error->message);
		g_clear_error(&error);
	}

	priv->device_set_up = TRUE;

	return FALSE;
}

static void stop(RyosEventhandler *eventhandler) {
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	guint i;

	priv->device_set_up = FALSE;

	g_object_unref(priv->dbus_server);
	priv->dbus_server = NULL;

	ryos_eventhandler_channel_stop(priv->channel);

	roccat_device_free(priv->device);
	priv->device = NULL;

	ryos_configuration_free(priv->config);
	priv->config = NULL;

	for (i = 0; i < RYOS_PROFILE_NUM; ++i) {
		ryos_rkp_free(priv->rkp[i]);
		priv->rkp[i] = NULL;
	}
}

static gboolean remove_cb(RoccatDevice *device, gpointer user_data) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(user_data);
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	if (priv->device && strcmp(roccat_device_get_identifier(device), roccat_device_get_identifier(priv->device)) == 0) {
		stop(eventhandler);
	}
	return TRUE;
}

static void ryos_eventhandler_init(RyosEventhandler *eventhandler) {
	eventhandler->priv = RYOS_EVENTHANDLER_GET_PRIVATE(eventhandler);
	RyosEventhandlerPrivate *priv = eventhandler->priv;
	priv->ripple_thread = NULL;
}

static void finalize(GObject *object) {
	RyosEventhandler *eventhandler = RYOS_EVENTHANDLER(object);
	RyosEventhandlerPrivate *priv = eventhandler->priv;

	g_object_unref(priv->channel);
	priv->channel = NULL;

	roccat_device_scanner_free(priv->device_scanner);
	priv->device_scanner = NULL;

	stop(eventhandler);

	if (priv->live_recording_note) {
		roccat_notification_live_recording_free(priv->live_recording_note);
		priv->live_recording_note = NULL;
	}

	if (priv->timer_note) {
		roccat_notification_timer_free(priv->timer_note);
		priv->timer_note = NULL;
	}

	if (priv->profile_note) {
		roccat_notification_profile_free(priv->profile_note);
		priv->profile_note = NULL;
	}

	if (priv->ripple_thread) {
		g_object_unref(priv->ripple_thread);
		priv->ripple_thread = NULL;
	}

	G_OBJECT_CLASS(ryos_eventhandler_parent_class)->finalize(object);
}

static GObject *constructor(GType gtype, guint n_properties, GObjectConstructParam *properties) {
	RyosEventhandler *eventhandler;
	RyosEventhandlerPrivate *priv;
	GObject *obj;

	obj = G_OBJECT_CLASS(ryos_eventhandler_parent_class)->constructor(gtype, n_properties, properties);
	eventhandler = RYOS_EVENTHANDLER(obj);
	priv = eventhandler->priv;

	priv->channel = ryos_eventhandler_channel_new();
	g_signal_connect(G_OBJECT(priv->channel), "open-driver", G_CALLBACK(open_gui_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->channel), "profile-changed", G_CALLBACK(profile_changed_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->channel), "quicklaunch", G_CALLBACK(quicklaunch_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->channel), "ripple", G_CALLBACK(ripple_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->channel), "talk", G_CALLBACK(talk_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->channel), "timer-start", G_CALLBACK(timer_start_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->channel), "timer-stop", G_CALLBACK(timer_stop_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->channel), "light-changed", G_CALLBACK(light_changed_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->channel), "live-recording", G_CALLBACK(live_recording_cb), eventhandler);

	priv->device_scanner = ryos_device_scanner_new(add_cb, remove_cb, eventhandler);

	return obj;
}

static void set_property(GObject *object, guint prop_id, GValue const *value, GParamSpec *pspec) {
	RyosEventhandlerPrivate *priv = RYOS_EVENTHANDLER(object)->priv;

	switch(prop_id) {
	case PROP_HOST:
		priv->host = g_value_get_pointer(value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
	}
}

static void ryos_eventhandler_class_init(RyosEventhandlerClass *klass) {
	GObjectClass *gobject_class;

	gobject_class = G_OBJECT_CLASS(klass);

	gobject_class->constructor = constructor;
	gobject_class->finalize = finalize;
	gobject_class->set_property = set_property;

	g_type_class_add_private(klass, sizeof(RyosEventhandlerPrivate));

	g_object_class_install_property(gobject_class, PROP_HOST,
			g_param_spec_pointer("host",
					"Host",
					"Pointer to RoccatEventhandlerHost interface",
					G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
}

ROCCAT_EVENTHANDLER_PLUGIN_API RoccatEventhandlerPlugin *roccat_eventhandler_plugin_new(RoccatEventhandlerHost *host) {
	return ROCCAT_EVENTHANDLER_PLUGIN(ryos_eventhandler_new(host));
}
