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

libusb_device_handle *savu_libusb_open_first_device(void) {
	return libusb_open_device_with_vid_pid(NULL, USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU);
}

enum {
	SAVU_USB_TIMEOUT = 1000,
};

gboolean savu_libusb_check_firmware_write(libusb_device_handle *handle) {
	SavuControl 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,
				savu_usb_command(SAVU_COMMAND_FIRMWARE_CONTROL),
				0,
				(unsigned char *)&buffer,
				sizeof(SavuControl),
				SAVU_USB_TIMEOUT
		);

		if (len < 0) {
			g_critical("error checking firmware write: %i", len);
			return FALSE;
		} else if (len != sizeof(SavuControl)) {
			g_critical("got wrong length checking firmware write: %i instead of %zu", len, sizeof(SavuControl));
			return FALSE;
		} else { // read right length
			switch (buffer.value) {
			case SAVU_CONTROL_VALUE_OK:
				return TRUE;
			case SAVU_CONTROL_VALUE_BUSY:
				continue;
			case SAVU_CONTROL_VALUE_INVALID:
				return FALSE;
			case SAVU_CONTROL_VALUE_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 savu_libusb_firmware_version_read(libusb_device_handle *handle) {
	SavuInfo buffer;
	int len;

	len = libusb_control_transfer(
			handle,
			LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
			LIBUSB_REQUEST_CLEAR_FEATURE,
			savu_usb_command(SAVU_COMMAND_INFO),
			0,
			(unsigned char *)&buffer,
			sizeof(SavuInfo),
			SAVU_USB_TIMEOUT
	);

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

	return buffer.firmware_version;
}

gboolean savu_libusb_firmware_write(libusb_device_handle *handle, SavuFirmware const *data) {
	int len;

	len = libusb_control_transfer(handle,
			LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
			LIBUSB_REQUEST_SET_CONFIGURATION,
			savu_usb_command(SAVU_COMMAND_FIRMWARE),
			0,
			(unsigned char *)data,
			sizeof(SavuFirmware),
			SAVU_USB_TIMEOUT
	);

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

	return savu_libusb_check_firmware_write(handle);
}
