/*
 * 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 Macro 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 Macro Public License for more details.
 *
 * You should have received a copy of the GNU Macro Public License
 * along with roccat-tools. If not, see <http://www.gnu.org/licenses/>.
 */

#include "roccat/ryos.h"
#include "roccat_helper.h"
#include <string.h>

static gchar const * const macro_name = "macro";
static gchar const * const light_macro_name = "light_macro";

static void ryos_macro_set_macroset_name(RyosMacro *macro, gchar const *new_name) {
	g_strlcpy((gchar *)(macro->macroset_name), new_name, RYOS_MACRO_MACROSET_NAME_LENGTH);
}

static void ryos_macro_set_macro_name(RyosMacro *macro, gchar const *new_name) {
	g_strlcpy((gchar *)(macro->macro_name), new_name, RYOS_MACRO_MACRO_NAME_LENGTH);
}

static guint16 ryos_macro_calc_checksum(RyosMacro const *macro) {
	gsize calc_size = G_STRUCT_OFFSET(RyosMacro, keystrokes[0]) + ryos_macro_get_count(macro) * sizeof(RyosKeystroke);
	return roccat_calc_bytesum(macro, calc_size);
}

static void ryos_macro_set_checksum(RyosMacro *macro, guint16 new_value) {
	gsize offset = G_STRUCT_OFFSET(RyosMacro, keystrokes[0]) + ryos_macro_get_count(macro) * sizeof(RyosKeystroke);
	*(guint16 *)G_STRUCT_MEMBER_P(macro, offset) = GUINT16_TO_LE(new_value);
}

static guint16 ryos_macro_calc_size(RyosMacro const *macro) {
	return G_STRUCT_OFFSET(RyosMacro, keystrokes[0]) + ryos_macro_get_count(macro) * sizeof(RyosKeystroke) + sizeof(guint16);
}

static void ryos_macro_set_size(RyosMacro *macro, guint16 new_value) {
	macro->size = GUINT16_TO_LE(new_value);
}

static void ryos_macro_finalize(RyosMacro *macro, guint profile_index, guint button_index) {
	macro->command = RYOS_COMMAND_MACRO;
	macro->profile_index = profile_index;
	macro->button_index = button_index;
	ryos_macro_set_size(macro, ryos_macro_calc_size(macro));
	ryos_macro_set_checksum(macro, ryos_macro_calc_checksum(macro));
}

gboolean ryos_macro_write(RoccatDevice *ryos, guint profile_index, guint button_index, RyosMacro *macro, GError **error) {
	g_assert(profile_index < RYOS_PROFILE_NUM);
	ryos_macro_finalize(macro, profile_index, button_index);
	return roccat_device_write(ryos, macro_name, (gchar const *)macro, sizeof(RyosMacro), error);
}

RyosMacro *ryos_macro_read(RoccatDevice *ryos, guint profile_index, guint button_index, GError **error) {
	RyosMacro *macro;

	g_assert(profile_index < RYOS_PROFILE_NUM);

	roccat_device_lock(ryos);

	if (!ryos_select(ryos, profile_index, button_index, error)) {
		roccat_device_unlock(ryos);
		return NULL;
	}

	macro = (RyosMacro *)roccat_device_read(ryos, macro_name, sizeof(RyosMacro), error);

	roccat_device_unlock(ryos);

	return macro;
}

gboolean ryos_light_macro_write(RoccatDevice *ryos, guint profile_index, RyosMacro *light_macro, GError **error) {
	g_assert(profile_index < RYOS_PROFILE_NUM);
	ryos_macro_finalize(light_macro, profile_index, 0);
	return roccat_device_write(ryos, light_macro_name, (gchar const *)light_macro, sizeof(RyosMacro), error);
}

