//------------------------------------------------------------------------------
// <copyright file="LqrSMACSerialPort.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>
//   Declaration of ISerialPort (abstract interface class to a serial port) and
//   SerialPort (concrete interface class to a serial port)
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#ifndef _LQRSMACSERIALPORT_H_
#define _LQRSMACSERIALPORT_H_

#include "LqrSMACByteArray.h"
#include "LqrSMACLogger.h"
#include "LqrSMACDeclareException.h"
#include <deque>
#include <string>
#include <stdexcept>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/thread/locks.hpp>
#include <boost/thread/recursive_mutex.hpp>

namespace LiquidRobotics {
namespace SMACFramework {

class SHARED_LIB_PUBLIC ISerialPort
{
public:

	// === Types ===

	typedef boost::shared_ptr<ISerialPort> Ptr;
	typedef ByteArray::byte byte;
	typedef std::string string;
	typedef boost::posix_time::time_duration TimeSpan;
	typedef boost::unique_lock<const ISerialPort> Lock;

	static const TimeSpan TimeSpan_Zero;
	static const TimeSpan TimeSpan_Infinite;

    enum BaudRate {
        BAUD_50      = B50,
        BAUD_75      = B75,
        BAUD_110     = B110,
        BAUD_134     = B134,
        BAUD_150     = B150,
        BAUD_200     = B200,
        BAUD_300     = B300,
        BAUD_600     = B600,
        BAUD_1200    = B1200,
        BAUD_1800    = B1800,
        BAUD_2400    = B2400,
        BAUD_4800    = B4800,
        BAUD_9600    = B9600,
        BAUD_19200   = B19200,
        BAUD_38400   = B38400,
        BAUD_57600   = B57600,
        BAUD_115200  = B115200,
        BAUD_230400  = B230400,
        BAUD_460800  = B460800,
        BAUD_DEFAULT = BAUD_57600
    } ;

    static string BaudRate2Str(BaudRate baudRate);
    static unsigned BaudRate2Num(BaudRate baudRate);
    static BaudRate NumToBaudRate(unsigned long num);

    enum CharacterSize {
        CHAR_SIZE_5  = CS5, //!< 5 bit characters.
        CHAR_SIZE_6  = CS6, //!< 6 bit characters.
        CHAR_SIZE_7  = CS7, //!< 7 bit characters.
        CHAR_SIZE_8  = CS8, //!< 8 bit characters.
        CHAR_SIZE_DEFAULT = CHAR_SIZE_8
    } ;

    enum StopBits {
        STOP_BITS_1,   //! 1 stop bit.
        STOP_BITS_2,   //! 2 stop bits.
        STOP_BITS_DEFAULT = STOP_BITS_1
    } ;

    enum Parity {
        PARITY_EVEN,     //!< Even parity.
        PARITY_ODD,      //!< Odd parity.
        PARITY_NONE,     //!< No parity i.e. parity checking disabled.
        PARITY_DEFAULT = PARITY_NONE
    } ;

    enum FlowControl {
        FLOW_CONTROL_HARD,
        FLOW_CONTROL_SOFT,
        FLOW_CONTROL_NONE,
        FLOW_CONTROL_DEFAULT = FLOW_CONTROL_NONE
    };

    // === Exceptions ===

    DECLARE_EXCEPTION(ConcurrentIORequested, std::logic_error);
    DECLARE_EXCEPTION(PortAlreadyOpen, std::logic_error);
    DECLARE_EXCEPTION(PortClosed, std::logic_error);
    DECLARE_EXCEPTION(PortException, std::runtime_error);
    DECLARE_EXCEPTION(ReadTimeout, std::runtime_error);
    DECLARE_EXCEPTION(SettingsReadbackFailure, std::runtime_error);
    DECLARE_EXCEPTION(WriteTimeout, std::runtime_error);

    class SHARED_LIB_PUBLIC SystemError : public std::runtime_error
    {
    public:
    	SystemError(const string& whatArg, int _errNum);
    	const int errNum;
    	inline string describe() const { return describe(errNum); }
    	static string describe(int errNum);
    };

