/*
 * 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 "konextd_eventhandler.h"
#include "konextd_eventhandler_channel.h"
#include "konextd_dbus_server.h"
#include "roccat_notification_cpi.h"
#include "roccat_notification_profile.h"
#include "roccat_notification_sensitivity.h"
#include "roccat_notification_timer.h"
#include "roccat_process_helper.h"
#include "roccat_talk.h"
#include "konextd.h"
#include "config.h"
#include <glib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define KONEXTD_EVENTHANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), KONEXTD_EVENTHANDLER_TYPE, KonextdEventhandlerClass))
#define IS_KONEXTD_EVENTHANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), KONEXTD_EVENTHANDLER_TYPE))
#define KONEXTD_EVENTHANDLER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), KONEXTD_EVENTHANDLER_TYPE, KonextdEventhandlerPrivate))

typedef struct _KonextdEventhandlerClass KonextdEventhandlerClass;

struct _KonextdEventhandlerClass {
	GObjectClass parent_class;
};

struct _KonextdEventhandlerPrivate {
	RoccatEventhandlerHost *host;
	KonextdDBusServer *dbus_server;
	RoccatDeviceScanner *device_scanner;
	RoccatDevice *device;
	gboolean device_set_up;
	RoccatKeyFile *config;
	guint actual_profile_index;
	guint actual_sensitivity_x;
	KoneplusRmp *rmp[KONEPLUS_PROFILE_NUM];
	KonextdEventhandlerChannel *channel;
	gulong active_window_changed_handler;

	/*
	 * FIXME notificator für Fehlermeldungen verwenden,
	 * notificator refaktorisieren und Basis für beides verwenden oder
	 * Eigene Implementierung eines Fehlernotificators
	 */
	RoccatNotificationProfile *profile_note;
	RoccatNotificationCpi *cpi_note;
	RoccatNotificationSensitivity *sensitivity_note;
	RoccatNotificationTimer *timer_note;
};

enum {
	PROP_0,
	PROP_HOST,
};

static void profile_changed(KonextdEventhandler *eventhandler, guint profile_number) {
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	guint profile_index = profile_number - 1;

	priv->actual_profile_index = profile_index;

	roccat_notification_profile_update(priv->profile_note, koneplus_configuration_get_profile_notification_type(priv->config),
			koneplus_configuration_get_notification_volume(priv->config),
			profile_number, koneplus_rmp_get_profile_name(priv->rmp[profile_index]));

	konextd_dbus_server_emit_profile_changed(priv->dbus_server, profile_number);
}

