//**************************************************************************************
/*! \copyright:     2020-2021 Thales Alenia Space Deutschland GmbH
 * \project:         multiMIND
 * \file:            (name of source file: uart.c)
 * \date:            (20.05.2021)
 * \author:          (Sarthak Kelapure)
 * \brief:           (UART thread to collect data on serial interface)
 * \language:        (C)
 **************************************************************************************
 */

#include "tas/uart.h"
#include "tas/hdlc.h"

#ifdef HDLC_ENABLE
#define HDLC_RX_STATE_IDLE      (0u)
#define HDLC_RX_STATE_RECEIVING (1u)
#define HDLC_RX_STATE_ESCAPE    (2u)
#endif

/**
 * @struct Serial device structure.
 * Encapsulates a serial connection.
 */
struct serial_s {
	int fd;                  //>! Connection file descriptor.
	int state;               //>! Signifies connection state.
	int running;             //>! Signifies thread state.

	char rxbuff[BUFF_SIZE];  //>! Buffer for RX data.
	int start, end;          //>! Pointers to start and end of buffer.

	pthread_t rx_thread;     //>! Listening thread.
};

// ---------------        Internal Functions        ---------------

/**
 * Connect to a serial device.
 * @param s - serial structure.
 * @param device - serial device name.
 * @param baud - baud rate for connection.
 * @return -ve on error, 0 on success.
 */
static int serial_connect(serial_t* s, char device[], int baud);

/**
 * Create the serial structure.
 * Convenience method to allocate memory
 * and instantiate objects.
 * @return serial structure.
 */
static serial_t* serial_create();

static int serial_resolve_baud(int baud);

/**
 * Recieve data.
 * Retrieves data from the serial device.
 * @param s - serial structure.
 * @param data - pointer to a buffer to read data into.
 * @param maxLength - size of input buffer.
 * @return amount of data recieved.
 */
static int serial_recieve(serial_t* obj, uint8_t data[], int maxLength);

/**
 * @brief Serial Listener Thread.
 * This blocks waiting for data to be recieved from the serial device,
 * and calls the serial_callback method with appropriate context when
 * data is recieved.
 * Exits when close method is called, or serial error occurs.
 * @param param - context passed from thread instantiation.
 */
static void *serial_data_listener(void *param);

/**
 * @brief Start the serial threads.
 * This spawns the listening and transmitting threads
 * if they are not already running.
 * @param s - serial structure.
 * @return 0 on success, or -1 on error.
 */
static int serial_start(serial_t* s);

/**
 * Stop serial listener thread.
 * @param s - serial structure.
 * @return 0;
 */
static int serial_stop(serial_t* s);

/**
 * Callback to handle recieved data.
 * Puts recieved data into the rx buffer.
 * @param s - serial structure.
 * @param data - data to be stored.
 * @param length - length of recieved data.
 */
static void serial_rx_callback(serial_t* s, char data[], int length);

// Put character in rx buffer.
static int buffer_put(serial_t* s, char c)
{
	//if there is space in the buffer
	if ( s->end != ((s->start + BUFF_SIZE - 1) % BUFF_SIZE)) {
		s->rxbuff[s->end] = c;
		s->end ++;
		s->end = s->end % BUFF_SIZE;
		//printf("Put: %x start: %d, end: %d\r\n", c, s->start, s->end);
		return 0;        //No error
	} else {
		//buffer is full, this is a bad state
		return -1;        //Report error
	}
}

// Get character from rx buffer.
static char buffer_get(serial_t* s)
{
	char c = (char)0;
	//if there is data to process
	if (s->end != s->start) {
		c = (s->rxbuff[s->start]);
		s->start ++;
		//wrap around
		s->start = s->start % BUFF_SIZE;
	} else {
	}
	//printf("Get: %x start: %d, end: %d\r\n", c, s->start, s->end);
	return c;
}

//Get data available in the rx buffer.
static int buffer_available(serial_t* s)
{
	return (s->end - s->start + BUFF_SIZE) % BUFF_SIZE;
}

// ---------------        External Functions        ---------------

//Create serial object.
serial_t* serial_create()
{
	//Allocate serial object.
	serial_t* s = malloc(sizeof(serial_t));
	//Reconfigure buffer object.
	s->start = 0;
	s->end = 0;
	//Return pointer.
	return s;
}


void uart_destroy(serial_t* s)
{
	free(s);
}


