//------------------------------------------------------------------------------
// <copyright file="LqrSMACSerialPort-inline.h" 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>
//   In-line functions and classes used with SerialPort
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#ifndef __LQRSMACSERIALPORT_INLINE_H__
#define __LQRSMACSERIALPORT_INLINE_H__

#include <iostream>
#include <sstream>
#include <sys/ioctl.h>
#include "LqrSMACGetErrorDescription.h"

namespace LiquidRobotics {
namespace SMACFramework {

template<class T>
void SerialPort::DoThrow(unsigned lineNum, const string& whatArg) const
{
	std::stringstream msg;
	msg << "SerialPort(" << lineNum << "): " << whatArg;

	throw T(msg.str());
}

template<class T>
void SerialPort::DoThrow(unsigned lineNum, int errNum, const string& whatArg) const
{
	std::stringstream msg;
	msg << "SerialPort(" << "(" << lineNum << "): " << whatArg;

	throw T(msg.str(), errNum);
}

inline void SerialPort::CheckAndThrow(int result, unsigned lineNum, int errNum, const string& operationType) const
{
	if (result == -1)
	{
		std::stringstream msg;
		msg << operationType << " failed for port " << portName << ": errno=" << errNum << " (" << GetErrorDescription(errNum) << ")";

		if (pLogger)
			pLogger->Debug << msg << std::endl;

		if (errNum == EINTR)
			DoThrow<SerialPort::SystemCallInterrupted>(lineNum, msg.str());
		else
			DoThrow<SerialPort::SystemError>(lineNum, errNum, msg.str());
	}
}

inline bool SerialPort::ReadModemBits(unsigned lineNum, const string& operationType, int mask) const
{
	ThrowIfNotOpen(lineNum, operationType);

	int flags;
	int status = ioctl(fd, TIOCMGET, &flags);
	int errSave = errno;

	if (status == -1)
	{
		std::stringstream msg;
		msg << operationType << " -- ioctl(TIOCMGET)";

		CheckAndThrow(status, lineNum, errSave, msg.str());
	}

	return !!(flags & mask);
}

inline void SerialPort::SetModemBits(unsigned lineNum, const string& operationType, unsigned mask, bool set)
{
	ThrowIfNotOpen(lineNum, operationType);

	int status = ioctl(fd, set ? TIOCMBIS : TIOCMBIC, &mask);
	int errSave = errno;

	if (status == -1)
	{
		std::stringstream msg;
		msg << operationType << " -- ioctl(" << (set ? "TIOCMBIS" : "TIOCBMIC") << ")";

		CheckAndThrow(status, lineNum, errSave, msg.str());
	}

}

inline void SerialPort::ThrowIfNotOpen(unsigned lineNum, const string& operationType) const
{
	if (!IsOpen())
	{
		std::stringstream msg;
		msg << operationType << " -- port is not open";
		DoThrow<PortClosed>(lineNum, msg.str());
	}
}

inline void SerialPort::TCSetAttr(int opts, termios& settingsState, const termios& newSettings, unsigned lineNum, const string& operationType)
{
	if (memcmp(&settingsState, &newSettings, sizeof(settingsState)))
	{
		int status = tcsetattr(fd, opts, &newSettings);
		int errSave = errno;

		if (status == -1)
		{
			std::stringstream msg;
			msg << operationType << " -- tcsetattr";

			CheckAndThrow(status, lineNum, errSave, msg.str());
		}

		status = tcgetattr(fd, &settingsState);
		errSave = errno;

		if (status == -1)
		{
			std::stringstream msg;
			msg << operationType << " -- tcgetattr read-back after tcsetattr";

			CheckAndThrow(status, lineNum, errSave, msg.str());
		}

		if (memcmp(&settingsState, &newSettings, sizeof(settingsState)))
		{
			std::stringstream msg;
			msg << operationType << " -- read-back of settings after tcsetattr did not confirm succesful write";

			DoThrow<SerialPort::SettingsReadbackFailure>(__LINE__, msg.str());
		}
	}
}

inline bool AffectBaudRate(termios& termSettings, const SerialPort::BaudRate baudRate)
{
	bool changed = (cfgetispeed(&termSettings) != (speed_t) baudRate) || (cfgetospeed(&termSettings) != (speed_t) baudRate);

	if (changed)
	{
		cfsetispeed(&termSettings, baudRate);
		cfsetospeed(&termSettings, baudRate);
	}

	return changed;
}

inline bool AffectCharacterSize(termios& termSettings, const SerialPort::CharacterSize charSize)
{
	bool changed = ((SerialPort::CharacterSize) (termSettings.c_cflag & CSIZE)) != charSize;

	if (changed)
	{
		termSettings.c_cflag &= ~CSIZE;
		termSettings.c_cflag |= charSize;
	}

	return changed;
}

inline bool AffectParity(termios& termSettings, const SerialPort::Parity parityType)
{
	termios save = termSettings;

	switch(parityType)
	{
	case SerialPort::PARITY_EVEN:

		termSettings.c_iflag &= ~IGNPAR;
		termSettings.c_iflag |= INPCK;
		termSettings.c_cflag &= ~PARODD;
		termSettings.c_cflag |= PARENB;
		break;

	case SerialPort::PARITY_ODD:

		termSettings.c_iflag &= ~IGNPAR;
		termSettings.c_iflag |= INPCK;
		termSettings.c_cflag |= PARENB | PARODD;
		break;

	case SerialPort::PARITY_NONE:

		termSettings.c_iflag &= ~INPCK;
		termSettings.c_iflag |= IGNPAR;
		termSettings.c_cflag &= ~PARENB;
		break;
	}

	return memcmp(&save, &termSettings, sizeof(save));
}

inline bool AffectStopBits(termios& termSettings, SerialPort::StopBits stopBits)
{
	bool changed = !!(termSettings.c_cflag & CSTOPB) != (stopBits == SerialPort::STOP_BITS_2);

	if (changed)
	{
		if (stopBits == SerialPort::STOP_BITS_2)
			termSettings.c_cflag |= CSTOPB;
		else
			termSettings.c_cflag &= ~CSTOPB;
	}

	return changed;
}

inline bool AffectFlowControl(termios& termSettings, SerialPort::FlowControl flowControl)
{
	termios save = termSettings;

	termSettings.c_cflag &= ~CRTSCTS;
	termSettings.c_iflag &= ~(IXON | IXOFF);

	if (flowControl == SerialPort::FLOW_CONTROL_HARD)
		termSettings.c_cflag |= CRTSCTS;
	else if (flowControl == SerialPort::FLOW_CONTROL_SOFT)
		termSettings.c_iflag |= IXON | IXOFF;

	return memcmp(&save, &termSettings, sizeof(save));
}

inline int ToDeciseconds(ISerialPort::TimeSpan timeSpan)
{
	return (timeSpan.total_milliseconds()+99)/100;
}

inline bool SerialPort::DoReadFromQueue(ByteArray& result, const byte* terminator)
{
	Lock lock(*this);
	bool foundTerminator = false;

	while (!foundTerminator && (result.size() < result.capacity()) && receivedQueue.size())
	{
		byte data = receivedQueue.front();
		result.push_back(data);
		receivedQueue.pop_front();
		foundTerminator = terminator && (*terminator == data);
	}

	return foundTerminator || (!terminator && (result.size() >= result.capacity()));
}

template<typename T, typename U>
bool RestrictMaxValue(T& value)
{
	if (sizeof(T) > sizeof(U))
	{
		const unsigned long n = ~((unsigned long) 0) >> 8*(sizeof(unsigned long)-sizeof(U));

		if (((unsigned long) value) > n)
		{
			value = (T) n;
			return true;
		}
	}

	return false;
}

inline bool SerialPort::DoRead(unsigned lineNum, ByteArray& result, cc_t timeValue, cc_t minRead, const byte* terminator,
							const string& operationType, bool throwOnTimeout)
{
	//stringstream terminater;
	//terminater << *terminator;
	//string displayedTerm = terminater.str();
	//cout << "-----------------TERMINATER=" << displayedTerm << endl;

	try
	{
		// determine how much to read and return immediately with no error if there's no space to read anything
		const ByteArray::size_type resultSize0 = result.size();
		const ByteArray::size_type toRead = result.capacity()-resultSize0;

		if (!toRead)
			return true;

		// change the port settings according to specs
		termios newSettings = termSettings;

		newSettings.c_cc[VTIME] = timeValue;
		newSettings.c_cc[VMIN] = minRead;
		TCSetAttr(TCSANOW, termSettings, newSettings, lineNum, operationType);

		result.resize(result.capacity());

		// read data and save the results
		const int status = read(fd, &result.at(resultSize0), toRead);
		const int errSave = errno;

		// resize the array back to what was actually read
		const ByteArray::size_type newSize = (status > 0) ? (status+resultSize0) : resultSize0;
		result.resize(newSize);

		// if anything was read ...
		if (status > 0)
		{
			// pare back the amount that was read if the terminator was found
			if (terminator && status)
			{
				Lock lock(*this);

				for (ByteArray::size_type idx = resultSize0; idx < newSize; ++idx)
					if  (result[idx] == *terminator)
					{
						for (ByteArray::size_type idx2 = newSize; idx2 > (idx+1); )
							receivedQueue.push_front(result[--idx2]);

						result.resize(idx+1);
						break;
					}

				//done = true;
				return true;
			}
		}

		// nothing was read here
		else if (status == 0)
			return false;

		// don't bother counting this as a failure in non-blocking mode and nothing to be read
		else if (errno == EAGAIN)
			return true;

		// otherwise, throw an exception
		else
		{
			std::stringstream msg;
			msg << operationType << " -- read()";
			CheckAndThrow(status, lineNum, errSave, msg.str());
		}

		if (timeValue && (((unsigned) status) < ((unsigned) minRead)))
		{
			if (throwOnTimeout)
				DoThrow<ReadTimeout>(lineNum, operationType);

			return false;
		}
		else
			return true;
	}
	catch(...)
	{
		Lock lock(*this);
		ByteArray temp;

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

}
}

#endif
