// Copyright (C) 1999-2005 Open Source Telecom Corporation.
// Copyright (C) 2006-2014 David Sugar, Tycho Softworks.
// Copyright (C) 2015-2020 Cherokees of Idaho.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// As a special exception, you may use this file as part of a free software
// library without restriction.  Specifically, if other files instantiate
// templates or use macros or inline functions from this file, or you compile
// this file and link it with other files to produce an executable, this
// file does not by itself cause the resulting executable to be covered by
// the GNU General Public License.  This exception does not however
// invalidate any other reasons why the executable file might be covered by
// the GNU General Public License.
//
// This exception applies only to the code released under the name GNU
// Common C++.  If you copy code from other releases into a copy of GNU
// Common C++, as the General Public License permits, the exception does
// not apply to the code that you add in this way.  To avoid misleading
// anyone as to the status of such modified files, you must delete
// this exception notice from them.
//
// If you write modifications of your own for GNU Common C++, it is your choice
// whether to permit this exception to apply to your modifications.
// If you do not wish that, delete this exception notice.
//

#include <ucommon-config.h>
#include <commoncpp/config.h>
#include <commoncpp/export.h>
#include <commoncpp/exception.h>
#include <commoncpp/thread.h>
#include <commoncpp/serial.h>
#include <cstdlib>
#include <climits>

#ifdef  _MSWINDOWS_

#define B256000     CBR_256000
#define B128000     CBR_128000
#define B115200     CBR_115200
#define B57600      CBR_57600
#define B56000      CBR_56000
#define B38400      CBR_38400
#define B19200      CBR_19200
#define B14400      CBR_14400
#define B9600       CBR_9600
#define B4800       CBR_4800
#define B2400       CBR_2400
#define B1200       CBR_1200
#define B600        CBR_600
#define B300        CBR_300
#define B110        CBR_110

#include <io.h>
#else
#include <sys/ioctl.h>
#ifdef  HAVE_TERMIOS_H
#include <termios.h>
#endif
#endif

#include <fcntl.h>
#include <cerrno>
#include <iostream>

