/*
 * 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_device_scanner.h"
#include "arvo.h"
#include "roccat.h"
#include "roccat_device.h"
#include <g_udev_roccat_helper.h>
#include <string.h>

#define ARVO_DEVICE_SCANNER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ARVO_DEVICE_SCANNER_TYPE, ArvoDeviceScannerClass))
#define IS_ARVO_DEVICE_SCANNER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ARVO_DEVICE_SCANNER_TYPE))
#define ARVO_DEVICE_SCANNER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ARVO_DEVICE_SCANNER_TYPE, ArvoDeviceScannerPrivate))

typedef struct _ArvoDeviceScannerClass ArvoDeviceScannerClass;

struct _ArvoDeviceScannerClass {
	GObjectClass parent_class;
};

struct _ArvoDeviceScannerPrivate {
	GUdevClient *add_client;
	gulong add_handler;
	GUdevClient *remove_client;
	gulong remove_handler;
	guint const *product_ids;
	guint interface_protocol;
	gchar const *class_name;
};

static gchar const * const roccat = "roccat";
static gchar const * const driver_name = "arvo";
static guint const device_ids[2] = { USB_DEVICE_ID_ROCCAT_ARVO, 0 };

static void fill_input(RoccatDevice *roccat_dev, GUdevDevice *event_device) {
	GUdevDevice *usbhid_device;
	gint protocol;

	usbhid_device = g_udev_roccat_get_parent_usbhid_device(event_device);
	if (!usbhid_device)
		return;

	protocol = g_udev_roccat_usbhid_get_interfaceprotocol(usbhid_device);
	g_object_unref(usbhid_device);

	switch(protocol) {
	case ROCCAT_USB_INTERFACE_PROTOCOL_NONE:
		roccat_device_set_none_evdev(roccat_dev, g_udev_device_get_device_file(event_device));
		break;
	case ROCCAT_USB_INTERFACE_PROTOCOL_KEYBOARD:
		roccat_device_set_keyboard_evdev(roccat_dev, g_udev_device_get_device_file(event_device));
		break;
	case ROCCAT_USB_INTERFACE_PROTOCOL_MOUSE:
		roccat_device_set_mouse_evdev(roccat_dev, g_udev_device_get_device_file(event_device));
		break;
	default:
		g_warning("Device scanner: unknown protocol");
	}
}

static void fill_inputs(RoccatDevice *roccat_dev, GUdevClient *client, GUdevDevice *usb_device) {
	GUdevDevice *event_device;
	GList *list, *iterator;

	list = g_udev_client_query_by_subsystem(client, "input");
	if (list == NULL)
		return;

	for (iterator = g_list_first(list); iterator; iterator = g_list_next(iterator)) {
		event_device = (GUdevDevice *)iterator->data;
		if (!event_device)
			continue;

		/* take only event* devnodes */
		if (!g_udev_device_get_name(event_device) || strncmp("event", g_udev_device_get_name(event_device), 5))
			continue;

		if (g_udev_roccat_device_is_sibling(usb_device, event_device))
			fill_input(roccat_dev, event_device);
	}

	g_udev_roccat_device_list_free(list);
}

static gboolean product_id_fits(GUdevDevice *usb_device, guint const *product_ids) {
	guint device_id;

	device_id = g_udev_roccat_usb_get_device_id(usb_device);

	while (*product_ids) {
		if (device_id == *product_ids)
			return TRUE;
		++product_ids;
	}
	return FALSE;
}

static gboolean vendor_id_fits(GUdevDevice *usb_device) {
	return (g_udev_roccat_usb_get_vendor_id(usb_device) == USB_VENDOR_ID_ROCCAT) ? TRUE : FALSE;
}

static gboolean usb_device_fits(GUdevDevice *usb_device, guint const *product_ids) {
	if (!vendor_id_fits(usb_device))
		return FALSE;

	if (!product_id_fits(usb_device, product_ids))
		return FALSE;

	return TRUE;
}

static gboolean usbhid_device_fits(GUdevDevice *usbhid_device, guint interface_protocol) {
	gint protocol;

	protocol = g_udev_roccat_usbhid_get_interfaceprotocol(usbhid_device);
	if (protocol != interface_protocol)
		return FALSE;

	return TRUE;
}

static gboolean have_special_driver(GUdevDevice *hid_device) {
	gchar const *driver;
	driver = g_udev_device_get_driver(hid_device);
	return (!g_strcmp0(driver, "hid-generic") || !g_strcmp0(driver, "generic-usb")) ? FALSE : TRUE;
}