    class SHARED_LIB_PUBLIC SystemCallInterrupted : public SystemError
    {
    public:
    	SystemCallInterrupted(const string& whatArg);
    };

    // === Construction/Destruction ===

    virtual ~ISerialPort();

    // === open/close ===

    virtual void Open(const BaudRate      baudRate    = BAUD_DEFAULT,
					  const CharacterSize charSize    = CHAR_SIZE_DEFAULT,
					  const Parity        parityType  = PARITY_DEFAULT,
					  const StopBits      stopBits    = STOP_BITS_DEFAULT,
					  const FlowControl   flowControl = FLOW_CONTROL_DEFAULT) = 0;

    virtual bool IsOpen() const = 0;
    virtual bool IsTTY() const = 0;

    virtual void Close(bool throwIfNotOpen = false) = 0;

    // === port runtime parameters and behavior  ===

    virtual BaudRate GetBaudRate() const = 0;
    virtual void SetBaudRate( const BaudRate baudRate ) = 0;

    virtual void SetBreak() = 0;
    virtual void SetBreak(const TimeSpan& breakLength) = 0;

    virtual CharacterSize GetCharSize() const = 0;
    virtual void SetCharSize(CharacterSize characterSize) = 0;

    virtual bool GetCts() const = 0;

    virtual bool GetDsr() const = 0;

    virtual bool GetDcd() const = 0;

    virtual bool GetDtr() const = 0;
    virtual void SetDtr(bool set = true) = 0;

    virtual Parity GetParity() const = 0;
    virtual void SetParity(Parity parity) = 0;

    virtual const string& GetPortName() const = 0;

    virtual string GetTTYName() const = 0;

    virtual bool GetRts() const = 0;
    virtual void SetRts(bool set = true) = 0;

    virtual StopBits GetStopBits() const = 0;
    virtual void SetStopBits(StopBits stopBits) = 0;

    virtual FlowControl GetFlowControl() const = 0;
    virtual void SetFlowControl(FlowControl flowControl) = 0;

    virtual float GetBreakTimeoutMultiplier() const = 0;
    virtual void SetBreakTimeoutMultiplier(float newValue) = 0;

    virtual void FlushInput() = 0;
    virtual void FlushOutput() = 0;
    virtual void FlushAll() = 0;

    virtual bool IsPseudoTerminal() const = 0;

    virtual bool ReadFixedCount(ByteArray& result,
    		const ByteArray::size_type minBytes,
    		const ByteArray::size_type maxBytes,
    		const byte* terminator = NULL) = 0;

    virtual bool ReadImmediate(ByteArray& result,
    		const ByteArray::size_type maxBytes,
    		const byte* terminator = NULL) = 0;

    virtual bool ReadWithIntervalTimer(ByteArray& result,
    		const ByteArray::size_type maxBytes,
    		const TimeSpan& timeout,
    		const byte* terminator = NULL,
    		const bool throwOnTimeout = false) = 0;

    virtual bool ReadWithTimeout(ByteArray& result,
    		const ByteArray::size_type maxBytes,
    		const TimeSpan& timeout,
    		const byte* terminator = NULL,
    		const bool throwOnTimeout = false) = 0;

    virtual bool ReadFixedCount(string& result,
    		const string::size_type minBytes,
    		const string::size_type maxBytes,
    		const char* terminator = NULL) = 0;

    virtual bool ReadImmediate(string& result,
    		const string::size_type maxBytes,
    		const char* terminator = NULL) = 0;

    virtual bool ReadWithIntervalTimer(string& result,
    		const string::size_type maxBytes,
    		const TimeSpan& timeout,
    		const char* terminator = NULL,
    		const bool throwOnTimeout = false) = 0;

    virtual bool ReadWithTimeout(string& result,
    		const string::size_type maxBytes,
    		const TimeSpan& timeout,
    		const char* terminator = NULL,
    		const bool throwOnTimeout = false) = 0;

    virtual void Unread(byte data) = 0;