namespace ost {
using std::streambuf;
using std::iostream;
using std::ios;

#ifndef MAX_INPUT
#define MAX_INPUT 255
#endif

#ifndef MAX_CANON
#define MAX_CANON MAX_INPUT
#endif

#ifdef  __FreeBSD__
#undef  _PC_MAX_INPUT
#undef  _PC_MAX_CANON
#endif

#if defined(__QNX__)
#define CRTSCTS (IHFLOW | OHFLOW)
#endif

#if defined(_THR_UNIXWARE) || defined(__hpux) || defined(_AIX)
#include <sys/termiox.h>
#define CRTSCTS (CTSXON | RTSXOFF)
#endif

// IRIX

#ifndef CRTSCTS
#ifdef  CNEW_RTSCTS
#define CRTSCTS (CNEW_RTSCTS)
#endif
#endif

#if defined(CTSXON) && defined(RTSXOFF) && !defined(CRTSCTS)
#define CRTSCTS (CTSXON | RTSXOFF)
#endif

#ifndef CRTSCTS
#define CRTSCTS 0
#endif

Serial::Serial(const char *fname)
{
    initSerial();

#if defined(_MSWINDOWS_) || defined(HAVE_TERMIOS_H)
    open(fname);
#endif

#ifdef  _MSWINDOWS_
    if(dev == INVALID_HANDLE_VALUE)
#elif defined(HAVE_TERMIOS_H)
    if(dev < 0)
#else
    if(1)
#endif
    {
        error(errOpenFailed);
        return;
    }

#ifdef  _MSWINDOWS_
    COMMTIMEOUTS  CommTimeOuts ;

    GetCommTimeouts(dev, &CommTimeOuts);

//    CommTimeOuts.ReadIntervalTimeout = MAXDWORD;
    CommTimeOuts.ReadIntervalTimeout = 0;

    CommTimeOuts.ReadTotalTimeoutMultiplier = 0 ;
    CommTimeOuts.ReadTotalTimeoutConstant = 0;
    CommTimeOuts.WriteTotalTimeoutMultiplier = 0 ;
    CommTimeOuts.WriteTotalTimeoutConstant = 1000;

    SetCommTimeouts(dev, &CommTimeOuts) ;

#elif defined(HAVE_TERMIOS_H)

    if(!isatty(dev)) {
        Serial::close();
        error(errOpenNoTty);
        return;
    }
#endif
}

Serial::~Serial()
{
    endSerial();
}

void Serial::initConfig(void)
{
#ifdef  _MSWINDOWS_

#define ASCII_XON       0x11
#define ASCII_XOFF      0x13

    DCB * attr = (DCB *)current;
    DCB * orig = (DCB *)original;

    attr->DCBlength = sizeof(DCB);
    orig->DCBlength = sizeof(DCB);

    GetCommState(dev, orig);
    GetCommState(dev, attr);

    attr->DCBlength = sizeof(DCB);
    attr->BaudRate = 1200;
    attr->Parity = NOPARITY;
    attr->ByteSize = 8;

    attr->XonChar = ASCII_XON;
    attr->XoffChar = ASCII_XOFF;
    attr->XonLim = 100;
    attr->XoffLim = 100;
    attr->fOutxDsrFlow = 0;
    attr->fDtrControl = DTR_CONTROL_ENABLE;
    attr->fOutxCtsFlow = 1;
    attr->fRtsControl = RTS_CONTROL_ENABLE;
    attr->fInX = attr->fOutX = 0;

    attr->fBinary = true;
    attr->fParity = true;

    SetCommState(dev, attr);

#elif defined(HAVE_TERMIOS_H)
    struct termios *attr = (struct termios *)current;
    struct termios *orig = (struct termios *)original;
    long ioflags = fcntl(dev, F_GETFL);

    tcgetattr(dev, (struct termios *)original);
    tcgetattr(dev, (struct termios *)current);

    attr->c_oflag = attr->c_lflag = 0;
    attr->c_cflag = CLOCAL | CREAD | HUPCL;
    attr->c_iflag = IGNBRK;

    memset(attr->c_cc, 0, sizeof(attr->c_cc));
    attr->c_cc[VMIN] = 1;

    // inherit original settings, maybe we should keep more??
    cfsetispeed(attr, cfgetispeed(orig));
    cfsetospeed(attr, cfgetospeed(orig));
    attr->c_cflag |= orig->c_cflag & (CRTSCTS | CSIZE | PARENB | PARODD | CSTOPB);
    attr->c_iflag |= orig->c_iflag & (IXON | IXANY | IXOFF);

    tcsetattr(dev, TCSANOW, attr);
    fcntl(dev, F_SETFL, ioflags & ~O_NDELAY);

#if defined(TIOCM_RTS) && defined(TIOCMODG)
    int mcs = 0;
    ioctl(dev, TIOCMODG, &mcs);
    mcs |= TIOCM_RTS;
    ioctl(dev, TIOCMODS, &mcs);
#endif

#ifdef  _COHERENT
    ioctl(dev, TIOCSRTS, 0);
#endif

#endif  // WIN32
    }

void Serial::restore(void)
{
#ifdef  _MSWINDOWS_
    memcpy(current, original, sizeof(DCB));
    SetCommState(dev, (DCB *)current);
#elif defined(HAVE_TERMIOS_H)
    memcpy(current, original, sizeof(struct termios));
    tcsetattr(dev, TCSANOW, (struct termios *)current);
#endif
}

void Serial::initSerial(void)
{
    flags.thrown = false;
    flags.linebuf = false;
    errid = errSuccess;
    errstr = NULL;

    dev = INVALID_HANDLE_VALUE;
#ifdef  _MSWINDOWS_
    current = new DCB;
    original = new DCB;
#elif defined(HAVE_TERMIOS_H)
    current = new struct termios;
    original = new struct termios;
#endif
}

void Serial::endSerial(void)
{
#ifdef  _MSWINDOWS_
    if(dev == INVALID_HANDLE_VALUE && original)
        SetCommState(dev, (DCB *)original);

    if(current)
        delete (DCB *)current;

    if(original)
        delete (DCB *)original;
#elif defined(HAVE_TERMIOS_H)
    if(dev < 0 && original)
        tcsetattr(dev, TCSANOW, (struct termios *)original);

    if(current)
        delete (struct termios *)current;

    if(original)
        delete (struct termios *)original;
#endif
    Serial::close();

    current = NULL;
    original = NULL;
}

Serial::Error Serial::error(Error err, char *errs)
{
    errid = err;
    errstr = errs;
    if(!err)
        return err;

    if(flags.thrown)
        return err;

    // prevents recursive throws

    flags.thrown = true;
#ifdef  CCXX_EXCEPTIONS
    if(Thread::getException() == Thread::throwObject)
            throw((Serial *)this);
#ifdef  COMMON_STD_EXCEPTION
    else if(Thread::getException() == Thread::throwException) {
        if(!errs)
            errs = (char *)"";
        throw SerException(String(errs));
    }
#endif
#endif
    return err;
}

int Serial::setPacketInput(int size, uint8_t btimer)
{
#ifdef  _MSWINDOWS_
    //  Still to be done......
    return 0;
#elif defined(HAVE_TERMIOS_H)

#ifdef  _PC_MAX_INPUT
    int max = fpathconf(dev, _PC_MAX_INPUT);
#else
    int max = MAX_INPUT;
#endif
    struct termios *attr = (struct termios *)current;

    if(size > max)
        size = max;

    attr->c_cc[VEOL] = attr->c_cc[VEOL2] = 0;
    attr->c_cc[VMIN] = (uint8_t)size;
    attr->c_cc[VTIME] = btimer;
    attr->c_lflag &= ~ICANON;
    tcsetattr(dev, TCSANOW, attr);
    bufsize = size;
    return size;
#endif
}

int Serial::setLineInput(char newline, char nl1)
{
#ifdef  _MSWINDOWS_
    //  Still to be done......
    return 0;
#elif defined(HAVE_TERMIOS_H)

    struct termios *attr = (struct termios *)current;
    attr->c_cc[VMIN] = attr->c_cc[VTIME] = 0;
    attr->c_cc[VEOL] = newline;
    attr->c_cc[VEOL2] = nl1;
    attr->c_lflag |= ICANON;
    tcsetattr(dev, TCSANOW, attr);
#ifdef _PC_MAX_CANON
    bufsize = fpathconf(dev, _PC_MAX_CANON);
#else
    bufsize = MAX_CANON;
#endif
    return bufsize;
#endif
}

void Serial::flushInput(void)
{
#ifdef  _MSWINDOWS_
    PurgeComm(dev, PURGE_RXABORT | PURGE_RXCLEAR);
#elif defined(HAVE_TERMIOS_H)
    tcflush(dev, TCIFLUSH);
#endif
}

void Serial::flushOutput(void)
{
#ifdef  _MSWINDOWS_
    PurgeComm(dev, PURGE_TXABORT | PURGE_TXCLEAR);
#elif defined(HAVE_TERMIOS_H)
    tcflush(dev, TCOFLUSH);
#endif
}

void Serial::waitOutput(void)
{
#ifdef  _MSWINDOWS_

#elif defined(HAVE_TERMIOS_H)
#if defined(__ANDROID__)
    ioctl(dev, TCSBRK, 1);
#else
    tcdrain(dev);
#endif
#endif
}

Serial &Serial::operator=(const Serial &ser)
{
    Serial::close();

    if(ser.dev < 0)
        return *this;

#ifdef  _MSWINDOWS_
    HANDLE process = GetCurrentProcess();

    int result = DuplicateHandle(process, ser.dev, process, &dev, DUPLICATE_SAME_ACCESS, 0, 0);

    if (0 == result) {
        memcpy(current, ser.current, sizeof(DCB));
        memcpy(original, ser.original, sizeof(DCB));
    }
#elif defined(HAVE_TERMIOS_H)
    dev = dup(ser.dev);

    memcpy(current, ser.current, sizeof(struct termios));
    memcpy(original, ser.original, sizeof(struct termios));
#endif
    return *this;
}


void Serial::open(const char * fname)
{

#ifndef _MSWINDOWS_
    int cflags = O_RDWR | O_NDELAY;
    dev = ::open(fname, cflags);
    if(dev > -1)
        initConfig();
#elif defined(HAVE_TERMIOS_H)
    // open COMM device
    dev = CreateFile(fname,
        GENERIC_READ | GENERIC_WRITE,
        0,                    // exclusive access
        NULL,                 // no security attrs
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING,
        NULL);
    if(dev != INVALID_HANDLE_VALUE)
        initConfig();
#endif
}

#ifdef  _MSWINDOWS_
int Serial::aRead(char * Data, const int Length)
{

    unsigned long   dwLength = 0, dwError, dwReadLength;
    COMSTAT cs;
    OVERLAPPED ol;

    // Return zero if handle is invalid
    if(dev == INVALID_HANDLE_VALUE)
        return 0;

    // Read max length or only what is available
    ClearCommError(dev, &dwError, &cs);

    // If not requiring an exact byte count, get whatever is available
    if(Length > (int)cs.cbInQue)
        dwReadLength = cs.cbInQue;
    else
        dwReadLength = Length;

    memset(&ol, 0, sizeof(OVERLAPPED));
    ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!ol.hEvent || ol.hEvent == INVALID_HANDLE_VALUE)
        return 0;

