/* $Id$ */

#include <cstring>
#include <cassert>
#include <string>
#include <cerrno>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <fcntl.h>
#include "throw.h"
#include "intl.h"
#include "comm.h"

CComm::CComm(): m_fd(-1), m_timeout(0)
{
}

void CComm::Open(const std::string &dev, unsigned timeout)
{
	m_fd = open(dev.c_str(), O_RDWR | O_NOCTTY);
	if (m_fd == -1)
		Throw(_("Cannot open dev %s: %s"), dev.c_str(), strerror(errno));

	struct termios t;
	if (tcgetattr(m_fd, &t) == -1)
		Throw(_("Cannot get attributes: %s"), strerror(errno));

	t.c_iflag = 0;
	t.c_oflag = 0;
	t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
	t.c_cflag &= ~(CSIZE | PARENB);
	t.c_cflag |= CS8;

	t.c_cc[VMIN]  = 1;
	t.c_cc[VTIME] = 0;

	if (cfsetispeed(&t, B9600) == -1 || cfsetospeed(&t, B9600) == -1)
		Throw(_("Cannot set speed: %s"), strerror(errno));

	if (tcsetattr(m_fd, TCSAFLUSH, &t) == -1)
		Throw(_("Cannot set attributes: %s"), strerror(errno));

	m_timeout = timeout;
}

CComm::~CComm()
{
	if (m_fd != -1)
	{
		tcdrain(m_fd);
		close(m_fd);
	}
}

void CComm::Send(const void *data, size_t len)
{
	const char *p((const char *) data);
	size_t rem(len);

	while (rem)
	{
		ssize_t rs(write(m_fd, p, rem));

		if (rs == -1)
		{
			if (errno == EAGAIN || errno == EINTR)
				continue;

			Throw(_("Cannot write to device: %s"), strerror(errno));
		}

		if (!rs || (size_t) rs > rem)
			Throw(_("Internal inconsistency (%u %u)"), (size_t) rs, rem);

		p += (size_t) rs;
		rem -= (size_t) rs;
	}

//	if (tcdrain(m_fd) == -1)
//		Throw(_("Cannot drain device: %s"), strerror(errno));

	// we don't check drain return value because on cygwin 
	// it causes problems sometimes.
	tcdrain(m_fd);
}

void CComm::Recv(void *data, size_t len)
{
	char *p((char *) data);
	size_t rem(len);

	while (rem)
	{
		if (m_timeout)
		{
			fd_set rfd;

			FD_ZERO(&rfd);
			FD_SET(m_fd, &rfd);

			struct timeval tv;

			tv.tv_sec = m_timeout;
			tv.tv_usec = 0;

			int selrs(select(m_fd + 1, &rfd, 0, 0, &tv));
			if (selrs == -1)
			{
				if (errno == EAGAIN || errno == EINTR)
					continue;

				Throw(_("Cannot select on device: %s"), strerror(errno));
			}

			if (selrs == 0)
				Throw(_("Radio not responding"));
		}

		ssize_t rs(read(m_fd, p, rem));

		if (!rs)
			Throw(_("Cannot read from device: EOF"));

		if (rs == -1)
		{
			if (errno == EAGAIN || errno == EINTR)
				continue;

			Throw(_("Cannot read from device: %s"), strerror(errno));
		}

		if ((size_t) rs > rem)
			Throw(_("Internal inconsistency (%u %u)"), (size_t) rs, rem);

		p += (size_t) rs;
		rem -= (size_t) rs;
	}
}

void CComm::SendChar(const char ch)
{
	Send(&ch, sizeof(ch));
}

char CComm::RecvChar()
{
	char ch;
	Recv(&ch, sizeof(ch));
	return ch;
}
