/* ----------------------------------------------------------------------------
 *         SAM Software Package License
 * ----------------------------------------------------------------------------
 * Copyright (c) 2011-2012, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following condition is met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

#include "sam.h"
#include <string.h>
#include "sam_ba_monitor.h"
#include "usart_sam_ba.h"
#include "uart_driver.h"
#include "compiler.h"
#include "cdc_enumerate.h"

const char RomBOOT_Version[] = SAM_BA_VERSION;

/* Provides one common interface to handle both USART and USB-CDC */
typedef struct
{
	/* send one byte of data */
	int (*put_c)(int value);
	/* Get one byte */
	int (*get_c)(void);
	/* Receive buffer not empty */
	bool (*is_rx_ready)(void);
	/* Send given data (polling) */
	uint32_t (*putdata)(void const* data, uint32_t length);
	/* Get data from comm. device */
	uint32_t (*getdata)(void* data, uint32_t length);
	/* Send given data (polling) using xmodem (if necessary) */
	uint32_t (*putdata_xmd)(void const* data, uint32_t length);
	/* Get data from comm. device using xmodem (if necessary) */
	uint32_t (*getdata_xmd)(void* data, uint32_t length);
} t_monitor_if;

#if SAM_BA_INTERFACE == SAM_BA_UART_ONLY  ||  SAM_BA_INTERFACE == SAM_BA_BOTH_INTERFACES
/* Initialize structures with function pointers from supported interfaces */
const t_monitor_if uart_if =
{ usart_putc, usart_getc, usart_is_rx_ready, usart_putdata, usart_getdata,
		usart_putdata_xmd, usart_getdata_xmd };
#endif

#if SAM_BA_INTERFACE == SAM_BA_USBCDC_ONLY  ||  SAM_BA_INTERFACE == SAM_BA_BOTH_INTERFACES
//Please note that USB doesn't use Xmodem protocol, since USB already includes flow control and data verification
//Data are simply forwarded without further coding.
const t_monitor_if usbcdc_if =
{ cdc_putc, cdc_getc, cdc_is_rx_ready, cdc_write_buf,
		cdc_read_buf, cdc_write_buf, cdc_read_buf_xmd };
#endif

/* The pointer to the interface object use by the monitor */
t_monitor_if * ptr_monitor_if;

/* b_terminal_mode mode (ascii) or hex mode */
volatile bool b_terminal_mode = false;
volatile bool b_sam_ba_interface_usart = false;

void sam_ba_monitor_init(uint8_t com_interface)
{
#if SAM_BA_INTERFACE == SAM_BA_UART_ONLY  ||  SAM_BA_INTERFACE == SAM_BA_BOTH_INTERFACES       
	//Selects the requested interface for future actions
	if (com_interface == SAM_BA_INTERFACE_USART) {
		ptr_monitor_if = (t_monitor_if*) &uart_if;
		b_sam_ba_interface_usart = true;
	}
#endif
#if SAM_BA_INTERFACE == SAM_BA_USBCDC_ONLY  ||  SAM_BA_INTERFACE == SAM_BA_BOTH_INTERFACES        
	if (com_interface == SAM_BA_INTERFACE_USBCDC) {
		ptr_monitor_if = (t_monitor_if*) &usbcdc_if;
	}
#endif
}

/**
 * \brief This function allows data rx by USART
 *
 * \param *data  Data pointer
 * \param length Length of the data
 */
void sam_ba_putdata_term(uint8_t* data, uint32_t length)
{
	uint8_t temp, buf[12], *data_ascii;
	uint32_t i, int_value;

	if (b_terminal_mode)
	{
		if (length == 4)
			int_value = *(uint32_t *) data;
		else if (length == 2)
			int_value = *(uint16_t *) data;
		else
			int_value = *(uint8_t *) data;

		data_ascii = buf + 2;
		data_ascii += length * 2 - 1;

		for (i = 0; i < length * 2; i++)
		{
			temp = (uint8_t) (int_value & 0xf);

			if (temp <= 0x9)
				*data_ascii = temp | 0x30;
			else
				*data_ascii = temp + 0x37;

			int_value >>= 4;
			data_ascii--;
		}
		buf[0] = '0';
		buf[1] = 'x';
		buf[length * 2 + 2] = '\n';
		buf[length * 2 + 3] = '\r';
		ptr_monitor_if->putdata(buf, length * 2 + 4);
	}
	else
		ptr_monitor_if->putdata(data, length);
	return;
}

volatile uint32_t sp;
void call_applet(uint32_t address)
{
	uint32_t app_start_address;

	cpu_irq_disable();

	sp = __get_MSP();

	/* Rebase the Stack Pointer */
	__set_MSP(*(uint32_t *) address);

	/* Load the Reset Handler address of the application */
	app_start_address = *(uint32_t *)(address + 4);

	/* Jump to application Reset Handler in the application */
	asm("bx %0"::"r"(app_start_address));
}