RyosMacro *ryos_light_macro_read(RoccatDevice *ryos, guint profile_index, GError **error) {
	RyosMacro *light_macro;

	g_assert(profile_index < RYOS_PROFILE_NUM);

	roccat_device_lock(ryos);

	if (!ryos_select(ryos, profile_index, RYOS_CONTROL_REQUEST_LIGHT_MACRO, error)) {
		roccat_device_unlock(ryos);
		return NULL;
	}

	light_macro = (RyosMacro *)roccat_device_read(ryos, light_macro_name, sizeof(RyosMacro), error);

	roccat_device_unlock(ryos);

	return light_macro;
}

static void roccat_macro_keystroke_to_ryos_keystroke(RoccatMacroKeystroke const *from, RyosKeystroke *to) {
	to->key = from->key;
	ryos_keystroke_set_period(to, roccat_macro_keystroke_get_period(from));

	to->action = (from->action == ROCCAT_MACRO_KEYSTROKE_ACTION_PRESS) ?
		RYOS_KEYSTROKE_ACTION_PRESS : RYOS_KEYSTROKE_ACTION_RELEASE;
}

static void ryos_keystroke_to_roccat_macro_keystroke(RyosKeystroke const *from, RoccatMacroKeystroke *to) {
	to->key = from->key;
	roccat_macro_keystroke_set_period(to, ryos_keystroke_get_period(from));

	to->action = (from->action == RYOS_KEYSTROKE_ACTION_PRESS) ?
		ROCCAT_MACRO_KEYSTROKE_ACTION_PRESS : ROCCAT_MACRO_KEYSTROKE_ACTION_RELEASE;
}

gboolean roccat_macro_to_ryos_macro(RoccatMacro const *roccat_macro, RyosMacro *ryos_macro, GError **error) {
	guint i, count;

	g_return_val_if_fail(error == NULL || *error == NULL, FALSE);

	count = roccat_macro_keystrokes_get_count(&roccat_macro->keystrokes);
	if (count > RYOS_MACRO_KEYSTROKES_NUM) {
		g_set_error(error, ROCCAT_ERROR, ROCCAT_ERROR_MACRO_TOO_LONG, "Macro is too long for Ryos");
		return FALSE;
	}

	memset(ryos_macro, 0, sizeof(RyosMacro));
	ryos_macro_set_count(ryos_macro, count);
	ryos_macro->loop = roccat_macro->keystrokes.loop;
	ryos_macro_set_macroset_name(ryos_macro, roccat_macro->macro_set);
	ryos_macro_set_macro_name(ryos_macro, roccat_macro->macro);

	for (i = 0; i < count; ++i)
		roccat_macro_keystroke_to_ryos_keystroke(&roccat_macro->keystrokes.keystrokes[i], &ryos_macro->keystrokes[i]);

	return TRUE;
}

RoccatMacro *ryos_macro_to_roccat_macro(RyosMacro const *ryos_macro) {
	RoccatMacro *roccat_macro;
	RoccatMacroKeystroke keystroke;
	guint i, count;

	roccat_macro = roccat_macro_new((gchar const *)ryos_macro->macroset_name, (gchar const *)ryos_macro->macro_name, NULL);

	roccat_macro->keystrokes.loop = ryos_macro->loop;

	count = ryos_macro_get_count(ryos_macro);
	count = MIN(count, ROCCAT_MACRO_KEYSTROKES_NUM);
	count = MIN(count, RYOS_MACRO_KEYSTROKES_NUM);

	for (i = 0; i < count; ++i) {
		ryos_keystroke_to_roccat_macro_keystroke(&ryos_macro->keystrokes[i], &keystroke);
		roccat_macro_keystrokes_add(&roccat_macro->keystrokes, &keystroke);
	}

	return roccat_macro;
}

gboolean ryos_macro_equal(RyosMacro const *left, RyosMacro const *right) {
	gboolean equal;
	equal = ROCCAT_MEMCMP_PARTIALLY(left, right, RyosMacro, loop, checksum);
	return equal ? FALSE : TRUE;
}

void ryos_macro_copy(RyosMacro *destination, RyosMacro const *source) {
	memcpy(destination, source, sizeof(RyosMacro));
}
