/*
 *   Razer device access library
 *
 *   Copyright (C) 2007-2018 Michael Buesch <m@bues.ch>
 *
 *   This program 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.
 *
 *   This program 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.
 */

#include "librazer.h"
#include "razer_private.h"
#include "config.h"
#include "profile_emulation.h"

#include "hw_deathadder.h"
#include "hw_deathadder2013.h"
#include "hw_deathadder_chroma.h"
#include "hw_naga.h"
#include "hw_krait.h"
#include "hw_lachesis.h"
#include "hw_lachesis5k6.h"
#include "hw_copperhead.h"
#include "hw_boomslangce.h"
#include "hw_imperator.h"
#include "hw_taipan.h"
#include "hw_mamba_tournament_edition.h"
#include "hw_diamondback_chroma.h"

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>


enum razer_devtype {
	RAZER_DEVTYPE_MOUSE,
};

/** struct razer_mouse_base_ops - Basic device-init operations
 *
 * @type: The type ID.
 *
 * @init: Initialize the device and its private data structures.
 *
 * @release: Release device and data structures.
 */
struct razer_mouse_base_ops {
	enum razer_mouse_type type;
	int (*init)(struct razer_mouse *m, struct libusb_device *udev);
	void (*release)(struct razer_mouse *m);
};

struct razer_usb_device {
	uint16_t vendor;	/* Vendor ID */
	uint16_t product;	/* Product ID */
	enum razer_devtype type;
	union {
		const struct razer_mouse_base_ops *mouse_ops;
	} u;
};

static const struct razer_mouse_base_ops razer_deathadder_base_ops = {
	.type			= RAZER_MOUSETYPE_DEATHADDER,
	.init			= razer_deathadder_init,
	.release		= razer_deathadder_release,
};

static const struct razer_mouse_base_ops razer_deathadder2013_base_ops = {
	.type			= RAZER_MOUSETYPE_DEATHADDER,
	.init			= razer_deathadder2013_init,
	.release		= razer_deathadder2013_release,
};

static const struct razer_mouse_base_ops razer_deathadder_chroma_base_ops = {
	.type			= RAZER_MOUSETYPE_DEATHADDER,
	.init			= razer_deathadder_chroma_init,
	.release		= razer_deathadder_chroma_release,
};

static const struct razer_mouse_base_ops razer_naga_base_ops = {
	.type			= RAZER_MOUSETYPE_NAGA,
	.init			= razer_naga_init,
	.release		= razer_naga_release,
};

static const struct razer_mouse_base_ops razer_krait_base_ops = {
	.type			= RAZER_MOUSETYPE_KRAIT,
	.init			= razer_krait_init,
	.release		= razer_krait_release,
};

static const struct razer_mouse_base_ops razer_lachesis_base_ops = {
	.type			= RAZER_MOUSETYPE_LACHESIS,
	.init			= razer_lachesis_init,
	.release		= razer_lachesis_release,
};

#if 0
static const struct razer_mouse_base_ops razer_lachesis5k6_base_ops = {
	.type			= RAZER_MOUSETYPE_LACHESIS,
	.init			= razer_lachesis5k6_init,
	.release		= razer_lachesis5k6_release,
};
#endif

static const struct razer_mouse_base_ops razer_copperhead_base_ops = {
	.type			= RAZER_MOUSETYPE_COPPERHEAD,
	.init			= razer_copperhead_init,
	.release		= razer_copperhead_release,
};

static const struct razer_mouse_base_ops razer_boomslangce_base_ops = {
	.type			= RAZER_MOUSETYPE_BOOMSLANGCE,
	.init			= razer_boomslangce_init,
	.release		= razer_boomslangce_release,
};

static const struct razer_mouse_base_ops razer_imperator_base_ops = {
	.type			= RAZER_MOUSETYPE_IMPERATOR,
	.init			= razer_imperator_init,
	.release		= razer_imperator_release,
};

static const struct razer_mouse_base_ops razer_taipan_base_ops = {
	.type			= RAZER_MOUSETYPE_TAIPAN,
	.init			= razer_taipan_init,
	.release		= razer_taipan_release,
};

static const struct razer_mouse_base_ops razer_mamba_te_base_ops = {
	.type			= RAZER_MOUSETYPE_MAMBA_TE,
	.init			= razer_mamba_te_init,
	.release		= razer_mamba_te_release,
};

static const struct razer_mouse_base_ops razer_diamondback_chroma_base_ops = {
	.type			= RAZER_MOUSETYPE_DIAMONDBACK_CHROMA,
	.init			= razer_diamondback_chroma_init,
	.release		= razer_diamondback_chroma_release,
};

#define USBVENDOR_ANY	0xFFFF
#define USBPRODUCT_ANY	0xFFFF

#define USB_MOUSE(_vendor, _product, _mouse_ops)	\
	{ .vendor = _vendor, .product = _product,	\
	  .type = RAZER_DEVTYPE_MOUSE,			\
	  .u = { .mouse_ops = _mouse_ops, }, }