    if(dwReadLength > 0) {
        if(ReadFile(dev, Data, dwReadLength, &dwLength, &ol) == FALSE) {
            if(GetLastError() == ERROR_IO_PENDING) {
                WaitForSingleObject(ol.hEvent, INFINITE);
                GetOverlappedResult(dev, &ol, &dwLength, TRUE);
            }
            else
                ClearCommError(dev, &dwError, &cs);
        }
    }

    CloseHandle(ol.hEvent);

    return dwLength;
}

int Serial::aWrite(const char * Data, const int Length)
{
    COMSTAT cs;
    unsigned long dwError = 0;
    OVERLAPPED ol;

    // Clear the com port of any error condition prior to read
    ClearCommError(dev, &dwError, &cs);

    unsigned long retSize = 0;

    memset(&ol, 0, sizeof(OVERLAPPED));
    ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!ol.hEvent || ol.hEvent == INVALID_HANDLE_VALUE)
        return  0;

    if(WriteFile(dev, Data, Length, &retSize, &ol) == FALSE) {
        if(GetLastError() == ERROR_IO_PENDING) {
            WaitForSingleObject(ol.hEvent, INFINITE);
            GetOverlappedResult(dev, &ol, &retSize, TRUE);
        }
        else
            ClearCommError(dev, &dwError, &cs);
    }
    CloseHandle(ol.hEvent);

    return retSize;
}
#else
int Serial::aRead(char *Data, const int Length)
{
    return ::read(dev, Data, Length);
}

int Serial::aWrite(const char *Data, const int Length)
{
    return ::write(dev, Data, Length);
}
#endif

void Serial::close()
{
#ifdef  _MSWINDOWS_
    CloseHandle(dev);
#else
    ::close(dev);
#endif

    dev = INVALID_HANDLE_VALUE;
}

