//------------------------------------------------------------------------------
// <copyright file="LqrSMACSerialPortSettings.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>
// </summary>
//
// <owner></owner>
//------------------------------------------------------------------------------


#include <sstream>
#include <stdexcept>
#include <utility>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
#include "LqrSMACSerialPortSettings.h"

using namespace std;

namespace LiquidRobotics {
namespace SMACFramework {

SerialPortSettings::SerialPortSettings()
: baudRate(SerialPort::BAUD_9600)
, parity(SerialPort::PARITY_NONE)
, characterSize(SerialPort::CHAR_SIZE_8)
, stopBits(SerialPort::STOP_BITS_1)
, flowControl(SerialPort::FLOW_CONTROL_NONE)
, rts(true)
, dtr(true)
, breakTimeoutMultiplier(1.0f)
{
}

SerialPortSettings::SerialPortSettings(const char* _portName, const ISerialPort& _serialPort)
: portName(_portName)
, baudRate(_serialPort.GetBaudRate())
, parity(_serialPort.GetParity())
, characterSize(_serialPort.GetCharSize())
, stopBits(_serialPort.GetStopBits())
, flowControl(_serialPort.GetFlowControl())
, rts(_serialPort.GetRts())
, dtr(_serialPort.GetDtr())
, breakTimeoutMultiplier(_serialPort.GetBreakTimeoutMultiplier())
{
}

SerialPortSettings::SerialPortSettings(const char* _portName, BaudRate _baudRate, Parity _parity, CharacterSize _characterSize, StopBits _stopBits,
											FlowControl _flowControl, bool _rts, bool _dtr, float _breakTimeoutMultiplier)
: portName(_portName)
, baudRate(_baudRate)
, parity(_parity)
, characterSize(_characterSize)
, stopBits(_stopBits)
, flowControl(_flowControl)
, rts(_rts)
, dtr(_dtr)
, breakTimeoutMultiplier(_breakTimeoutMultiplier)
{
	if (breakTimeoutMultiplier <= 0)
		throw invalid_argument("Break timeout multiplier must be a positive value");
}

SerialPortSettings::SerialPortSettings(const SerialPortSettings& right)
: portName(right.portName)
, baudRate(right.baudRate)
, parity(right.parity)
, characterSize(right.characterSize)
, stopBits(right.stopBits)
, flowControl(right.flowControl)
, rts(right.rts)
, dtr(right.dtr)
, breakTimeoutMultiplier(right.breakTimeoutMultiplier)
{
}


SerialPortSettings::~SerialPortSettings()
{
}

SerialPortSettings& SerialPortSettings::operator=(const SerialPortSettings& right)
{
	if (this != &right)
	{
		portName = right.portName;
		baudRate = right.baudRate;
		parity = right.parity;
		characterSize = right.characterSize;
		stopBits = right.stopBits;
		flowControl = right.flowControl;
		rts = right.rts;
		dtr = right.dtr;
		breakTimeoutMultiplier = right.breakTimeoutMultiplier;
	}

	return *this;
}

bool SerialPortSettings::operator==(const SerialPortSettings& right) const
{
	return !portName.compare(right.portName)
	    && (baudRate == right.baudRate)
	    && (parity == right.parity)
	    && (characterSize == right.characterSize)
	    && (stopBits == right.stopBits)
	    && (flowControl == right.flowControl)
	    && (rts == right.rts)
	    && (dtr == right.dtr)
	    && (breakTimeoutMultiplier == right.breakTimeoutMultiplier);
}

SerialPortSettings::SerialPortPtr SerialPortSettings::CreatePort() const
{
	SerialPortPtr sp;
	SerialPort* pPort = new SerialPort(portName.c_str());

	sp.reset(pPort);
	pPort->Open(*this);
	return sp;
}

SerialPortSettings::SerialPortPtr SerialPortSettings::CreatePort(Logger& logger) const
{
	SerialPortPtr sp;
	SerialPort* pPort = new SerialPort(portName.c_str(), logger);

	sp.reset(pPort);
	pPort->Open(*this);
	return sp;
}

SerialPortSettings::string SerialPortSettings::ToString() const
{
	stringstream sstm;

	sstm << *this;
	return sstm.str();
}

typedef pair<string, SerialPort::BaudRate> BaudRateTableType;

static const BaudRateTableType BaudRateTable[] =
{
	BaudRateTableType("50",     SerialPort::BAUD_50),
	BaudRateTableType("75",     SerialPort::BAUD_75),
	BaudRateTableType("110",    SerialPort::BAUD_110),
	BaudRateTableType("134",    SerialPort::BAUD_134),
	BaudRateTableType("150",    SerialPort::BAUD_150),
	BaudRateTableType("200",    SerialPort::BAUD_200),
	BaudRateTableType("300",    SerialPort::BAUD_300),
	BaudRateTableType("600",    SerialPort::BAUD_600),
	BaudRateTableType("1200",   SerialPort::BAUD_1200),
	BaudRateTableType("1800",   SerialPort::BAUD_1800),
	BaudRateTableType("2400",   SerialPort::BAUD_2400),
	BaudRateTableType("4800",   SerialPort::BAUD_4800),
	BaudRateTableType("9600",   SerialPort::BAUD_9600),
	BaudRateTableType("19200",  SerialPort::BAUD_19200),
	BaudRateTableType("38400",  SerialPort::BAUD_38400),
	BaudRateTableType("57600",  SerialPort::BAUD_57600),
	BaudRateTableType("115200", SerialPort::BAUD_115200),
	BaudRateTableType("230400", SerialPort::BAUD_230400),
	BaudRateTableType("460800", SerialPort::BAUD_460800)
};

SerialPortSettings::string SerialPortSettings::BaudRate_ToString(BaudRate baudRate)
{
	BOOST_FOREACH(const BaudRateTableType& tableEntry, BaudRateTable)
		if (baudRate == tableEntry.second)
			return string(tableEntry.first);

	stringstream sstm;

	sstm << baudRate << "?";
	return sstm.str();
}

SerialPortSettings::BaudRate SerialPortSettings::BaudRate_FromString(const string& str)
{
	BOOST_FOREACH(const BaudRateTableType& tableEntry, BaudRateTable)
		if (!str.compare(tableEntry.first))
			return tableEntry.second;

	throw invalid_argument("str");
}



typedef pair<string, SerialPort::CharacterSize> CharacterSizeTableType;

static const CharacterSizeTableType CharacterSizeTable[] =
{
	CharacterSizeTableType("5", SerialPort::CHAR_SIZE_5),
	CharacterSizeTableType("6", SerialPort::CHAR_SIZE_6),
	CharacterSizeTableType("7", SerialPort::CHAR_SIZE_7),
	CharacterSizeTableType("8", SerialPort::CHAR_SIZE_8)
};

SerialPortSettings::string SerialPortSettings::CharacterSize_ToString(CharacterSize characterSize)
{
	BOOST_FOREACH(const CharacterSizeTableType& tableEntry, CharacterSizeTable)
		if (characterSize == tableEntry.second)
			return string(tableEntry.first);

	stringstream sstm;

	sstm << characterSize << "?";
	return sstm.str();
}

SerialPortSettings::CharacterSize SerialPortSettings::CharacterSize_FromString(const string& str)
{
	BOOST_FOREACH(const CharacterSizeTableType& tableEntry, CharacterSizeTable)
		if (!str.compare(tableEntry.first))
			return tableEntry.second;

	throw invalid_argument("str");
}

typedef pair<string, SerialPort::FlowControl> FlowControlTableType;

static const FlowControlTableType FlowControlTable[] =
{
	FlowControlTableType("HARDWARE", SerialPort::FLOW_CONTROL_HARD),
	FlowControlTableType("XON/XOFF", SerialPort::FLOW_CONTROL_SOFT),
	FlowControlTableType("NONE",     SerialPort::FLOW_CONTROL_NONE)
};

SerialPortSettings::string SerialPortSettings::FlowControl_ToString(FlowControl flowControl)
{
	BOOST_FOREACH(const FlowControlTableType& tableEntry, FlowControlTable)
		if (flowControl == tableEntry.second)
			return string(tableEntry.first);

	stringstream sstm;

	sstm << flowControl << "?";
	return sstm.str();
}

SerialPortSettings::FlowControl SerialPortSettings::FlowControl_FromString(const string& str)
{
	BOOST_FOREACH(const FlowControlTableType& tableEntry, FlowControlTable)
		if (boost::iequals(str, tableEntry.first))
			return tableEntry.second;

	throw invalid_argument("str");
}

typedef pair<string, SerialPort::Parity> ParityTableType;

static const ParityTableType ParityTable[] =
{
	ParityTableType("EVEN", SerialPort::PARITY_EVEN),
	ParityTableType("ODD",  SerialPort::PARITY_ODD),
	ParityTableType("NONE", SerialPort::PARITY_NONE)
};

SerialPortSettings::string SerialPortSettings::Parity_ToString(Parity parity)
{
	BOOST_FOREACH(const ParityTableType& tableEntry, ParityTable)
		if (parity == tableEntry.second)
			return string(tableEntry.first);

	stringstream sstm;

	sstm << parity << "?";
	return sstm.str();
}

SerialPortSettings::Parity SerialPortSettings::SerialPortSettings::Parity_FromString(const string& str)
{
	BOOST_FOREACH(const ParityTableType& tableEntry, ParityTable)
		if (boost::iequals(str, tableEntry.first))
			return tableEntry.second;

	throw invalid_argument("str");
}

typedef pair<string, SerialPort::StopBits> StopBitsTableType;

static const StopBitsTableType StopBitsTable[] =
{
	StopBitsTableType("1", SerialPort::STOP_BITS_1),
	StopBitsTableType("2", SerialPort::STOP_BITS_2)
};

SerialPortSettings::string SerialPortSettings::StopBits_ToString(StopBits stopBits)
{
	BOOST_FOREACH(const StopBitsTableType& tableEntry, StopBitsTable)
		if (stopBits == tableEntry.second)
			return string(tableEntry.first);

	stringstream sstm;

	sstm << stopBits << "?";
	return sstm.str();
}

SerialPortSettings::StopBits SerialPortSettings::SerialPortSettings::StopBits_FromString(const string& str)
{
	BOOST_FOREACH(const StopBitsTableType& tableEntry, StopBitsTable)
		if (!str.compare(tableEntry.first))
			return tableEntry.second;

	throw invalid_argument("str");
}

SerialPortSettings::string SerialPortSettings::GetPortName() const
{
	return portName;
}

void SerialPortSettings::SetPortName(const char* newValue)
{
	if (newValue == NULL)	throw invalid_argument("portName");

	portName = newValue;
}

SerialPortSettings::BaudRate SerialPortSettings::GetBaudRate() const
{
	return baudRate;
}

void SerialPortSettings::SetBaudRate(BaudRate newValue)
{
	bool found = false;

	BOOST_FOREACH(const BaudRateTableType& tableEntry, BaudRateTable)
		if (newValue == tableEntry.second)
		{
			found = true;
			break;
		}

	if (!found)
		throw invalid_argument("baudRate");

	baudRate = newValue;
}

SerialPortSettings::Parity SerialPortSettings::GetParity() const
{
	return parity;
}

void SerialPortSettings::SetParity(Parity newValue)
{
	bool found = false;

	BOOST_FOREACH(const ParityTableType& tableEntry, ParityTable)
		if (newValue == tableEntry.second)
		{
			found = true;
			break;
		}

	if (!found)
		throw invalid_argument("parity");

	parity = newValue;
}

SerialPortSettings::CharacterSize SerialPortSettings::GetCharacterSize() const
{
	return characterSize;
}

void SerialPortSettings::SetCharacterSize(CharacterSize newValue)
{
	bool found = false;

	BOOST_FOREACH(const CharacterSizeTableType& tableEntry, CharacterSizeTable)
		if (newValue == tableEntry.second)
		{
			found = true;
			break;
		}

	if (!found)
		throw invalid_argument("characterSize");

	characterSize = newValue;
}

SerialPortSettings::StopBits SerialPortSettings::GetStopBits() const
{
	return stopBits;
}

void SerialPortSettings::SetStopBits(StopBits newValue)
{
	bool found = false;

	BOOST_FOREACH(const StopBitsTableType& tableEntry, StopBitsTable)
		if (newValue == tableEntry.second)
		{
			found = true;
			break;
		}

	if (!found)
		throw invalid_argument("stopBits");

	stopBits = newValue;
}

SerialPortSettings::FlowControl SerialPortSettings::GetFlowControl() const
{
	return flowControl;
}

void SerialPortSettings::SetFlowControl(FlowControl newValue)
{
	bool found = false;

	BOOST_FOREACH(const FlowControlTableType& tableEntry, FlowControlTable)
		if (newValue == tableEntry.second)
		{
			found = true;
			break;
		}

	if (!found)
		throw invalid_argument("flowControl");

	flowControl = newValue;
}

bool SerialPortSettings::GetRts() const
{
	return rts;
}

void SerialPortSettings::SetRts(bool newValue)
{
	rts = newValue;
}

bool SerialPortSettings::GetDtr() const
{
	return dtr;
}

void SerialPortSettings::SetDtr(bool newValue)
{
	dtr = newValue;
}

float SerialPortSettings::GetBreakTimeoutMultiplier() const
{
	return breakTimeoutMultiplier;
}

void SerialPortSettings::SetBreakTimeoutMultiplier(float newValue)
{
	if (newValue <= 0)
		throw invalid_argument("Break timeout multiplier must be a positive value");

	breakTimeoutMultiplier = newValue;
}

std::ostream& operator<<(std::ostream& out, const SerialPortSettings& settings)
{
	out << "PortName=" << settings.GetPortName()
	    << " BaudRate=" << SerialPortSettings::BaudRate_ToString(settings.GetBaudRate())
		<< " Parity=" << SerialPortSettings::Parity_ToString(settings.GetParity())
		<< " CharacterSize=" << SerialPortSettings::CharacterSize_ToString(settings.GetCharacterSize())
		<< " StopBits=" << SerialPortSettings::StopBits_ToString(settings.GetStopBits())
		<< " FlowControl=" << SerialPortSettings::FlowControl_ToString(settings.GetFlowControl())
		<< " Rts=" << settings.GetRts()
		<< " Dtr=" << settings.GetDtr()
	    << " BreakTimeoutMultiplier=" << settings.GetBreakTimeoutMultiplier();

	return out;
}

void SerialPortSettings::CheckValuesAfterLoading()
{
	if (breakTimeoutMultiplier <= 0)
	{
		stringstream msg;
		msg << "SerialPortSettings member BreakTimeoutMultiplier is " << breakTimeoutMultiplier << " after deserializing, but the value must be positive";
		throw runtime_error(msg.str());
	}
}

}
}
