//////////////////////////////////////////////////////////////////
//
// App:			USB
// Author:		Milan Korinek
// Version:		1.0
// Notice:		USB functions and callbacks
//////////////////////////////////////////////////////////////////

#include "USB.h"
#include "tasks/controller.h"
#include "tasks/run_led.h"
#include "support/DIO_Ports.h"



// Buffers for interrupt and bulk transfer (only one type of trasfer will be used in one time)
static uint8_t buffer_data_out [EP_DATA_BUFFER_SIZE];
static uint8_t buffer_data_in [EP_DATA_BUFFER_SIZE];


/* ============================================ */
/*		      	USB general events			    */
/* ============================================ */

void usb_vbus_callback(bool b_vbus_high)
{
	// NOT USED
}

void usb_sof_callback(void)
{
	// NOT USED
}

void usb_suspend_callback(void)
{
	run_led_msg_t msgLED;
	// This macro is called immediate after detach of USB (but usb_vendor stay enabled...)
	if ((DeviceStateInt.DeviceConnectionState >= E_DEVICE_CONNECTION_STATE_WAS_CONNECTED) && 
			(DeviceStateInt.DeviceState != E_DEVICE_STATE_CLOSE)) {
		// Device was connected to Card Driver
		DeviceStateInt.DeviceConnectionState = E_DEVICE_CONNECTION_STATE_WAS_CONNECTED;
		debug_event(DIO_SEVERITY_NORMAL, "E_DEVICE_CONNECTION_STATE_WAS_CONNECTED");
		msgLED.message = RUN_LED_MSG_WARNING;
		xQueueSendFromISR(run_led_handle.control_pipe, (void *)(& msgLED), 0);
	} else {
		// Device was not connected to Card Driver
		DeviceStateInt.DeviceConnectionState = E_DEVICE_CONNECTION_STATE_DISCONNECTED;
		debug_event(DIO_SEVERITY_NORMAL, "E_DEVICE_CONNECTION_STATE_DISCONNECTED");
	}
}

void usb_resume_callback(void)
{
	// Only means that USB was attach != is connected (with Card Driver)! Nonething to do here.
	debug_event(DIO_SEVERITY_NORMAL, "");
}

bool usb_vendor_enable_callback(void)
{
	run_led_msg_t msgLED;
	// Called only after configuration USB interface by Card Driver
	if ((DeviceStateInt.DeviceConnectionState == E_DEVICE_CONNECTION_STATE_WAS_CONNECTED) && 	// Device was configured then USB was detach and then attach.
			(DeviceStateInt.DeviceState != E_DEVICE_STATE_CLOSE)) {
		DeviceStateInt.DeviceConnectionState = E_DEVICE_CONNECTION_STATE_WAS_DISCONNECTED;
		debug_event(DIO_SEVERITY_NORMAL, "E_DEVICE_CONNECTION_STATE_WAS_DISCONNECTED");
		msgLED.message = RUN_LED_MSG_RUN;
	} else {
		DeviceStateInt.DeviceConnectionState = E_DEVICE_CONNECTION_STATE_CONNECTED;
		debug_event(DIO_SEVERITY_NORMAL, "E_DEVICE_CONNECTION_STATE_CONNECTED, control buffer = %u, data buffers = %u", EP_CONTROL_BUFFER_SIZE, EP_DATA_BUFFER_SIZE);
		msgLED.message = RUN_LED_MSG_ON;
	}
	
	xQueueSendFromISR(run_led_handle.control_pipe, (void *)(& msgLED), 0);
	buffer_control_lock = false;
	
	// Enable interrupt endpoint
	udi_vendor_interrupt_out_run(buffer_data_out, EP_DATA_BUFFER_SIZE, usb_interrupt_out_callback);
	return true;
}

void usb_vendor_disable_callback(void)
{
	run_led_msg_t msgLED;
	if ((DeviceStateInt.DeviceConnectionState >= E_DEVICE_CONNECTION_STATE_WAS_CONNECTED) && 	// Device was configured then USB was detach and then attach.
			(DeviceStateInt.DeviceState != E_DEVICE_STATE_CLOSE)) {
		DeviceStateInt.DeviceConnectionState = E_DEVICE_CONNECTION_STATE_WAS_CONNECTED;
		debug_event(DIO_SEVERITY_NORMAL, "E_DEVICE_CONNECTION_STATE_WAS_CONNECTED");
	} else {
		DeviceStateInt.DeviceConnectionState = E_DEVICE_CONNECTION_STATE_DISCONNECTED;
		debug_event(DIO_SEVERITY_NORMAL, "E_DEVICE_CONNECTION_STATE_DISCONNECTED");
	}
	msgLED.message = RUN_LED_MSG_OFF;
	xQueueSendFromISR(run_led_handle.control_pipe, (void *)(& msgLED), 0);
	
	buffer_control_lock = true;
}

/* ============================================ */
/*		      	Control endpoints			    */
/* ============================================ */