/*
const int iAsync::getTimeOuts(unsigned long & readTimeout, unsigned long & writeTimeout)
{
    return GetCommTimeouts(_TheHandle, &CommTimeOuts);
    }

const int iAsync::setTimeOuts(unsigned long readTimeout, unsigned long writeTimeout)
{
    COMMTIMEOUTS  CommTimeOuts ;

    getTimeOuts(CommTimeOuts);

    CommTimeOuts.ReadIntervalTimeout = readTimeout;
    CommTimeOuts.ReadTotalTimeoutMultiplier = 0 ;
    CommTimeOuts.ReadTotalTimeoutConstant = 0;
    CommTimeOuts.WriteTotalTimeoutMultiplier = 0 ;
    CommTimeOuts.WriteTotalTimeoutConstant = 1000;

    return GetCommTimeouts(_TheHandle, &CommTimeOuts);
    }
    return SetCommTimeouts(_TheHandle, &CommTimeOuts) ;
{
    DCB        dcb ;

    dcb.DCBlength = sizeof(DCB) ;

    GetCommState(_TheHandle, &dcb) ;

    // hardcode this stuff for now.....
    dcb.BaudRate = _TheBaudrate;
    dcb.ByteSize = 8;
    dcb.Parity   = NOPARITY;
    dcb.StopBits = ONESTOPBIT;
    dcb.fOutxDsrFlow = 0;
    dcb.fDtrControl = DTR_CONTROL_ENABLE ;
    dcb.fOutxCtsFlow = 1;
    dcb.fRtsControl = RTS_CONTROL_HANDSHAKE ;
    dcb.fInX = dcb.fOutX = 0;

    dcb.XonChar = ASCII_XON;
    dcb.XoffChar = ASCII_XOFF;
    dcb.XonLim = 100;
    dcb.XoffLim = 100;

    // other various settings

    dcb.fBinary = TRUE;
    dcb.fParity = TRUE;

    GetCommState(_TheHandle, &dcb) ;
    dcb.DCBlength = sizeof(DCB) ;
    dcb.BaudRate = _TheBaudrate;
    SetCommState(_TheHandle, &dcb) ;

    }
*/

Serial::Error Serial::setSpeed(unsigned long speed)
{
    unsigned long rate;

    switch(speed) {
#ifdef B115200
    case 115200:
        rate = B115200;
        break;
#endif
#ifdef B57600
    case 57600:
        rate = B57600;
        break;
#endif
#ifdef B38400
    case 38400:
        rate = B38400;
        break;
#endif
    case 19200:
        rate = B19200;
        break;
    case 9600:
        rate = B9600;
        break;
    case 4800:
        rate = B4800;
        break;
    case 2400:
        rate = B2400;
        break;
    case 1200:
        rate = B1200;
        break;
    case 600:
        rate = B600;
        break;
    case 300:
        rate = B300;
        break;
    case 110:
        rate = B110;
        break;
#ifdef  B0
    case 0:
        rate = B0;
        break;
#endif
    default:
        return error(errSpeedInvalid);
    }

#ifdef  _MSWINDOWS_

    DCB     * dcb = (DCB *)current;
    dcb->DCBlength = sizeof(DCB);
    GetCommState(dev, dcb);

    dcb->BaudRate = rate;
    SetCommState(dev, dcb) ;

#else
    struct termios *attr = (struct termios *)current;
    cfsetispeed(attr, rate);
    cfsetospeed(attr, rate);
    tcsetattr(dev, TCSANOW, attr);
#endif
    return errSuccess;
}

Serial::Error Serial::setFlowControl(Flow flow)
{
#ifdef  _MSWINDOWS_

    DCB * attr = (DCB *)current;
    attr->XonChar = ASCII_XON;
    attr->XoffChar = ASCII_XOFF;
    attr->XonLim = 100;
    attr->XoffLim = 100;

    switch(flow) {
    case flowSoft:
        attr->fInX = attr->fOutX = 1;
        break;
    case flowBoth:
        attr->fInX = attr->fOutX = 1;
    case flowHard:
        attr->fOutxCtsFlow = 1;
        attr->fRtsControl = RTS_CONTROL_HANDSHAKE;
        break;
    case flowNone:
        break;
    default:
        return error(errFlowInvalid);
    }

    SetCommState(dev, attr);
#else

    struct termios *attr = (struct termios *)current;

    attr->c_cflag &= ~CRTSCTS;
    attr->c_iflag &= ~(IXON | IXANY | IXOFF);

    switch(flow) {
    case flowSoft:
        attr->c_iflag |= (IXON | IXANY | IXOFF);
        break;
    case flowBoth:
        attr->c_iflag |= (IXON | IXANY | IXOFF);
    case flowHard:
        attr->c_cflag |= CRTSCTS;
        break;
    case flowNone:
        break;
    default:
        return error(errFlowInvalid);
    }

    tcsetattr(dev, TCSANOW, attr);

#endif
    return errSuccess;
}

Serial::Error Serial::setStopBits(int bits)
{
#ifdef  _MSWINDOWS_

    DCB * attr = (DCB *)current;
    switch(bits) {
    case 1:
        attr->StopBits = ONESTOPBIT;
        break;
    case 2:
        attr->StopBits = TWOSTOPBITS;
        break;
    default:
        return error(errStopbitsInvalid);
    }

    SetCommState(dev, attr);
#else
    struct termios *attr = (struct termios *)current;
    attr->c_cflag &= ~CSTOPB;

    switch(bits) {
    case 1:
        break;
    case 2:
        attr->c_cflag |= CSTOPB;
        break;
    default:
        return error(errStopbitsInvalid);
    }
    tcsetattr(dev, TCSANOW, attr);
#endif
    return errSuccess;
}

Serial::Error Serial::setCharBits(int bits)
{
#ifdef  _MSWINDOWS_

    DCB * attr = (DCB *)current;
    switch(bits) {
    case 5:
    case 6:
    case 7:
    case 8:
        attr->ByteSize = bits;
        break;
    default:
        return error(errCharsizeInvalid);
    }
    SetCommState(dev, attr);
#else
    struct termios *attr = (struct termios *)current;
    attr->c_cflag &= ~CSIZE;

    switch(bits) {
    case 5:
        attr->c_cflag |= CS5;
        break;
    case 6:
        attr->c_cflag |= CS6;
        break;
    case 7:
        attr->c_cflag |= CS7;
        break;
    case 8:
        attr->c_cflag |= CS8;
        break;
    default:
        return error(errCharsizeInvalid);
    }
    tcsetattr(dev, TCSANOW, attr);
#endif
    return errSuccess;
}

