/*
 * 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 "arvo_eventhandler.h"
#include "arvo_eventhandler_channel.h"
#include "arvo_dbus_server.h"
#include "roccat/arvo.h"
#include "config.h"
#include <glib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <linux/input.h>

#define ARVO_EVENTHANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ARVO_EVENTHANDLER_TYPE, ArvoEventhandlerClass))
#define IS_ARVO_EVENTHANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ARVO_EVENTHANDLER_TYPE))
#define ARVO_EVENTHANDLER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ARVO_EVENTHANDLER_TYPE, ArvoEventhandlerPrivate))

typedef struct _ArvoEventhandlerClass ArvoEventhandlerClass;

struct _ArvoEventhandlerClass {
	GObjectClass parent_class;
};

struct _ArvoEventhandlerPrivate {
	RoccatEventhandlerHost *host;
	ArvoDBusServer *dbus_server;
	RoccatDeviceScanner *device_scanner;
	RoccatDevice *device;
	gboolean device_set_up;
	RoccatKeyFile *config;
	guint actual_profile_index;
	ArvoRkp *rkp[ARVO_PROFILE_NUM];
	ArvoEventhandlerChannel *channel;
	
	RoccatNotificationProfile *profile_note;
	RoccatNotificationTimer *timer_note;

	int kbd_dev;
	int misc_dev;
	RoccatMacroThreads *threads;
};

enum {
	PROP_0,
	PROP_HOST,
};

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

	priv->actual_profile_index = profile_index;

	roccat_notification_profile_update(priv->profile_note, arvo_configuration_get_profile_notification_type(priv->config),
			profile_number, arvo_rkp_get_profile_name(priv->rkp[profile_index]));

	arvo_dbus_server_emit_profile_changed(priv->dbus_server, profile_number);
}

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

	if (priv->actual_profile_index != profile_index) {
		if (!arvo_actual_profile_write(priv->device, profile_number, &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) {
	ArvoEventhandler *eventhandler = ARVO_EVENTHANDLER(user_data);
	ArvoEventhandlerPrivate *priv = eventhandler->priv;
	guint profile_index;
	guint game_file_index;
	gchar *pattern;

	for (profile_index = 0; profile_index < ARVO_PROFILE_NUM; ++profile_index) {
		for (game_file_index = 0; game_file_index < ARVO_GAMEFILE_NUM; ++game_file_index) {
			pattern = arvo_rkp_get_game_file_name(priv->rkp[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, arvo_configuration_get_default_profile_number(priv->config));
	return;
}

static guint arvo_eventhandler_get_talk_device(RoccatEventhandlerPlugin *self) {
	return 0;
}

static void arvo_roccat_eventhandler_interface_init(RoccatEventhandlerPluginInterface *iface) {
	iface->get_talk_device = arvo_eventhandler_get_talk_device;
	iface->talk_easyshift = NULL;
	iface->talk_easyshift_lock = NULL;
	iface->talk_easyaim = NULL;
	iface->set_led_rgb = NULL;
	iface->restore_led_rgb = NULL;
}

G_DEFINE_TYPE_WITH_CODE(ArvoEventhandler, arvo_eventhandler, G_TYPE_OBJECT,
		G_IMPLEMENT_INTERFACE(ROCCAT_EVENTHANDLER_PLUGIN_TYPE, arvo_roccat_eventhandler_interface_init));

GObject *arvo_eventhandler_new(RoccatEventhandlerHost *host) {
	return g_object_new(ARVO_EVENTHANDLER_TYPE,
			"host", host,
			NULL);
}

static void open_gui(void) {
	gchar *path;
	path = g_build_path("/", INSTALL_PREFIX, "bin", "arvoconfig", (gchar const *)NULL);
	roccat_double_fork(path);
	g_free(path);
}

static void open_gui_cb(gpointer source, gpointer user_data) {
	open_gui();
}

static void profile_data_changed_outside_cb(ArvoDBusServer *server, guchar profile_number, gpointer user_data) {
	ArvoEventhandler *eventhandler = ARVO_EVENTHANDLER(user_data);
	ArvoEventhandlerPrivate *priv = eventhandler->priv;
	guint profile_index = profile_number - 1;
	
	arvo_rkp_free(priv->rkp[profile_index]);
	priv->rkp[profile_index] = arvo_rkp_load_actual(profile_number);
}

static void configuration_reload(ArvoEventhandler *eventhandler) {
	ArvoEventhandlerPrivate *priv = eventhandler->priv;
	if (priv->config)
		arvo_configuration_free(priv->config);
	priv->config = arvo_configuration_load();
}

static void configuration_changed_outside_cb(ArvoDBusServer *server, gpointer user_data) {
	ArvoEventhandler *eventhandler = ARVO_EVENTHANDLER(user_data);
	configuration_reload(eventhandler);
}

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

static gboolean arvo_profile_increment(ArvoEventhandler *eventhandler, guint profile_index) {
	++profile_index;
	if (profile_index >= ARVO_PROFILE_NUM)
		return TRUE;
	return set_profile(eventhandler, profile_index + 1);
}

static gboolean arvo_profile_decrement(ArvoEventhandler *eventhandler, guint profile_index) {
	if (profile_index == 0)
		return TRUE;
	--profile_index;
	return set_profile(eventhandler, profile_index + 1);
}

static gboolean arvo_profile_cycle_up(ArvoEventhandler *eventhandler, guint profile_index) {
	++profile_index;
	if (profile_index >= ARVO_PROFILE_NUM)
		profile_index = 0;

	return set_profile(eventhandler, profile_index + 1);
}

static void button_cb(ArvoEventhandlerChannel *channel, guchar action, guchar button, gpointer user_data) {
	ArvoEventhandler *eventhandler = ARVO_EVENTHANDLER(user_data);
	ArvoEventhandlerPrivate *priv = eventhandler->priv;
	ArvoRkpButtonInfo *button_info;
	RoccatMacro *macro;
	static gboolean state = FALSE;

	button_info = arvo_rkp_get_button_info(priv->rkp[priv->actual_profile_index], button - 1);

	switch (button_info->type) {
	case ARVO_RKP_BUTTON_INFO_TYPE_MULTIMEDIA_PREV_TRACK:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			roccat_input_event_write_single(priv->misc_dev, KEY_PREVIOUSSONG);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_MULTIMEDIA_NEXT_TRACK:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			roccat_input_event_write_single(priv->misc_dev, KEY_NEXTSONG);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_MULTIMEDIA_PLAY_PAUSE:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			roccat_input_event_write_single(priv->misc_dev, KEY_PLAYPAUSE);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_MULTIMEDIA_STOP:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			roccat_input_event_write_single(priv->misc_dev, KEY_STOPCD);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_MULTIMEDIA_MUTE:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			roccat_input_event_write_single(priv->misc_dev, KEY_MUTE);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_MULTIMEDIA_VOLUME_UP:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			roccat_input_event_write_single(priv->misc_dev, KEY_VOLUMEUP);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_MULTIMEDIA_VOLUME_DOWN:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			roccat_input_event_write_single(priv->misc_dev, KEY_VOLUMEDOWN);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_OFF:
		/* just ignore */
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_MACRO:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS) {
			macro = arvo_rkp_button_info_to_roccat_macro(button_info);
			roccat_play_macro_threaded(priv->threads, button - 1, priv->kbd_dev, priv->misc_dev, macro);
			roccat_macro_free(macro);
		}
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_TIMER_START:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			roccat_notification_timer_start(priv->timer_note,
					arvo_configuration_get_timer_notification_type(priv->config),
					(gchar const *)button_info->timer_name,
					arvo_rkp_button_info_get_timer_length(button_info));
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_TIMER_STOP:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			roccat_notification_timer_stop(priv->timer_note);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_QUICKLAUNCH_NS:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			arvo_play_quicklaunch_forked_ns(button_info);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_OPEN_DRIVER_NS:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			open_gui();
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_PROFILE_CYCLE_NS:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			arvo_profile_cycle_up(eventhandler, priv->actual_profile_index);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_PROFILE_UP_NS:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			arvo_profile_increment(eventhandler, priv->actual_profile_index);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_PROFILE_DOWN_NS:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS)
			arvo_profile_decrement(eventhandler, priv->actual_profile_index);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_TALK_OTHER_EASYSHIFT_NS:
		roccat_eventhandler_plugin_emit_talk_easyshift(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ROCCAT_TALK_DEVICE_MOUSE,
				(action == ARVO_ROCCAT_REPORT_ACTION_PRESS) ? TRUE : FALSE);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_TALK_OTHER_EASYSHIFT_LOCK_NS:
		if (action == ARVO_ROCCAT_REPORT_ACTION_PRESS) {
			state = !state;
			roccat_eventhandler_plugin_emit_talk_easyshift(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
					ROCCAT_TALK_DEVICE_MOUSE, state);
		}
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_TALK_OTHER_EASYAIM_1_NS:
		roccat_eventhandler_plugin_emit_talk_easyaim(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ROCCAT_TALK_DEVICE_MOUSE,
				(action == ARVO_ROCCAT_REPORT_ACTION_PRESS) ? ROCCAT_TALK_EASYAIM_1 : ROCCAT_TALK_EASYAIM_OFF);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_TALK_OTHER_EASYAIM_2_NS:
		roccat_eventhandler_plugin_emit_talk_easyaim(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ROCCAT_TALK_DEVICE_MOUSE,
				(action == ARVO_ROCCAT_REPORT_ACTION_PRESS) ? ROCCAT_TALK_EASYAIM_2 : ROCCAT_TALK_EASYAIM_OFF);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_TALK_OTHER_EASYAIM_3_NS:
		roccat_eventhandler_plugin_emit_talk_easyaim(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ROCCAT_TALK_DEVICE_MOUSE,
				(action == ARVO_ROCCAT_REPORT_ACTION_PRESS) ? ROCCAT_TALK_EASYAIM_3 : ROCCAT_TALK_EASYAIM_OFF);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_TALK_OTHER_EASYAIM_4_NS:
		roccat_eventhandler_plugin_emit_talk_easyaim(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ROCCAT_TALK_DEVICE_MOUSE,
				(action == ARVO_ROCCAT_REPORT_ACTION_PRESS) ? ROCCAT_TALK_EASYAIM_4 : ROCCAT_TALK_EASYAIM_OFF);
		break;
	case ARVO_RKP_BUTTON_INFO_TYPE_TALK_OTHER_EASYAIM_5_NS:
		roccat_eventhandler_plugin_emit_talk_easyaim(ROCCAT_EVENTHANDLER_PLUGIN(eventhandler),
				ROCCAT_TALK_DEVICE_MOUSE,
				(action == ARVO_ROCCAT_REPORT_ACTION_PRESS) ? ROCCAT_TALK_EASYAIM_5 : ROCCAT_TALK_EASYAIM_OFF);
		break;
	default:
		g_debug("unsupported event of type 0x%02x", button_info->type);
		break;
	}
	arvo_rkp_button_info_free(button_info);
}

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

	for (i = 0; i < ARVO_PROFILE_NUM; ++i) {
		priv->rkp[i] = arvo_rkp_load(priv->device, i + 1, error);
		if (priv->rkp[i] == NULL)
			return FALSE;
	}
	return TRUE;
}