    virtual void Unread(ByteArray& data, ByteArray::size_type offset = 0, ByteArray::size_type count = (ByteArray::size_type) -1) = 0;

    virtual bool WaitForPortState(const TimeSpan& timeout, bool& readable, bool& writeable, bool throwOnTimeout = false) = 0;

    virtual int Write(const ByteArray& data) = 0;

    virtual int Write(const ByteArray& data,
    		const TimeSpan& writeTimeout,
    		bool throwOnTimeout = false) = 0;

    virtual int Write(const string& data) = 0;

    virtual int Write(const string& data,
    		const TimeSpan& writeTimeout,
    		bool throwOnTimeout = false) = 0;

    template<class InputIterator>
    int Write(InputIterator start, InputIterator end)
    {
    	const ByteArray::size_type len = end-start;
    	ByteArray temp;

    	temp.reserve(len);

    	while(start < end)
    		temp.push_back(*start++);

    	return Write(temp);
    }

    template<class InputIterator>
    int Write(InputIterator start, InputIterator end,
    		const TimeSpan& writeTimeout,
    		bool throwOnTimeout = false)
    {
    	const ByteArray::size_type len = end-start;
    	ByteArray temp;

    	temp.reserve(len);

    	while(start < end)
    		temp.push_back(*start++);

    	return Write(temp, writeTimeout, throwOnTimeout);
    }

    static bool ValidBaudRate(BaudRate br);
    static bool ValidCharacterSize(CharacterSize cs);
    static bool ValidFlowControl(FlowControl fc);
    static bool ValidParity(Parity pty);
    static bool ValidStopBits(StopBits sbits);

	// Lockable concept
	virtual void lock() const = 0;
	virtual bool try_lock() const = 0;
	virtual void unlock() const = 0;
};

class SerialPortSettings;

class SHARED_LIB_PUBLIC SerialPort : public ISerialPort, public boost::noncopyable
{
public:

	SerialPort(const string& portName);
	SerialPort(const string& portName, Logger& _logger);

	virtual ~SerialPort();

	int GetFileDes() const;

	virtual void Open(const SerialPortSettings& settings);

    virtual void Open(const BaudRate      baudRate    = BAUD_DEFAULT,
					  const CharacterSize charSize    = CHAR_SIZE_DEFAULT,
					  const Parity        parityType  = PARITY_DEFAULT,
					  const StopBits      stopBits    = STOP_BITS_DEFAULT,
					  const FlowControl   flowControl = FLOW_CONTROL_DEFAULT);

    virtual bool IsOpen() const;

    virtual void Close(bool throwIfNotOpen = false);

    virtual BaudRate GetBaudRate() const;
    virtual void SetBaudRate( const BaudRate baudRate );

    // Sends break for the standard amount of time (parameter to tcsendbreak is 0, or at least 250 milliseconds)
    virtual void SetBreak();

    // Sends break for the given time span, counting on the Break Timeout Multiplier to convert the number of
    // milliseconds implied by breakLength to the correct corresponding value; tcsendbreak indicates that a
    // non-zero value creates a delay of an implementation-dependent amount of time.
    virtual void SetBreak(const TimeSpan& breakLength);

    virtual CharacterSize GetCharSize() const;
    virtual void SetCharSize(CharacterSize characterSize);

    virtual bool GetCts() const;

    virtual bool GetDsr() const;

    virtual bool GetDcd() const;

    virtual bool GetDtr() const;
    virtual void SetDtr(bool set = true);

    virtual Parity GetParity() const;
    virtual void SetParity(Parity parity);

    virtual const string& GetPortName() const;

    virtual bool GetRts() const;
    virtual void SetRts(bool set = true);

    virtual StopBits GetStopBits() const;
    virtual void SetStopBits(StopBits stopBits);


    virtual string GetTTYName() const;

    virtual FlowControl GetFlowControl() const;
    virtual void SetFlowControl(FlowControl flowControl);

    virtual float GetBreakTimeoutMultiplier() const;
    virtual void SetBreakTimeoutMultiplier(float newValue);

    virtual void FlushInput();
    virtual void FlushOutput();
    virtual void FlushAll();

