//--------------------------------------------------------------------------------
// <copyright file="LqrSMACSerialPort.cpp" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
//   Implementation of ISerialPort (abstract interface class to a serial port) and
//   SerialPort (concrete interface class to a serial port)
// </summary>
//
// <owner>Mike Cookson</owner>
//--------------------------------------------------------------------------------

#include "LqrSMACSerialPort.h"
#include "LqrSMACSerialPort-inline.h"
#include "LqrSMACSerialPortSettings.h"
#include "LqrSMACGetErrorDescription.h"
#include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/foreach.hpp>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include "nsextend.h"

using namespace std;
using namespace boost::posix_time;
using namespace LiquidRobotics::SMACFramework;

namespace LiquidRobotics {
namespace SMACFramework {

const ISerialPort::TimeSpan ISerialPort::TimeSpan_Zero(seconds(0));
const ISerialPort::TimeSpan ISerialPort::TimeSpan_Infinite(boost::posix_time::pos_infin);

IMPLEMENT_INNER_EXCEPTION(ISerialPort, ConcurrentIORequested, std::logic_error)
IMPLEMENT_INNER_EXCEPTION(ISerialPort, PortAlreadyOpen, std::logic_error)
IMPLEMENT_INNER_EXCEPTION(ISerialPort, PortClosed, std::logic_error)
IMPLEMENT_INNER_EXCEPTION(ISerialPort, PortException, std::runtime_error)
IMPLEMENT_INNER_EXCEPTION(ISerialPort, ReadTimeout, std::runtime_error)
IMPLEMENT_INNER_EXCEPTION(ISerialPort, SettingsReadbackFailure, std::runtime_error)
IMPLEMENT_INNER_EXCEPTION(ISerialPort, WriteTimeout, std::runtime_error)

SHARED_LIB_PUBLIC ISerialPort::SystemError::SystemError(const string& whatArg, int _errNum)
: runtime_error(whatArg)
, errNum(_errNum)
{
}

SHARED_LIB_PUBLIC string ISerialPort::SystemError::describe(int errNum)
{
	return GetErrorDescription(errNum);
}

SHARED_LIB_PUBLIC ISerialPort::SystemCallInterrupted::SystemCallInterrupted(const string& whatArg)
: SystemError(whatArg, EINTR)
{
}

ISerialPort::~ISerialPort()
{
}

ISerialPort::string ISerialPort::BaudRate2Str(BaudRate baudRate)
{
	return utos(BaudRate2Num(baudRate));
}

unsigned ISerialPort::BaudRate2Num(BaudRate baudRate)
{
	unsigned result;

	switch(baudRate)
	{
	case BAUD_50:		result = 50;			break;
	case BAUD_75:		result = 75;			break;
	case BAUD_110:		result = 110;			break;
	case BAUD_134:		result = 134;			break;
	case BAUD_150:		result = 150;			break;
	case BAUD_200:		result = 200;			break;
	case BAUD_300:		result = 300;			break;
	case BAUD_600:		result = 600;			break;
	case BAUD_1200:		result = 1200;			break;
	case BAUD_1800:		result = 1800;			break;
	case BAUD_2400:		result = 2400;			break;
	case BAUD_4800:		result = 4800;			break;
	case BAUD_9600:		result = 9600;			break;
	case BAUD_19200:	result = 19200;			break;
	case BAUD_38400:	result = 38400;			break;
	case BAUD_57600:	result = 57600;			break;
	case BAUD_115200:	result = 115200;		break;
	case BAUD_230400:	result = 230400;		break;
	case BAUD_460800:	result = 460800;		break;

	default:
		{
			stringstream stm;
			stm << "Unknown value for baud rate enumeration (" << ((unsigned) baudRate) << ")";
			throw invalid_argument(stm.str());
		}
	}

	return result;
}

ISerialPort::BaudRate ISerialPort::NumToBaudRate(unsigned long num)
{
	BaudRate result;

	switch(num)
	{
	case 50:		result = BAUD_50;			break;
	case 75:		result = BAUD_75;			break;
	case 110:		result = BAUD_110;			break;
	case 134:		result = BAUD_134;			break;
	case 150:		result = BAUD_150;			break;
	case 200:		result = BAUD_200;			break;
	case 300:		result = BAUD_300;			break;
	case 600:		result = BAUD_600;			break;
	case 1200:		result = BAUD_1200;			break;
	case 1800:		result = BAUD_1800;			break;
	case 2400:		result = BAUD_2400;			break;
	case 4800:		result = BAUD_4800;			break;
	case 9600:		result = BAUD_9600;			break;
	case 19200:		result = BAUD_19200;		break;
	case 38400:		result = BAUD_38400;		break;
	case 57600:		result = BAUD_57600;		break;
	case 115200:	result = BAUD_115200;		break;
	case 230400:	result = BAUD_230400;		break;
	case 460800:	result = BAUD_460800;		break;

	default:
		{
			stringstream stm;
			stm << "Unknown value for baud rate (" << num << ")";
			throw invalid_argument(stm.str());
		}
	}

	return result;
}


SerialPort::SerialPort(const string& _portName)
: mutexPtr(NULL)
, fd(-1)
, portName(_portName)
, pLogger(NULL)
{
	mutexPtr = new Mutex;
}

SerialPort::SerialPort(const string& _portName, Logger& logger)
: mutexPtr(NULL)
, fd(-1)
, portName(_portName)
, pLogger(&logger)
{
	mutexPtr = new Mutex;
}

SerialPort::~SerialPort()
{
	if (fd != -1)
	{
		DoClosePort(fd);
		fd = -1;
	}

	if (mutexPtr)
		delete mutexPtr;
}

void SerialPort::Open(const SerialPortSettings& settings)
{
 	Open(settings.GetBaudRate(), settings.GetCharacterSize(), settings.GetParity(), settings.GetStopBits(), settings.GetFlowControl());

	try
	{
		SetRts(settings.GetRts());
		SetDtr(settings.GetDtr());
		SetBreakTimeoutMultiplier(settings.GetBreakTimeoutMultiplier());
	}
	catch(...)
	{
		DoClosePort(fd);
		fd = -1;
		throw;
	}
}

void SerialPort::Open(const BaudRate baudRate, const CharacterSize charSize, const Parity parityType, const StopBits stopBits, const FlowControl flowControl)
{
	Lock lock(*this);

	if (!ValidBaudRate(baudRate))
	{
		stringstream msg;
		msg << "Open() -- invalid baud rate setting (" << baudRate << ") for port " << portName;

		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	if (!ValidCharacterSize(charSize))
	{
		stringstream msg;
		msg << "Open() -- invalid character size setting (" << charSize << ") for port " << portName;

		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	if (!ValidParity(parityType))
	{
		stringstream msg;
		msg << "Open() -- invalid parity setting (" << parityType << ") for port " << portName;

		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	if (!ValidStopBits(stopBits))
	{
		stringstream msg;
		msg << "Open() -- invalid stop bits setting (" << stopBits << ") for port " << portName;

		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	if (!ValidFlowControl(flowControl))
	{
		stringstream msg;
		msg << "Open() -- invalid flow control setting (" << flowControl << ") for port " << portName;

		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	if (IsOpen())
	{
		stringstream msg;
		msg << "Open() -- port " << portName << " is already open";

		DoThrow<PortAlreadyOpen>(__LINE__, msg.str());
	}

	fd = DoOpenPort(portName);
	CheckAndThrow(fd, __LINE__, errno, "Open() -- open");

	try
	{
		int status;
		status = tcgetattr(fd, &termSettings);
		CheckAndThrow(status, __LINE__, errno, "Open() -- tcgetattr");

		termios newSettings = termSettings;

		cfmakeraw(&newSettings);
		newSettings.c_iflag &= ~ISTRIP;

		AffectBaudRate(newSettings, baudRate);
		AffectCharacterSize(newSettings, charSize);
		AffectParity(newSettings, parityType);
		AffectStopBits(newSettings, stopBits);
		AffectFlowControl(newSettings, flowControl);

		newSettings.c_cc[VEOF] = 0;						// no EOF
		newSettings.c_cc[VEOL] = 0;						// no EOL
		newSettings.c_cc[VERASE] = 0;					// no ERASE
		newSettings.c_cc[VINTR] = 0;					// no interrupt char
		newSettings.c_cc[VKILL] = 0;					// no KILL
		newSettings.c_cc[VSTART] = 'Q'-'@';				// XON byte
		newSettings.c_cc[VSTOP] = 'S'-'@';				// XOFF byte
		newSettings.c_cc[VSUSP] = 0;					// no suspend char
		newSettings.c_cc[VQUIT] = 0;					// no quit char

		TCSetAttr(TCSANOW, termSettings, newSettings, __LINE__, "Open()");

		status = tcflush(fd, TCIOFLUSH);
		CheckAndThrow(status, __LINE__, errno, "Open() -- tcflush");
	}
	catch(...)
	{
		DoClosePort(fd);
		fd = -1;
		throw;
	}
}

bool SerialPort::IsOpen() const
{
	Lock lock(*this);
	return fd != -1;
}

bool SerialPort::IsTTY() const
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "IsTTY()");

	return isatty(fd);
}

void SerialPort::Close(bool throwIfNotOpen)
{
	Lock lock(*this);
	if (IsOpen())
	{
		int status = DoClosePort(fd);
		int errSave = errno;

		fd = -1;
		CheckAndThrow(status, __LINE__, errSave, "Close() -- close");
	}
	else if (throwIfNotOpen)
		DoThrow<PortClosed>(__LINE__, "Close() -- port is already closed");
}

SerialPort::BaudRate SerialPort::GetBaudRate() const
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "GetBaudRate()");
	return (SerialPort::BaudRate) cfgetispeed(&termSettings);
}

void SerialPort::SetBaudRate( const BaudRate baudRate )
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "SetBaudRate()");

