/*
 *   Razer device access library
 *   Profile emulation
 *
 *   Copyright (C) 2011 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 "profile_emulation.h"


static int mouse_profemu_commit(struct razer_mouse_profile_emu *emu)
{
	struct razer_mouse_profile *hw_profile = emu->hw_profile;
	unsigned int active_prof_nr = emu->active_profile->nr;
	struct razer_mouse_profile_emu_data *data;
	struct razer_mouse *mouse = emu->mouse;
	unsigned int i;
	int err;

	if (WARN_ON(active_prof_nr >= ARRAY_SIZE(emu->data)))
		return -EINVAL;
	data = &emu->data[active_prof_nr];

	err = mouse->claim(mouse);
	if (err) {
		razer_error("profile emulation: Failed to claim mouse\n");
		return err;
	}

	if (hw_profile->set_dpimapping) {
		struct razer_axis *axes = NULL;

		if (mouse->supported_axes) {
			err = mouse->supported_axes(mouse, &axes);
			if (err < 0)
				goto error;
			WARN_ON((unsigned int)err != data->nr_dpimappings);
		}
		for (i = 0; i < data->nr_dpimappings; i++) {
			if (!data->dpimappings[i])
				continue;
			err = hw_profile->set_dpimapping(
						hw_profile,
						axes ? &axes[i] : NULL,
						data->dpimappings[i]);
			if (err)
				goto error;
		}
	}
	if (hw_profile->set_button_function) {
		struct razer_button *buttons = NULL;

		if (mouse->supported_buttons) {
			err = mouse->supported_buttons(mouse, &buttons);
			if (err < 0)
				goto error;
			WARN_ON((unsigned int)err != data->nr_butfuncs);
		}
		for (i = 0; i < data->nr_butfuncs; i++) {
			if (!data->butfuncs[i])
				continue;
			err = hw_profile->set_button_function(
						hw_profile,
						buttons ? &buttons[i] : NULL,
						data->butfuncs[i]);
			if (err)
				goto error;
		}
	}
	if (hw_profile->set_freq) {
		err = hw_profile->set_freq(hw_profile, data->freq);
		if (err)
			goto error;
	}

	mouse->release(mouse);
	razer_debug("profile emulation: Committed active profile\n");

	return 0;

error:
	razer_error("profile emulation: Failed to commit settings\n");
	mouse->release(mouse);

	return err;
}

static const razer_utf16_t * mouse_profemu_get_name(struct razer_mouse_profile *p)
{
	struct razer_mouse *mouse = p->mouse;
	struct razer_mouse_profile_emu *emu = mouse->profemu;

	if (WARN_ON(p->nr >= ARRAY_SIZE(emu->profiles)))
		return NULL;
	return emu->data[p->nr].name;
}

static int mouse_profemu_set_name(struct razer_mouse_profile *p,
				  const razer_utf16_t *new_name)
{
	struct razer_mouse *mouse = p->mouse;
	struct razer_mouse_profile_emu *emu = mouse->profemu;

	if (WARN_ON(p->nr >= ARRAY_SIZE(emu->profiles)))
		return -EINVAL;
	razer_utf16_cpy(emu->data[p->nr].name, new_name, PROFEMU_NAME_MAX);

	return 0;
}

static enum razer_mouse_freq mouse_profemu_get_freq(struct razer_mouse_profile *p)
{
	struct razer_mouse *mouse = p->mouse;
	struct razer_mouse_profile_emu *emu = mouse->profemu;

	if (WARN_ON(p->nr >= ARRAY_SIZE(emu->profiles)))
		return RAZER_MOUSE_FREQ_UNKNOWN;

	return emu->data[p->nr].freq;
}

static int mouse_profemu_set_freq(struct razer_mouse_profile *p,
				  enum razer_mouse_freq freq)
{
	struct razer_mouse *mouse = p->mouse;
	struct razer_mouse_profile_emu *emu = mouse->profemu;

	if (WARN_ON(p->nr >= ARRAY_SIZE(emu->profiles)))
		return -EINVAL;

	emu->data[p->nr].freq = freq;

	if (p == emu->active_profile)
		return mouse_profemu_commit(emu);
	return 0;
}

static struct razer_mouse_dpimapping * mouse_profemu_get_dpimapping(
				struct razer_mouse_profile *p,
				struct razer_axis *axis)
{
	struct razer_mouse *mouse = p->mouse;
	struct razer_mouse_profile_emu *emu = mouse->profemu;
	struct razer_mouse_profile_emu_data *data;
	unsigned int axis_index;

	if (WARN_ON(p->nr >= ARRAY_SIZE(emu->profiles)))
		return NULL;
	data = &emu->data[p->nr];
	axis_index = axis ? axis->id : 0;
	if (WARN_ON(axis_index >= ARRAY_SIZE(data->dpimappings)))
		return NULL;

	return data->dpimappings[axis_index];
}

static int mouse_profemu_set_dpimapping(struct razer_mouse_profile *p,
					struct razer_axis *axis,
					struct razer_mouse_dpimapping *d)
{
	struct razer_mouse *mouse = p->mouse;
	struct razer_mouse_profile_emu *emu = mouse->profemu;
	struct razer_mouse_profile_emu_data *data;
	unsigned int i;

	if (WARN_ON(p->nr >= ARRAY_SIZE(emu->profiles)))
		return -EINVAL;
	data = &emu->data[p->nr];

	if (axis) {
		if (WARN_ON(axis->id >= ARRAY_SIZE(data->dpimappings)))
			return -EINVAL;
		data->dpimappings[axis->id] = d;
	} else {
		for (i = 0; i < data->nr_dpimappings; i++) {
			if (data->dpimappings[i])
				data->dpimappings[i] = d;
		}
	}

	if (p == emu->active_profile)
		return mouse_profemu_commit(emu);
	return 0;
}

static struct razer_button_function * mouse_profemu_get_button_function(
				struct razer_mouse_profile *p,
				struct razer_button *b)
{
	struct razer_mouse *mouse = p->mouse;
	struct razer_mouse_profile_emu *emu = mouse->profemu;
	struct razer_mouse_profile_emu_data *data;

	if (WARN_ON(p->nr >= ARRAY_SIZE(emu->profiles)))
		return NULL;
	data = &emu->data[p->nr];
	if (WARN_ON(b->id >= ARRAY_SIZE(data->butfuncs)))
		return NULL;

	return data->butfuncs[b->id];
}

static int mouse_profemu_set_button_function(struct razer_mouse_profile *p,
					     struct razer_button *b,
					     struct razer_button_function *f)
{
	struct razer_mouse *mouse = p->mouse;
	struct razer_mouse_profile_emu *emu = mouse->profemu;
	struct razer_mouse_profile_emu_data *data;

	if (WARN_ON(p->nr >= ARRAY_SIZE(emu->profiles)))
		return -EINVAL;
	data = &emu->data[p->nr];
	if (WARN_ON(b->id >= ARRAY_SIZE(data->butfuncs)))
		return -EINVAL;

	data->butfuncs[b->id] = f;

	if (p == emu->active_profile)
		return mouse_profemu_commit(emu);
	return 0;
}

static struct razer_mouse_profile * mouse_profemu_get(struct razer_mouse *m)
{
	return &m->profemu->profiles[0];
}

static struct razer_mouse_profile * mouse_profemu_get_active(struct razer_mouse *m)
{
	return m->profemu->active_profile;
}

static int mouse_profemu_set_active(struct razer_mouse *m,
				    struct razer_mouse_profile *p)
{
	struct razer_mouse_profile_emu *emu = m->profemu;

	if (p == emu->active_profile)
		return 0;
	emu->active_profile = p;

	return mouse_profemu_commit(emu);
}

static void mouse_profemu_initial_name(razer_utf16_t *namebuf, unsigned int profnr)
{
	char name[PROFEMU_NAME_MAX + 1];

	switch (profnr) {
	case 0:
		snprintf(name, sizeof(name), "Desktop");
		break;
	case 1:
		snprintf(name, sizeof(name), "FPS gaming");
		break;
	case 2:
		snprintf(name, sizeof(name), "RPG gaming");
		break;
	case 3:
		snprintf(name, sizeof(name), "Generic gaming");
		break;
	case 4:
		snprintf(name, sizeof(name), "CAD");
		break;
	default:
		snprintf(name, sizeof(name), "Profile %u", profnr + 1);
	}
	razer_ascii_to_utf16(namebuf, PROFEMU_NAME_MAX, name);
}

int razer_mouse_init_profile_emulation(struct razer_mouse *m)
{
	struct razer_mouse_profile_emu *emu;
	struct razer_mouse_profile_emu_data *data;
	struct razer_mouse_profile *prof, *hw_profile;
	unsigned int i, j;
	int err;
	struct razer_axis *axes = NULL;
	int nr_axes = 1;
	struct razer_button *buttons = NULL;
	int nr_buttons = 0;

	emu = zalloc(sizeof(*emu));
	if (!emu)
		return -ENOMEM;
	emu->mouse = m;

	hw_profile = m->get_profiles(m);
	emu->hw_profile = hw_profile;
	if (WARN_ON(!emu->hw_profile))
		goto err_free;

	if (m->supported_axes) {
		nr_axes = m->supported_axes(m, &axes);
		if (WARN_ON(nr_axes < 0))
			goto err_free;
	}

	if (m->supported_buttons) {
		nr_buttons = m->supported_buttons(m, &buttons);
		if (WARN_ON(nr_buttons < 0))
			goto err_free;
	}

	for (i = 0; i < ARRAY_SIZE(emu->profiles); i++) {
		prof = &emu->profiles[i];
		data = &emu->data[i];

		prof->mouse = m;
		prof->nr = i;

		prof->get_name = mouse_profemu_get_name;
		prof->set_name = mouse_profemu_set_name;

		/* Assign callbacks, if the driver supports the feature. */