/* Table of supported USB devices. */
static const struct razer_usb_device razer_usbdev_table[] = {
	USB_MOUSE(0x1532, 0x0007, &razer_deathadder_base_ops), /* classic */
	USB_MOUSE(0x1532, 0x0016, &razer_deathadder_base_ops), /* 3500 DPI */
	USB_MOUSE(0x1532, 0x0029, &razer_deathadder_base_ops), /* black edition */
	USB_MOUSE(0x1532, 0x0037, &razer_deathadder2013_base_ops), /* 2013 edition */
	USB_MOUSE(0x1532, 0x0038, &razer_deathadder2013_base_ops), /* 1800 edition/Korea PCBang edition */
	USB_MOUSE(0x1532, 0x0043, &razer_deathadder_chroma_base_ops), /* Chroma edition */
//	USB_MOUSE(0x04B4, 0xE006, &razer_deathadder_base_ops), /* cypress bootloader */
	USB_MOUSE(0x1532, 0x0003, &razer_krait_base_ops),
	USB_MOUSE(0x1532, 0x000C, &razer_lachesis_base_ops), /* classic */
//	USB_MOUSE(0x1532, 0x001E, &razer_lachesis5k6_base_ops), /* 5600 DPI */
	USB_MOUSE(0x1532, RAZER_NAGA_PID_CLASSIC, &razer_naga_base_ops),
	USB_MOUSE(0x1532, RAZER_NAGA_PID_EPIC, &razer_naga_base_ops),
	USB_MOUSE(0x1532, RAZER_NAGA_PID_2012, &razer_naga_base_ops),
	USB_MOUSE(0x1532, RAZER_NAGA_PID_HEX, &razer_naga_base_ops),
	USB_MOUSE(0x1532, RAZER_NAGA_PID_2014, &razer_naga_base_ops),
	USB_MOUSE(0x1532, RAZER_NAGA_PID_HEX_2014, &razer_naga_base_ops),
	USB_MOUSE(0x1532, 0x0101, &razer_copperhead_base_ops),
	USB_MOUSE(0x1532, 0x0005, &razer_boomslangce_base_ops),
	USB_MOUSE(0x1532, 0x0017, &razer_imperator_base_ops),
	USB_MOUSE(0x1532, 0x0034, &razer_taipan_base_ops),
	USB_MOUSE(0x1532, 0x0046, &razer_mamba_te_base_ops), /*mamba tournament edition*/
	USB_MOUSE(0x1532, 0x004C, &razer_diamondback_chroma_base_ops),
	{ 0, }, /* List end */
};
#undef USB_MOUSE



static struct libusb_context *libusb_ctx;
static struct razer_mouse *mice_list = NULL;
/* We currently only have one handler. */
static razer_event_handler_t event_handler;
static struct config_file *razer_config_file = NULL;
static bool profile_emu_enabled;

razer_logfunc_t razer_logfunc_info;
razer_logfunc_t razer_logfunc_error;
razer_logfunc_t razer_logfunc_debug;


static inline bool razer_initialized(void)
{
	return !!libusb_ctx;
}

int razer_register_event_handler(razer_event_handler_t handler)
{
	if (event_handler)
		return -EEXIST;
	event_handler = handler;
	return 0;
}

void razer_unregister_event_handler(razer_event_handler_t handler)
{
	event_handler = NULL;
}

static void razer_notify_event(enum razer_event type,
			       const struct razer_event_data *data)
{
	if (event_handler)
		event_handler(type, data);
}

static int match_usbdev(const struct libusb_device_descriptor *desc,
			const struct razer_usb_device *id)
{
	if ((desc->idVendor != id->vendor) &&
	    (id->vendor != USBVENDOR_ANY))
		return 0;
	if ((desc->idProduct != id->product) &&
	    (id->product != USBPRODUCT_ANY))
		return 0;
	return 1;
}

static const struct razer_usb_device * usbdev_lookup(const struct libusb_device_descriptor *desc)
{
	const struct razer_usb_device *id = &(razer_usbdev_table[0]);

	while (id->vendor || id->product) {
		if (match_usbdev(desc, id))
			return id;
		id++;
	}
	return NULL;
}

static void mouse_list_add(struct razer_mouse **base, struct razer_mouse *new_entry)
{
	struct razer_mouse *i;

	new_entry->next = NULL;
	if (!(*base)) {
		*base = new_entry;
		return;
	}
	for (i = *base; i->next; i = i->next)
		;
	i->next = new_entry;
}

static void mouse_list_del(struct razer_mouse **base, struct razer_mouse *del_entry)
{
	struct razer_mouse *i;

	if (del_entry == *base) {
		*base = (*base)->next;
		return;
	}
	for (i = *base; i && (i->next != del_entry); i = i->next)
		;
	if (i)
		i->next = del_entry->next;
}

static struct razer_mouse * mouse_list_find(struct razer_mouse *base,
					    struct libusb_device *udev)
{
	struct razer_mouse *m, *next;
	uint8_t busnr = libusb_get_bus_number(udev);
	uint8_t devaddr = libusb_get_device_address(udev);

	razer_for_each_mouse(m, next, base) {
		if (m->usb_ctx) {
			if (libusb_get_bus_number(m->usb_ctx->dev) == busnr &&
			    libusb_get_device_address(m->usb_ctx->dev) == devaddr)
				return m;
		}
	}

	return NULL;
}

static int parse_idstr(char *idstr, char **devtype, char **devname,
				    char **buspos, char **devid)
{
	*devtype = idstr;
	*devname = razer_strsplit(*devtype, ':');
	*buspos = razer_strsplit(*devname, ':');
	*devid = razer_strsplit(*buspos, ':');
	if (!*devtype || !*devname || !*buspos || !*devid)
		return -EINVAL;
	return 0;
}

static bool simple_globcmp(const char *string,
			   const char *template)
{
	char s, t, tnext;

	while (1) {
		s = string[0];
		t = template[0];

		if (s == '\0' && t == '\0')
			break;

		if (t == '*') {
			tnext = template[1];
			if (s == '\0') {
				if (tnext == '\0')
					break;
				return 0;
			}
			if (s == tnext) {
				template++;
				continue;
			}
		} else {
			if (s == '\0' || t == '\0')
				return 0;
			if (s != t)
				return 0;
			template++;
		}
		string++;
	}

	return 1; /* Match */
}

static bool mouse_idstr_glob_match(struct config_file *f,
				   void *context, void *data,
				   const char *section)
{
	struct razer_mouse *m = context;
	const char **matched_section = data;
	char idstr[RAZER_IDSTR_MAX_SIZE + 1] = { 0, };
	char *idstr_devtype, *idstr_devname, *idstr_buspos, *idstr_devid;
	char globstr[RAZER_IDSTR_MAX_SIZE + 1] = { 0, };
	char *globstr_devtype, *globstr_devname, *globstr_buspos, *globstr_devid;

	if (strlen(section) > RAZER_IDSTR_MAX_SIZE) {
		razer_error("globbed idstr \"%s\" in config too long\n",
			section);
		return 1;
	}
	strcpy(globstr, section);
	strcpy(idstr, m->idstr);
	if (parse_idstr(globstr, &globstr_devtype, &globstr_devname,
				 &globstr_buspos, &globstr_devid))
		return 1;
	if (parse_idstr(idstr, &idstr_devtype, &idstr_devname,
			       &idstr_buspos, &idstr_devid)) {
		razer_error("INTERNAL-ERROR: Failed to parse idstr \"%s\"\n",
			idstr);
		return 1;
	}

	if (!simple_globcmp(idstr_devtype, globstr_devtype))
		return 1;
	if (!simple_globcmp(idstr_devname, globstr_devname))
		return 1;
	if (!simple_globcmp(idstr_buspos, globstr_buspos))
		return 1;
	if (!simple_globcmp(idstr_devid, globstr_devid))
		return 1;

	*matched_section = section;

	return 0; /* Match */
}