uint32_t current_number;
uint32_t i, length;
uint8_t command, *ptr_data, *ptr, data[SIZEBUFMAX];
uint8_t j;
uint32_t u32tmp;


/**
 * \brief This function starts the SAM-BA monitor.
 */
void sam_ba_monitor_run(void)
{
	ptr_data = NULL;
	command = 'z';
	while (1) {
		sam_ba_monitor_loop();
	}
}

void sam_ba_monitor_loop(void)
{
	length = ptr_monitor_if->getdata(data, SIZEBUFMAX);
	ptr = data;
	for (i = 0; i < length; i++, ptr++)
	{
		if (*ptr == 0xff) continue;

		if (*ptr == '#')
		{
			if (b_terminal_mode)
			{
				ptr_monitor_if->putdata("\n\r", 2);
			}
			if (command == 'S')
			{
				//Check if some data are remaining in the "data" buffer
				if(length>i)
				{
					//Move current indexes to next avail data (currently ptr points to "#")
					ptr++;
					i++;
					//We need to add first the remaining data of the current buffer already read from usb
					//read a maximum of "current_number" bytes
					u32tmp=min((length-i),current_number);
					memcpy(ptr_data, ptr, u32tmp);
					i += u32tmp;
					ptr += u32tmp;
					j = u32tmp;
				}
				//update i with the data read from the buffer
				i--;
				ptr--;
				//Do we expect more data ?
				if(j<current_number)
					ptr_monitor_if->getdata_xmd(ptr_data, current_number-j);
				
				__asm("nop");
			}
			else if (command == 'R')
			{
				ptr_monitor_if->putdata_xmd(ptr_data, current_number);
			}
			else if (command == 'O')
			{
				*ptr_data = (char) current_number;
			}
			else if (command == 'H')
			{
				*((uint16_t *) ptr_data) = (uint16_t) current_number;
			}
			else if (command == 'W')
			{
				*((int *) ptr_data) = current_number;
			}
			else if (command == 'o')
			{
				sam_ba_putdata_term(ptr_data, 1);
			}
			else if (command == 'h')
			{
				current_number = *((uint16_t *) ptr_data);
				sam_ba_putdata_term((uint8_t*) &current_number, 2);
			}
			else if (command == 'w')
			{
				current_number = *((uint32_t *) ptr_data);
				sam_ba_putdata_term((uint8_t*) &current_number, 4);
			}
			else if (command == 'G')
			{
				call_applet(current_number);
				/* Rebase the Stack Pointer */
				__set_MSP(sp);
				cpu_irq_enable();
				if (b_sam_ba_interface_usart) {
					ptr_monitor_if->put_c(0x6);
				}
			}
			else if (command == 'T')
			{
				b_terminal_mode = 1;
				ptr_monitor_if->putdata("\n\r", 2);
			}
			else if (command == 'N')
			{
				if (b_terminal_mode == 0)
				{
					ptr_monitor_if->putdata("\n\r", 2);
				}
				b_terminal_mode = 0;
			}
			else if (command == 'V')
			{
				ptr_monitor_if->putdata("v", 1);
				ptr_monitor_if->putdata((uint8_t *) RomBOOT_Version,
						strlen(RomBOOT_Version));
				ptr_monitor_if->putdata(" ", 1);
				ptr = (uint8_t*) &(__DATE__);
				i = 0;
				while (*ptr++ != '\0')
					i++;
				ptr_monitor_if->putdata((uint8_t *) &(__DATE__), i);
				ptr_monitor_if->putdata(" ", 1);
				i = 0;
				ptr = (uint8_t*) &(__TIME__);
				while (*ptr++ != '\0')
					i++;
				ptr_monitor_if->putdata((uint8_t *) &(__TIME__), i);
				ptr_monitor_if->putdata("\n\r", 2);
			}

			command = 'z';
			current_number = 0;

			if (b_terminal_mode)
			{
				ptr_monitor_if->putdata(">", 1);
			}
		}
		else
		{
			if (('0' <= *ptr) && (*ptr <= '9'))
			{
				current_number = (current_number << 4) | (*ptr - '0');
			}
			else if (('A' <= *ptr) && (*ptr <= 'F'))
			{
				current_number = (current_number << 4) | (*ptr - 'A' + 0xa);
			}
			else if (('a' <= *ptr) && (*ptr <= 'f'))
			{
				current_number = (current_number << 4) | (*ptr - 'a' + 0xa);
			}
			else if (*ptr == ',')
			{
				ptr_data = (uint8_t *) current_number;
				current_number = 0;
			}
			else
			{
				command = *ptr;
				current_number = 0;
			}
		}
	}
}