static gboolean add_cb(RoccatDevice *device, gpointer user_data) {
	ArvoEventhandler *eventhandler = ARVO_EVENTHANDLER(user_data);
	ArvoEventhandlerPrivate *priv = eventhandler->priv;
	GError *error = NULL;

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

	priv->device = 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), ARVO_DEVICE_NAME);
	if (!priv->timer_note)
		priv->timer_note = roccat_notification_timer_new(roccat_eventhandler_host_get_notificator(priv->host), ARVO_DEVICE_NAME);
	
	priv->actual_profile_index = arvo_actual_profile_read(priv->device, &error) - 1;
	if (error) {
		g_warning("Error reading actual profile: %s", error->message);
		g_clear_error(&error);
	}

	priv->kbd_dev = roccat_device_open_keyboard_evdev(priv->device);
	if (priv->kbd_dev == -1)
		g_critical("Can't play keyboard events.");
	
	priv->misc_dev = roccat_device_open_misc_evdev(priv->device);
	if (priv->misc_dev == -1)
		g_critical("Can't play mouse and multimedia events.");

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

	priv->dbus_server = arvo_dbus_server_new();
	arvo_dbus_server_connect(priv->dbus_server);
	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->threads = roccat_macro_threads_new(ARVO_BUTTON_NUM);
	
	g_signal_connect(G_OBJECT(priv->host), "active-window-changed", G_CALLBACK(window_changed_cb), eventhandler);

	arvo_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(ArvoEventhandler *eventhandler) {
	ArvoEventhandlerPrivate *priv = eventhandler->priv;
	guint i;

	priv->device_set_up = FALSE;

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

	arvo_eventhandler_channel_stop(priv->channel);

	roccat_macro_threads_free(priv->threads);
	priv->threads = NULL;
	
	if (priv->kbd_dev != -1)
		close(priv->kbd_dev);
	
	if (priv->misc_dev != -1)
		roccat_device_close_misc_evdev(priv->misc_dev);

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

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

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

static gboolean remove_cb(RoccatDevice *device, gpointer user_data) {
	ArvoEventhandler *eventhandler = ARVO_EVENTHANDLER(user_data);
	ArvoEventhandlerPrivate *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 arvo_eventhandler_init(ArvoEventhandler *eventhandler) {
	eventhandler->priv = ARVO_EVENTHANDLER_GET_PRIVATE(eventhandler);
}

static void finalize(GObject *object) {
	ArvoEventhandler *eventhandler = ARVO_EVENTHANDLER(object);
	ArvoEventhandlerPrivate *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->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;
	}
	
	G_OBJECT_CLASS(arvo_eventhandler_parent_class)->finalize(object);
}

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

	obj = G_OBJECT_CLASS(arvo_eventhandler_parent_class)->constructor(gtype, n_properties, properties);
	eventhandler = ARVO_EVENTHANDLER(obj);
	priv = eventhandler->priv;

	priv->channel = arvo_eventhandler_channel_new();
	g_signal_connect(G_OBJECT(priv->channel), "button", G_CALLBACK(button_cb), eventhandler);

	priv->kbd_dev = -1;
	priv->misc_dev = -1;

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

	return obj;
}

static void set_property(GObject *object, guint prop_id, GValue const *value, GParamSpec *pspec) {
	ArvoEventhandlerPrivate *priv = ARVO_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 arvo_eventhandler_class_init(ArvoEventhandlerClass *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(ArvoEventhandlerPrivate));

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