static struct razer_mouse_profile * find_prof(struct razer_mouse *m, unsigned int nr)
{
	struct razer_mouse_profile *list;
	unsigned int i;

	if (!m->get_profiles)
		return NULL;
	list = m->get_profiles(m);
	if (!list)
		return NULL;
	for (i = 0; i < m->nr_profiles; i++) {
		if (list[i].nr == nr)
			return &list[i];
	}
	return NULL;
}

static int parse_int_int_pair(const char *str, int *val0, int *val1)
{
	char a[64] = { 0, }, b[64] = { 0, };
	int err;

	*val0 = *val1 = -1;
	err = razer_split_tuple(str, ':', min(sizeof(a), sizeof(b)),
				a, b, NULL);
	if (err) {
		/* It's not a pair. Interpret it as one value. */
		razer_strlcpy(a, str, sizeof(a));
		err = razer_string_to_int(razer_string_strip(a), val1);
		if (err)
			return -EINVAL;
		return 1;
	}
	err = razer_string_to_int(razer_string_strip(a), val0);
	err |= razer_string_to_int(razer_string_strip(b), val1);
	if (err)
		return -EINVAL;

	return 0;
}

static bool mouse_apply_one_config(struct config_file *f,
				   void *context, void *data,
				   const char *section,
				   const char *item,
				   const char *value)
{
	struct razer_mouse *m = context;
	struct razer_mouse_profile *prof;
	bool *error_status = data;
	int err, nr;
	static const size_t tmplen = 128;
	char a[tmplen], b[tmplen], c[tmplen];

//FIXME fixes for glob/prof configs
	if (strcasecmp(item, "profile") == 0) {
		int profile;

		err = razer_string_to_int(value, &profile);
		if (err || profile < 1 || (unsigned int)profile > m->nr_profiles)
			goto error;
		if (m->set_active_profile) {
			prof = find_prof(m, profile - 1);
			if (!prof)
				goto error;
			err = m->set_active_profile(m, prof);
			if (err)
				goto error;
		}
	} else if (strcasecmp(item, "res") == 0) {
		int profile, resolution, i;
		struct razer_mouse_dpimapping *mappings;

		err = parse_int_int_pair(value, &profile, &resolution);
		if (err == 1) {
			prof = m->get_active_profile(m);
			profile = prof->nr + 1;
		} else if (err)
			goto error;
		if (profile < 1 || resolution < 1)
			goto error;
		prof = find_prof(m, profile - 1);
		if (!prof)
			goto error;
		nr = m->supported_dpimappings(m, &mappings);
		if (nr <= 0)
			goto error;
		//FIXME dims
		for (i = 0; i < nr; i++) {
			if (resolution >= 100) {
				if ((int)(mappings[i].res[RAZER_DIM_0]) != resolution)
					continue;
			} else {
				if (mappings[i].nr != (unsigned int)resolution)
					continue;
			}
			err = prof->set_dpimapping(prof, NULL, &mappings[i]);
			if (err)
				goto error;
			goto ok;
		}
		goto invalid; /* res is invalid. Ignore it. */
	} else if (strcasecmp(item, "freq") == 0) {
		int profile, freq, i;
		enum razer_mouse_freq *freqs;

		err = parse_int_int_pair(value, &profile, &freq);
		if (err == 1) {
			prof = m->get_active_profile(m);
			profile = prof->nr + 1;
		} else if (err)
			goto error;
		if (profile < 1 || freq < 1)
			goto error;
		prof = find_prof(m, profile - 1);
		if (!prof)
			goto error;
		nr = m->supported_freqs(m, &freqs);
		if (nr <= 0)
			goto error;
		for (i = 0; i < nr; i++) {
			if (freqs[i] != (enum razer_mouse_freq)freq)
				continue;
			if (!prof->set_freq)
				goto invalid;
			err = prof->set_freq(prof, freqs[i]);
			razer_free_freq_list(freqs, nr);
			if (err)
				goto error;
			goto ok;
		}
		razer_free_freq_list(freqs, nr);
		goto error;
	} else if (strcasecmp(item, "led") == 0) {
		bool on;
		struct razer_led *leds, *led;
		const char *ledname, *ledstate;
		int profile;

		err = razer_split_tuple(value, ':', tmplen, a, b, c, NULL);
		if (err && err != -ENODATA)
			goto error;
		if (!strlen(a) || !strlen(b))
			goto error;
		if (strlen(c)) {
			/* A profile was specified */
			err = razer_string_to_int(razer_string_strip(a), &profile);
			if (err || profile < 1)
				goto error;
			prof = find_prof(m, profile - 1);
			if (!prof)
				goto error;
			ledname = razer_string_strip(b);
			ledstate = razer_string_strip(c);
		} else {
			/* Modify global LEDs */
			prof = NULL;
			ledname = razer_string_strip(a);
			ledstate = razer_string_strip(b);
		}
		err = razer_string_to_bool(ledstate, &on);
		if (err)
			goto error;
		if (prof) {
			if (prof->get_leds) {
				err = prof->get_leds(prof, &leds);
			} else {
				/* Try to fall back to global */
				if (!m->global_get_leds)
					goto ok; /* No LEDs. Ignore config. */
				err = m->global_get_leds(m, &leds);
			}
		} else {
			if (!m->global_get_leds)
				goto ok; /* No LEDs. Ignore config. */
			err = m->global_get_leds(m, &leds);
		}
		if (err < 0)
			goto error;
		if (err == 0)
			goto ok; /* No LEDs. Ignore config. */
		for (led = leds; led; led = led->next) {
			if (strcasecmp(led->name, ledname) != 0)
				continue;
			if (!led->toggle_state) {
				razer_free_leds(leds);
				goto invalid;
			}
			err = led->toggle_state(led,
				on ? RAZER_LED_ON : RAZER_LED_OFF);
			razer_free_leds(leds);
			if (err)
				goto error;
			goto ok;
		}
		razer_free_leds(leds);
		goto error;
	} else if (strcasecmp(item, "mode") == 0) {
		enum razer_led_mode mode;
		struct razer_led *leds, *led;
		const char *ledname, *ledmode;
		int profile;

		err = razer_split_tuple(value, ':', tmplen, a, b, c, NULL);
		if (err && err != -ENODATA)
			goto error;
		if (!strlen(a) || !strlen(b))
			goto error;
		if (strlen(c)) {
			/* A profile was specified */
			err = razer_string_to_int(razer_string_strip(a), &profile);
			if (err || profile < 1)
				goto error;
			prof = find_prof(m, profile - 1);
			if (!prof)
				goto error;
			ledname = razer_string_strip(b);
			ledmode = razer_string_strip(c);
		} else {
			/* Modify global LEDs */
			prof = NULL;
			ledname = razer_string_strip(a);
			ledmode = razer_string_strip(b);
		}
		err = razer_string_to_mode(ledmode, &mode);
		if (err)
			goto error;
		if (prof) {
			if (prof->get_leds) {
				err = prof->get_leds(prof, &leds);
			} else {
				/* Try to fall back to global */
				if (!m->global_get_leds)
					goto ok; /* No LEDs. Ignore config. */
				err = m->global_get_leds(m, &leds);
			}
		} else {
			if (!m->global_get_leds)
				goto ok; /* No LEDs. Ignore config. */
			err = m->global_get_leds(m, &leds);
		}
		if (err < 0)
			goto error;
		if (err == 0)
			goto ok; /* No LEDs. Ignore config. */
		for (led = leds; led; led = led->next) {
			if (strcasecmp(led->name, ledname) != 0)
				continue;
			if (!led->set_mode) {
				razer_free_leds(leds);
				goto invalid;
			}
			err = led->set_mode(led, mode);
			razer_free_leds(leds);
			if (err)
				goto error;
			goto ok;
		}
		razer_free_leds(leds);
		goto error;
	} else if (strcasecmp(item, "color") == 0) {
		struct razer_rgb_color color;
		struct razer_led *leds, *led;
		const char *ledname, *ledcolor;
		int profile;

		err = razer_split_tuple(value, ':', tmplen, a, b, c, NULL);
		if (err && err != -ENODATA)
			goto error;
		if (!strlen(a) || !strlen(b))
			goto error;
		if (strlen(c)) {
			/* A profile was specified */
			err = razer_string_to_int(razer_string_strip(a), &profile);
			if (err || profile < 1)
				goto error;
			prof = find_prof(m, profile - 1);
			if (!prof)
				goto error;
			ledname = razer_string_strip(b);
			ledcolor = razer_string_strip(c);
		} else {
			/* Modify global LEDs */
			prof = NULL;
			ledname = razer_string_strip(a);
			ledcolor = razer_string_strip(b);
		}
		err = razer_string_to_color(ledcolor, &color);
		if (err)
			goto error;
		if (prof) {
			if (prof->get_leds) {
				err = prof->get_leds(prof, &leds);
			} else {
				/* Try to fall back to global */
				if (!m->global_get_leds)
					goto ok; /* No LEDs. Ignore config. */
				err = m->global_get_leds(m, &leds);
			}
		} else {
			if (!m->global_get_leds)
				goto ok; /* No LEDs. Ignore config. */
			err = m->global_get_leds(m, &leds);
		}
		if (err < 0)
			goto error;
		if (err == 0)
			goto ok; /* No LEDs. Ignore config. */
		for (led = leds; led; led = led->next) {
			if (strcasecmp(led->name, ledname) != 0)
				continue;
			if (!led->change_color) {
				razer_free_leds(leds);
				goto invalid;
			}
			err = led->change_color(led, &color);
			razer_free_leds(leds);
			if (err)
				goto error;
			goto ok;
		}
		razer_free_leds(leds);
		goto error;
	} else if (strcasecmp(item, "disabled") == 0) {
		goto ok;
	} else
		goto invalid;
ok:
	return 1;
error:
	*error_status = 1;
invalid:
	razer_error("Config section \"%s\" item \"%s\" "
		"invalid.\n", section, item);
	return *error_status ? 0 : 1;
}

