#include <libusb-1.0/libusb.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "humanfx.h"

// Logging
static void log_fatal(const char* msg) {
	fprintf(stderr, "fatal: %s\n", msg);
	exit(1);
}

static void log_error(const char* msg) {
	fprintf(stderr, "error: %s\n", msg);
}

// Device
static libusb_context* context = NULL;
static libusb_device_handle* handle = NULL;
static bool acquired = false;

void device_open(void) {
	if (context)
		log_fatal("libusb already initialized");
	if (handle)
		log_fatal("device already opened");

	libusb_init(&context);
	libusb_set_option(context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_WARNING);

	libusb_device** devices;
	ssize_t count = libusb_get_device_list(context, &devices);
	if (count < 0)
		log_fatal("get device list");

	libusb_device* device = NULL;
	for (ssize_t i = 0; i < count; i++) {
		struct libusb_device_descriptor descriptor = { 0 };
		if (libusb_get_device_descriptor(devices[i], &descriptor) != 0)
			log_fatal("get device descriptor");
		if (descriptor.idVendor == 0x187c && descriptor.idProduct == 0x0551) {
			device = devices[i];
			break;
		}
	}
	libusb_free_device_list(devices, 1);
	if (device == NULL)
		log_fatal("find device");

	if (libusb_open(device, &handle) != 0)
		log_fatal("open device");
}

void device_acquire(void) {
	if (!handle)
		log_fatal("device not opened");
	if (acquired)
		return;
	if (libusb_kernel_driver_active(handle, 0))
		if (libusb_detach_kernel_driver(handle, 0) != 0)
			log_fatal("detach kernel driver");
	if (libusb_claim_interface(handle, 0) != 0)
		log_fatal("claim interface");
	acquired = true;
}

void device_release(void) {
	if (!handle)
		log_fatal("device not opened");
	if (!acquired)
		return;
	libusb_release_interface(handle, 0);
	libusb_attach_kernel_driver(handle, 0);
	acquired = false;
}

void device_close(void) {
	if (!handle)
		log_fatal("device not opened");
	if (acquired)
		device_release();
	libusb_close(handle);
	handle = NULL;
	libusb_exit(context);
	context = NULL;
}

void device_send(const uint8_t data[], uint16_t length) {
	if (!acquired)
		log_fatal("device not acquired");
	unsigned char buffer[33];
	memset(buffer, 0, sizeof(buffer));
	memcpy(buffer, data, length);
	if (libusb_control_transfer(handle, 0x21, 9, 0x202, 0, buffer, 33, 0) != 33)
		log_error("could't write full packet");
}

void device_receive(uint8_t data[], uint16_t length) {
	if (!acquired)
		log_fatal("device not acquired");
	unsigned char buffer[33];
	if (libusb_control_transfer(handle, 0xA1, 1, 0x101, 0, buffer, 33, 0) != 33)
		log_error("could't read full packet");
	memcpy(data, buffer, length > 33 ? 33 : length);
}

// Interface
void send_request_firmware_version(void) {
	device_send((uint8_t[]) {
		PREAMBLE,
		REQUEST,
		REQUEST_FIRMWARE_VERSION,
	}, 3);
}

void send_request_status(void) {
	device_send((uint8_t[]) {
		PREAMBLE,
		REQUEST,
		REQUEST_STATUS,
	}, 3);
}

void send_request_elc_config(void) {
	device_send((uint8_t[]) {
		PREAMBLE,
		REQUEST,
		REQUEST_ELC_CONFIG,
	}, 3);
}

void send_request_animation_count(void) {
	device_send((uint8_t[]) {
		PREAMBLE,
		REQUEST,
		REQUEST_ANIMATION_COUNT,
	}, 3);
}

void send_animation_config_start(uint16_t animation_id) {
	device_send((uint8_t[]) {
		PREAMBLE,
		ANIMATION,
		(ANIMATION_CONFIG_START >> 8) & 0xFF,
		(ANIMATION_CONFIG_START) & 0xFF,
		(animation_id >> 8) & 0xFF,
		(animation_id) & 0xFF,
	}, 6);
}