static void fill_syspath(RoccatDevice *roccat_device, GUdevDevice *usbhid_device, GUdevDevice *some_device) {
	if (!g_strcmp0(g_udev_device_get_subsystem(some_device), "hid"))
		roccat_device_set_syspath(roccat_device, g_udev_device_get_sysfs_path(usbhid_device));
	else if (!g_strcmp0(g_udev_device_get_subsystem(some_device), roccat))
		roccat_device_set_syspath(roccat_device, g_udev_device_get_sysfs_path(usbhid_device));
	else
		roccat_device_set_syspath(roccat_device, g_udev_device_get_sysfs_path(some_device));
}

static RoccatDevice *roccat_device_from_some_device(GUdevClient *client, GUdevDevice *some_device, guint const *product_ids, guint interface_protocol) {
	RoccatDevice *roccat_device = NULL;
	GUdevDevice *usbhid_device;
	GUdevDevice *usb_device;
	GUdevDevice *hid_device;

	hid_device = g_udev_roccat_get_parent_hid_device(some_device);
	if (!hid_device)
		goto exit0;

	/*
	 * Case: special driver without chardev and class
	 * Accept hid only with special driver
	 */
	if (!have_special_driver(hid_device) && !g_strcmp0(g_udev_device_get_subsystem(some_device), "hid"))
		goto exit1;

	usbhid_device = g_udev_roccat_get_parent_usbhid_device(some_device);
	if (!usbhid_device)
		goto exit1;

	if (!usbhid_device_fits(usbhid_device, interface_protocol))
		goto exit2;

	usb_device = g_udev_roccat_get_parent_usb_device(some_device);
	if (!usb_device)
		goto exit2;

	if (!usb_device_fits(usb_device, product_ids))
		goto exit3;

	roccat_device = roccat_device_new(g_udev_device_get_name(usb_device), g_udev_roccat_usb_get_device_id(usb_device));
	fill_inputs(roccat_device, client, usb_device);

	roccat_device_set_roccat_dev(roccat_device, g_udev_device_get_device_file(some_device));
	fill_syspath(roccat_device, usbhid_device, some_device);

exit3:
	g_object_unref(usb_device);
exit2:
	g_object_unref(usbhid_device);
exit1:
	g_object_unref(hid_device);
exit0:
	return roccat_device;
}

static void uevent_add_cb(GUdevClient *client, gchar *action, GUdevDevice *some_device, gpointer user_data) {
	ArvoDeviceScanner *device_scanner = ARVO_DEVICE_SCANNER(user_data);
	ArvoDeviceScannerPrivate *priv = device_scanner->priv;
	RoccatDevice *roccat_device;

	if (g_strcmp0(action, G_UDEV_ROCCAT_DEVICE_ACTION_ADD) != 0)
		return;

	roccat_device = roccat_device_from_some_device(client, some_device, priv->product_ids, priv->interface_protocol);
	if (!roccat_device)
		return;

	roccat_device_scanner_interface_device_added(ROCCAT_DEVICE_SCANNER_INTERFACE(user_data), roccat_device);
	g_object_unref(G_OBJECT(roccat_device));
}

static void uevent_remove_cb(GUdevClient *client, gchar *action, GUdevDevice *usb_device, gpointer user_data) {
	RoccatDevice *roccat_device;

	if (g_strcmp0(action, G_UDEV_ROCCAT_DEVICE_ACTION_REMOVE) != 0)
		return;

	/*
	 * Sending more events than needed.
	 * Not testing on vendor/product id because these values aren't available
	 * anymore after firmware update.
	 * hiddev/interface_protocol is not checked because device is already gone.
	 */

	roccat_device = roccat_device_new(g_udev_device_get_name(usb_device), 0);

	roccat_device_scanner_interface_device_removed(ROCCAT_DEVICE_SCANNER_INTERFACE(user_data), roccat_device);
	g_object_unref(G_OBJECT(roccat_device));
}

static void iterate_existing(ArvoDeviceScanner *device_scanner) {
	ArvoDeviceScannerPrivate *priv = device_scanner->priv;
	GUdevClient *client;
	GList *list;
	GList *iterator;

	client = g_udev_client_new(NULL);
	list = g_udev_client_query_by_subsystem(client, priv->class_name);

	for (iterator = g_list_first(list); iterator; iterator = g_list_next(iterator))
		uevent_add_cb(client, G_UDEV_ROCCAT_DEVICE_ACTION_ADD, (GUdevDevice *)iterator->data, device_scanner);

	g_udev_roccat_device_list_free(list);
	g_object_unref(client);
}