static void mouse_apply_initial_config(struct razer_mouse *m)
{
	const char *section = NULL;
	int err;
	bool error_status = 0;

	config_for_each_section(razer_config_file,
				m, &section,
				mouse_idstr_glob_match);
	if (!section)
		return;
	if (config_get_bool(razer_config_file, section,
			    "disabled", 0, CONF_NOCASE)) {
		razer_debug("Initial config for \"%s\" is disabled. Not applying.\n",
			    m->idstr);
		return;
	}
	razer_debug("Applying config section \"%s\" to \"%s\"\n",
		section, m->idstr);
	err = m->claim(m);
	if (err) {
		razer_error("Failed to claim \"%s\"\n", m->idstr);
		return;
	}
	config_for_each_item(razer_config_file,
			     m, &error_status,
			     section,
			     mouse_apply_one_config);
	m->release(m);
	if (error_status) {
		razer_error("Failed to apply initial config "
			"to \"%s\"\n", m->idstr);
	}
}

static struct razer_usb_context * razer_create_usb_ctx(struct libusb_device *dev)
{
	struct razer_usb_context *ctx;

	ctx = zalloc(sizeof(*ctx));
	if (!ctx)
		return NULL;
	ctx->dev = dev;
	ctx->bConfigurationValue = 1;

	return ctx;
}

static int mouse_default_claim(struct razer_mouse *m)
{
	return razer_generic_usb_claim_refcount(m->usb_ctx, &m->claim_count);
}

