/*
 * Copyright (C) 2015 by Multi-Tech Systems
 *
 * This file is part of libmts-io.
 *
 * libmts-io is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * libmts-io is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libmts-io.  If not, see .
 *
 */
/*! 
 \file MTS_IO_SerialConnection.cpp
 \brief A brief description 
 \date Jan 15, 2013
 \author sgodinez
 A more elaborate description
*/
#include 
#include 
#include 
#include 
#ifdef WIN32
#else
#include    /* File control definitions */
#include    /* Error number definitions */
#include  /* POSIX terminal control definitions */
#include   /* String function definitions */
#endif
using namespace MTS::IO;
SerialConnection::Builder::Builder(const std::string& sPortName)
: m_sPortName(sPortName)
, m_iBaudRate(9600)
, m_eType(RS232)
, m_eParity(OFF)
, m_eDataBits(B8)
, m_eStopBits(B1)
, m_eFlowControl(NONE)
, m_bBuilt(false)
{
}
SerialConnection::Builder& SerialConnection::Builder::type(const ETYPE& eType) {
    m_eType = eType;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::rs232() {
    m_eType = RS232;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::rs422() {
    m_eType = RS422;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::rs485() {
    m_eType = RS485;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::parity(const EPARITY& eParity) {
    m_eParity = eParity;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::parityOff() {
    m_eParity = OFF;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::parityOdd() {
    m_eParity = ODD;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::parityEven() {
    m_eParity = EVEN;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::dataBits(const EDATABITS& eDataBits) {
    m_eDataBits = eDataBits;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::fiveDataBits() {
    m_eDataBits = B5;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::sixDataBits() {
    m_eDataBits = B6;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::sevenDataBits() {
    m_eDataBits = B7;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::eightDataBits() {
    m_eDataBits = B8;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::stopBits(const ESTOPBITS& eStopBits) {
    m_eStopBits = eStopBits;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::oneStopBit() {
    m_eStopBits = B1;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::twoStopBits() {
    m_eStopBits = B2;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::baudRate(uint32_t iBaud) {
    m_iBaudRate = iBaud;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::useLockFile(bool bEnabled) {
    m_bLockFile = bEnabled;
    return *this;
}
SerialConnection::Builder& SerialConnection::Builder::build() {
    m_bBuilt = true;
    return *this;
}
const std::string& SerialConnection::Builder::getPortName() const {
    return m_sPortName;
}
const uint32_t& SerialConnection::Builder::getBaudRate() const {
    return m_iBaudRate;
}
SerialConnection::ETYPE SerialConnection::Builder::getType() const {
    return m_eType;
}
SerialConnection::EPARITY SerialConnection::Builder::getParity() const {
    return m_eParity;
}
SerialConnection::EDATABITS SerialConnection::Builder::getDataBits() const {
    return m_eDataBits;
}
SerialConnection::ESTOPBITS SerialConnection::Builder::getStopBits() const {
    return m_eStopBits;
}
bool SerialConnection::Builder::isUsingLockFile() const {
    return m_bLockFile;
}
std::string SerialConnection::Builder::getTypeAsString() const {
    std::string sType;
    switch(m_eType) {
        case RS232:
            sType = "RS232";
            break;
        case RS422:
            sType = "RS422";
            break;
        case RS485:
            sType = "RS485";
            break;
        default:
            break;
    }
    return sType;
}
SerialConnection::Builder& SerialConnection::Builder::flowControl(const SerialConnection::FLOW_CONTROL& eFlowControl) {
	m_eFlowControl = eFlowControl;
	return *this;
}
SerialConnection::FLOW_CONTROL SerialConnection::Builder::getFlowControl() const {
	return m_eFlowControl;
}
bool SerialConnection::Builder::isBuilt() const {
    return m_bBuilt;
}
SerialConnection::SerialConnection(const Builder& oBuilder)
: Connection(oBuilder.getTypeAsString())
, m_sPortName(oBuilder.getPortName())
, m_iBaudRate(oBuilder.getBaudRate())
, m_eType(oBuilder.getType())
, m_eParity(oBuilder.getParity())
, m_eDataBits(oBuilder.getDataBits())
, m_eStopBits(oBuilder.getStopBits())
, m_eFlowControl(oBuilder.getFlowControl())
{
    if(!oBuilder.isBuilt()) {
        //Throw Exception?
    }
#ifdef WIN32
    m_iHandle = INVALID_HANDLE_VALUE;
#else
    m_iHandle = -1;
#endif
    m_apHandleLock.reset(new Lock());
    if(oBuilder.isUsingLockFile()) {
        std::vector vParts = MTS::Text::split(m_sPortName, "/");
        m_apLockFile.reset(new LockFile("/var/lock/LCK.." + vParts[vParts.size()-1]));
    }
}
SerialConnection::~SerialConnection() {
    doClose();
    m_apHandleLock.reset();
}
const std::string& SerialConnection::getPortName() const {
    return m_sPortName;
}
uint32_t SerialConnection::getBaudRate() const {
    return m_iBaudRate;
}
int SerialConnection::getFileDescriptor() {
    m_apHandleLock->lock();
    int h = m_iHandle;
    m_apHandleLock->unlock();
    return h;
}
const char* SerialConnection::humanSpeed(speed_t speed) {
  const char *hspeed;
  switch (speed) {
    case B0:       hspeed = "B0";
                   break;
    case B50:      hspeed = "B50";
                   break;
    case B75:      hspeed = "B75";
                   break;
    case B110:     hspeed = "B110";
                   break;
    case B134:     hspeed = "B134";
                   break;
    case B150:     hspeed = "B150";
                   break;
    case B200:     hspeed = "B200";
                   break;
    case B300:     hspeed = "B300";
                   break;
    case B600:     hspeed = "B600";
                   break;
    case B1200:    hspeed = "B1200";
                   break;
    case B1800:    hspeed = "B1800";
                   break;
    case B2400:    hspeed = "B2400";
                   break;
    case B4800:    hspeed = "B4800";
                   break;
    case B9600:    hspeed = "B9600";
                   break;
    case B19200:   hspeed = "B19200";
                   break;
    case B57600:   hspeed = "B57600";
                   break;
    case B115200:  hspeed = "B115200";
                   break;
    default:       hspeed = "unknown";
  }
  return hspeed;
}
void SerialConnection::printPortSetting(const termios *options){
    printDebug("SERIAL| port settings:");
    printDebug("SERIAL| in speed:%s out speed:%s",
               humanSpeed(cfgetispeed(options)),
               humanSpeed(cfgetospeed(options)));
    int data_length=0;
    if ((options->c_cflag&CSIZE) == CS5) {
        data_length = 5;
    }
    if ((options->c_cflag&CSIZE) == CS6) {
        data_length = 6;
    }
    if ((options->c_cflag&CSIZE) == CS7) {
        data_length = 7;
    }
    if ((options->c_cflag&CSIZE) == CS8) {
        data_length = 8;
    }
    printDebug("SERIAL| data:%d stop bits:%d", data_length,options->c_cflag&CSTOPB?2:1);
    if (!(options->c_cflag&PARENB)) {
        printDebug("SERIAL| parity: NONE");
    } else if (options->c_cflag&PARODD) {
        printDebug("SERIAL| parity: ODD");
    } else {
        printDebug("SERIAL| parity: EVEN");
    }
    printDebug("SERIAL| CRTSCTS:%d", options->c_cflag&CRTSCTS?1:0);
}
bool SerialConnection::doOpen(const int32_t& timeoutMillis) {
    m_apHandleLock->lock();
    if (m_iHandle != -1) {
        m_apHandleLock->unlock();
        return true;
    }
    if(!m_apLockFile.isNull() && !m_apLockFile->lock(timeoutMillis)) {
        printError("SERIAL| Failed to obtain file lock");
        m_apHandleLock->unlock();
        return false;
    }
#ifdef WIN32
#else
    m_iHandle = ::open(m_sPortName.c_str(), O_RDWR | O_NOCTTY );
    if (m_iHandle == -1) {
        printWarning("SERIAL| Failed to open port [%s] [%d]%s", m_sPortName.c_str(), errno,
                     errno == 13 ? " (Permission Denied)" : "");
        if(!m_apLockFile.isNull()) {
            m_apLockFile->unlock();
        }
        m_apHandleLock->unlock();
        return false;
    }
    int result = tcgetattr(m_iHandle, &m_stPrevOptions);
    if (result == -1) {
        printWarning("SERIAL| Failed to get attributes on port [%s] [%d]%s", m_sPortName.c_str(), errno,
                     errno == 13 ? " (Permission Denied)" : "");
    }
    termios options = m_stPrevOptions;
    //Set to Raw Mode
    cfmakeraw(&options);
    //Set the Baud Rate
    speed_t speed = B9600;
    switch (m_iBaudRate) {
        case 300:
            speed = B300;
            break;
        case 600:
            speed = B600;
            break;
        case 1200:
            speed = B1200;
            break;
        case 2400:
            speed = B2400;
            break;
        case 4800:
            speed = B4800;
            break;
        case 9600:
            speed = B9600;
            break;
        case 19200:
            speed = B19200;
            break;
        case 38400:
            speed = B38400;
            break;
        case 57600:
            speed = B57600;
            break;
        case 115200:
            speed = B115200;
            break;
        case 230400:
            speed = B230400;
            break;
        case 460800:
            speed = B460800;
            break;
        case 921600:
            speed = B921600;
            break;
        default:
            printWarning("SERIAL| Baud Rate [%d] not supported. Defaulting to 9600.", m_iBaudRate);
            m_iBaudRate = 9600;
            speed = B9600;
            break;
    }
    result = cfsetispeed(&options, speed);
    if (result == -1) {
        printWarning("SERIAL| Failed to set input speed of [%d] on port [%s] [%d]", m_iBaudRate, m_sPortName.c_str(), errno);
        goto CLEANUP;
    }
    result = cfsetospeed(&options, speed);
    if (result == -1) {
        printWarning("SERIAL| Failed to set output speed of [%d] on port [%s] [%d]", m_iBaudRate, m_sPortName.c_str(), errno);
        goto CLEANUP;
    }
    //Set Parity, Stop Bits, and Data Length
    options.c_cflag &= ~(PARODD | PARENB);
    switch(m_eParity) {
        case ODD:
            options.c_cflag |= ( PARODD | PARENB );
            break;
        case EVEN:
            options.c_cflag |= PARENB;
            break;
        case OFF:
        default:
            break;
    }
    options.c_cflag &= ~CSIZE;
    switch(m_eDataBits) {
        case B5:
            options.c_cflag |= CS5;
            break;
        case B6:
            options.c_cflag |= CS6;
            break;
        case B7:
            options.c_cflag |= CS7;
            break;
        case B8:
        default:
            options.c_cflag |= CS8;
            break;
    }
    switch(m_eStopBits) {
        case B2:
            options.c_cflag |= CSTOPB;
            break;
        case B1:
        default:
            options.c_cflag &= ~CSTOPB;
		break;
	}
	switch (m_eFlowControl) {
		case RTS_CTS:
			options.c_cflag |= CRTSCTS;
			break;
		case NONE:
		default:
			options.c_cflag &= ~CRTSCTS;
			break;
    }
	//Set Control Modes
    options.c_cc[VMIN] = 0;
    options.c_cc[VTIME] = 2; // tenths of seconds
    printPortSetting(&options);
    result = tcsetattr(m_iHandle, TCSANOW, &options);
    if (result == -1) {
        printWarning("SERIAL| Failed to set configurations on port [%s] [%d]%s", m_sPortName.c_str(), errno,
                     errno == 13 ? " (Permission Denied)" : "");
        goto CLEANUP;
    }
    result = tcflush(m_iHandle, TCIOFLUSH);
    if (result == -1) {
        printWarning("SERIAL| Failed to flush port [%s] [%d]", m_sPortName.c_str(), errno);
        goto CLEANUP;
    }
#endif
    m_apHandleLock->unlock();
    printInfo("SERIAL| %s connection opened on %s @ %d", getName().c_str(), m_sPortName.c_str(), m_iBaudRate);
    return true;
CLEANUP:
    cleanup();
    m_apHandleLock->unlock();
    return false;
}
void SerialConnection::doClose() {
    m_apHandleLock->lock();
    cleanup();
    m_apHandleLock->unlock();
}
int SerialConnection::doRead(char* pBuffer, const uint32_t& iSize, int32_t& timeoutMillis) {
    int32_t result = -1;
#ifdef WIN32
#else
    int h = getFileDescriptor();
    if (h == -1) {
        printError("SERIAL| bad handle on port %s", m_sPortName.c_str());
        return result;
    }
    if(timeoutMillis < 0) {
        result = ::read(h, pBuffer, iSize);
    } else {
        fd_set readfs;
        struct timeval stTimeout;
        stTimeout.tv_usec = ((timeoutMillis%1000) * 1000);
        stTimeout.tv_sec  = timeoutMillis/1000;
        FD_ZERO(&readfs);
        FD_SET(h, &readfs);
        int res = select(h + 1, &readfs, NULL, NULL, &stTimeout);
        if(res == -1) {
            result = -1;
        } else if (res == 0) {
            //Timeout Occured
            //printTrace("SERIAL| read timed out on port [%s]", m_sPortName.c_str());
            result = 0;
        } else {
            if (FD_ISSET(h, &readfs)) {
                result = ::read(h, pBuffer, iSize);
            } else {
                //socket closed?
                result = -1;
            }
        }
        timeoutMillis = (stTimeout.tv_usec / 1000) + (stTimeout.tv_sec * 1000);
    }
#endif
    if (result < 0) {
       printWarning("SERIAL| failed to read from %s [%d]", m_sPortName.c_str(), errno);
   }
    return result;
}
int SerialConnection::doWrite(const char* pBuffer, const uint32_t& iSize, int32_t& timeoutMillis) {
    int32_t result = -1;
#ifdef WIN32
#else
    int h = getFileDescriptor();
    if (h == -1) {
        printError("SERIAL| bad handle on port %s", m_sPortName.c_str());
        return result;
    }
    if(timeoutMillis < 0) {
        result = ::write(h, pBuffer, iSize);
    } else {
        //No difference between timeout and no timeout
        result = ::write(h, pBuffer, iSize);
    }
#endif
    if (result < 0) {
        printWarning("SERIAL| failed to write to %s [%d]", m_sPortName.c_str(), errno);
    }
    return result;
}
void SerialConnection::cleanup() {
#ifdef WIN32
    CloseHandle(m_iHandle);
    m_iHandle = INVALID_HANDLE_VALUE;
#else
    if(m_iHandle >= 0) {
        int result = tcsetattr(m_iHandle, TCSANOW, &m_stPrevOptions);
        if (result == -1) {
            printWarning("SERIAL| Failed to revert configurations on port [%s] [%d]", m_sPortName.c_str(), errno);
        }
        ::close(m_iHandle);
        m_iHandle = -1;
    }
#endif
    if(!m_apLockFile.isNull()) {
        m_apLockFile->unlock();
	}
}
void SerialConnection::drain() {
	int result = tcdrain(m_iHandle);
	if (result == -1) {
		printWarning("SERIAL| Failed to drain port [%s] [%d]", m_sPortName.c_str(), errno);
	}
}
void SerialConnection::flush() {
	int result = tcflush(m_iHandle, TCIOFLUSH);
	if (result == -1) {
		printWarning("SERIAL| Failed to flush port [%s] [%d]", m_sPortName.c_str(), errno);
	}
}