/*
 * 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 <gaminggear/threads.h>
#include <gaminggear/evdev.h>
#include "roccat.h"
#include "roccat_device.h"
#include "roccat_talk.h"
#include "config.h"
#include <fcntl.h>
#include <errno.h>

#define ROCCAT_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ROCCAT_DEVICE_TYPE, RoccatDeviceClass))
#define IS_ROCCAT_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ROCCAT_DEVICE_TYPE))
#define ROCCAT_DEVICE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ROCCAT_DEVICE_TYPE, RoccatDevicePrivate))

typedef struct _RoccatDeviceClass RoccatDeviceClass;

struct _RoccatDeviceClass {
	GaminggearDeviceClass parent_class;
};

struct _RoccatDevicePrivate {
	int misc_fd;
};

G_DEFINE_TYPE(RoccatDevice, roccat_device, GAMINGGEAR_DEVICE_TYPE);

static gchar const * const syspath_name = "syspath";
static gchar const * const mouse_name = "mouse";
static gchar const * const keyboard_name = "keyboard";
static gchar const * const none_name = "none";
static gchar const * const roccat_name = "roccat";

gchar *create_hidraw_endpoint_name(guint endpoint) {
	return g_strdup_printf("hidraw%u", endpoint);
}

gchar const *roccat_device_get_syspath(RoccatDevice const *roccat_dev) {
	return gaminggear_device_get_path(GAMINGGEAR_DEVICE(roccat_dev), syspath_name);
}

void roccat_device_set_syspath(RoccatDevice *roccat_dev, gchar const *syspath) {
	gaminggear_device_set_path(GAMINGGEAR_DEVICE(roccat_dev), syspath_name, syspath);
}

void roccat_device_set_mouse_evdev(RoccatDevice *roccat_dev, gchar const *mouse_evdev) {
	gaminggear_device_set_path(GAMINGGEAR_DEVICE(roccat_dev), mouse_name, mouse_evdev);
}

void roccat_device_set_keyboard_evdev(RoccatDevice *roccat_dev, gchar const *keyboard_evdev) {
	gaminggear_device_set_path(GAMINGGEAR_DEVICE(roccat_dev), keyboard_name, keyboard_evdev);
}

void roccat_device_set_none_evdev(RoccatDevice *roccat_dev, gchar const *none_evdev) {
	gaminggear_device_set_path(GAMINGGEAR_DEVICE(roccat_dev), none_name, none_evdev);
}

gchar const *roccat_device_get_roccat_dev(RoccatDevice const *roccat_dev) {
	return gaminggear_device_get_path(GAMINGGEAR_DEVICE(roccat_dev), roccat_name);
}

void roccat_device_set_roccat_dev(RoccatDevice *roccat_dev, gchar const *device) {
	gaminggear_device_set_path(GAMINGGEAR_DEVICE(roccat_dev), roccat_name, device);
}

gchar const *roccat_device_get_hidraw_endpoint_dev(RoccatDevice const *roccat_dev, guint endpoint) {
	g_assert(endpoint < gaminggear_device_get_num_interfaces(GAMINGGEAR_DEVICE(roccat_dev)));
	gchar *name = create_hidraw_endpoint_name(endpoint);
	gchar const *path = gaminggear_device_get_path(GAMINGGEAR_DEVICE(roccat_dev), name);
	g_free(name);
	return path;
}

void roccat_device_set_hidraw_endpoint_dev(RoccatDevice *roccat_dev, gchar const *device, guint endpoint) {
	g_assert(endpoint < gaminggear_device_get_num_interfaces(GAMINGGEAR_DEVICE(roccat_dev)));
	gchar *name = create_hidraw_endpoint_name(endpoint);
	gaminggear_device_set_path(GAMINGGEAR_DEVICE(roccat_dev), name, device);
	g_free(name);
}

int roccat_device_get_hidraw_endpoint(RoccatDevice *roccat_dev, guint endpoint, GError **error) {
	g_assert(endpoint < gaminggear_device_get_num_interfaces(GAMINGGEAR_DEVICE(roccat_dev)));
	gchar *name = create_hidraw_endpoint_name(endpoint);
	int fd = gaminggear_device_open(GAMINGGEAR_DEVICE(roccat_dev), name, O_RDONLY, error);
	g_free(name);
	return fd;
}

int roccat_device_get_keyboard(RoccatDevice *roccat_dev, GError **error) {
	return gaminggear_device_open(GAMINGGEAR_DEVICE(roccat_dev), keyboard_name, O_WRONLY, error);
}

int roccat_device_get_mouse(RoccatDevice *roccat_dev, GError **error) {
	return gaminggear_device_open(GAMINGGEAR_DEVICE(roccat_dev), mouse_name, O_WRONLY, error);
}

int roccat_device_get_none(RoccatDevice *roccat_dev, GError **error) {
	return gaminggear_device_open(GAMINGGEAR_DEVICE(roccat_dev), none_name, O_WRONLY, error);
}

int roccat_device_get_roccat(RoccatDevice *roccat_dev, GError **error) {
	return gaminggear_device_open(GAMINGGEAR_DEVICE(roccat_dev), roccat_name, O_RDONLY, error);
}

int roccat_device_get_misc(RoccatDevice *roccat_dev, GError **error) {
	RoccatDevicePrivate *priv = roccat_dev->priv;
	GaminggearDevice *gaminggear_dev = GAMINGGEAR_DEVICE(roccat_dev);

	if (priv->misc_fd < 0) {
		priv->misc_fd = gaminggear_open_misc_evdev(gaminggear_device_get_identifier(gaminggear_dev),
				USB_VENDOR_ID_ROCCAT, gaminggear_device_get_product_id(gaminggear_dev), error);
	}

	return priv->misc_fd;
}

RoccatDevice *roccat_device_new(gchar const *identifier, guint vendor_id, guint product_id, guint num_interfaces) {
	return ROCCAT_DEVICE(g_object_new(ROCCAT_DEVICE_TYPE,
			"identifier", identifier,
			"vendor_id", vendor_id,
			"product_id", product_id,
			"num-interfaces", num_interfaces,
			NULL));
}

static void roccat_device_init(RoccatDevice *roccat_dev) {
	RoccatDevicePrivate *priv = ROCCAT_DEVICE_GET_PRIVATE(roccat_dev);
	roccat_dev->priv = priv;

	priv->misc_fd = -1;
}

static void roccat_device_finalize(GObject *object) {
	RoccatDevicePrivate *priv = ROCCAT_DEVICE(object)->priv;
	gaminggear_close_misc_evdev(priv->misc_fd, NULL);
	G_OBJECT_CLASS(roccat_device_parent_class)->finalize(object);
}

static void roccat_device_class_init(RoccatDeviceClass *klass) {
	GObjectClass *gobject_class;

	gobject_class = (GObjectClass*)klass;

	gobject_class->finalize = roccat_device_finalize;

	g_type_class_add_private(klass, sizeof(RoccatDevicePrivate));
}

void roccat_device_debug(RoccatDevice const *roccat_dev) {
	GaminggearDevice *gaminggear_dev = GAMINGGEAR_DEVICE(roccat_dev);
	guint num_interfaces = gaminggear_device_get_num_interfaces(gaminggear_dev);
	guint i;
	gchar const *path;

	if (!roccat_dev)
		return;

	g_debug("Roccat Device   0x%04x:0x%04x (%s)", gaminggear_device_get_vendor_id(gaminggear_dev),
			gaminggear_device_get_product_id(gaminggear_dev),
			roccat_device_get_name_static(roccat_dev));
	g_debug("    Identifier  %s", gaminggear_device_get_identifier(gaminggear_dev));
	g_debug("    Syspath     %s", gaminggear_device_get_path(gaminggear_dev, syspath_name));
	g_debug("    Roccat      %s", gaminggear_device_get_path(gaminggear_dev, roccat_name));
	g_debug("    Interfaces  %u", num_interfaces);

	for (i = 0; i < num_interfaces; ++i) {
		path = roccat_device_get_hidraw_endpoint_dev(roccat_dev, i);
		if (path)
			g_debug("      Hidraw(%u) %s", i, path);
	}

	g_debug("    Input None  %s", gaminggear_device_get_path(gaminggear_dev, none_name));
	g_debug("    Input Mouse %s", gaminggear_device_get_path(gaminggear_dev, mouse_name));
	g_debug("    Input Kbd   %s", gaminggear_device_get_path(gaminggear_dev, keyboard_name));
}

gchar const *roccat_device_get_name_static(RoccatDevice const *roccat_dev) {
	return roccat_talk_device_get_text_static(gaminggear_device_get_product_id(GAMINGGEAR_DEVICE(roccat_dev)));
}