static int mouse_default_release(struct razer_mouse *m)
{
	int err = 0;

	if (m->claim_count == 1) {
		if (m->commit)
			err = m->commit(m, 0);
	}
	razer_generic_usb_release_refcount(m->usb_ctx, &m->claim_count);

	return err;
}

static struct razer_mouse * mouse_new(const struct razer_usb_device *id,
				      struct libusb_device *udev)
{
	struct razer_event_data ev;
	struct razer_mouse *m;
	int err;

	libusb_ref_device(udev);

	m = zalloc(sizeof(*m));
	if (!m)
		return NULL;
	m->usb_ctx = razer_create_usb_ctx(udev);
	if (!m->usb_ctx)
		goto err_free_mouse;

	/* Set default values and callbacks */
	m->nr_profiles = 1;
	m->claim = mouse_default_claim;
	m->release = mouse_default_release;

	/* Call the driver init */
	m->base_ops = id->u.mouse_ops;
	err = m->base_ops->init(m, udev);
	if (err)
		goto err_free_ctx;
	udev = m->usb_ctx->dev;

	if (WARN_ON(m->nr_profiles <= 0))
		goto err_release;
	if (m->nr_profiles == 1 && !m->get_active_profile)
		m->get_active_profile = m->get_profiles;
	if (profile_emu_enabled && m->nr_profiles == 1) {
		err = razer_mouse_init_profile_emulation(m);
		if (err)
			goto err_release;
	}

	mouse_apply_initial_config(m);

	razer_debug("Allocated and initialized new mouse \"%s\"\n",
		m->idstr);

	ev.u.mouse = m;
	razer_notify_event(RAZER_EV_MOUSE_ADD, &ev);

	return m;

err_release:
	m->base_ops->release(m);
err_free_ctx:
	razer_free(m->usb_ctx, sizeof(*(m->usb_ctx)));
err_free_mouse:
	razer_free(m, sizeof(*m));
	libusb_unref_device(udev);

	return NULL;
}

static void razer_free_mouse(struct razer_mouse *m)
{
	struct razer_event_data ev;

	razer_debug("Freeing mouse (type=%d)\n",
		m->base_ops->type);

	ev.u.mouse = m;
	razer_notify_event(RAZER_EV_MOUSE_REMOVE, &ev);

	if (m->release == mouse_default_release) {
		while (m->claim_count)
			m->release(m);
	}
	razer_mouse_exit_profile_emulation(m);
	m->base_ops->release(m);

	libusb_unref_device(m->usb_ctx->dev);

	razer_free(m->usb_ctx, sizeof(*(m->usb_ctx)));
	razer_free(m, sizeof(*m));
}

static void razer_free_mice(struct razer_mouse *mouse_list)
{
	struct razer_mouse *mouse, *next;

	for (mouse = mouse_list; mouse; ) {
		next = mouse->next;
		razer_free_mouse(mouse);
		mouse = next;
	}
}

struct new_razer_usb_device {
	const struct razer_usb_device *id;
	struct usb_device *udev;
};

struct razer_mouse * razer_rescan_mice(void)
{
	struct libusb_device **devlist, *dev;
	ssize_t nr_devices;
	unsigned int i;
	int err;
	struct libusb_device_descriptor desc;
	const struct razer_usb_device *id;
	struct razer_mouse *m, *next;

	nr_devices = libusb_get_device_list(libusb_ctx, &devlist);
	if (nr_devices < 0) {
		razer_error("razer_rescan_mice: Failed to get USB device list\n");
		return NULL;
	}

	for (i = 0; i < nr_devices; i++) {
		dev = devlist[i];
		err = libusb_get_device_descriptor(dev, &desc);
		if (err) {
			razer_error("razer_rescan_mice: Failed to get descriptor\n");
			continue;
		}
		id = usbdev_lookup(&desc);
		if (!id || id->type != RAZER_DEVTYPE_MOUSE)
			continue;
		m = mouse_list_find(mice_list, dev);
		if (m) {
			/* We already had this mouse */
			m->flags |= RAZER_MOUSEFLG_PRESENT;
		} else {
			/* We don't have this mouse, yet. Create a new one */
			m = mouse_new(id, dev);
			if (m) {
				m->flags |= RAZER_MOUSEFLG_PRESENT;
				mouse_list_add(&mice_list, m);
			}
		}
	}
	/* Remove mice that are not connected anymore. */
	razer_for_each_mouse(m, next, mice_list) {
		if (m->flags & RAZER_MOUSEFLG_PRESENT) {
			m->flags &= ~RAZER_MOUSEFLG_PRESENT;
			continue;
		}
		mouse_list_del(&mice_list, m);
		razer_free_mouse(m);
	}

	libusb_free_device_list(devlist, 1);

	return mice_list;
}

int razer_reconfig_mice(void)
{
	struct razer_mouse *m, *next;
	int err;

	razer_for_each_mouse(m, next, mice_list) {
		err = m->claim(m);
		if (err)
			return err;
		if (m->commit)
			err = m->commit(m, 1);
		m->release(m);
		if (err)
			return err;
	}

	return 0;
}

void razer_free_freq_list(enum razer_mouse_freq *freq_list, int count)
{
	if (freq_list)
		free(freq_list);
}

void razer_free_resolution_list(enum razer_mouse_res *res_list, int count)
{
	if (res_list)
		free(res_list);
}

void razer_free_leds(struct razer_led *led_list)
{
	struct razer_led *led, *next;

	for (led = led_list; led; ) {
		next = led->next;
		free(led);
		led = next;
	}
}

int razer_init(int enable_profile_emu)
{
	int err = 0;

	if (!razer_initialized())
		err = libusb_init(&libusb_ctx);
	if (!err)
		profile_emu_enabled = enable_profile_emu;

	return err ? -EINVAL : 0;
}

void razer_exit(void)
{
	if (!razer_initialized())
		return;
	razer_free_mice(mice_list);
	mice_list = NULL;
	config_file_free(razer_config_file);
	razer_config_file = NULL;

	libusb_exit(libusb_ctx);
	libusb_ctx = NULL;
}

