/**
 * usart_interface.c
 *
 * Implements a command line interface for various features on the board.
 * 
 * Copyright (c) 2022 Bigscreen, Inc.
 */

#include <asf.h>
#include <string.h>
#include "usart_interface.h"

TaskHandle_t xUsartTaskHandle;

uint8_t usart_in_buffer[USART_BUFFER_SIZE];			// Rx Buffer loaded by interrupt
volatile uint32_t usart_in_place;
uint8_t usart_received_buffer[USART_BUFFER_SIZE];	// "Safe" rx buffer, copied from interrupt, 
													// won't have interference from IRQ triggering while being used
uint32_t usart_received_place;
uint8_t usart_out_buffer[USART_BUFFER_SIZE];		// Tx buffer
uint32_t usart_out_place;

Data_Packet_T received_packet;

uint8_t error_flags;

static uint8_t usart_check_command(void);

void usart_interface_init(void) {
	
	// Clear the buffers
	memset(usart_in_buffer, 0, USART_BUFFER_SIZE);
	memset(usart_out_buffer, 0, USART_BUFFER_SIZE);
	usart_in_place = 0;
	usart_out_place = 0;
	
	error_flags = 0;
	// Ensure that nearby pins don't cause us to think there's a UART 
	// byte coming in, enable the pad pullup
	ioport_set_pin_mode(PIN_UART_RX, IOPORT_MODE_PULLUP);
	
	// Initialize the receiver interrupt
	NVIC_EnableIRQ(CONSOLE_UART_ID);
	NVIC_SetPriority(CONSOLE_UART_ID, USART_INTERRUPT_PRIO);
	// trigger whenever a byte is waiting in the receiver
	CONSOLE_UART->US_IER = US_IER_RXRDY;

	if(xTaskCreate(usart_task, "USART", TASK_USART_STACK_SIZE, NULL,
	TASK_USART_PRIORITY, &xUsartTaskHandle) != pdPASS) {
		printf("Failed to create usart task\r\n");
	}

}

void usart_task(void *pvParameters) {
	UNUSED(pvParameters);
	
	// xUsartTaskHandle = xTaskGetCurrentTaskHandle();

	while(1) {
		vTaskDelay(1);
		// Check if we have received a command packet
		
		if(pdTRUE == usart_check_command()) {
			// Perform the requested action
			
			UI_process_command(&received_packet);
			
			if(pdPASS == UI_build_packet(usart_out_buffer, USART_BUFFER_SIZE, UI_get_response())) {
				usart_out_place = (UI_get_response()->Length) + USART_DATA_OFFSET;
				for(uint8_t j = 0; j < usart_out_place; j++) {
					usart_putchar(CONSOLE_UART, usart_out_buffer[j]);
				}
				usart_out_place = 0;
			}
		}
	}
}

static uint8_t usart_check_command(void) {
	uint32_t bytes_to_copy;
	
	// Read the number of bytes already in the queue, placed there by interrupt
	// This should be done with IRQs disabled to ensure no RTOS context switches or
	// further interrupts occur
	irqflags_t flags = cpu_irq_save();
	if((usart_in_place + usart_received_place) < USART_BUFFER_SIZE) {
		bytes_to_copy = usart_in_place;
	} else {
		// fill as much of the remaining buffer as possible
		bytes_to_copy = usart_in_place - ((usart_in_place + usart_received_place) - USART_BUFFER_SIZE);
		error_flags |= ERROR_FLAG_OVERRUN;
	}
	memcpy(&(usart_received_buffer[usart_received_place]), usart_in_buffer, bytes_to_copy);
	usart_received_place += bytes_to_copy;
	usart_in_place = 0;
	cpu_irq_restore(flags);
	
	// Check the buffer for a valid comms packet
	
	uint8_t* start_byte = UI_check_packet(usart_received_buffer, usart_received_place, &received_packet);
	
	if(NULL == start_byte) {
		// There was no start byte in the whole buffer.
		// We can safely delete the buffer and start fresh.
		usart_received_place = 0;
	} else {
		uint32_t start_place = ((uint32_t)start_byte) - ((uint32_t)usart_received_buffer);
		uint32_t end_place = start_place + USART_DATA_OFFSET + received_packet.Length;
		
		if(received_packet.ErrorCode == UI_ERR_SUCCESS) {
			// shift the remainder of the received buffer down, deleting this received packet
			// memmove won't do anything if length (3rd argument) is zero, no need to check if the two "place"s are equal
			memmove(usart_received_buffer, &(usart_received_buffer[end_place]), usart_received_place - end_place);
			usart_received_place -= end_place;
			return pdTRUE;
		} else {
			// So there's a start byte, but an incomplete or invalid packet.
			// Let's find out why.
			switch(received_packet.ErrorCode) {
				case UI_ERR_NO_PACKET:
					// We get here if a start byte exists but there aren't enough data
					// bytes in the buffer for even a zero-data packet.
					// Can remove extraneous bytes from before the start byte to keep the 
					// buffer from overfilling.
					
					if(start_place <= USART_BUFFER_SIZE) {
						memmove(usart_received_buffer, start_byte, usart_received_place - start_place);
						usart_received_place -= start_place;
					}
				break;
				
				case UI_ERR_BAD_START_SEQ:
				case UI_ERR_BAD_TYPE:
				case UI_ERR_INVALID_LENGTH:
				default:
					// Any of these represent invalid packets. Can safely delete from
					// the beginning of the buffer up to and including the start byte in the buffer.
					end_place = start_place + 1;
					if(end_place <= USART_BUFFER_SIZE) {
						memmove(usart_received_buffer, &(usart_received_buffer[end_place]), usart_received_place - end_place);
						usart_received_place -= end_place;
					} else {
						// Packet length extends beyond valid buffer region, somehow.
						// Just delete the whole buffer
						usart_received_place = 0;
					}
				break;
				
			}
		}
	}
	return pdFALSE;
}



#if BOARD == USER_EXT_BOARD
// Console is on USART3 (PA3/PA4)
void FLEXCOM3_Handler(void) {
#else
// Console is on USART7 (PA27,28)
void FLEXCOM7_Handler(void) {
#endif
	// Check for RXRDY interrupt
	if(((CONSOLE_UART->US_IMR) & (US_IMR_RXRDY)) != 0) {
		if(((CONSOLE_UART->US_CSR) & (US_CSR_RXRDY)) != 0) {
			// New character received, add it to the holding buffer
			if(usart_in_place < USART_BUFFER_SIZE) {
				usart_in_buffer[usart_in_place++] = CONSOLE_UART->US_RHR;
			}	
			// If there's an overrun, notify the application
			else {
				error_flags |= ERROR_FLAG_OVERRUN;
				// Overwrite the last byte in the buffer. We need to read
				// the USART rx holding register otherwise we will be stuck
				// in this interrupt
				usart_in_buffer[USART_BUFFER_SIZE-1] = CONSOLE_UART->US_RHR;
			}
		}
	}

}
