/*
 * 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 "kova2016_rmp.h"
#include "kova2016_device.h"
#include "roccat_swarm_rmp.h"
#include "roccat_helper.h"
#include "g_roccat_helper.h"

typedef struct _Kova2016RmpMacro Kova2016RmpMacro;

struct _Kova2016RmpMacro {
	guint8 report_id;
	guint16 size;
	Kova2016Macro macro;
	guint16 checksum;
} __attribute__ ((packed));

static void kova2016_rmp_macro_set_size(Kova2016RmpMacro *macro, guint16 value) {
	macro->size = GUINT16_TO_LE(value);
}

static gboolean read_header(Kova2016ProfileData *profile_data, gconstpointer *data) {
	guint32 size;
	RoccatSwarmRmpHeader const *header;
	gchar *string;
	
	size = roccat_swarm_rmp_read_bigendian32(data);
	if (size != sizeof(RoccatSwarmRmpHeader)) {
		g_warning("%u != sizeof(RoccatSwarmRmpHeader)", size);
		return FALSE;
	}
	
	header = (RoccatSwarmRmpHeader const *)*data;

	string = roccat_swarm_rmp_header_get_profile_name(header);
	kova2016_profile_data_eventhandler_set_profile_name(&profile_data->eventhandler, string);
	g_free(string);
	
	*data += size;
	return TRUE;
}

static gboolean read_gamefiles(Kova2016ProfileData *profile_data, gconstpointer *data) {
	guint32 size;
	gchar *gamefile;
	guint i;

	size = roccat_swarm_rmp_read_bigendian32(data);
	if (size != ROCCAT_SWARM_RMP_GAMEFILE_NUM * ROCCAT_SWARM_RMP_GAMEFILE_LENGTH * 2) {
		g_warning("%u != ROCCAT_SWARM_RMP_GAMEFILE_NUM * ROCCAT_SWARM_RMP_GAMEFILE_LENGTH * 2", size);
		return FALSE;
	}

	for (i = 0; i < ROCCAT_SWARM_RMP_GAMEFILE_NUM; ++i) {
		gamefile = roccat_swarm_rmp_read_bigendian_string(data, ROCCAT_SWARM_RMP_GAMEFILE_LENGTH);
		kova2016_profile_data_eventhandler_set_gamefile_name(&profile_data->eventhandler, i, gamefile);
		g_free(gamefile);
	}

	return TRUE;
}

static gboolean read_sound_feedback(Kova2016ProfileData *profile_data, gconstpointer *data) {
	guint32 size;
	guint16 __attribute__((__unused__)) sound_feedback;

	size = roccat_swarm_rmp_read_bigendian32(data);
	if (size != 2) {
		g_warning("%u != sizeof(sound_feedback)", size);
		return FALSE;
	}

	sound_feedback = *(guint16 const *)data;

	*data += size;
	return TRUE;
}

static gboolean read_openers(Kova2016ProfileData *profile_data, gconstpointer *data) {
	guint32 count;
	guint32 button_index;
	guint i;
	gchar *string;

	count = roccat_swarm_rmp_read_bigendian32(data);
	if (count > KOVA2016_PROFILE_BUTTON_NUM) {
		g_warning("%u > KOVA2016_PROFILE_BUTTON_NUM", count);
		return FALSE;
	}

	for (i = 0; i < count; ++i) {
		button_index = roccat_swarm_rmp_read_bigendian32(data);

		string = roccat_swarm_rmp_read_bigendian_string_with_size(data);
		kova2016_profile_data_eventhandler_set_opener(&profile_data->eventhandler, button_index, string);
		g_free(string);
	}
	return TRUE;
}

static gboolean read_profile_buttons(Kova2016ProfileData *profile_data, gconstpointer *data) {
	guint32 size;

	size = roccat_swarm_rmp_read_bigendian32(data);
	if (size != sizeof(Kova2016ProfileButtons)) {
		g_warning("%u != sizeof(Kova2016ProfileButtons)", size);
		return FALSE;
	}

	kova2016_profile_data_hardware_set_profile_buttons(&profile_data->hardware, (Kova2016ProfileButtons const *)*data);

	*data += size;
	return TRUE;
}

static gboolean read_profile_settings(Kova2016ProfileData *profile_data, gconstpointer *data) {
	guint32 size;

	size = roccat_swarm_rmp_read_bigendian32(data);
	if (size != sizeof(Kova2016ProfileSettings)) {
		g_warning("%u != sizeof(Kova2016ProfileSettings)", size);
		return FALSE;
	}

	kova2016_profile_data_hardware_set_profile_settings(&profile_data->hardware, (Kova2016ProfileSettings const *)*data);

	*data += size;
	return TRUE;
}

static gboolean read_macros(Kova2016ProfileData *profile_data, gconstpointer *data) {
	Kova2016RmpMacro const *rmp_macro;
	guint32 count;
	guint32 size;
	guint i;

	count = roccat_swarm_rmp_read_bigendian32(data);
	if (count != KOVA2016_PROFILE_BUTTON_NUM) {
		g_warning("%u != KOVA2016_PROFILE_BUTTON_NUM", count);
		return FALSE;
	}
	
	for (i = 0; i < count; ++i) {
		size = roccat_swarm_rmp_read_bigendian32(data);
		if (size != sizeof(Kova2016RmpMacro)) {
			g_warning("%u != sizeof(Kova2016RmpMacro)", size);
			return FALSE;
		}
		
		rmp_macro = (Kova2016RmpMacro const *)*data;
		kova2016_profile_data_hardware_set_macro(&profile_data->hardware, i, &rmp_macro->macro);
		*data += size;
	}

	return TRUE;
}

Kova2016ProfileData *kova2016_rmp_to_profile_data(gconstpointer rmp, gsize length) {
	Kova2016ProfileData *profile_data;
	gconstpointer pointer;
	
	profile_data = kova2016_profile_data_new();
	pointer = rmp;
	
	if (!roccat_swarm_rmp_read_unknown(&pointer)) goto error;
	if (!read_header(profile_data, &pointer)) goto error;
	if (!read_gamefiles(profile_data, &pointer)) goto error;
	if (!read_sound_feedback(profile_data, &pointer)) goto error;
	if (!roccat_swarm_rmp_read_timers(&pointer)) goto error;
	if (!roccat_swarm_rmp_read_unknown_with_count(&pointer)) goto error;
	if (!roccat_swarm_rmp_read_pictures(&pointer)) goto error;
	if (!read_openers(profile_data, &pointer)) goto error;
	if (!roccat_swarm_rmp_read_unknown(&pointer)) goto error;
	if (!roccat_swarm_rmp_read_unknown(&pointer)) goto error;
	if (!read_profile_buttons(profile_data, &pointer)) goto error;
	if (!read_profile_settings(profile_data, &pointer)) goto error;
	if (!roccat_swarm_rmp_read_unknown(&pointer)) goto error;
	if (!read_macros(profile_data, &pointer)) goto error;

	if (pointer > rmp + length)
		g_warning("%zu > %zu", (gsize)(pointer - rmp), length);

	return profile_data;
error:
	g_free(profile_data);
	return NULL;
}

static void write_header(Kova2016ProfileData const *profile_data, gpointer *data, gsize *length) {
	RoccatSwarmRmpHeader *header;
	
	header = (RoccatSwarmRmpHeader *)roccat_swarm_rmp_write_with_size(data, length, sizeof(RoccatSwarmRmpHeader));
	roccat_swarm_rmp_header_set_profile_name(header, profile_data->eventhandler.profile_name);
	/* default values */
	header->volume = 50;
	header->profile_autoswitch = 0;

}