int razer_usb_add_used_interface(struct razer_usb_context *ctx,
				 int bInterfaceNumber,
				 int bAlternateSetting)
{
	struct razer_usb_interface *interf;

	if (ctx->nr_interfaces >= ARRAY_SIZE(ctx->interfaces)) {
		razer_error("USB context interface array overflow\n");
		return -ENOSPC;
	}

	interf = &ctx->interfaces[ctx->nr_interfaces];
	interf->bInterfaceNumber = bInterfaceNumber;
	interf->bAlternateSetting = bAlternateSetting;
	ctx->nr_interfaces++;

	return 0;
}

static void razer_reattach_usb_kdrv(struct razer_usb_context *ctx,
				    int bInterfaceNumber)
{
	int res;

	res = libusb_kernel_driver_active(ctx->h, bInterfaceNumber);
	if (res == 1)
		return;
	if (res) {
		razer_error("Failed to get kernel driver state\n");
		return;
	}

	res = libusb_attach_kernel_driver(ctx->h, bInterfaceNumber);
	if (res) {
		razer_error("Failed to reconnect the kernel driver (%d).\n"
			"The device most likely won't work now. Try to replug it.\n", res);
		return;
	}
}

static void razer_usb_release(struct razer_usb_context *ctx,
			      int bInterfaceNumber)
{
	libusb_release_interface(ctx->h, bInterfaceNumber);
	razer_reattach_usb_kdrv(ctx, bInterfaceNumber);
}

int razer_generic_usb_claim(struct razer_usb_context *ctx)
{
	unsigned int tries, i;
	int err, config;
	struct razer_usb_interface *interf;

	err = libusb_open(ctx->dev, &ctx->h);
	if (err) {
		razer_error("razer_generic_usb_claim: Failed to open USB device\n");
		return -ENODEV;
	}

	/* Detach kernel drivers for all interfaces. */
	for (i = 0; i < ctx->nr_interfaces; i++) {
		interf = &ctx->interfaces[i];
		err = libusb_kernel_driver_active(ctx->h, interf->bInterfaceNumber);
		if (err == 1) {
			err = libusb_detach_kernel_driver(ctx->h, interf->bInterfaceNumber);
			if (err) {
				err = -EBUSY;
				razer_error("Failed to detach kernel driver\n");
				goto err_close;
			}
		} else if (err) {
			err = -ENODEV;
			razer_error("Failed to get kernel driver state\n");
			goto err_close;
		}
	}

	tries = 0;
	while (1) {
		if (tries >= 10) {
			razer_error("razer_generic_usb_claim: Failed to claim config\n");
			goto err_close;
		}

		/* Select the correct configuration */
		err = libusb_get_configuration(ctx->h, &config);
		if (err) {
			err = -EBUSY;
			razer_error("razer_generic_usb_claim: Failed to get configuration\n");
			goto err_close;
		}
		if (config != ctx->bConfigurationValue) {
			err = libusb_set_configuration(ctx->h, ctx->bConfigurationValue);
			if (err) {
				err = -EBUSY;
				razer_error("razer_generic_usb_claim: Failed to set configuration\n");
				goto err_close;
			}
		}

		/* And finally claim all interfaces. */
		for (i = 0; i < ctx->nr_interfaces; i++) {
			interf = &ctx->interfaces[i];
			err = libusb_claim_interface(ctx->h, interf->bInterfaceNumber);
			if (err) {
				err = -EIO;
				razer_error("Failed to claim USB interface\n");
				goto err_close;
			}
			err = libusb_set_interface_alt_setting(ctx->h, interf->bInterfaceNumber,
							       interf->bAlternateSetting);
			if (err) {
				/* This error triggers on some devices.
				 * Just print a warning and ignore. */
				razer_info("razer_generic_usb_claim: "
					   "Failed to set alt setting %d on interface %d. "
					   "Ignoring...\n",
					   (int)interf->bAlternateSetting,
					   (int)interf->bInterfaceNumber);
			}
		}

		/* To make sure there was no race, check config value again */
		err = libusb_get_configuration(ctx->h, &config);
		if (err) {
			err = -EBUSY;
			razer_error("razer_generic_usb_claim: Failed to get configuration\n");
			goto err_close;
		}
		if (config == ctx->bConfigurationValue)
			break;
		razer_msleep(100);
		tries++;
	}

	return 0;

err_close:
	libusb_close(ctx->h);

	return err;
}

int razer_generic_usb_claim_refcount(struct razer_usb_context *ctx,
				     unsigned int *refcount)
{
	int err;

	if (!(*refcount)) {
		err = razer_generic_usb_claim(ctx);
		if (err)
			return err;
	}
	(*refcount)++;

	return 0;
}

void razer_generic_usb_release(struct razer_usb_context *ctx)
{
	int i;

	for (i = ctx->nr_interfaces - 1; i >= 0; i--)
		razer_usb_release(ctx, ctx->interfaces[i].bInterfaceNumber);
	libusb_close(ctx->h);
}

void razer_generic_usb_release_refcount(struct razer_usb_context *ctx,
					unsigned int *refcount)
{
	if (*refcount) {
		(*refcount)--;
		if (!(*refcount))
			razer_generic_usb_release(ctx);
	}
}