	if (!ValidBaudRate(baudRate))
	{
		stringstream msg;
		msg << "SetBaudRate() -- invalid baud rate setting (" << baudRate << ") for port " << portName;

		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	termios newSettings = termSettings;

	if (AffectBaudRate(newSettings, baudRate))
		TCSetAttr(TCSANOW, termSettings, newSettings, __LINE__, "SetBaudRate()");
}

void SerialPort::SetBreak()
{
	{ Lock lock(*this); }
	ThrowIfNotOpen(__LINE__, "SetBreak()");

	int status = tcsendbreak(fd, 0);
	CheckAndThrow(status, __LINE__, errno, "SetBreak() -- tcsendbreak");
}

void SerialPort::SetBreak(const TimeSpan& breakLength)
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "SetBreak(TimeSpan)");

	const time_duration::tick_type equivalentMS = breakLength.total_milliseconds();
	const int units = (int) ceil(equivalentMS * breakTimeoutMultiplier);

	int status = tcsendbreak(fd, units);
	CheckAndThrow(status, __LINE__, errno, "SetBreak(TimeSpan) -- tcsendbreak");
}

SerialPort::CharacterSize SerialPort::GetCharSize() const
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "GetCharSize()");

	return (CharacterSize) (termSettings.c_iflag & CSIZE);
}

