#include "CSerial.h"

#include <cstring>

#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>

CSerial::CSerial() : com_fd_(-1),
					 terminated_(false),
					 prev_config_()
{
}

CSerial::~CSerial()
{
}

void *CSerial::ThreadFunc(void *p)
{
	CSerial *this_ptr = reinterpret_cast<CSerial *>(p);
	return reinterpret_cast<void *>(this_ptr->Receive());
}

bool CSerial::Open(void *p)
{
	// Open com device
	com_fd_ = open(reinterpret_cast<char *>(p), O_RDWR | O_NOCTTY | O_NDELAY);
	if (com_fd_ == -1)
		return false;

	// Save the terminal parameters
	if (tcgetattr(com_fd_, &prev_config_) < 0)
	{
		close(com_fd_);
		return false;
	}

	// Purge any information in the buffer
	if (tcflush(com_fd_, TCIOFLUSH) < 0)
	{
		close(com_fd_);
		return false;
	}

	// Configures a communications device
	struct termios config;
	memcpy(&config, &prev_config_, sizeof(termios));
	config.c_iflag = 0;
	config.c_oflag = 0;
	config.c_cflag = B115200 | CS8 | CREAD | CRTSCTS;
	config.c_lflag = 0;
	config.c_cc[VMIN] = 1;
	config.c_cc[VTIME] = 0;
	if (tcsetattr(com_fd_, TCSAFLUSH, &config) < 0 ||
		cfsetispeed(&config, B115200) < 0 ||
		cfsetospeed(&config, B115200) < 0)
	{
		tcsetattr(com_fd_, TCSAFLUSH, &prev_config_);
		close(com_fd_);
		return false;
	}

	// Create a recieve thread
	terminated_ = false;
	if (pthread_create(&thread_, NULL, CSerial::ThreadFunc, this) != 0)
	{
		tcsetattr(com_fd_, TCSAFLUSH, &prev_config_);
		close(com_fd_);
		return false;
	}

	return true;
}

void CSerial::Close()
{
	terminated_ = true;

	// Wait until the thread is over
	pthread_join(thread_, NULL);

	if (com_fd_ > -1)
	{
		// Retrieves the termination status of the thread
		tcflush(com_fd_, TCIOFLUSH);

		// Restore the terminal parameters
		tcsetattr(com_fd_, TCSAFLUSH, &prev_config_);
		close(com_fd_);
	}
}

error_t CSerial::Receive()
{
	uint8_t buffer[RECV_BUFF_SIZE];
	size_t receive_size, read_size;

	int epfd = epoll_create1(0);
	if (epfd < 0)
	{
		return errno;
	}

	// A character was received and placed in the input buffer
	epoll_event event = {};
	event.events = EPOLLIN;
	event.data.fd = com_fd_;
	epoll_ctl(epfd, EPOLL_CTL_ADD, event.data.fd, &event);

	while (!terminated_)
	{
		// Wait for data receive event to occur
		if (epoll_wait(epfd, &event, 1, 1000) < 0)
		{
			return errno;
		}

		if (event.data.fd == com_fd_)
		{
			if (ioctl(event.data.fd, TIOCINQ, &receive_size) < 0)
			{
				return errno;
			}

			if (receive_size > 0)
			{
				// Read receive data
				if (read_size = read(event.data.fd, &buffer, receive_size))
				{
					WriteBuffer(buffer, read_size);
				}
				else
				{
					return errno;
				}
			}
		}
	}

	return 0;
}

size_t CSerial::Transmit(const uint8_t *buf, size_t size)
{
	size_t w, index = 0;

	while (size > 0)
	{
		w = write(com_fd_, buf + index, size);
		index += w;
		if (!w)
		{
			return 0;
		}
		size -= w;
	}

	return w;
}
