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

libusb_device_handle *koneplus_libusb_open_first_device(void) {
	return libusb_open_device_with_vid_pid(NULL, USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEPLUS);
}

enum {
	KONEPLUS_USB_TIMEOUT = 1000,
};

gboolean koneplus_libusb_check_firmware_write(libusb_device_handle *handle) {
	KoneplusFirmwareWriteControl 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,
				koneplus_usb_command(KONEPLUS_COMMAND_FIRMWARE_WRITE_CONTROL),
				0,
				(unsigned char *)&buffer,
				sizeof(KoneplusFirmwareWriteControl),
				KONEPLUS_USB_TIMEOUT
		);

		if (len < 0) {
			g_critical("error checking firmware write: %i", len);
			return FALSE;
		} else if (len != sizeof(KoneplusFirmwareWriteControl)) {
			g_critical("got wrong length checking firmware write: %i instead of %zu", len, sizeof(KoneplusFirmwareWriteControl));
			return FALSE;
		} else { // read right length
			switch (buffer.value) {
			case KONEPLUS_CONTROL_REQUEST_STATUS_OK:
				return TRUE;
			case KONEPLUS_CONTROL_REQUEST_STATUS_INVALID:
				return FALSE;
			case KONEPLUS_CONTROL_REQUEST_STATUS_BUSY:
				continue;
			case KONEPLUS_CONTROL_REQUEST_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;
			}
		}
	}
}

gboolean koneplus_libusb_check_write(libusb_device_handle *handle) {
	KoneplusControl buffer;
	int len;

	for (;;) {
		len = libusb_control_transfer(
				handle,
				LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
				LIBUSB_REQUEST_CLEAR_FEATURE,
				koneplus_usb_command(KONEPLUS_COMMAND_CONTROL),
				0,
				(unsigned char *)&buffer,
				sizeof(KoneplusControl),
				KONEPLUS_USB_TIMEOUT
		);

		if (len < 0) {
			g_critical("error checking write: %i", len);
			return FALSE;
		} else if (len != sizeof(KoneplusControl)) {
			g_critical("got wrong length checking write: %i instead of %zu", len, sizeof(KoneplusControl));
			return FALSE;
		} else { // read right length
			switch (buffer.value) {
			case KONEPLUS_CONTROL_REQUEST_STATUS_OK:
				return TRUE;
			case KONEPLUS_CONTROL_REQUEST_STATUS_INVALID:
				return FALSE;
			case KONEPLUS_CONTROL_REQUEST_STATUS_BUSY:
				g_usleep(100000);
				continue;
			case KONEPLUS_CONTROL_REQUEST_STATUS_CRITICAL:
				g_critical("error checking write: critical");
				return FALSE;
			default:
				g_critical("got wrong status checking write: %i", buffer.value);
				return FALSE;
			}
		}
	}
}

guint koneplus_libusb_firmware_version_read(libusb_device_handle *handle) {
	KoneplusInfo buffer;
	int len;

	len = libusb_control_transfer(
			handle,
			LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
			LIBUSB_REQUEST_CLEAR_FEATURE,
			koneplus_usb_command(KONEPLUS_COMMAND_INFO),
			0,
			(unsigned char *)&buffer,
			sizeof(KoneplusInfo),
			KONEPLUS_USB_TIMEOUT
	);

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

	return buffer.firmware_version;
}

gboolean koneplus_libusb_firmware_write(libusb_device_handle *handle, KoneplusFirmwareWrite const *data) {
	int len;

	len = libusb_control_transfer(handle,
			LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
			LIBUSB_REQUEST_SET_CONFIGURATION,
			koneplus_usb_command(KONEPLUS_COMMAND_FIRMWARE_WRITE),
			0,
			(unsigned char *)data,
			sizeof(KoneplusFirmwareWrite),
			KONEPLUS_USB_TIMEOUT
	);

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

	return koneplus_libusb_check_firmware_write(handle);
}

gboolean koneplus_libusb_e_write(libusb_device_handle *handle, guint8 unknown) {
	KoneplusE e = { .command = KONEPLUS_COMMAND_E,
		.size = sizeof(KoneplusE),
		.unknown = unknown
	};
	int len;

	len = libusb_control_transfer(handle,
			LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
			LIBUSB_REQUEST_SET_CONFIGURATION,
			koneplus_usb_command(KONEPLUS_COMMAND_E),
			0,
			(unsigned char *)&e,
			sizeof(KoneplusE),
			KONEPLUS_USB_TIMEOUT
	);

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

	return koneplus_libusb_check_write(handle);
}