bool usb_control_setup_out_received(void)
{
	debug_info(DIO_SEVERITY_HIGH, "lock = %d, device connection state = %d, payload length = %u, packet type = %u, index = %u.", 
		buffer_control_lock, DeviceStateInt.DeviceConnectionState, (unsigned int)udd_g_ctrlreq.req.wLength, 
			(unsigned int)udd_g_ctrlreq.req.wValue, (unsigned int)udd_g_ctrlreq.req.wIndex);
	
	// Buffer must be unlocked
	if (buffer_control_lock) return false;
	
	// Device must be connected first (enabled USB interface - usb_vendor_enable_callback())
	if (DeviceStateInt.DeviceConnectionState < E_DEVICE_CONNECTION_STATE_WAS_DISCONNECTED) {
		return false;
	}
	
	// Packet length must be smaller than control buffer (all desire packets are shorted)
	if (udd_g_ctrlreq.req.wLength > EP_CONTROL_BUFFER_SIZE) {
		return false;
	}
	
	// Resolve packet type
	switch (to_enum(udd_g_ctrlreq.req.wValue, E_Packet_Types_t)) {
		case E_PACKET_TYPE_BASIC_CONFIGURE:
			udd_g_ctrlreq.callback = usb_control_setup_out_basic_config_callback;
			break;
		default:	// unknown packet type
			return false;
	}
	
	// Not need now: udd_g_ctrlreq.over_under_run = usb_control_setup_out_over_under_run;
	buffer_control[0] = (uint8_t)udd_g_ctrlreq.req.wLength;	// save length of packet, (packet has maximum 64 B, it is ok save to uint8)
	udd_g_ctrlreq.payload = &buffer_control[1];
	udd_g_ctrlreq.payload_size = udd_g_ctrlreq.req.wLength;
	
	return true;
}

void usb_control_setup_out_basic_config_callback(void)
{
	portBASE_TYPE xHigherPriorityTaskWoken;
	controller_msg_t msg;
	
	buffer_control_lock = true;
	
	/* We have not woken a task at the start of the ISR. */
    xHigherPriorityTaskWoken = pdFALSE;
	
	// Create message to task Controller
	msg.message = CONTROLLER_BASIC_CONFIG;		// message type
    
	xQueueSendFromISR(controller_handle.control_pipe, (void *)(& msg), &xHigherPriorityTaskWoken);
}

bool usb_control_setup_out_over_under_run(void)
{
	// NOT USED
	return true;
}

bool usb_control_setup_in_received(void)
{
	// Buffer must be unlocked
	if (buffer_control_lock) return false;
	
	// NOT USED, will be used for sending current state
	return true;
}

void usb_control_setup_in_callback(void)
{
	// NOT USED
}

bool usb_control_setup_in_over_under_run(void)
{
	// NOT USED
	return true;
}

/* ============================================ */
/*		    	Interrupt endpoints			    */
/* ============================================ */

void usb_interrupt_in_send_callback(udd_ep_status_t status, iram_size_t nb_transfered, udd_ep_id_t ep)
{
	if (status == UDD_EP_TRANSFER_OK)
	{
	}
	else
	{
	}
}

void usb_interrupt_out_callback(udd_ep_status_t status, iram_size_t nb_transfered, udd_ep_id_t ep)
{
	E_Packet_Types_t packet;
	uint8_t offset;
	uint8_t ports;
	int i, index;
	
	
	if ((DeviceStateInt.DeviceConnectionState < E_DEVICE_CONNECTION_STATE_WAS_DISCONNECTED) || 
			(DeviceStateInt.DeviceState == E_DEVICE_STATE_CLOSE) ||
				(DeviceStateInt.DeviceBehavior.Mode != DIO_E_MODE_ACCES_DIO)) {
		
		debug_error(DIO_SEVERITY_CRITICAL, "Receive packet on interrupt out, but device is not ready.");
		return;
	}
	
	if ((status != UDD_EP_TRANSFER_OK) || (nb_transfered != EP_INTERRUPT_IMMEDIATE_RW_BUFFER_SIZE)) {
		debug_error(DIO_SEVERITY_CRITICAL, "Receive packet on interrupt out - length not match (receive %u).", 
			(unsigned int) nb_transfered);
		return;
	}
	
	packet = buffer_data_out[0];
	ports = buffer_data_out[1];
	offset = buffer_data_out[2];
	
	if (packet == E_PACKET_TYPE_IMMEDIATE_WRITE) {			// Set outputs
		if (ports == 0) {	// Set channel
			DIO_set_output_channel(offset, buffer_data_out[3]);
		} else {			// Set port
			index = 0;
			for (i = offset; i < (offset + ports); i++) {
				DIO_set_outputs_port(i, buffer_data_out[3 + index++]);
			}
		}
	} else if (packet == E_PACKET_TYPE_IMMEDIATE_READ) {	// Get inputs
		// Create packet to send
		buffer_data_in[0] = E_PACKET_TYPE_IMMEDIATE_READ;
		buffer_data_in[1] = buffer_data_out[1];
		buffer_data_in[2] = buffer_data_out[2];
		
		// Read inputs
		if (ports == 0) {	// Read channel
			buffer_data_in[3] = (uint8_t)DIO_get_input_channel(offset);
		} else {			// Read port
			index = 0;
			for (i = offset; i < (offset + ports); i++) {
				buffer_data_in[3 + index++] = DIO_get_inputs_port(i);
			}
		}
		
		if (udi_vendor_interrupt_in_run(buffer_data_in, EP_INTERRUPT_IMMEDIATE_RW_BUFFER_SIZE, usb_interrupt_in_send_callback)){
			puts("success udi_vendor_interrupt_in_run \r\n");
		}
		else
		{
			puts("fail udi_vendor_interrupt_in_run \r\n");
		}
				
	}
	
	udi_vendor_interrupt_out_run(buffer_data_out, EP_DATA_BUFFER_SIZE, usb_interrupt_out_callback);
}


/* ============================================ */
/*		      	Bulk endpoints				    */
/* ============================================ */

void usb_bulk_out_callback(udd_ep_status_t status, iram_size_t nb_transfered, udd_ep_id_t ep)
{
	// NOT USED, will be used for receive test script
}

void usb_bulk_in_callback(udd_ep_status_t status, iram_size_t nb_transfered, udd_ep_id_t ep)
{
	// NOT USED
}