static void write_profile_settings(Kova2016ProfileData const *profile_data, gpointer *data, gsize *length) {
	Kova2016ProfileSettings *settings;
	
	settings = (Kova2016ProfileSettings *)roccat_swarm_rmp_write_with_size(data, length, sizeof(Kova2016ProfileSettings));
	kova2016_profile_settings_copy(settings, &profile_data->hardware.profile_settings);
}

static void write_profile_buttons(Kova2016ProfileData const *profile_data, gpointer *data, gsize *length) {
	Kova2016ProfileButtons *buttons;
	
	buttons = (Kova2016ProfileButtons *)roccat_swarm_rmp_write_with_size(data, length, sizeof(Kova2016ProfileButtons));
	kova2016_profile_buttons_copy(buttons, &profile_data->hardware.profile_buttons);
}

static void write_sound_feedback(Kova2016ProfileData const *profile_data, gpointer *data, gsize *length) {
	guint16 *sound_feedback;

	sound_feedback = (guint16 *)roccat_swarm_rmp_write_with_size(data, length, sizeof(guint16));
	*sound_feedback = 0; /* default value */
}

static void write_gamefiles(Kova2016ProfileData const *profile_data, gpointer *data, gsize *length) {
	guint i;
	gsize size;
	
	size = ROCCAT_SWARM_RMP_GAMEFILE_NUM * ROCCAT_SWARM_RMP_GAMEFILE_LENGTH * sizeof(gunichar2);
	roccat_swarm_rmp_write_bigendian32(data, length, size);
	for (i = 0; i < ROCCAT_SWARM_RMP_GAMEFILE_NUM; ++i)
		roccat_swarm_rmp_write_bigendian_string(data, length, profile_data->eventhandler.gamefile_names[i], ROCCAT_SWARM_RMP_GAMEFILE_LENGTH);
}