Serial::Error Serial::setParity(Parity parity)
{
#ifdef  _MSWINDOWS_

    DCB * attr = (DCB *)current;
    switch(parity) {
    case parityEven:
        attr->Parity = EVENPARITY;
        break;
    case parityOdd:
        attr->Parity = ODDPARITY;
        break;
    case parityNone:
        attr->Parity = NOPARITY;
        break;
    default:
        return error(errParityInvalid);
    }
    SetCommState(dev, attr);
#else
    struct termios *attr = (struct termios *)current;
    attr->c_cflag &= ~(PARENB | PARODD);

    switch(parity) {
    case parityEven:
        attr->c_cflag |= PARENB;
        break;
    case parityOdd:
        attr->c_cflag |= (PARENB | PARODD);
        break;
    case parityNone:
        break;
    default:
        return error(errParityInvalid);
    }
    tcsetattr(dev, TCSANOW, attr);
#endif
    return errSuccess;
}

void Serial::sendBreak(void)
{
#ifdef  _MSWINDOWS_
    SetCommBreak(dev);
    Thread::sleep(100L);
    ClearCommBreak(dev);
#else
    tcsendbreak(dev, 0);
#endif
}

void Serial::toggleDTR(timeout_t millisec)
{
#ifdef  _MSWINDOWS_
    EscapeCommFunction(dev, CLRDTR);
    if(millisec) {
        Thread::sleep(millisec);
        EscapeCommFunction(dev, SETDTR);
    }

#else
    struct termios tty, old;
    tcgetattr(dev, &tty);
    tcgetattr(dev, &old);
    cfsetospeed(&tty, B0);
    cfsetispeed(&tty, B0);
    tcsetattr(dev, TCSANOW, &tty);

    if(millisec) {
        Thread::sleep(millisec);
        tcsetattr(dev, TCSANOW, &old);
    }
#endif
}

bool Serial::isPending(Pending pending, timeout_t timeout)
{
#ifdef  _MSWINDOWS_
    unsigned long   dwError;
    COMSTAT cs;

    ClearCommError(dev, &dwError, &cs);

    if(timeout == 0 || ((pending == pendingInput) && (0 != cs.cbInQue)) ||
       ((pending == pendingOutput) && (0 != cs.cbOutQue)) || (pending == pendingError))
    {
        switch(pending) {
        case pendingInput:
            return (0 != cs.cbInQue);
        case pendingOutput:
            return (0 != cs.cbOutQue);
        case pendingError:
            return false;
        }
    }
    else {
        OVERLAPPED ol;
        DWORD dwMask;
        DWORD dwEvents = 0;
        BOOL suc;

        memset(&ol, 0, sizeof(OVERLAPPED));
        ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (!ol.hEvent)
            return false;

        if(pending == pendingInput)
            dwMask = EV_RXCHAR;
        else if(pending == pendingOutput)
            dwMask = EV_TXEMPTY;
        else   // on error
            dwMask = EV_ERR;

        SetCommMask(dev, dwMask);
        // let's wait for event or timeout
        if((suc = WaitCommEvent(dev, &dwEvents, &ol)) == FALSE) {
            if(GetLastError() == ERROR_IO_PENDING) {
                DWORD transferred;

                dwError = WaitForSingleObject(ol.hEvent, timeout);
                if (dwError != WAIT_OBJECT_0)
                    SetCommMask(dev, 0);

                suc = GetOverlappedResult(dev, &ol, &transferred, TRUE);
                if (suc) {
                    suc = (dwEvents & dwMask) ? TRUE : FALSE;
                }
            }
        else
            ClearCommError(dev, &dwError, &cs);
        }

        CloseHandle(ol.hEvent);

        if(suc == FALSE)
                return false;
        return true;
    }
#else


    int status = 0;
#ifdef HAVE_POLL
    struct pollfd pfd;

    pfd.fd = dev;
    pfd.revents = 0;
    switch(pending) {
    case pendingInput:
        pfd.events = POLLIN;
        break;
    case pendingOutput:
        pfd.events = POLLOUT;
        break;
    case pendingError:
        pfd.events = POLLERR | POLLHUP;
        break;
    }

    status = 0;
    while(status < 1) {
        if(timeout == TIMEOUT_INF)
            status = poll(&pfd, 1, -1);
        else
            status = poll(&pfd, 1, timeout);

        if(status < 1) {
            if(status == -1 && errno == EINTR)
                continue;
            return false;
        }
    }

    if(pfd.revents & pfd.events)
        return true;

#else
    struct timeval tv;
    fd_set grp;
    struct timeval *tvp = &tv;

    if(timeout == TIMEOUT_INF)
        tvp = NULL;
    else {
        tv.tv_usec = (timeout % 1000) * 1000;
        tv.tv_sec = timeout / 1000;
    }

    FD_ZERO(&grp);
    FD_SET(dev, &grp);
    switch(pending) {
    case pendingInput:
        status = select(dev + 1, &grp, NULL, NULL, tvp);
        break;
    case pendingOutput:
        status = select(dev + 1, NULL, &grp, NULL, tvp);
        break;
    case pendingError:
        status = select(dev + 1, NULL, NULL, &grp, tvp);
        break;
    }
    if(status < 1)
        return false;

    if(FD_ISSET(dev, &grp))
        return true;

#endif

#endif  //  WIN32

    return false;
}