static gboolean set_profile(KonextdEventhandler *eventhandler, guint profile_number) {
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	guint profile_index;
	GError *local_error = NULL;

	if (profile_number == 0)
		return TRUE;

	profile_index = profile_number - 1;

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

static void window_changed_cb(RoccatEventhandlerHost *host, gchar const *title, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	guint profile_index;
	guint game_file_index;
	gchar *pattern;

	for (profile_index = 0; profile_index < KONEPLUS_PROFILE_NUM; ++profile_index) {

		if (priv->rmp[profile_index] == NULL)
			continue;

		for (game_file_index = 0; game_file_index < KONEPLUS_GAMEFILE_NUM; ++game_file_index) {
			pattern = koneplus_rmp_get_game_file_name(priv->rmp[profile_index], game_file_index);
			if (strcmp(pattern, "") == 0) {
				g_free(pattern);
				continue;
			}

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

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

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

	if (priv->device_set_up) {
		retval = koneplus_talk_easyshift(priv->device,
				(state != FALSE) ? KONEPLUS_TALK_EASYSHIFT_ON : KONEPLUS_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(KonextdEventhandler *eventhandler, gboolean state) {
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	GError *error = NULL;
	gboolean retval = FALSE;

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

	return retval;
}

static gboolean talk_easyaim(KonextdEventhandler *eventhandler, guchar state) {
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	GError *error = NULL;
	gboolean retval = FALSE;

	if (priv->device_set_up) {
		retval = koneplus_talk_easyaim(priv->device, state, &error);
		if (error) {
			g_warning("Error issuing easyaim: %s", error->message);
			g_clear_error(&error);
		}
	}

	return retval;
}

static gboolean set_led_rgb(KonextdEventhandler *eventhandler, guint32 effect, guint32 ambient_color, guint32 event_color) {
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	GError *error = NULL;
	gboolean retval = FALSE;

	if (priv->device_set_up) {
		retval = koneplus_talkfx(priv->device, effect, ambient_color, event_color, &error);
		if (error) {
			g_warning("Error issuing talkfx: %s", error->message);
			g_clear_error(&error);
		}
	}

	return retval;
}

static gboolean restore_led_rgb(KonextdEventhandler *eventhandler) {
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	GError *error = NULL;
	gboolean retval = FALSE;

	if (priv->device_set_up) {
		retval = koneplus_talkfx_off(priv->device, &error);
		if (error) {
			g_warning("Error issuing talkfx_off: %s", error->message);
			g_clear_error(&error);
		}
	}

	return retval;
}

static guint konextd_eventhandler_get_talk_device(RoccatEventhandlerPlugin *self) {
	KonextdEventhandlerPrivate *priv = KONEXTD_EVENTHANDLER(self)->priv;

	if (!priv->device_set_up)
		return 0;

	return gaminggear_device_get_product_id(GAMINGGEAR_DEVICE(priv->device));
}

static void konextd_eventhandler_talk_easyshift(RoccatEventhandlerPlugin *self, gboolean state) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(self);
	talk_easyshift(eventhandler, state);
}

static void konextd_eventhandler_talk_easyshift_lock(RoccatEventhandlerPlugin *self, gboolean state) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(self);
	talk_easyshift_lock(eventhandler, state);
}

static void konextd_eventhandler_talk_easyaim(RoccatEventhandlerPlugin *self, guchar state) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(self);
	talk_easyaim(eventhandler, state);
}

static void konextd_eventhandler_set_led_rgb(RoccatEventhandlerPlugin *self, guint32 effect, guint32 ambient_color, guint32 event_color) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(self);
	set_led_rgb(eventhandler, effect, ambient_color, event_color);
}

static void konextd_eventhandler_restore_led_rgb(RoccatEventhandlerPlugin *self) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(self);
	restore_led_rgb(eventhandler);
}

static void konextd_roccat_eventhandler_interface_init(RoccatEventhandlerPluginInterface *iface) {
	iface->get_talk_device = konextd_eventhandler_get_talk_device;
	iface->talk_easyshift = konextd_eventhandler_talk_easyshift;
	iface->talk_easyshift_lock = konextd_eventhandler_talk_easyshift_lock;
	iface->talk_easyaim = konextd_eventhandler_talk_easyaim;
	iface->set_led_rgb = konextd_eventhandler_set_led_rgb;
	iface->restore_led_rgb = konextd_eventhandler_restore_led_rgb;
}

G_DEFINE_TYPE_WITH_CODE(KonextdEventhandler, konextd_eventhandler, G_TYPE_OBJECT,
		G_IMPLEMENT_INTERFACE(ROCCAT_EVENTHANDLER_PLUGIN_TYPE, konextd_roccat_eventhandler_interface_init));

GObject *konextd_eventhandler_new(RoccatEventhandlerHost *host) {
	return g_object_new(KONEXTD_EVENTHANDLER_TYPE,
			"host", host,
			NULL);
}

static void talk_easyshift_cb(KonextdDBusServer *server, guchar state, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	talk_easyshift(eventhandler, state);
}

static void talk_easyshift_lock_cb(KonextdDBusServer *server, guchar state, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	talk_easyshift_lock(eventhandler, state);
}

static void talk_easyaim_cb(KonextdDBusServer *server, guchar state, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	talk_easyaim(eventhandler, state);
}

static void set_led_rgb_cb(KonextdDBusServer *server, guint effect, guint ambient_color, guint event_color, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	set_led_rgb(eventhandler, effect, ambient_color, event_color);
}

static void restore_led_rgb_cb(KonextdDBusServer *server, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	restore_led_rgb(eventhandler);
}

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

static void profile_data_changed_outside_cb(KonextdDBusServer *server, guchar number, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	guint profile_index = number - 1;

	/* Just load rmp without updating data from hardware */
	koneplus_rmp_free(priv->rmp[profile_index]);
	priv->rmp[profile_index] = konextd_rmp_load_actual(profile_index);
}

static void configuration_reload(KonextdEventhandler *eventhandler) {
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	if (priv->config)
		koneplus_configuration_free(priv->config);
	priv->config = konextd_configuration_load();
}

static void configuration_changed_outside_cb(KonextdDBusServer *server, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	configuration_reload(eventhandler);
}

static void cpi_changed_cb(KonextdEventhandlerChannel *channel, guchar cpi_index, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	guint cpi_value;
	
	cpi_value = koneplus_rmp_get_cpi_level_x(priv->rmp[priv->actual_profile_index], cpi_index) * 200;
	roccat_notification_cpi_update(priv->cpi_note,
			koneplus_configuration_get_cpi_notification_type(priv->config),
			koneplus_configuration_get_notification_volume(priv->config),
			cpi_value, cpi_value);
}

static void profile_changed_cb(gpointer source, guchar profile_number, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	profile_changed(eventhandler, profile_number);
}

static void quicklaunch_cb(KonextdEventhandlerChannel *channel, guchar key_index, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	KoneplusRmpMacroKeyInfo *macro_key_info;

	macro_key_info = koneplus_rmp_get_macro_key_info(priv->rmp[priv->actual_profile_index], key_index);
	koneplus_play_quicklaunch_forked(macro_key_info);
	koneplus_rmp_macro_key_info_free(macro_key_info);
}

static void sensitivity_changed_cb(KonextdEventhandlerChannel *channel, guchar x, guchar y, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	gint notification_type;

	notification_type = koneplus_configuration_get_sensitivity_notification_type(priv->config);

	if (notification_type == ROCCAT_NOTIFICATION_TYPE_OSD)
		roccat_notification_sensitivity_update(priv->sensitivity_note, notification_type, x - 6, y - 6);
	else if (x > priv->actual_sensitivity_x)
		roccat_notification_sensitivity_update_up(priv->sensitivity_note, notification_type,
				koneplus_configuration_get_notification_volume(priv->config));
	else
		roccat_notification_sensitivity_update_down(priv->sensitivity_note, notification_type,
				koneplus_configuration_get_notification_volume(priv->config));

	priv->actual_sensitivity_x = x;
}

static void talk_cb(KonextdEventhandlerChannel *channel, guchar key_index, guchar event, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	KoneplusRmpMacroKeyInfo *macro_key_info;
	static gboolean state = FALSE;

	macro_key_info = koneplus_rmp_get_macro_key_info(priv->rmp[priv->actual_profile_index], key_index);
	switch (macro_key_info->type) {
	case KONEPLUS_PROFILE_BUTTON_TYPE_EASYSHIFT:
		break;
	case KONEPLUS_PROFILE_BUTTON_TYPE_TALK_BOTH_EASYSHIFT:
		roccat_eventhandler_plugin_emit_talk_easyshift(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ROCCAT_TALK_DEVICE_KEYBOARD,
				(event == KONEPLUS_CHRDEV_EVENT_ACTION_PRESS) ? TRUE : FALSE);
		break;
	case KONEPLUS_PROFILE_BUTTON_TYPE_TALK_OTHER_EASYSHIFT:
		roccat_eventhandler_plugin_emit_talk_easyshift(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				koneplus_rmp_macro_key_info_get_talk_device(macro_key_info),
				(event == KONEPLUS_CHRDEV_EVENT_ACTION_PRESS) ? TRUE : FALSE);
		break;
	case KONEPLUS_PROFILE_BUTTON_TYPE_TALK_OTHER_EASYSHIFT_LOCK:
		if (event == KONEPLUS_CHRDEV_EVENT_ACTION_PRESS) {
			state = !state;
			roccat_eventhandler_plugin_emit_talk_easyshift_lock(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
					koneplus_rmp_macro_key_info_get_talk_device(macro_key_info),
					state);
		}
		break;
	default:
		g_debug("unknown talk event of type 0x%2x", macro_key_info->type);
	}
	koneplus_rmp_macro_key_info_free(macro_key_info);
}

static void timer_start_cb(KonextdEventhandlerChannel *channel, guchar key_index, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	KoneplusRmpMacroKeyInfo *macro_key_info;

	macro_key_info = koneplus_rmp_get_macro_key_info(priv->rmp[priv->actual_profile_index], key_index);
	roccat_notification_timer_start(priv->timer_note,
			koneplus_configuration_get_timer_notification_type(priv->config),
			koneplus_configuration_get_notification_volume(priv->config),
			(gchar const *)macro_key_info->timer_name,
			koneplus_rmp_macro_key_info_get_timer_length(macro_key_info));
	koneplus_rmp_macro_key_info_free(macro_key_info);
}

static void timer_stop_cb(KonextdEventhandlerChannel *channel, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	KonextdEventhandlerPrivate *priv = eventhandler->priv;

	roccat_notification_timer_stop(priv->timer_note);
}

static gboolean profile_data_load(KonextdEventhandler *eventhandler, GError **error) {
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	guint i;

	for (i = 0; i < KONEPLUS_PROFILE_NUM; ++i) {
		priv->rmp[i] = konextd_rmp_load(priv->device, i, error);
		if (priv->rmp[i] == NULL)
			return FALSE;
	}
	return TRUE;
}

static void device_add_cb(RoccatDeviceScannerInterface *interface, RoccatDevice *device, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	GError *error = NULL;
	KoneplusProfileSettings *profile_settings;

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

	priv->device = device;
	g_object_ref(G_OBJECT(device));
	roccat_device_debug(device);

	configuration_reload(eventhandler);

	/* Notificator gets initialized only once if a device is found.
	 * Gets deinited once in finalizer.
	 */
	if (!priv->profile_note)
		priv->profile_note = roccat_notification_profile_new(roccat_eventhandler_host_get_notificator(priv->host), KONEXTD_DEVICE_NAME);
	if (!priv->cpi_note)
		priv->cpi_note = roccat_notification_cpi_new(roccat_eventhandler_host_get_notificator(priv->host), KONEXTD_DEVICE_NAME);
	if (!priv->sensitivity_note)
		priv->sensitivity_note = roccat_notification_sensitivity_new(roccat_eventhandler_host_get_notificator(priv->host), KONEXTD_DEVICE_NAME);
	if (!priv->timer_note)
		priv->timer_note = roccat_notification_timer_new(roccat_eventhandler_host_get_notificator(priv->host), KONEXTD_DEVICE_NAME);
	
	priv->actual_profile_index = koneplus_actual_profile_read(priv->device, &error);
	if (error) {
		g_warning("Error reading actual profile: %s", error->message);
		g_clear_error(&error);
		priv->actual_profile_index = 0; /* guessing */
	} else {
		profile_settings = koneplus_profile_settings_read(priv->device, priv->actual_profile_index, &error);
		if (error) {
			g_warning("Error reading profile settings: %s", error->message);
			g_clear_error(&error);
			priv->actual_sensitivity_x = (KONEPLUS_PROFILE_SETTINGS_SENSITIVITY_MIN + KONEPLUS_PROFILE_SETTINGS_SENSITIVITY_MAX) >> 1; /* guessing */
		}
		else {
			priv->actual_sensitivity_x = profile_settings->sensitivity_x;
			g_free(profile_settings);
		}
	}

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

	// FIXME create in init and just disconnect/reconnect?
	priv->dbus_server = konextd_dbus_server_new();
	konextd_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), "easyaim", G_CALLBACK(talk_easyaim_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "set-led-rgb", G_CALLBACK(set_led_rgb_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->dbus_server), "restore-led-rgb", G_CALLBACK(restore_led_rgb_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);

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

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

	priv->device_set_up = TRUE;
}

static void stop(KonextdEventhandler *eventhandler) {
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	guint i;

	priv->device_set_up = FALSE;

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

	konextd_eventhandler_channel_stop(priv->channel);

	g_signal_handler_disconnect(G_OBJECT(priv->host), priv->active_window_changed_handler);

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

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

	for (i = 0; i < KONEPLUS_PROFILE_NUM; ++i) {
		koneplus_rmp_free(priv->rmp[i]);
		priv->rmp[i] = NULL;
	}
}

static void device_remove_cb(RoccatDeviceScanner *scanner, RoccatDevice *device, gpointer user_data) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(user_data);
	KonextdEventhandlerPrivate *priv = eventhandler->priv;
	if (priv->device && gaminggear_device_equal(GAMINGGEAR_DEVICE(device), GAMINGGEAR_DEVICE(priv->device)))
		stop(eventhandler);
}