//Connect to serial device.
int serial_connect(serial_t* s, char device[], int baud)
{
	struct termios oldtio;

	// Resolve baud.
	int speed = serial_resolve_baud(baud);
	if (speed < 0) {
		printf("Error: Baud rate not recognized.\r\n");
		return -1;
	}

	//Open device.
	s->fd = open(device, O_RDWR | O_NOCTTY);
	//Catch file open error.
	if (s->fd < 0) {
		perror(device);
		return -2;
	}
	//Retrieve settings.
	tcgetattr(s->fd, &oldtio);
	//Set baud rate.
	cfsetspeed(&oldtio, speed);
	//Flush cache.
	tcflush(s->fd, TCIFLUSH);

	//Set UART settings, standard ones. 8N1
	oldtio.c_cflag = (oldtio.c_cflag & ~CSIZE) | CS8;     // 8-bit chars
	// disable IGNBRK for mismatched speed tests; otherwise receive break
	// as \000 chars
	oldtio.c_iflag &= ~IGNBRK;         // disable break processing
	oldtio.c_lflag = 0;                // no signaling chars, no echo,
	// no canonical processing
	oldtio.c_oflag = 0;                // no remapping, no delays
	oldtio.c_cc[VMIN]  = 0;            // read doesn't block
	oldtio.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

	oldtio.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl
	oldtio.c_iflag &= ~(IGNCR | ICRNL | INLCR);  // CR and LF characters are not affected

	oldtio.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
	// enable reading
	oldtio.c_cflag &= ~(PARENB | PARODD);      // shut off parity
	oldtio.c_cflag |= 0;
	oldtio.c_cflag &= ~CSTOPB;
	oldtio.c_cflag &= ~(020000000000);
	//Apply settings.
	if(tcsetattr(s->fd, TCSANOW, &oldtio) !=0){
		printf("ERROR: serial settings failed\r\n");
		return -1;
	}

	//Start listener thread.
	int res = serial_start(s);
	//Catch error.
	if (res < 0) {
		printf("Error: serial thread could not be spawned\r\n");
		return -3;
	}

	//Indicate connection was successful.
	s->state = 1;
	return 0;
}

serial_t* uart_init(char device[], int baud)
{
	serial_t* s = serial_create();
	if(serial_connect(s, device, baud)< 0)
	{
		return NULL;
	}
	return s;
}

//Send data.
uint32_t uart_length_send(serial_t* s, uint8_t data[], int length)
{
//	uint16_t ii;
//	int res;
//	for (ii = 0; ii < length; ii++)
//	{
//		res = write(s->fd, &data[ii], 1);
//	}
	int res = write(s->fd, data, length);
	return res;
}

void uart_send(serial_t* s, uint8_t data)
{
	char arr[1];
	arr[0] = data;
	write(s->fd, arr, 1);
}

//Determine characters available.
int uart_available(serial_t* s)
{
	return buffer_available(s);
}

//Fetch a character.
char uart_get(serial_t* s)
{
	char c = buffer_get(s);

	return c;
}

int uart_length_get(serial_t* s, char* buff, int len, bool start_of_packet)
{
	int ret = 0;
	if (len > 0 && len < BUFF_SIZE)
	{
#ifdef HDLC_ENABLE
		uint8_t ch;
		uint8_t hdlc_rx_state;
		int     rxb = 0;

		if (start_of_packet)
			hdlc_rx_state = HDLC_RX_STATE_IDLE;
		else
			hdlc_rx_state = HDLC_RX_STATE_RECEIVING;

		while (rxb < len)
		{
			ch = uart_blocking_get(s);

			switch (hdlc_rx_state)
			{
				case HDLC_RX_STATE_IDLE:
					if (ch == HDLC_START_BYTE)
					{
						rxb = 0;
						ret = 0;
						hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
					}
					break;
				case HDLC_RX_STATE_RECEIVING:
					if (ch == HDLC_START_BYTE)
					{
						rxb = 0;
						ret = 0;
						break;
					}
					if (ch == HDLC_ESC_BYTE)
					{
						hdlc_rx_state = HDLC_RX_STATE_ESCAPE;
						break;
					}
					buff[rxb++] = ch;
					ret++;
					break;
				case HDLC_RX_STATE_ESCAPE:
					if (ch == HDLC_START_BYTE)
					{
						rxb = 0;
						ret = 0;
						break;
					}
					buff[rxb++] = ch ^ HDLC_ESCAPE_CHAR;
					ret++;
					hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
					break;
			}
		}
#else
		for (int i=0;i<len;i++)
		{
			buff[i] = uart_blocking_get(s);
			ret++;
		}
#endif
	}
	return ret;
}

uint16_t uart_get_hdlc_packet(serial_t* s, uint8_t *buff, uint16_t buff_len)
{
	uint8_t hdlc_rx_state = HDLC_RX_STATE_IDLE;
	uint8_t ch;
	uint16_t buff_pos = 0u;

	while (1)
	{
		ch = uart_blocking_get(s);

		switch (hdlc_rx_state)
		{
			case HDLC_RX_STATE_IDLE:
				if (ch == HDLC_START_BYTE)
				{
					buff_pos = 0u;
					hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
				}
				break;

			case HDLC_RX_STATE_RECEIVING:
				if (ch == HDLC_START_BYTE)
				{
					buff_pos = 0u;
					break;
				}
				if (ch == HDLC_END_BYTE)
				{
					if (buff_pos > 2u) // do not include HDLC CRC16
					{
						return buff_pos;
					}
					buff_pos = 0u;
					hdlc_rx_state = HDLC_RX_STATE_IDLE;
					break;
				}
				if (ch == HDLC_ESC_BYTE)
				{
					hdlc_rx_state = HDLC_RX_STATE_ESCAPE;
					break;
				}
				if (buff_pos >= buff_len)
				{
					hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
					break;
				}
				buff[buff_pos++] = ch;
				break;

			case HDLC_RX_STATE_ESCAPE:
				if ((ch == HDLC_START_BYTE) || (ch == HDLC_END_BYTE))
				{
					buff_pos = 0;
					hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
					break;
				}
				if (buff_pos >= buff_len)
				{
					hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
					break;
				}
				buff[buff_pos++] = ch ^ HDLC_ESCAPE_CHAR;
				hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
				break;

			default:
				buff_pos = 0u;
				hdlc_rx_state = HDLC_RX_STATE_IDLE;
				break;
		}
	}
}

