/*
 * 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 "roccat_firmware.h"
#include "roccat_device_hidraw.h"
#include "roccat_control.h"
#include <string.h>

typedef struct _RoccatFirmwareWrite RoccatFirmwareWrite;

struct _RoccatFirmwareWrite {
	guint8 command;
	guint8 number;
	guint8 data[ROCCAT_FIRMWARE_CHUNK_SIZE];
} __attribute__ ((__packed__));

enum {
	ROCCAT_COMMAND_FIRMWARE_WRITE = 0x1b,
	ROCCAT_COMMAND_FIRMWARE_WRITE_CONTROL = 0x1c,
};

typedef enum {
	ROCCAT_FIRMWARE_WRITE_TYPE_0 = 0x00,
	ROCCAT_FIRMWARE_WRITE_TYPE_1 = 0x01,
	ROCCAT_FIRMWARE_WRITE_TYPE_2 = 0x02,
	ROCCAT_FIRMWARE_WRITE_TYPE_3 = 0x03,
	ROCCAT_FIRMWARE_WRITE_TYPE_4 = 0x04,
	ROCCAT_FIRMWARE_WRITE_TYPE_F = 0x0f,
} RoccatFirmwareWriteType;

struct _RoccatFirmwareState {
	RoccatDevice *device;
	guint update_state;
	guint chunk_number;
	gboolean chunk_init;
	guint firmware_size;
	guint firmware_chunks;
	guint8 *firmware;
	guint wait_after_0;
	guint wait_after_1;
	guint wait_after_2;
	guint wait_after_34f;
};

static guint16 calc_chunk_bytesum(guint8 const *data) {
	guint i;
	guint16 result = 0;

	for (i = 0; i < ROCCAT_FIRMWARE_CHUNK_SIZE; ++i)
		result += data[i];

	return result;
}

static gboolean roccat_firmware_write(RoccatDevice *roccat_device, RoccatFirmwareWrite const *buffer,
		guint check_wait, GError **error) {
	gboolean retval;

	retval = roccat_device_hidraw_write(roccat_device, (gchar const *)buffer, sizeof(RoccatFirmwareWrite), error);
	if (!retval)
		return FALSE;

	return roccat_check_write(roccat_device, ROCCAT_COMMAND_FIRMWARE_WRITE_CONTROL, check_wait, 110, error);
}

static gboolean roccat_firmware_write_0(RoccatDevice *roccat_device, guint check_wait, GError **error) {
	RoccatFirmwareWrite data;

	data.command = ROCCAT_COMMAND_FIRMWARE_WRITE;
	data.number = ROCCAT_FIRMWARE_WRITE_TYPE_0;
	memset(&data.data, 0, ROCCAT_FIRMWARE_CHUNK_SIZE);
	data.data[2] = 0x08;
	data.data[3] = 0xAC;
	data.data[4] = 0x32;
	data.data[5] = 0x10;
	data.data[6] = 0x03;
	data.data[7] = 0xCB;
	data.data[9] = 0x01;

	return roccat_firmware_write(roccat_device, &data, check_wait, error);
}

static gboolean roccat_firmware_write_1(RoccatDevice *roccat_device, guint firmware_size, guint check_wait, GError **error) {
	RoccatFirmwareWrite data;

	data.command = ROCCAT_COMMAND_FIRMWARE_WRITE;
	data.number = ROCCAT_FIRMWARE_WRITE_TYPE_1;
	memset(&data.data, 0, ROCCAT_FIRMWARE_CHUNK_SIZE);
	data.data[2] = (guint8)firmware_size;
	data.data[3] = (guint8)(firmware_size >> 8);
	data.data[4] = (guint8)(firmware_size >> 16);
	data.data[5] = (guint8)(firmware_size >> 24);
	data.data[6] = 0x08;
	data.data[7] = 0xAC;
	data.data[8] = 0x22;
	data.data[9] = 0x88;

	return roccat_firmware_write(roccat_device, &data, check_wait, error);
}

static gboolean roccat_firmware_write_2(RoccatDevice *roccat_device, guint check_wait, GError **error) {
	RoccatFirmwareWrite data;

	data.command = ROCCAT_COMMAND_FIRMWARE_WRITE;
	data.number = ROCCAT_FIRMWARE_WRITE_TYPE_2;
	memset(&data.data, 0, ROCCAT_FIRMWARE_CHUNK_SIZE);
	data.data[2] = 0xEC;

	/* Play it save with increased waiting time
	 * At least Savu might bail out with 110
	 */
	return roccat_firmware_write(roccat_device, &data, check_wait, error);
}

static gboolean roccat_firmware_write_3(RoccatDevice *roccat_device, guint16 number, guint8 const *chunk, guint check_wait, GError **error) {
	RoccatFirmwareWrite data;
	guint16 bytesum;

	bytesum = calc_chunk_bytesum(chunk);

	data.command = ROCCAT_COMMAND_FIRMWARE_WRITE;
	data.number = ROCCAT_FIRMWARE_WRITE_TYPE_3;
	memset(&data.data, 0, ROCCAT_FIRMWARE_CHUNK_SIZE);
	data.data[2] = (guint8)number;
	data.data[3] = (guint8)(number >> 8);
	data.data[4] = (guint8)bytesum;
	data.data[5] = (guint8)(bytesum >> 8);
	data.data[6] = 0x08;
	data.data[7] = 0xAC;
	data.data[8] = 0x22;
	data.data[9] = 0x88;

	return roccat_firmware_write(roccat_device, &data, check_wait, error);
}

