/*
 * 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/isku.h"
#include <errno.h>
#include <string.h>
#include <libusb.h>

static inline guint16 isku_usb_command(guint8 command) { return 0x300 | command; }

libusb_device_handle *isku_libusb_open_first_device(void) {
	return libusb_open_device_with_vid_pid(NULL, USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ISKU);
}

enum {
	ISKU_USB_TIMEOUT = 1000,
};

gboolean isku_libusb_check_firmware_write(libusb_device_handle *handle) {
	IskuFirmwareWriteControl buffer;
	int len;

	for (;;) {
		g_usleep(100000);
		len = libusb_control_transfer(
				handle,
				LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
				LIBUSB_REQUEST_CLEAR_FEATURE,
				isku_usb_command(ISKU_COMMAND_FIRMWARE_WRITE_CONTROL),
				0,
				(unsigned char *)&buffer,
				sizeof(IskuFirmwareWriteControl),
				ISKU_USB_TIMEOUT
		);

		if (len < 0) {
			g_critical("error checking firmware write: %i", len);
			return FALSE;
		} else if (len != sizeof(IskuFirmwareWriteControl)) {
			g_critical("got wrong length checking firmware write: %i instead of %zu", len, sizeof(IskuFirmwareWriteControl));
			return FALSE;
		} else { // read right length
			switch (buffer.value) {
			case ISKU_CONTROL_VALUE_STATUS_OK:
				return TRUE;
			case ISKU_CONTROL_VALUE_STATUS_INVALID:
				return FALSE;
			case ISKU_CONTROL_VALUE_STATUS_BUSY:
				continue;
			case ISKU_CONTROL_VALUE_STATUS_CRITICAL:
				g_critical("error checking firmware write: critical");
				return FALSE;
			default:
				g_critical("got wrong status checking firmware write: %i", buffer.value);
				return FALSE;
			}
		}
	}
}

guint isku_libusb_firmware_version_read(libusb_device_handle *handle) {
	IskuInfo buffer;
	int len;

	len = libusb_control_transfer(
			handle,
			LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
			LIBUSB_REQUEST_CLEAR_FEATURE,
			isku_usb_command(ISKU_COMMAND_INFO),
			0,
			(unsigned char *)&buffer,
			sizeof(IskuInfo),
			ISKU_USB_TIMEOUT
	);

	if (len < 0) {
		g_critical("error reading firmware version: %i", len);
		return 0;
	} else if (len != sizeof(IskuInfo)) {
		g_critical("got wrong length reading firmware version: %i instead of %zu", len, sizeof(IskuInfo));
		return 0;
	}

	return buffer.firmware_version;
}

gboolean isku_libusb_firmware_write(libusb_device_handle *handle, IskuFirmwareWrite const *data) {
	int len;

	len = libusb_control_transfer(handle,
			LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
			LIBUSB_REQUEST_SET_CONFIGURATION,
			isku_usb_command(ISKU_COMMAND_FIRMWARE_WRITE),
			0,
			(unsigned char *)data,
			sizeof(IskuFirmwareWrite),
			ISKU_USB_TIMEOUT
	);

	if (len < 0) {
		g_critical("error writing firmware: %i", len);
		return FALSE;
	} else if (len != sizeof(IskuFirmwareWrite)) {
		g_critical("sent wrong length writing firmware: %i instead of %zu", len, sizeof(IskuFirmwareWrite));
		return FALSE;
	}

	return isku_libusb_check_firmware_write(handle);
}