TTYStream::TTYStream(const char *filename, timeout_t to)
    :   streambuf(),
        Serial(filename),
        iostream((streambuf *)this)
{
    gbuf = pbuf = NULL;
    timeout = to;

    if(INVALID_HANDLE_VALUE != dev)
        allocate();
        }

TTYStream::TTYStream()
    :   streambuf(),
        Serial(),
        iostream((streambuf *)this)
{
    timeout = 0;
    gbuf = pbuf = NULL;
}

TTYStream::~TTYStream()
{
    endStream();
    endSerial();
}

void TTYStream::endStream(void)
{
    if(bufsize)
        sync();

    if(gbuf) {
        delete[] gbuf;
        gbuf = NULL;
    }
    if(pbuf) {
        delete[] pbuf;
        pbuf = NULL;
    }
    bufsize = 0;
    clear();
}

void TTYStream::allocate(void)
{
    if(INVALID_HANDLE_VALUE == dev)
        return;

#ifdef _PC_MAX_INPUT
    bufsize = fpathconf(dev, _PC_MAX_INPUT);
#else
    bufsize = MAX_INPUT;
#endif

    gbuf = new char[bufsize];
    pbuf = new char[bufsize];

    if(!pbuf || !gbuf) {
        error(errResourceFailure);
        return;
    }

    clear();

#if !(defined(STLPORT) || defined(__KCC))
    setg(gbuf, gbuf + bufsize, 0);
#endif

    setg(gbuf, gbuf + bufsize, gbuf + bufsize);
    setp(pbuf, pbuf + bufsize);
}

int TTYStream::doallocate()
{
    if(bufsize)
        return 0;

    allocate();
    return 1;
}

void TTYStream::interactive(bool iflag)
{
#ifdef  _MSWINDOWS_
    if(dev == INVALID_HANDLE_VALUE)
#else
    if(dev < 0)
#endif
        return;

    if(bufsize >= 1)
        endStream();

    if(iflag) {
        // setting to unbuffered mode

        bufsize = 1;
        gbuf = new char[bufsize];

#if !(defined(STLPORT) || defined(__KCC))
#if defined(__GNUC__) && (__GNUC__ < 3)
        setb(0,0);
#endif
#endif
        setg(gbuf, gbuf+bufsize, gbuf+bufsize);
        setp(pbuf, pbuf);
        return;
    }

    if(bufsize < 2)
        allocate();
}

int TTYStream::uflow(void)
{
    int rlen;
    uint8_t ch;

    if(bufsize < 2) {
        if(timeout) {
            if(Serial::isPending(pendingInput, timeout))
                rlen = aRead((char *)&ch, 1);
            else
                rlen = -1;
        }
        else
            rlen = aRead((char *)&ch, 1);
        if(rlen < 1) {
            if(rlen < 0)
                clear(ios::failbit | rdstate());
            return EOF;
        }
        return ch;
    }
    else {
        ch = underflow();
        gbump(1);
        return ch;
    }
}

int TTYStream::underflow(void)
{
    ssize_t rlen = 1;

    if(!gptr())
        return EOF;

    if(gptr() < egptr())
        return (uint8_t)*gptr();

    rlen = (ssize_t)((gbuf + bufsize) - eback());
    if(timeout && !Serial::isPending(pendingInput, timeout))
        rlen = -1;
    else
        rlen = aRead((char *)eback(), rlen);

    if(rlen < 1) {
        if(rlen < 0) {
            clear(ios::failbit | rdstate());
            error(errInput);
        }
        return EOF;
    }

    setg(eback(), eback(), eback() + rlen);
    return (uint8_t) *gptr();
}

int TTYStream::sync(void)
{
    if(bufsize > 1 && pbase() && ((pptr() - pbase()) > 0)) {
        overflow(0);
        waitOutput();
        setp(pbuf, pbuf + bufsize);
    }
    setg(gbuf, gbuf + bufsize, gbuf + bufsize);
    return 0;
}

int TTYStream::overflow(int c)
{
    uint8_t ch;
    ssize_t rlen, req;

    if(bufsize < 2) {
        if(c == EOF)
            return 0;

        ch = (uint8_t)(c);
        rlen = aWrite((char *)&ch, 1);
        if(rlen < 1) {
            if(rlen < 0)
                clear(ios::failbit | rdstate());
            return EOF;
        }
        else
            return c;
    }

    if(!pbase())
        return EOF;

    req = (ssize_t)(pptr() - pbase());
    if(req) {
        rlen = aWrite((char *)pbase(), req);
        if(rlen < 1) {
            if(rlen < 0)
                clear(ios::failbit | rdstate());
            return EOF;
        }
        req -= rlen;
    }

    if(req)
//      memmove(pptr(), pptr() + rlen, req);
        memmove(pbuf, pbuf + rlen, req);
    setp(pbuf + req, pbuf + bufsize);

    if(c != EOF) {
        *pptr() = (uint8_t)c;
        pbump(1);
    }
    return c;
}

bool TTYStream::isPending(Pending pending, timeout_t timer)
{
//  if(pending == pendingInput && in_avail())
//      return true;
//  else if(pending == pendingOutput)
//      flush();

    return Serial::isPending(pending, timer);
}






ttystream::ttystream() :
TTYStream()
{
    setError(false);
}