void send_animation_config_play(uint16_t animation_id) {
	device_send((uint8_t[]) {
		PREAMBLE,
		ANIMATION,
		(ANIMATION_CONFIG_PLAY >> 8) & 0xFF,
		(ANIMATION_CONFIG_PLAY) & 0xFF,
		(animation_id >> 8) & 0xFF,
		(animation_id) & 0xFF,
	}, 6);
}

void send_animation_config_save(uint16_t animation_id) {
	device_send((uint8_t[]) {
		PREAMBLE,
		ANIMATION,
		(ANIMATION_CONFIG_SAVE >> 8) & 0xFF,
		(ANIMATION_CONFIG_SAVE) & 0xFF,
		(animation_id >> 8) & 0xFF,
		(animation_id) & 0xFF,
	}, 6);
}

void send_animation_remove(uint16_t animation_id) {
	device_send((uint8_t[]) {
		PREAMBLE,
		ANIMATION,
		(ANIMATION_REMOVE >> 8) & 0xFF,
		(ANIMATION_REMOVE) & 0xFF,
		(animation_id >> 8) & 0xFF,
		(animation_id) & 0xFF,
	}, 6);
}

void send_animation_play(uint16_t animation_id) {
	device_send((uint8_t[]) {
		PREAMBLE,
		ANIMATION,
		(ANIMATION_PLAY >> 8) & 0xFF,
		(ANIMATION_PLAY) & 0xFF,
		(animation_id >> 8) & 0xFF,
		(animation_id) & 0xFF,
	}, 6);
}

void send_animation_set_default(uint16_t animation_id) {
	device_send((uint8_t[]) {
		PREAMBLE,
		ANIMATION,
		(ANIMATION_SET_DEFAULT >> 8) & 0xFF,
		(ANIMATION_SET_DEFAULT) & 0xFF,
		(animation_id >> 8) & 0xFF,
		(animation_id) & 0xFF,
	}, 6);
}

void send_animation_set_startup(uint16_t animation_id) {
	device_send((uint8_t[]) {
		PREAMBLE,
		ANIMATION,
		(ANIMATION_SET_STARTUP >> 8) & 0xFF,
		(ANIMATION_SET_STARTUP) & 0xFF,
		(animation_id >> 8) & 0xFF,
		(animation_id) & 0xFF,
	}, 6);
}

void send_zone_select(uint8_t loop, uint16_t zone_count, ...) {
	va_list args;
	va_start(args, zone_count);
	uint8_t packet[5 + zone_count];
	packet[0] = PREAMBLE;
	packet[1] = ZONE_SELECT;
	packet[2] = loop;
	packet[3] = (zone_count >> 8) & 0xFF;
	packet[4] = (zone_count) & 0xFF;
	for (uint16_t i = 0; i < zone_count; i++)
		packet[5 + i] = (uint8_t) va_arg(args, int);
	device_send(packet, sizeof(packet));
	va_end(args);
}

void send_add_action(uint16_t action, uint16_t duration, uint16_t tempo, uint32_t color) {
	device_send((uint8_t[]) {
		PREAMBLE,
		ADD_ACTION,
		action,
		(duration >> 8) & 0xFF,
		(duration) & 0xFF,
		(tempo >> 8) & 0xFF,
		(tempo) & 0xFF,
		(color >> 16) & 0xFF,
		(color >> 8 & 0xFF),
		(color) & 0xFF,
	}, 10);
}

void send_set_dim(uint8_t dim, uint16_t zone_count, ...) {
	va_list args;
	va_start(args, zone_count);
	uint8_t packet[5 + zone_count];
	packet[0] = PREAMBLE;
	packet[1] = SET_DIM;
	packet[2] = dim;
	packet[3] = (zone_count >> 8) & 0xFF;
	packet[4] = (zone_count) & 0xFF;
	for (uint16_t i = 0; i < zone_count; i++)
		packet[5 + i] = (uint8_t) va_arg(args, int);
	device_send(packet, sizeof(packet));
	va_end(args);
}