char uart_blocking_get(serial_t* s)
{
	while (uart_available(s) == 0);
	return uart_get(s);
}

void uart_clear(serial_t* s)
{
	//Clear the buffer.
	while (buffer_available(s)) {
		buffer_get(s);
	}
	tcflush(s->fd, TCIFLUSH);
}

//Close serial port.
int uart_close(serial_t* s)
{
	//Stop thread.
	serial_stop(s);
	return 0;
}

void uart_deinit(serial_t* s){
	uart_clear(s);
	uart_close(s);
	uart_destroy(s);
}

// ---------------        Internal Functions        --------------

//Stop serial listener thread.
static int serial_stop(serial_t* s)
{
	s->running = 0;
	return close(s->fd);
}

// Resolves standard baud rates to linux constants.
static int serial_resolve_baud(int baud)
{
	int speed;
	// Switch common baud rates to temios constants.
	switch (baud) {
	case 9600:
		speed = B9600;
		break;
	case 19200:
		speed = B19200;
		break;
	case 38400:
		speed = B38400;
		break;
	case 57600:
		speed = B57600;
		break;
	case 115200:
		speed = B115200;
		break;
	case 230400:
		speed = B230400;
		break;
	case 460800:
		speed = B460800;
		break;
	case 500000:
		speed = B500000;
		break;
	case 576000:
		speed = B576000;
		break;
	case 921600:
		speed = B921600;
		break;
	case 1000000:
		speed = B1000000;
		break;
	case 1152000:
		speed = B1152000;
		break;
	case 1500000:
		speed = B1500000;
		break;
	case 2000000:
		speed = B2000000;
		break;
	case 3000000:
		speed = B3000000;
		break;
	default:
		speed = -1;
		break;
	}
	// Return.
	return speed;
}

// Start serial listener.
static int serial_start(serial_t* s)
{
	//Only start if it is not currently running.
	if (s->running != 1) {
		//Set running.
		s->running = 1;
		//Spawn thread.
		int res;
		res = pthread_create(&s->rx_thread, NULL, serial_data_listener, (void*) s);
		if (res != 0) {
			return -2;
		}
		//Return result.
		return 0;
	} else {
		return -1;
	}
}



//Recieve data.
static int serial_recieve(serial_t* s, uint8_t data[], int maxLength)
{
	return read(s->fd, data, maxLength);
}

//Callback to store data in buffer.
static void serial_rx_callback(serial_t* s, char data[], int length)
{
	//Put data into buffer.
	int i;
	//Put data into buffer.
	for (i = 0; i < length; i++) {
		buffer_put(s, data[i]);
	}

}

//Serial data listener thread.
static void *serial_data_listener(void *param)
{
	int res = 0;
	int err = 0;
	struct pollfd ufds;
	uint8_t buff[BUFF_SIZE];

	//Retrieve paramaters and store locally.
	serial_t* serial = (serial_t*) param;
	int fd = serial->fd;

	//Set up poll file descriptors.
	ufds.fd = fd;        //Attach socket to watch.
	ufds.events = POLLIN;        //Set events to notify on.

	//Run until ended.
	while (serial->running != 0) {
		//Poll socket for data.
		res = poll(&ufds, 1, POLL_TIMEOUT);
		//If data was recieved.
		if (res > 0) {
			//Fetch the data.
			int count = serial_recieve(serial, buff, BUFF_SIZE - 1);
			//If data was recieved.
			if (count > 0) {
				//Pad end of buffer to ensure there is a termination symbol.
				buff[count] = '\0';
				// Call the serial callback.
				serial_rx_callback(serial, (char *)buff, count);
				//If an error occured.
			} else if (count < 0) {
				//Inform user and exit thread.
				printf("Error: Serial disconnect\r\n");
				err = 1;
				break;
			}
			//If there was an error.
		} else if (res < 0) {
			//Inform user and exit thread.
			printf("Error: Polling error in serial thread");
			err = 1;
			break;
		}
		//Otherwise, keep going around.
	}
	//If there was an error, close socket.
	if (err) {
		uart_close(serial);
		//raise(SIGLOST);
	}
	//Close file.
	res = close(serial->fd);

	return NULL;
}