ttystream::ttystream(const char *name) :
TTYStream()
{
    setError(false);
    open(name);
}

void ttystream::close(void)
{
#ifdef  _MSWINDOWS_
    if (INVALID_HANDLE_VALUE == dev)
#else
    if(dev < 0)
#endif
        return;

    endStream();
    restore();
    TTYStream::close();
}

void ttystream::open(const char *name)
{
    const char *cpp;
    char *cp;
    char pathname[256];
    size_t namelen;
    long opt;

    if (INVALID_HANDLE_VALUE != dev) {
        restore();
        close();
    }

    cpp = strrchr(name, ':');
    if(cpp)
        namelen = cpp - name;
    else
        namelen = strlen(name);

    cp = pathname;

#ifndef _MSWINDOWS_
    if(*name != '/') {
        setString(pathname, sizeof(pathname), "/dev/");
        cp += 5;
    }

    if((cp - pathname) + namelen > 255) {
        error(errResourceFailure);
        return;
    }
#endif
    setString(cp, pathname - cp + sizeof(pathname), name);
    cp += namelen;
#ifdef  _MSWINDOWS_
    *cp++ = ':';
#endif
    *cp = 0;

    Serial::open(pathname);

    if(INVALID_HANDLE_VALUE == dev) {
        error(errOpenFailed);
        return;
    }

    allocate();

    setString(pathname, sizeof(pathname), name + namelen);
    cp = pathname + 1;

    if(*pathname == ':')
        cp = strtok(cp, ",");
    else
        cp = NULL;

    while(cp) {
        switch(*cp) {
        case 'h':
        case 'H':
            setFlowControl(flowHard);
            break;
        case 's':
        case 'S':
            setFlowControl(flowSoft);
            break;
        case 'b':
        case 'B':
            setFlowControl(flowBoth);
            break;
        case 'n':
        case 'N':
            setParity(parityNone);
            break;
        case 'O':
        case 'o':
            setParity(parityOdd);
            break;
        case 'e':
        case 'E':
            setParity(parityEven);
            break;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            opt = atol(cp);
            if(opt == 1 || opt == 2) {
                setStopBits((int)opt);
                break;
            }
            if(opt > 4 && opt < 9) {
                setCharBits((int)opt);
                break;
            }
            setSpeed(opt);
            break;
        default:
            error(errOptionInvalid);
        }
        cp = strtok(NULL, ",");
    }
}

TTYSession::TTYSession(const char *filename, int pri, int stack) :
Thread(pri, stack), TTYStream(filename)
{
    setError(false);
}


TTYSession::~TTYSession()
{
    terminate();
    endSerial();
}

#ifndef _MSWINDOWS_
//  Not supporting this right now........
//

SerialPort::SerialPort(SerialService *svc, const char *name) :
Serial(name),
detect_pending(true),
detect_output(false),
detect_disconnect(true)
{
    next = prev = NULL;
    service = NULL;

#ifdef  _MSWINDOWS_
    if(INVALID_HANDLE_VALUE != dev)
#else
    if(dev > -1)
#endif
    {
        setError(false);
        service = svc;
        svc->attach(this);
    }
}

SerialPort::~SerialPort()
{
    if(service)
        service->detach(this);

    endSerial();
}

void SerialPort::expired(void)
{
}

void SerialPort::pending(void)
{
}

void SerialPort::disconnect(void)
{
}

void SerialPort::output(void)
{
}

void SerialPort::setTimer(timeout_t ptimer)
{
    TimerPort::setTimer(ptimer);
    service->update();
}

void SerialPort::incTimer(timeout_t ptimer)
{
    TimerPort::incTimer(ptimer);
    service->update();
}


void SerialPort::setDetectPending( bool val )
{
    if ( detect_pending != val ) {
        detect_pending = val;
#ifdef USE_POLL
        if ( ufd ) {
            if ( val ) {
                ufd->events |= POLLIN;
            } else {
                ufd->events &= ~POLLIN;
            }
        }
#endif
        service->update();
    }
}

void SerialPort::setDetectOutput( bool val )
{
    if ( detect_output != val ) {
        detect_output = val;
#ifdef  USE_POLL
        if ( ufd ) {
            if ( val ) {
                ufd->events |= POLLOUT;
            } else {
                ufd->events &= ~POLLOUT;
            }
        }
#endif
        service->update();
    }
}


SerialService::SerialService(int pri, size_t stack, const char *id) :
Thread(pri, stack), Mutex()
{
    long opt;

    first = last = NULL;
    count = 0;
    FD_ZERO(&connect);
    if(::pipe(iosync)) {
#ifdef  CCXX_EXCEPTIONS
        switch(Thread::getException()) {
        case throwObject:
            throw(this);
            return;
#ifdef  COMMON_STD_EXCEPTION
        case throwException:
            throw(ThrException("no service pipe"));
            return;
#endif
        default:
            return;
        }
#else
        return;
#endif
    }
    hiwater = iosync[0] + 1;
    FD_SET(iosync[0], &connect);

    opt = fcntl(iosync[0], F_GETFL);
    fcntl(iosync[0], F_SETFL, opt | O_NDELAY);
}

SerialService::~SerialService()
{
    update(0);
    terminate();

    SerialPort *port = first;
    while (port) {
        SerialPort *tmp = port;
        port = port->next;
        delete tmp;
    }
}

void SerialService::onUpdate(uint8_t flag)
{
}