void razer_generic_usb_gen_idstr(struct libusb_device *udev,
				 struct libusb_device_handle *h,
				 const char *devname,
				 bool include_devicenr,
				 const char *serial,
				 char *idstr_buf)
{
	char devid[96];
	char serial_buf[64];
	char buspos[16];
	char c;
	size_t i;
	unsigned int serial_index = 0;
	int err;
	struct libusb_device_descriptor devdesc;
	struct razer_usb_context usbctx = {
		.dev = udev,
		.h = h,
	};

	err = libusb_get_device_descriptor(udev, &devdesc);
	if (err) {
		razer_error("razer_generic_usb_gen_idstr: Failed to get "
			"device descriptor (%d)\n", err);
		return;
	}

	if (serial && strlen(serial)) {
		/* Enforce ASCII characters. */
		for (i = 0; i < ARRAY_SIZE(serial_buf) - 1; i++) {
			c = serial[i];
			if (c == '\0')
				break;
			if ((unsigned char)c <= 0x7Fu)
				serial_buf[i] = c;
			else
				serial_buf[i] = '?'; /* Non-ASCII char. */
		}
		serial_buf[i] = '\0';
		serial = serial_buf;
	} else {
		serial_buf[0] = '\0';
		serial_index = devdesc.iSerialNumber;
		err = -EINVAL;
		if (serial_index) {
			err = 0;
			if (!h)
				err = razer_generic_usb_claim(&usbctx);
			if (err) {
				razer_error("Failed to claim device for serial fetching.\n");
			} else {
				err = libusb_get_string_descriptor_ascii(
					usbctx.h, serial_index,
					(unsigned char *)serial_buf, sizeof(serial_buf));
				if (!h)
					razer_generic_usb_release(&usbctx);
				/* Enforce ASCII characters. */
				for (i = 0; i < ARRAY_SIZE(serial_buf); i++) {
					if (serial_buf[i] == '\0')
						break;
					if ((unsigned char)serial_buf[i] > 0x7Fu)
						serial_buf[i] = '?'; /* Non-ASCII char. */
				}
			}
		}
		if (err <= 0)
			strcpy(serial_buf, "0");
		serial = serial_buf;
	}

	snprintf(devid, sizeof(devid), "%04X-%04X-%s",
		 devdesc.idVendor,
		 devdesc.idProduct, serial);
	if (include_devicenr) {
		snprintf(buspos, sizeof(buspos), "%03d-%03d",
			 libusb_get_bus_number(udev),
			 libusb_get_device_address(udev));
	} else {
		snprintf(buspos, sizeof(buspos), "%03d",
			 libusb_get_bus_number(udev));
	}

	razer_create_idstr(idstr_buf, BUSTYPESTR_USB, buspos,
			   DEVTYPESTR_MOUSE, devname, devid);
}

/** razer_usb_force_hub_reset
 * Force reset of the hub the specified device is on
 */
int razer_usb_force_hub_reset(struct razer_usb_context *device_ctx)
{
	struct libusb_device_handle *h;
	struct libusb_device *hub = NULL, *dev;
	uint8_t hub_bus_number, hub_device_address;
	struct razer_usb_reconnect_guard rg;
	int err;
	struct libusb_device **devlist;
	ssize_t devlist_size, i;

	razer_debug("Forcing hub reset for device %03u:%03u\n",
		libusb_get_bus_number(device_ctx->dev),
		libusb_get_device_address(device_ctx->dev));

	razer_usb_reconnect_guard_init(&rg, device_ctx);

	hub_bus_number = libusb_get_bus_number(device_ctx->dev);
	hub_device_address = 1; /* Constant */

	devlist_size = libusb_get_device_list(libusb_ctx, &devlist);
	for (i = 0; i < devlist_size; i++) {
		dev = devlist[i];
		if (libusb_get_bus_number(dev) == hub_bus_number &&
		    libusb_get_device_address(dev) == hub_device_address) {
			hub = dev;
			break;
		}
	}
	if (!hub) {
		razer_error("razer_usb_force_reinit: Failed to find hub\n");
		err = -ENODEV;
		goto error;
	}
	razer_debug("Resetting root hub %03u:%03u\n",
		hub_bus_number, hub_device_address);

	err = libusb_open(hub, &h);
	if (err) {
		razer_error("razer_usb_force_reinit: Failed to open hub device\n");
		err = -ENODEV;
		goto error;
	}
	libusb_reset_device(h);
	libusb_close(h);

	err = razer_usb_reconnect_guard_wait(&rg, 1);
	if (err) {
		razer_error("razer_usb_force_reinit: "
			"Failed to discover the reconnected device\n");
		goto error;
	}
	razer_debug("Hub reset completed. Device rediscovered as %03u:%03u\n",
		libusb_get_bus_number(device_ctx->dev),
		libusb_get_device_address(device_ctx->dev));

	err = 0;
error:
	libusb_free_device_list(devlist, 1);

	return err;
}

/** razer_usb_reconnect_guard_init - Init the reconnect-guard context
 *
 * Call this _before_ triggering any device operations that might
 * reset the device.
 */
int razer_usb_reconnect_guard_init(struct razer_usb_reconnect_guard *guard,
				   struct razer_usb_context *ctx)
{
	int err;

	guard->ctx = ctx;
	err = libusb_get_device_descriptor(ctx->dev, &guard->old_desc);
	if (err) {
		razer_error("razer_usb_reconnect_guard_init: Failed to "
			"get device descriptor\n");
		return err;
	}
	guard->old_busnr = libusb_get_bus_number(ctx->dev);
	guard->old_devaddr = libusb_get_device_address(ctx->dev);

	return 0;
}

static struct libusb_device * guard_find_usb_dev(const struct libusb_device_descriptor *expected_desc,
						 uint8_t expected_bus_number,
						 uint8_t expected_dev_addr,
						 bool exact_match)
{
	struct libusb_device **devlist, *dev;
	struct libusb_device_descriptor desc;
	uint8_t dev_addr;
	ssize_t nr_devices, i, j;
	int err;

	nr_devices = libusb_get_device_list(libusb_ctx, &devlist);
	if (nr_devices < 0) {
		razer_error("guard_find_usb_dev: Failed to get device list\n");
		return NULL;
	}

	for (i = 0; i < nr_devices; i++) {
		dev = devlist[i];
		if (libusb_get_bus_number(dev) != expected_bus_number)
			continue;
		err = libusb_get_device_descriptor(dev, &desc);
		if (err)
			continue;
		if (memcmp(&desc, expected_desc, sizeof(desc)) != 0)
			continue;
		dev_addr = libusb_get_device_address(dev);
		if (exact_match) {
			if (dev_addr == expected_dev_addr)
				goto found_dev;
		} else {
			for (j = 0; j < 64; j++) {
				if (dev_addr == ((expected_dev_addr + j) & 0x7F))
					goto found_dev;
			}
		}
	}
	libusb_free_device_list(devlist, 1);

	return NULL;

found_dev:
	libusb_ref_device(dev);
	libusb_free_device_list(devlist, 1);

	return dev;
}