static void konextd_eventhandler_init(KonextdEventhandler *eventhandler) {
	eventhandler->priv = KONEXTD_EVENTHANDLER_GET_PRIVATE(eventhandler);
}

static void finalize(GObject *object) {
	KonextdEventhandler *eventhandler = KONEXTD_EVENTHANDLER(object);
	KonextdEventhandlerPrivate *priv = eventhandler->priv;

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

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

	stop(eventhandler);

	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->cpi_note) {
		roccat_notification_cpi_free(priv->cpi_note);
		priv->cpi_note = NULL;
	}

	if (priv->sensitivity_note) {
		roccat_notification_sensitivity_free(priv->sensitivity_note);
		priv->sensitivity_note = NULL;
	}
	
	G_OBJECT_CLASS(konextd_eventhandler_parent_class)->finalize(object);
}

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

	obj = G_OBJECT_CLASS(konextd_eventhandler_parent_class)->constructor(gtype, n_properties, properties);
	eventhandler = KONEXTD_EVENTHANDLER(obj);
	priv = eventhandler->priv;

	priv->channel = konextd_eventhandler_channel_new();
	g_signal_connect(G_OBJECT(priv->channel), "cpi-changed", G_CALLBACK(cpi_changed_cb), eventhandler);
	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), "sensitivity-changed", G_CALLBACK(sensitivity_changed_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);

	priv->device_scanner = konextd_device_scanner_new();
	g_signal_connect(G_OBJECT(priv->device_scanner), "device-added", G_CALLBACK(device_add_cb), eventhandler);
	g_signal_connect(G_OBJECT(priv->device_scanner), "device-removed", G_CALLBACK(device_remove_cb), eventhandler);
	roccat_device_scanner_interface_start(ROCCAT_DEVICE_SCANNER_INTERFACE(priv->device_scanner));

	return obj;
}

static void set_property(GObject *object, guint prop_id, GValue const *value, GParamSpec *pspec) {
	KonextdEventhandlerPrivate *priv = KONEXTD_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 konextd_eventhandler_class_init(KonextdEventhandlerClass *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(KonextdEventhandlerPrivate));

	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(konextd_eventhandler_new(host));
}