void SerialService::onEvent(void)
{
}

void SerialService::onCallback(SerialPort *port)
{
}

void SerialService::attach(SerialPort *port)
{
    enterMutex();
#ifdef  USE_POLL
    port->ufd = 0;
#endif
    if(last)
        last->next = port;

    port->prev = last;
    last = port;
    FD_SET(port->dev, &connect);
    if(port->dev >= hiwater)
        hiwater = port->dev + 1;

    if(!first) {
        first = port;
        leaveMutex();
        ++count;
        start();
    }
    else {
        leaveMutex();
        update();
        ++count;
    }
}

void SerialService::detach(SerialPort *port)
{
    enterMutex();

#ifndef USE_POLL
    FD_CLR(port->dev, &connect);
#endif

    if(port->prev)
        port->prev->next = port->next;
    else
        first = port->next;

    if(port->next)
        port->next->prev = port->prev;
    else
        last = port->prev;

    --count;
    leaveMutex();
    update();
}

void SerialService::update(uint8_t flag)
{
    if(::write(iosync[1], (char *)&flag, 1) < 1) {

#ifdef  CCXX_EXCEPTIONS
                switch(Thread::getException()) {
                case throwObject:
                        throw(this);
                        return;
#ifdef  COMMON_STD_EXCEPTION
                case throwException:
                        throw(ThrException("update failed"));
                        return;
#endif
                default:
                        return;
                        }
#else
                return;
#endif
    }
}


void SerialService::run(void)
{
    timeout_t timer, expires;
    SerialPort *port;
    uint8_t buf;

#ifdef  USE_POLL

    Poller  mfd;
    pollfd *p_ufd;
    int lastcount = 0;

    // initialize ufd in all attached ports :
    // probably don't need this but it can't hurt.
    enterMutex();
    port = first;
    while(port) {
        port->ufd = 0;
        port = port->next;
    }
    leaveMutex();

#else
    struct timeval timeout, *tvp;
    fd_set inp, out, err;
    int dev;
    FD_ZERO(&inp);
    FD_ZERO(&out);
    FD_ZERO(&err);
#endif

    for(;;) {
        timer = TIMEOUT_INF;
        while(1 == ::read(iosync[0], (char *)&buf, 1)) {
            if(buf) {
                onUpdate(buf);
                continue;
            }

            Thread::exit();
        }

#ifdef  USE_POLL

        bool    reallocate = false;

        enterMutex();
        onEvent();
        port = first;
        while(port) {
            onCallback(port);
            if ( ( p_ufd = port->ufd ) ) {

                if ( ( POLLHUP | POLLNVAL ) & p_ufd->revents ) {
                    // Avoid infinite loop from disconnected sockets
                    port->detect_disconnect = false;
                    p_ufd->events &= ~POLLHUP;
                    port->disconnect();
                }

                if ( ( POLLIN | POLLPRI ) & p_ufd->revents )
                    port->pending();

                if ( POLLOUT & p_ufd->revents )
                    port->output();

            } else {
                reallocate = true;
            }

retry:
            expires = port->getTimer();
            if(expires > 0)
                if(expires < timer)
                    timer = expires;

            if(!expires) {
                port->endTimer();
                port->expired();
                goto retry;
            }

            port = port->next;
        }

        //
        // reallocate things if we saw a ServerPort without
        // ufd set !
        if ( reallocate || ( ( count + 1 ) != lastcount ) ) {
            lastcount = count + 1;
            p_ufd = mfd.getList( count + 1 );

            // Set up iosync polling
            p_ufd->fd = iosync[0];
            p_ufd->events = POLLIN | POLLHUP;
            p_ufd ++;

            port = first;
            while(port) {
                p_ufd->fd = port->dev;
                p_ufd->events =
                    ( port->detect_disconnect ? POLLHUP : 0 )
                    | ( port->detect_output ? POLLOUT : 0 )
                    | ( port->detect_pending ? POLLIN : 0 )
                ;
                port->ufd = p_ufd;
                p_ufd ++;
                port = port->next;
            }
        }
        leaveMutex();

        poll( mfd.getList(), count + 1, timer );

#else
        enterMutex();
        onEvent();
        port = first;

        while(port) {
            onCallback(port);
            dev = port->dev;
            if(FD_ISSET(dev, &err)) {
                port->detect_disconnect = false;
                port->disconnect();
            }

            if(FD_ISSET(dev, &inp))
                port->pending();

            if(FD_ISSET(dev, &out))
                port->output();

retry:
            expires = port->getTimer();
            if(expires > 0)
                if(expires < timer)
                    timer = expires;

            if(!expires) {
                port->endTimer();
                port->expired();
                goto retry;
            }

            port = port->next;
        }

        FD_ZERO(&inp);
        FD_ZERO(&out);
        FD_ZERO(&err);
        int so;
        port = first;
        while(port) {
            so = port->dev;

            if(port->detect_pending)
                FD_SET(so, &inp);

            if(port->detect_output)
                FD_SET(so, &out);

            if(port->detect_disconnect)
                FD_SET(so, &err);

            port = port->next;
        }

        leaveMutex();
        if(timer == TIMEOUT_INF)
            tvp = NULL;
        else {
            tvp = &timeout;
            timeout.tv_sec = timer / 1000;
            timeout.tv_usec = (timer % 1000) * 1000;
        }
        select(hiwater, &inp, &out, &err, tvp);
#endif
    }
}

#endif

} // namespace ost