/** razer_usb_reconnect_guard_wait - Protect against a firmware reconnect.
 *
 * If the firmware does a reconnect of the device on the USB bus, this
 * function tries to keep track of the device and it will update the
 * usb context information.
 * Of course, this is not completely race-free, but we try to do our best.
 *
 * hub_reset is true, if the device reconnects due to a HUB reset event.
 * Otherwise it's assumed that the device reconnects on behalf of itself.
 * If hub_reset is false, the device is expected to be claimed.
 */
int razer_usb_reconnect_guard_wait(struct razer_usb_reconnect_guard *guard, bool hub_reset)
{
	uint8_t reconn_dev_addr;
	uint8_t old_dev_addr = guard->old_devaddr;
	uint8_t old_bus_number = guard->old_busnr;
	int res, errorcode = 0;
	struct libusb_device *dev;
	struct timeval now, timeout;

	if (!hub_reset) {
		/* Release the device, so the kernel can detect the bus reconnect. */
		razer_generic_usb_release(guard->ctx);
	}

	/* Wait for the device to disconnect. */
	gettimeofday(&timeout, NULL);
	razer_timeval_add_msec(&timeout, 3000);
	while (1) {
		dev = guard_find_usb_dev(&guard->old_desc,
				old_bus_number, old_dev_addr, 1);
		if (!dev)
			break;
		libusb_unref_device(dev);
		gettimeofday(&now, NULL);
		if (razer_timeval_after(&now, &timeout)) {
			/* Timeout. Hm. It seems the device won't reconnect.
			 * That's probably OK. Reclaim it. */
			razer_error("razer_usb_reconnect_guard: "
				"The device did not disconnect! If it "
				"does not work anymore, try to replug it.\n");
			goto reclaim;
		}
		razer_msleep(50);
	}

	/* Construct the device address it will reconnect on.
	 * On a device reset the new dev addr will be >= reconn_dev_addr.
	 */
	reconn_dev_addr = (old_dev_addr + 1) & 0x7F;

	/* Wait for the device to reconnect. */
	gettimeofday(&timeout, NULL);
	razer_timeval_add_msec(&timeout, 3000);
	while (1) {
		dev = guard_find_usb_dev(&guard->old_desc,
				old_bus_number, reconn_dev_addr, 0);
		if (dev)
			break;
		gettimeofday(&now, NULL);
		if (razer_timeval_after(&now, &timeout)) {
			razer_error("razer_usb_reconnect_guard: The device did not "
				"reconnect! It might not work anymore. Try to replug it.\n");
			razer_debug("Expected reconnect busid was: %02u:>=%03u\n",
				old_dev_addr, reconn_dev_addr);
			errorcode = -EBUSY;
			goto out;
		}
		razer_msleep(50);
	}

	/* Update the USB context. */
	libusb_unref_device(guard->ctx->dev);
	guard->ctx->dev = dev;

reclaim:
	if (!hub_reset) {
		/* Reclaim the new device. */
		res = razer_generic_usb_claim(guard->ctx);
		if (res) {
			razer_error("razer_usb_reconnect_guard: Reclaim failed.\n");
			return res;
		}
	}
out:
	return errorcode;
}

int razer_load_config(const char *path)
{
	struct config_file *conf = NULL;

	if (!razer_initialized())
		return -EINVAL;

	if (!path)
		path = RAZER_DEFAULT_CONFIG;
	if (strlen(path)) {
		conf = config_file_parse(path, 1);
		if (!conf)
			return -ENOENT;
	}
	config_file_free(razer_config_file);
	razer_config_file = conf;

	return 0;
}

void razer_set_logging(razer_logfunc_t info_callback,
		       razer_logfunc_t error_callback,
		       razer_logfunc_t debug_callback)
{
	razer_logfunc_info = info_callback;
	razer_logfunc_error = error_callback;
	razer_logfunc_debug = debug_callback;
}

static void do_init_axis(struct razer_axis *axis,
			 unsigned int id, const char *name, unsigned int flags)
{
	if (name) {
		axis->id = id;
		axis->name = name;
		axis->flags = flags;
	}
}

void razer_init_axes(struct razer_axis *axes,
		     const char *name0, unsigned int flags0,
		     const char *name1, unsigned int flags1,
		     const char *name2, unsigned int flags2)
{
	do_init_axis(&axes[0], 0, name0, flags0);
	do_init_axis(&axes[1], 1, name1, flags1);
	do_init_axis(&axes[2], 2, name2, flags2);
}

struct razer_mouse_dpimapping * razer_mouse_get_dpimapping_by_res(
		struct razer_mouse_dpimapping *mappings, size_t nr_mappings,
		enum razer_dimension dim, enum razer_mouse_res res)
{
	struct razer_mouse_dpimapping *mapping = NULL;
	size_t i;

	for (i = 0; i < nr_mappings; i++) {
		if (mappings[i].res[dim] == res) {
			mapping = &mappings[i];
			break;
		}
	}

	return mapping;
}

void razer_event_spacing_init(struct razer_event_spacing *es,
			      unsigned int msec)
{
	memset(es, 0, sizeof(*es));
	es->spacing_msec = msec;
}

void razer_event_spacing_enter(struct razer_event_spacing *es)
{
	struct timeval now, deadline;
	int wait_msec;

	gettimeofday(&now, NULL);
	deadline = es->last_event;
	razer_timeval_add_msec(&deadline, es->spacing_msec);

	if (razer_timeval_after(&deadline, &now)) {
		/* We have to sleep long enough to ensure we're
		 * after the deadline. */
		wait_msec = razer_timeval_msec_diff(&deadline, &now);
		WARN_ON(wait_msec < 0);
		razer_msleep(wait_msec + 1);
		gettimeofday(&now, NULL);
		razer_error_on(razer_timeval_after(&deadline, &now),
			       "Failed to maintain event spacing\n");
	}
}

void razer_event_spacing_leave(struct razer_event_spacing *es)
{
	gettimeofday(&es->last_event, NULL);
}