static gboolean roccat_firmware_write_4(RoccatDevice *roccat_device, guint8 const *chunk, guint check_wait, GError **error) {
	RoccatFirmwareWrite data;

	data.command = ROCCAT_COMMAND_FIRMWARE_WRITE;
	data.number = ROCCAT_FIRMWARE_WRITE_TYPE_4;
	memcpy(&data.data, chunk, ROCCAT_FIRMWARE_CHUNK_SIZE);

	return roccat_firmware_write(roccat_device, &data, check_wait, error);
}

static gboolean roccat_firmware_write_f(RoccatDevice *roccat_device, guint check_wait, GError **error) {
	RoccatFirmwareWrite data;

	data.command = ROCCAT_COMMAND_FIRMWARE_WRITE;
	data.number = ROCCAT_FIRMWARE_WRITE_TYPE_F;
	memset(&data.data, 0, ROCCAT_FIRMWARE_CHUNK_SIZE);

	return roccat_firmware_write(roccat_device, &data, check_wait, error);
}

guint roccat_firmware_fractions(RoccatFirmwareState const *state) {
	return state->firmware_chunks * 2 + 4;
}

void roccat_firmware_state_reset(RoccatFirmwareState *state) {
	state->update_state = 0;
}

void roccat_firmware_state_set_wait_0(RoccatFirmwareState *state, guint wait) {
	state->wait_after_0 = wait;
}

void roccat_firmware_state_set_wait_1(RoccatFirmwareState *state, guint wait) {
	state->wait_after_1 = wait;
}

void roccat_firmware_state_set_wait_2(RoccatFirmwareState *state, guint wait) {
	state->wait_after_2 = wait;
}

void roccat_firmware_state_set_wait_34f(RoccatFirmwareState *state, guint wait) {
	state->wait_after_34f = wait;
}

static void set_firmware_size(RoccatFirmwareState *state, guint firmware_size) {
	state->firmware_size = firmware_size;
	state->firmware_chunks = (firmware_size + ROCCAT_FIRMWARE_CHUNK_SIZE - 1) / ROCCAT_FIRMWARE_CHUNK_SIZE;
}

RoccatFirmwareState *roccat_firmware_state_new(RoccatDevice *device, guint firmware_size) {
	RoccatFirmwareState *state;
	state = (RoccatFirmwareState *)g_malloc(sizeof(RoccatFirmwareState));
	state->device = device;
	set_firmware_size(state, firmware_size);
	state->firmware = NULL;
	roccat_firmware_state_set_wait_0(state, 600);
	roccat_firmware_state_set_wait_1(state, 110);
	roccat_firmware_state_set_wait_2(state, 600);
	roccat_firmware_state_set_wait_34f(state, 110);
	roccat_firmware_state_reset(state);
	return state;
}

/* loads firmware and reallocs it into @chunks * @chunk_size memory */
gboolean roccat_firmware_state_read_firmware(RoccatFirmwareState *state, gchar const *path, GError **error) {
	gchar *result;
	gsize bytes;
	guint i;

	if (!g_file_get_contents(path, &result, &bytes, error))
		return FALSE;

	if (state->firmware_size == 0) {
			set_firmware_size(state, bytes);
	} else {
		if (bytes != state->firmware_size) {
			g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "Wrong size: %zu bytes instead of %u", bytes, state->firmware_size);
			return FALSE;
		}
	}

	result = g_realloc(result, ROCCAT_FIRMWARE_CHUNK_SIZE * state->firmware_chunks);

	for (i = state->firmware_size; i < ROCCAT_FIRMWARE_CHUNK_SIZE * state->firmware_chunks; ++i)
		result[i] = 0;

	g_free(state->firmware);
	state->firmware = (guint8 *)result;
	return TRUE;
}

gboolean roccat_firmware_state_tick(RoccatFirmwareState *state, GError **error) {
	guint8 const *chunk;
	GError *local_error = NULL;

	switch (state->update_state) {
	case 0:
		if (!roccat_firmware_write_0(state->device, state->wait_after_0, error))
			return FALSE;
		++state->update_state;
		break;
	case 1:
		if (!roccat_firmware_write_1(state->device, state->firmware_size, state->wait_after_1, error))
			return FALSE;
		++state->update_state;
		break;
	case 2:
		if (!roccat_firmware_write_2(state->device, state->wait_after_2, error))
			return FALSE;
		++state->update_state;
		state->chunk_number = 0;
		state->chunk_init = 1;
		break;
	case 3:
		if (state->chunk_number < state->firmware_chunks) {
			chunk = state->firmware + state->chunk_number * ROCCAT_FIRMWARE_CHUNK_SIZE;

			if (state->chunk_init) {
				if (!roccat_firmware_write_3(state->device, state->chunk_number, chunk, state->wait_after_34f, error))
					return FALSE;
				state->chunk_init = FALSE;
			} else {
				if (!roccat_firmware_write_4(state->device, chunk, state->wait_after_34f, error))
					return FALSE;
				++state->chunk_number;
				state->chunk_init = TRUE;
			}
		} else
			++state->update_state;
		break;
	case 4:
		if (!roccat_firmware_write_f(state->device, state->wait_after_34f, &local_error)) {
			if (g_error_matches(local_error, G_FILE_ERROR, G_FILE_ERROR_PIPE)) {
				/* It is normal for e.g. Tyon to reset without even answering to f */
				g_clear_error(&local_error);
			} else {
				g_propagate_error(error, local_error);
				return FALSE;
			}
		}
		++state->update_state;
		return FALSE;
		break;
	default:
		return FALSE;
		break;
	}

	return TRUE;
}

void roccat_firmware_state_free(RoccatFirmwareState *state) {
	g_free(state->firmware);
	g_free(state);
}

gchar *roccat_firmware_version_to_string(guint firmware_version) {
	return g_strdup_printf("%i.%02i", firmware_version / 100, firmware_version % 100);
}