void SerialPort::SetCharSize(CharacterSize characterSize)
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "SetCharSize()");

	if (!ValidCharacterSize(characterSize))
	{
		stringstream msg;
		msg << "SetCharSize() -- invalid character size setting (" << characterSize << ") for port " << portName;

		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	termios newSettings = termSettings;

	if (AffectCharacterSize(newSettings, characterSize))
		TCSetAttr(TCSANOW, termSettings, newSettings, __LINE__, "SetCharSize()");
}

bool SerialPort::GetCts() const
{
	Lock lock(*this);
	bool result;

	if (IsPseudoTerminal())
		result = true;
	else
		result = ReadModemBits(__LINE__, "GetCts()", TIOCM_CTS);

	return result;
}

bool SerialPort::GetDsr() const
{
	Lock lock(*this);
	bool result;

	if (IsPseudoTerminal())
		result = true;
	else
		result = ReadModemBits(__LINE__, "GetDsr()", TIOCM_DSR);

	return result;
}

bool SerialPort::GetDcd() const
{
	Lock lock(*this);
	bool result;

	if (IsPseudoTerminal())
		result = true;
	else
		result = ReadModemBits(__LINE__, "GetDcd()", TIOCM_CAR);

	return result;
}

bool SerialPort::GetDtr() const
{
	Lock lock(*this);
	bool result;

	if (IsPseudoTerminal())
		result = true;
	else
		result = ReadModemBits(__LINE__, "GetDtr()", TIOCM_DTR);

	return result;
}