//TODO LEDs
		if (hw_profile->get_freq)
			prof->get_freq = mouse_profemu_get_freq;
		if (hw_profile->set_freq)
			prof->set_freq = mouse_profemu_set_freq;
		if (hw_profile->get_dpimapping)
			prof->get_dpimapping = mouse_profemu_get_dpimapping;
		if (hw_profile->set_dpimapping)
			prof->set_dpimapping = mouse_profemu_set_dpimapping;
		if (hw_profile->get_button_function)
			prof->get_button_function = mouse_profemu_get_button_function;
		if (hw_profile->set_button_function)
			prof->set_button_function = mouse_profemu_set_button_function;

		/* Load initial settings */
		mouse_profemu_initial_name(data->name, i);
		if (hw_profile->get_freq)
			data->freq = hw_profile->get_freq(hw_profile);
		if (hw_profile->get_dpimapping) {
			for (j = 0; j < (unsigned int)nr_axes; j++) {
				if (WARN_ON(j >= ARRAY_SIZE(data->dpimappings)))
					break;
				if (!axes || (axes[j].flags & RAZER_AXIS_INDEPENDENT_DPIMAPPING)) {
					data->dpimappings[j] = hw_profile->get_dpimapping(
								hw_profile,
								axes ? &axes[j] : NULL);
				}
			}
			data->nr_dpimappings = j;
		}
		if (hw_profile->get_button_function) {
			for (j = 0; j < (unsigned int)nr_buttons; j++) {
				if (WARN_ON(j >= ARRAY_SIZE(data->butfuncs)))
					break;
				data->butfuncs[j] = hw_profile->get_button_function(
								hw_profile,
								buttons ? &buttons[j] : NULL);
			}
			data->nr_butfuncs = j;
		}
	}
	emu->active_profile = &emu->profiles[0];

	err = mouse_profemu_commit(emu);
	if (err)
		goto err_free;

	m->nr_profiles = ARRAY_SIZE(emu->profiles);
	m->get_profiles = mouse_profemu_get;
	m->get_active_profile = mouse_profemu_get_active;
	m->set_active_profile = mouse_profemu_set_active;

	m->profemu = emu;
	m->flags |= RAZER_MOUSEFLG_PROFEMU;

	razer_debug("Mouse profile emulation initialized for %s\n", m->idstr);

	return 0;

err_free:
	razer_free(emu, sizeof(*emu));

	return -EINVAL;
}

void razer_mouse_exit_profile_emulation(struct razer_mouse *m)
{
	struct razer_mouse_profile_emu *emu;

	if (!(m->flags & RAZER_MOUSEFLG_PROFEMU))
		return;
	emu = m->profemu;

	m->nr_profiles = 0;
	m->get_profiles = NULL;
	m->get_active_profile = NULL;
	m->set_active_profile = NULL;

	m->profemu = NULL;
	m->flags &= ~RAZER_MOUSEFLG_PROFEMU;

	razer_free(emu, sizeof(*emu));
}