void arvo_device_scanner_start(RoccatDeviceScannerInterface *self) {
	ArvoDeviceScanner *device_scanner = ARVO_DEVICE_SCANNER(self);
	ArvoDeviceScannerPrivate *priv = device_scanner->priv;
	priv->add_handler = g_signal_connect(G_OBJECT(priv->add_client), "uevent", G_CALLBACK(uevent_add_cb), device_scanner);
	priv->remove_handler = g_signal_connect(G_OBJECT(priv->remove_client), "uevent", G_CALLBACK(uevent_remove_cb), device_scanner);
	iterate_existing(device_scanner);
}

void arvo_device_scanner_stop(RoccatDeviceScannerInterface *self) {
	ArvoDeviceScanner *device_scanner = ARVO_DEVICE_SCANNER(self);
	ArvoDeviceScannerPrivate *priv = device_scanner->priv;
	g_signal_handler_disconnect(G_OBJECT(priv->add_client), priv->add_handler);
	g_signal_handler_disconnect(G_OBJECT(priv->remove_client), priv->remove_handler);
}

static void roccat_device_scanner_interface_init(RoccatDeviceScannerInterfaceInterface *iface) {
	iface->start = arvo_device_scanner_start;
	iface->stop = arvo_device_scanner_stop;
}

G_DEFINE_TYPE_WITH_CODE(ArvoDeviceScanner, arvo_device_scanner, G_TYPE_OBJECT,
		G_IMPLEMENT_INTERFACE(ROCCAT_DEVICE_SCANNER_INTERFACE_TYPE, roccat_device_scanner_interface_init));

ArvoDeviceScanner *arvo_device_scanner_new(void) {
	ArvoDeviceScanner *device_scanner;
	ArvoDeviceScannerPrivate *priv;
	gchar const * const add_subsystems[] = { roccat, driver_name, NULL };
	gchar const * const remove_subsystems[] = { "usb/usb_device", NULL };

	device_scanner = ARVO_DEVICE_SCANNER(g_object_new(ARVO_DEVICE_SCANNER_TYPE, NULL));
	priv = device_scanner->priv;

	priv->add_client = g_udev_client_new(add_subsystems);

	priv->remove_client = g_udev_client_new(remove_subsystems);
	priv->product_ids = device_ids;
	priv->interface_protocol = ROCCAT_USB_INTERFACE_PROTOCOL_NONE;
	priv->class_name = driver_name;

	return device_scanner;
}

static void arvo_device_scanner_init(ArvoDeviceScanner *device_scanner) {
	ArvoDeviceScannerPrivate *priv = ARVO_DEVICE_SCANNER_GET_PRIVATE(device_scanner);
	device_scanner->priv = priv;
}

static void arvo_device_scanner_dispose(GObject *object) {
	ArvoDeviceScannerPrivate *priv = ARVO_DEVICE_SCANNER(object)->priv;
	g_object_unref(priv->add_client);
	priv->add_client = NULL;
	g_object_unref(priv->remove_client);
	priv->remove_client = NULL;
	G_OBJECT_CLASS(arvo_device_scanner_parent_class)->dispose(object);
}

static void arvo_device_scanner_class_init(ArvoDeviceScannerClass *klass) {
	GObjectClass *gobject_class;

	gobject_class = (GObjectClass*)klass;

	gobject_class->dispose = arvo_device_scanner_dispose;

	g_type_class_add_private(klass, sizeof(ArvoDeviceScannerPrivate));
}

static RoccatDevice *device_list_get_first_wanted(GUdevClient *client, GList *list, guint const *device_ids, guint interface_protocol) {
	RoccatDevice *roccat_device;
	GList *iterator;
	GUdevDevice *some_device;

	for (iterator = g_list_first(list); iterator; iterator = g_list_next(iterator)) {
		some_device = (GUdevDevice *)iterator->data;
		roccat_device = roccat_device_from_some_device(client, some_device, device_ids, interface_protocol);
		if (roccat_device)
			return roccat_device;
	}

	return NULL;
}

RoccatDevice *arvo_device_first(void) {
	GUdevClient *client;
	GList *list;
	RoccatDevice *roccat_device;

	client = g_udev_client_new(NULL);
	list = g_udev_client_query_by_subsystem(client, driver_name);

	roccat_device = device_list_get_first_wanted(client, list, device_ids, ROCCAT_USB_INTERFACE_PROTOCOL_NONE);

	g_udev_roccat_device_list_free(list);
	g_object_unref(client);

	return roccat_device;
}