void SerialPort::SetDtr(bool set)
{
	Lock lock(*this);

	if (!IsPseudoTerminal())
		SetModemBits(__LINE__, "SetDtr()", TIOCM_DTR, set);
}

SerialPort::Parity SerialPort::GetParity() const
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "GetParity()");

	Parity result;

	if (termSettings.c_cflag & PARENB)
		result = (termSettings.c_cflag & PARODD) ? PARITY_ODD : PARITY_ODD;
	else
		result = PARITY_NONE;

	return result;
}

void SerialPort::SetParity(Parity parity)
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "SetParity()");

	if (!ValidParity(parity))
	{
		stringstream msg;
		msg << "SetParity() -- invalid parity setting (" << parity << ") for port " << portName;

		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	termios newSettings = termSettings;

	if (AffectParity(newSettings, parity))
		TCSetAttr(TCSANOW, termSettings, newSettings, __LINE__, "SetParity()");
}

const string& SerialPort::GetPortName() const
{
	Lock lock(*this);
	return portName;
}

bool SerialPort::GetRts() const
{
	Lock lock(*this);
	bool result;

	if (IsPseudoTerminal())
		result = true;
	else
		result = ReadModemBits(__LINE__, "GetRts()", TIOCM_RTS);

	return result;
}

void SerialPort::SetRts(bool set)
{
	Lock lock(*this);

	if (!IsPseudoTerminal())
		SetModemBits(__LINE__, "SetRts()", TIOCM_RTS, set);
}

SerialPort::StopBits SerialPort::GetStopBits() const
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "GetStopBits()");

	StopBits result = (termSettings.c_cflag & CSTOPB) ? STOP_BITS_2 : STOP_BITS_1;
	return result;
}

void SerialPort::SetStopBits(StopBits stopBits)
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "SetStopBits()");

	if (!ValidStopBits(stopBits))
	{
		stringstream msg;
		msg << "SetStopBits() -- invalid stop bits setting (" << stopBits << ") for port " << portName;

		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	termios newSettings = termSettings;

	if (AffectStopBits(newSettings, stopBits))
		TCSetAttr(TCSANOW, termSettings, newSettings, __LINE__, "SetStopBits()");

}

SerialPort::FlowControl SerialPort::GetFlowControl() const
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "GetFlowControl()");

	FlowControl result;

	if (termSettings.c_cflag & CRTSCTS)
		result = FLOW_CONTROL_HARD;
	else if (termSettings.c_iflag & (IXON | IXOFF))
		result = FLOW_CONTROL_SOFT;
	else
		result = FLOW_CONTROL_NONE;

	return result;
}

void SerialPort::SetFlowControl(FlowControl flowControl)
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "SetFlowControl()");

	if (!ValidFlowControl(flowControl))
	{
		stringstream msg;
		msg << "SetFlowControl() -- invalid flow control setting (" << flowControl << ") for port " << portName;

		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	termios newSettings = termSettings;

	if (AffectFlowControl(newSettings, flowControl))
		TCSetAttr(TCSANOW, termSettings, newSettings, __LINE__, "SetFlowControl()");
}

float SerialPort::GetBreakTimeoutMultiplier() const
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "GetBreakTimeoutMultiplier()");

	return breakTimeoutMultiplier;
}

void SerialPort::SetBreakTimeoutMultiplier(float newValue)
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "SetBreakTimeoutMultiplier()");

	if (newValue <= 0)
	{
		stringstream msg;
		msg << "SetBreakTimeoutMultiplier(" << newValue << ") for port " << portName << " -- break timeout multiplier must be a positive value";

		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	breakTimeoutMultiplier = newValue;
}

void SerialPort::FlushInput()
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "FlushInput()");

	receivedQueue.clear();

	int status = tcflush(fd, TCIFLUSH);
	CheckAndThrow(status, __LINE__, errno, "FlushInput() -- tcflush(TCIFLUSH)");
}

void SerialPort::FlushOutput()
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "FlushOutput()");

	int status = tcflush(fd, TCOFLUSH);
	CheckAndThrow(status, __LINE__, errno, "FlushOutput() -- tcflush(TCOFLUSH)");
}

void SerialPort::FlushAll()
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "FlushAll()");

	receivedQueue.clear();

	int status = tcflush(fd, TCIOFLUSH);
	CheckAndThrow(status, __LINE__, errno, "FlushAll() -- tcflush(TCIOFLUSH)");
}

