/*
 * 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/lua.h"
#include <g_udev_roccat_helper.h>

struct _LuaDeviceScanner {
	GUdevClient *client;
	roccat_device_scanner_callback add_callback;
	roccat_device_scanner_callback remove_callback;
	gpointer user_data;
};

#define UDEV_ROCCAT_DEVICE_ACTION_ADD "add"
#define UDEV_ROCCAT_DEVICE_ACTION_REMOVE "remove"

static gchar const * const driver_name = "lua";

static GList *client_query_by_subsystem(GUdevClient *client) {
	return g_udev_client_query_by_subsystem(client, "hid");
}

static gboolean device_is_wanted(GUdevDevice *hid_device) {
	if (g_strcmp0(g_udev_device_get_subsystem(hid_device), "hid"))
		return FALSE;

	return !g_strcmp0(g_udev_device_get_driver(hid_device), driver_name);
}

static RoccatDevice *roccat_device_new_from_hiddev(GUdevClient *client, GUdevDevice *hid_device) {
	GUdevDevice *usb_device;
	GUdevDevice *usbhid_device;
	RoccatDevice *roccat_dev;

	usb_device = g_udev_roccat_get_parent_usb_device(hid_device);
	if (!usb_device)
		return NULL;

	usbhid_device = g_udev_roccat_get_parent_usbhid_device(hid_device);
	if (!usbhid_device)
		return NULL;

	roccat_dev = roccat_device_new(g_udev_device_get_name(usb_device));
	roccat_device_set_syspath(roccat_dev, g_udev_device_get_sysfs_path(usbhid_device));
	roccat_device_set_drivername(roccat_dev, g_udev_device_get_driver(hid_device));
	roccat_device_set_product_id(roccat_dev, g_udev_roccat_get_device_id(usb_device));

	g_object_unref(usbhid_device);
	g_object_unref(usb_device);

	return roccat_dev;
}

static RoccatDevice *device_list_get_first_wanted(GList *list, GUdevClient *client) {
	GList *iterator;
	GUdevDevice *hid_device;

	for (iterator = g_list_first(list); iterator; iterator = g_list_next(iterator)) {
		hid_device = (GUdevDevice *)iterator->data;

		if (device_is_wanted(hid_device))
			return roccat_device_new_from_hiddev(client, hid_device);
	}

	return NULL;
}

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

	client = g_udev_client_new(NULL);

	list = client_query_by_subsystem(client);
	roccat_device = device_list_get_first_wanted(list, client);
	g_udev_roccat_device_list_free(list);

	g_object_unref(client);

	return roccat_device;
}

/* returns partially filled device meant for removal event propagation */
static RoccatDevice *roccat_device_new_for_removal(GUdevDevice *usb_device) {
	RoccatDevice *roccat_dev;
	roccat_dev = roccat_device_new(g_udev_device_get_name(usb_device));
	return roccat_dev;
}

static void call_add_callback(LuaDeviceScanner *scanner, GUdevDevice *device) {
	RoccatDevice *roccat_device;

	if (device_is_wanted(device)) {
		if (scanner->add_callback) {
			roccat_device = roccat_device_new_from_hiddev(scanner->client, device);
			if (!roccat_device)
				return;
			if (scanner->add_callback(roccat_device, scanner->user_data))
				roccat_device_free(roccat_device);
		}
	}
}

static void uevent_add_cb(GUdevClient *client, gchar *action, GUdevDevice *device, LuaDeviceScanner *scanner) {
	if (g_strcmp0(action, UDEV_ROCCAT_DEVICE_ACTION_ADD) == 0)
		call_add_callback(scanner, device);
}

static void uevent_remove_cb(GUdevClient *client, gchar *action, GUdevDevice *device, LuaDeviceScanner *scanner) {
	RoccatDevice *roccat_device;

	if (g_strcmp0(g_udev_device_get_subsystem(device), "usb") == 0 &&
			g_strcmp0(g_udev_device_get_devtype(device), "usb_device") == 0 &&
			g_strcmp0(action, UDEV_ROCCAT_DEVICE_ACTION_REMOVE) == 0) {
		/* device is usb_device */
		if (scanner->remove_callback) {
			roccat_device = roccat_device_new_for_removal(device);
			if (scanner->remove_callback(roccat_device, scanner->user_data))
				roccat_device_free(roccat_device);
		}
	}
}

static void scanner_call_callback_for_all_wanted(LuaDeviceScanner *scanner) {
	GList *list;
	GList *iterator;

	list = g_udev_client_query_by_subsystem(scanner->client, "hid");

	for (iterator = g_list_first(list); iterator; iterator = g_list_next(iterator))
		call_add_callback(scanner, (GUdevDevice *)iterator->data);

	g_udev_roccat_device_list_free(list);
}

LuaDeviceScanner *lua_device_scanner_new(roccat_device_scanner_callback add_callback,
		roccat_device_scanner_callback remove_callback, gpointer user_data) {
	LuaDeviceScanner *scanner;
	gchar const * const subsystems[] = { "usb/usb_device", "hid", NULL };

	scanner = (LuaDeviceScanner *)g_malloc0(sizeof(LuaDeviceScanner));

	scanner->client = g_udev_client_new(subsystems);
	scanner->add_callback = add_callback;
	scanner->remove_callback = remove_callback;
	scanner->user_data = user_data;

	g_signal_connect(G_OBJECT(scanner->client), "uevent", G_CALLBACK(uevent_add_cb), scanner);
	g_signal_connect(G_OBJECT(scanner->client), "uevent", G_CALLBACK(uevent_remove_cb), scanner);

	scanner_call_callback_for_all_wanted(scanner);

	return scanner;
}

void lua_device_scanner_free(LuaDeviceScanner *scanner) {
	g_return_if_fail(scanner != NULL);
	g_object_unref(scanner->client);
	g_free(scanner);
}