    virtual bool IsPseudoTerminal() const;
    virtual bool IsTTY() const;

    virtual bool ReadFixedCount(ByteArray& result,
    		const ByteArray::size_type minBytes,
    		const ByteArray::size_type maxBytes,
    		const byte* terminator = NULL);

    virtual bool ReadImmediate(ByteArray& result,
    		const ByteArray::size_type maxBytes,
    		const byte* terminator = NULL);

    virtual bool ReadWithIntervalTimer(ByteArray& result,
    		const ByteArray::size_type maxBytes,
    		const TimeSpan& timeout,
    		const byte* terminator = NULL,
    		const bool throwOnTimeout = false);

    virtual bool ReadWithTimeout(ByteArray& result,
    		const ByteArray::size_type maxBytes,
    		const TimeSpan& timeout,
    		const byte* terminator = NULL,
    		const bool throwOnTimeout = false);

    virtual bool ReadFixedCount(string& result,
    		const string::size_type minBytes,
    		const string::size_type maxBytes,
    		const char* terminator = NULL);

    virtual bool ReadImmediate(string& result,
    		const string::size_type maxBytes,
    		const char* terminator = NULL);

    virtual bool ReadWithIntervalTimer(string& result,
    		const string::size_type maxBytes,
    		const TimeSpan& timeout,
    		const char* terminator = NULL,
    		const bool throwOnTimeout = false);

    virtual bool ReadWithTimeout(string& result,
    		const string::size_type maxBytes,
    		const TimeSpan& timeout,
    		const char* terminator = NULL,
    		const bool throwOnTimeout = false);

    virtual void Unread(byte data);

    virtual void Unread(ByteArray& data, ByteArray::size_type offset = 0, ByteArray::size_type count = (ByteArray::size_type) -1);

    virtual bool WaitForPortState(const TimeSpan& timeout, bool& readable, bool& writeable, bool throwOnTimeout = false);

    virtual int Write(const ByteArray& data);

    virtual int Write(const ByteArray& data,
    		const TimeSpan& writeTimeout,
    		bool throwOnTimeout = false);

    virtual int Write(const string& data);

    virtual int Write(const string& data,
    		const TimeSpan& writeTimeout,
    		bool throwOnTimeout = false);

	// Lockable concept
    virtual void lock() const;
    virtual bool try_lock() const;
    virtual void unlock() const;

private:

	typedef std::deque<byte> QueueType;
	typedef boost::recursive_mutex Mutex;

	template<class T>
	void DoThrow(unsigned lineNum, const string& whatArg) const;

	template<class T>
	void DoThrow(unsigned lineNum, int errNum, const string& whatArg) const;

	inline void CheckAndThrow(int result, unsigned lineNum, int errNum, const string& operationType) const;
protected:
	virtual int DoClosePort(int _fd);
	virtual int DoOpenPort(const string& _portName);
private:
	inline bool DoRead(unsigned lineNum, ByteArray& result, cc_t timeValue, cc_t minRead, const byte* terminator, const string& operationType, bool throwOnTimeout);
	inline bool DoReadFromQueue(ByteArray& result, const byte* terminator);
	inline bool ReadModemBits(unsigned lineNum, const string& operationType, int mask) const;
	inline void SetModemBits(unsigned lineNum, const string& operationType, unsigned mask, bool set);
	inline void TCSetAttr(int opts, termios& settingsState, const termios& newSettings, unsigned lineNum, const string& operationType);
	inline void ThrowIfNotOpen(unsigned lineNum, const string& operationType) const;

	Mutex* mutexPtr;					// synchronization mutex
protected:
	int fd;								// file descriptor for the open serial port
private:
	string portName;					// name of the port to open
	termios termSettings;				// settings from the port
	QueueType receivedQueue;			// queue of bytes received (left over from previous operations)
protected:
	Logger* pLogger; 					// logger to be used
private:
	float breakTimeoutMultiplier;		// conversion from milliseconds to the units expected by tcsendbreak()
};

}
}

#endif /* SERIALPORT_H_ */