static void write_macros(Kova2016ProfileData const *profile_data, gpointer *data, gsize *length) {
	Kova2016RmpMacro *rmp_macro;
	guint i;
	
	/* The Windows version doesn't support the compressed format with button_index
	 * for macro section (nor button_index contained in macro structure).
	 * Always all macros are written, regardless of usage which increases the filesize massively.
	 */
	roccat_swarm_rmp_write_bigendian32(data, length, KOVA2016_PROFILE_BUTTON_NUM);
	for (i = 0; i < KOVA2016_PROFILE_BUTTON_NUM; ++i) {
		rmp_macro = (Kova2016RmpMacro *)roccat_swarm_rmp_write_with_size(data, length, sizeof(Kova2016RmpMacro));

		/* Windows version writes the wrong report id 0x0e used by Isku* and Ryos* */
		rmp_macro->report_id = KOVA2016_REPORT_ID_MACRO;

		kova2016_rmp_macro_set_size(rmp_macro, sizeof(Kova2016RmpMacro));
		memcpy(&rmp_macro->macro, &profile_data->hardware.macros[i], sizeof(Kova2016Macro));
	}
}

static void write_openers(Kova2016ProfileData const *profile_data, gpointer *data, gsize *length) {
	guint type;
	guint count;
	guint i;
	
	count = 0;
	for (i = 0; i < KOVA2016_PROFILE_BUTTON_NUM; ++i) {
		type = profile_data->hardware.profile_buttons.buttons[i].type;
		if (type == KOVA2016_BUTTON_TYPE_OPEN_APPLICATION || type == KOVA2016_BUTTON_TYPE_OPEN_DOCUMENT || type == KOVA2016_BUTTON_TYPE_OPEN_FOLDER || type == KOVA2016_BUTTON_TYPE_OPEN_WEBSITE) {
			++count;
		}
	}
	roccat_swarm_rmp_write_bigendian32(data, length, count);

	for (i = 0; i < KOVA2016_PROFILE_BUTTON_NUM; ++i) {
		type = profile_data->hardware.profile_buttons.buttons[i].type;
		if (type == KOVA2016_BUTTON_TYPE_OPEN_APPLICATION || type == KOVA2016_BUTTON_TYPE_OPEN_DOCUMENT || type == KOVA2016_BUTTON_TYPE_OPEN_FOLDER || type == KOVA2016_BUTTON_TYPE_OPEN_WEBSITE) {
			roccat_swarm_rmp_write_bigendian32(data, length, i);
			roccat_swarm_rmp_write_bigendian_string_with_size(data, length, (gchar const *)profile_data->eventhandler.openers[i], ROCCAT_SWARM_RMP_OPENER_LENGTH);
		}
	}
}

gpointer kova2016_profile_data_to_rmp(Kova2016ProfileData const *profile_data, gsize *length) {
	gpointer rmp = NULL;
	gsize local_length = 0;
	
	roccat_swarm_rmp_write_unknown(&rmp, &local_length, 0); // FIXME 4
	write_header(profile_data, &rmp, &local_length);
	write_gamefiles(profile_data, &rmp, &local_length);
	write_sound_feedback(profile_data, &rmp, &local_length);
	roccat_swarm_rmp_write_timers(&rmp, &local_length);
	roccat_swarm_rmp_write_unknown_with_count(&rmp, &local_length, 0, 0); // FIXME 4 x 4
	roccat_swarm_rmp_write_unknown(&rmp, &local_length, 0); // FIXME pictures
	write_openers(profile_data, &rmp, &local_length);
	roccat_swarm_rmp_write_unknown(&rmp, &local_length, 0);
	roccat_swarm_rmp_write_unknown(&rmp, &local_length, 0); // FIXME 4
	write_profile_buttons(profile_data, &rmp, &local_length);
	write_profile_settings(profile_data, &rmp, &local_length);
	roccat_swarm_rmp_write_unknown(&rmp, &local_length, 0x30);
	write_macros(profile_data, &rmp, &local_length);
	
	*length = local_length;
	return rmp;
}