bool SerialPort::ReadFixedCount(ByteArray& result,
		const ByteArray::size_type minBytes,
		const ByteArray::size_type maxBytes,
		const byte* terminator)
{
	{ Lock lock(*this); }
	result.clear();
	ThrowIfNotOpen(__LINE__, "ReadFixedCount()");

	const cc_t TIME = 0;
	ByteArray::size_type actualMin = min(minBytes, maxBytes);

	if (RestrictMaxValue<ByteArray::size_type,cc_t>(actualMin))
	{
		stringstream msg;
		msg << "ReadFixedCount: the minimum number of bytes (" << actualMin << ") is too large";
		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	const cc_t MIN = (cc_t) actualMin;

	// stop if nothing specified to be read
	if (MIN < 1)
		return true;

	// reserve space based on parameters coming in
	result.reserve(max(minBytes, maxBytes));

	// try to satisfy the request with data pending read first
	bool retVal = DoReadFromQueue(result, terminator);

	if (!retVal)
		retVal = DoRead(__LINE__, result, TIME, MIN, terminator, "ReadFixedCount()", false);

	return retVal;
}

bool SerialPort::ReadImmediate(ByteArray& result,
		const ByteArray::size_type maxBytes,
		const byte* terminator)
{
	{ Lock lock(*this); }
	result.clear();
	ThrowIfNotOpen(__LINE__, "ReadImmediate()");

	const cc_t TIME = 0;
	const cc_t MIN = 0;

	// stop if nothing specified to be read
	if (maxBytes < 1)
		return true;

	// reserve space based on parameters coming in
	result.reserve(maxBytes);

	// try to satisfy the request with data pending read first
	bool retVal = DoReadFromQueue(result, terminator);

	if (!retVal)
		retVal = DoRead(__LINE__, result, TIME, MIN, terminator, "ReadImmediate()", false);

	return retVal;
}

bool SerialPort::ReadWithIntervalTimer(ByteArray& result,
		const ByteArray::size_type maxBytes,
		const TimeSpan& timeout,
		const byte* terminator,
		const bool throwOnTimeout)
{
	{ Lock lock(*this); }
	result.clear();
	ThrowIfNotOpen(__LINE__, "ReadWithIntervalTimer()");

	// throw if timeout is invalid
	if ((timeout == TimeSpan_Zero) || (timeout == TimeSpan_Infinite))
		DoThrow<invalid_argument>(__LINE__, "ReadWithIntervalTimer: timeout cannot be zero or infinite");

	int timeoutDeciseconds = ToDeciseconds(timeout);

	if (RestrictMaxValue<int,cc_t>(timeoutDeciseconds))
	{
		stringstream msg;
		msg << "ReadWithIntervalTimer: timeout in deciseconds for timeout " << timeout << " is too large";
		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	const cc_t TIME = (cc_t) timeoutDeciseconds;

	// stop if nothing specified to be read
	if (maxBytes < 1)
		return true;

	// reserve space based on parameters coming in
	result.reserve(maxBytes);

	// try to satisfy the request with data pending read first
	bool retVal = DoReadFromQueue(result, terminator);

	// if not satisfied ...
	if (!retVal)
	{
		bool keepLooping;

		do
		{
			// I can't necessarily set MIN to the amount I want to read since it may be a byte type,
			// so I may need to issue more than one read; calculate the number of bytes left to read
			int amountLeft = result.capacity() - result.size();

			// restrict the amount left to the maximum value for cc_t and save it as MIN for this iteration
			RestrictMaxValue<int,cc_t>(amountLeft);
			const cc_t MIN = (cc_t) amountLeft;

			// do the read
			retVal = DoRead(__LINE__, result, TIME, MIN, terminator, "ReadWithIntervalTimer()", throwOnTimeout);

			// determine if there's more to be read and successful so far
			keepLooping = retVal && (result.size() < result.capacity());

			// continuing, so I already read something; I can't just read again because there is
			// no initial timeout; instead, use WaitForPortState to enforce the interval timeout
			// for the next byte, then issue an interval read again for the remainder of the
			// next interval
			if (retVal && (result.size() < result.capacity()))
			{
				bool readable = true;
				bool writeable = false;

				try
				{
					retVal = WaitForPortState(timeout, readable, writeable, throwOnTimeout);
				}
				catch(...)
				{
					ByteArray temp;

					temp.swap(result);
					Unread(temp);
					throw;
				}

				if (!readable || !retVal)
					keepLooping = retVal = false;
			}

		} while(keepLooping);
	}

	return retVal;
}

bool SerialPort::ReadWithTimeout(ByteArray& result,
		const ByteArray::size_type maxBytes,
		const TimeSpan& timeout,
		const byte* terminator,
		const bool throwOnTimeout)
{
	{ Lock lock(*this); }
	result.clear();
	ThrowIfNotOpen(__LINE__, "ReadWithTimeout()");

	// throw if timeout is invalid
	if ((timeout == TimeSpan_Zero) || (timeout == TimeSpan_Infinite))
			DoThrow<invalid_argument>(__LINE__, "ReadWithTimeout: timeout cannot be zero or infinite");

	int timeoutDeciseconds = ToDeciseconds(timeout);

	if (RestrictMaxValue<int,cc_t>(timeoutDeciseconds))
	{
		stringstream msg;
		msg << "ReadWithTimeout: timeout in deciseconds for timeout " << timeout << " is too large";
		DoThrow<invalid_argument>(__LINE__, msg.str());
	}

	const cc_t TIME = (cc_t) timeoutDeciseconds;
	const cc_t TIME0 = 0;
	const cc_t MIN = 0;

	// stop if nothing specified to be read
	if (maxBytes < 1)
		return true;

	// reserve space based on parameters coming in
	result.reserve(maxBytes);

	// try to satisfy the request with data pending read first
	bool retVal = DoReadFromQueue(result, terminator);

	if (!retVal)
	{
		// wait for up to the timeout for data (may only return 1 byte if successful)
		retVal = DoRead(__LINE__, result, TIME, MIN, terminator, "ReadWithTimeout()", throwOnTimeout);

		// if a byte was read and more bytes were desired and the termiantor wasn't found, then
		// do an instantaneous read and try to full out the block
		if (retVal && result.size() && (result.size() < result.capacity()) && (!terminator || (*terminator != result.back())))
			DoRead(__LINE__, result, TIME0, MIN, terminator, "ReadWithTimeout(2)", false);
	}

	return retVal;
}

bool SerialPort::ReadFixedCount(string& result,
		const string::size_type minBytes,
		const string::size_type maxBytes,
		const char* terminator)
{
	{ Lock lock(*this); }
	result.clear();
	ThrowIfNotOpen(__LINE__, "ReadFixedCount()");

	ByteArray byteResult;
	bool retVal = ReadFixedCount(byteResult, minBytes, maxBytes, (const byte*) terminator);

	result.reserve(byteResult.size());

	BOOST_FOREACH(byte b, byteResult)
		result.push_back((char) b);

	return retVal;
}

bool SerialPort::ReadImmediate(string& result,
		const string::size_type maxBytes,
		const char* terminator)
{
	{ Lock lock(*this); }
	result.clear();
	ThrowIfNotOpen(__LINE__, "ReadImmediate()");

	ByteArray byteResult;
	bool retVal = ReadImmediate(byteResult, maxBytes, (const byte*) terminator);

	result.reserve(byteResult.size());

	BOOST_FOREACH(byte b, byteResult)
		result.push_back((char) b);

	return retVal;
}

bool SerialPort::ReadWithIntervalTimer(string& result,
		const string::size_type maxBytes,
		const TimeSpan& timeout,
		const char* terminator,
		const bool throwOnTimeout)
{
	{ Lock lock(*this); }
	result.clear();
	ThrowIfNotOpen(__LINE__, "ReadWithIntervalTimer()");

	ByteArray byteResult;
	bool retVal = ReadWithIntervalTimer(byteResult, maxBytes, timeout, (const byte*) terminator, throwOnTimeout);

	result.reserve(byteResult.size());

	BOOST_FOREACH(byte b, byteResult)
		result.push_back((char) b);

	return retVal;
}

bool SerialPort::ReadWithTimeout(string& result,
		const string::size_type maxBytes,
		const TimeSpan& timeout,
		const char* terminator,
		const bool throwOnTimeout)
{
	{ Lock lock(*this); }
	result.clear();
	ThrowIfNotOpen(__LINE__, "ReadWithTimeout()");

	ByteArray byteResult;
	bool retVal = ReadWithTimeout(byteResult, maxBytes, timeout, (const byte*) terminator, throwOnTimeout);

	result.reserve(byteResult.size());

	BOOST_FOREACH(byte b, byteResult)
		result.push_back((char) b);

	return retVal;
}

void SerialPort::Unread(byte data)
{
	Lock lock(*this);
	receivedQueue.push_front(data);
}

void SerialPort::Unread(ByteArray& data, ByteArray::size_type offset, ByteArray::size_type count)
{
	Lock lock(*this);
	const bool useRemaining = (count == (ByteArray::size_type) -1);

	if (!count)
		return;

	else if (offset >= data.size())
	{
		if (useRemaining)
			return;
		else
			DoThrow<invalid_argument>(__LINE__, "Unread: offset argument does not start within the size of the array");
	}

	else if (useRemaining)
		count = data.size()-offset;

	else if ((count+offset) > data.size())
		DoThrow<invalid_argument>(__LINE__, "Unread: offset+count goes beyond the end of the array");

	ByteArray::size_type idx = data.size();

	while(count--)
		receivedQueue.push_front(data[--idx]);
}

bool SerialPort::WaitForPortState(const TimeSpan& timeout, bool& readable, bool& writeable, bool throwOnTimeout)
{
	static const string operationName = "WaitForPortState()";

	if (!readable && !writeable)
		DoThrow<logic_error>(__LINE__, "WaitForPortState: Either readable or writeable must be specified");

	{
		Lock lock(*this);
		ThrowIfNotOpen(__LINE__, operationName);

		if (readable && !!receivedQueue.size())
			return true;
	}

	fd_set readfds;
	fd_set* readArg;
	fd_set writefds;
	fd_set* writeArg;
	fd_set exceptfds;
	timeval tv;
	timeval* tvArg = NULL;

	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);

	if (readable)		{ FD_SET(fd, &readfds); readArg = &readfds; }
	else				readArg = NULL;

	if (writeable)		{ FD_SET(fd, &writefds); writeArg = &writefds; }
	else				writeArg = NULL;

	FD_SET(fd, &exceptfds);

	if (timeout == TimeSpan_Infinite)
		tvArg = NULL;

	else if (timeout.is_special())
		DoThrow<invalid_argument>(__LINE__, "WaitForPortState: timeout is an unsupported special value");

	else if (timeout == TimeSpan_Zero)
	{
		tv.tv_sec = tv.tv_usec = 0;
		tvArg = &tv;
	}

	else
	{
		TimeSpan fullSeconds(timeout.hours(), timeout.minutes(), timeout.seconds());
		TimeSpan fractionalPart = timeout - fullSeconds;

		tv.tv_sec = fullSeconds.total_seconds();
		tv.tv_usec = fractionalPart.total_milliseconds()*1000;
		tvArg = &tv;
	}

	const int status = select(fd+1, readArg, writeArg, &exceptfds, tvArg);
	const int errSave = errno;

	if ((status == -1) && pLogger)
		pLogger->Debug << "select returned " << status << " (errno=" << errSave << " '" << GetErrorDescription(errSave) << "')" << endl;

	if (status == -1)
		CheckAndThrow(status, __LINE__, errSave, operationName);

	const bool r = readable;

	readable = FD_ISSET(fd, &readfds);
	writeable = FD_ISSET(fd, &writefds);

	if ((status == 0) && throwOnTimeout)
	{
		if (r)
			DoThrow<ReadTimeout>(__LINE__, operationName);
		else
			DoThrow<WriteTimeout>(__LINE__, operationName);
	}
	else if (FD_ISSET(fd, &exceptfds))
		DoThrow<PortException>(__LINE__, operationName);

	return readable || writeable;
}

int SerialPort::Write(const ByteArray& data)
{
	{ Lock lock(*this); }
	ThrowIfNotOpen(__LINE__, "Write()");

	int status;
	int errSave;

	if (data.size())
	{
		status = write(fd, &data.at(0), data.size());
		errSave = errno;
	}
	else
		status = errSave = 0;

	CheckAndThrow(status, __LINE__, errSave, "Write()");
	return status;
}

int SerialPort::Write(const ByteArray& data,
		const TimeSpan& writeTimeout,
		bool throwOnTimeout)
{
	{ Lock lock(*this); }
	// TODO: implement timeout on write
	ThrowIfNotOpen(__LINE__, "Write()");
	return Write(data);
}

int SerialPort::Write(const std::string& data)
{
	{ Lock lock(*this); }
	ThrowIfNotOpen(__LINE__, "Write()");

	ByteArray temp;
	temp.reserve(data.length());

	BOOST_FOREACH(char c, data)
		temp.push_back((byte) c);

	return Write(temp);
}

int SerialPort::Write(const std::string& data,
		const TimeSpan& writeTimeout,
		bool throwOnTimeout)
{
	{ Lock lock(*this); }
	ThrowIfNotOpen(__LINE__, "Write()");

	ByteArray temp;
	temp.reserve(data.length());

	BOOST_FOREACH(char c, data)
		temp.push_back((byte) c);

	return Write(temp, writeTimeout, throwOnTimeout);
}

bool ISerialPort::ValidBaudRate(BaudRate br)
{
	bool valid;

	switch(br)
	{
	case BAUD_50:
	case BAUD_75:
	case BAUD_110:
	case BAUD_134:
	case BAUD_150:
	case BAUD_200:
	case BAUD_300:
	case BAUD_600:
	case BAUD_1200:
	case BAUD_1800:
	case BAUD_2400:
	case BAUD_4800:
	case BAUD_9600:
	case BAUD_19200:
	case BAUD_38400:
	case BAUD_57600:
	case BAUD_115200:
	case BAUD_230400:
	case BAUD_460800:

		valid = true;
		break;

	default:

		valid = false;
		break;
	}

	return valid;
}

bool ISerialPort::ValidCharacterSize(CharacterSize cs)
{
	bool valid;

	switch(cs)
	{
	case CHAR_SIZE_5:
	case CHAR_SIZE_6:
	case CHAR_SIZE_7:
	case CHAR_SIZE_8:

		valid = true;
		break;

	default:

		valid = false;
		break;
	}

	return valid;
}

bool ISerialPort::ValidFlowControl(FlowControl fc)
{
	bool valid;

	switch(fc)
	{
	case FLOW_CONTROL_HARD:
	case FLOW_CONTROL_SOFT:
	case FLOW_CONTROL_NONE:

		valid = true;
		break;

	default:

		valid = false;
		break;
	}

	return valid;
}

bool ISerialPort::ValidParity(Parity pty)
{
	bool valid;

	switch(pty)
	{
	case PARITY_EVEN:
	case PARITY_ODD:
	case PARITY_NONE:

		valid = true;
		break;

	default:

		valid = false;
		break;
	}

	return valid;
}

bool ISerialPort::ValidStopBits(StopBits sbits)
{
	bool valid;

	switch(sbits)
	{
	case STOP_BITS_1:
	case STOP_BITS_2:

		valid = true;
		break;

	default:

		valid = false;
		break;
	}

	return valid;
}

void SerialPort::lock() const
{
	if (mutexPtr)
		mutexPtr->lock();
	else
		DoThrow<runtime_error>(__LINE__, "Mutex has not been allocated");
}

bool SerialPort::try_lock() const
{
	if (!mutexPtr)
		DoThrow<runtime_error>(__LINE__, "Mutex has not been allocated");

	return mutexPtr->try_lock();
}

void SerialPort::unlock() const
{
	if (mutexPtr)
		mutexPtr->unlock();
	else
		DoThrow<runtime_error>(__LINE__, "Mutex has not been allocated");
}

int SerialPort::DoClosePort(int _fd)
{
	return close(_fd);
}

int SerialPort::DoOpenPort(const string& _portName)
{
	return open(_portName.c_str(), O_RDWR);
}

bool SerialPort::IsPseudoTerminal() const
{
	const string theName(GetTTYName());
	const bool result = boost::equals(theName, "/dev/ptmx") || boost::starts_with(theName, "/dev/pts/");

	return result;
}

string SerialPort::GetTTYName() const
{
	Lock lock(*this);
	ThrowIfNotOpen(__LINE__, "TTYName()");

	char result[TTY_NAME_MAX+1];

	memset(result, '\0', sizeof(result));

	const int status = ttyname_r(fd, result, sizeof(result));

	if (status);

	return result;
}

int SerialPort::GetFileDes() const
{
	return fd;
}

}
}
