/****************************************************************************
**
** Copyright (C) 2000-2008 TROLLTECH ASA. All rights reserved.
**
** This file is part of the Opensource Edition of the Qtopia Toolkit.
**
** This software is licensed under the terms of the GNU General Public
** License (GPL) version 2.
**
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
**
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#include <qserialportce.h>
#include <qsocketnotifier.h>
#include <qtimer.h>
#include <qdebug.h>
#include <QtGui>

#ifdef WINCE
#include <windows.h>
#include <winbase.h>
/* 10 ones to take 10 possible COMx: ports into account */
static int serial_ports[10] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
#else
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define USE_POSIX_SYSCALLS  1
#define USE_TERMIOS         1
#endif


class QSerialPortPrivate
{
public:
    QSerialPortPrivate( const QString& device, int rate, bool trackStatus )
    {
        this->device = device;
        this->rate   = rate;
        this->fd     = -1;
        this->handle = INVALID_HANDLE_VALUE;
        this->status = 0;
        this->track  = trackStatus;
        this->isTty  = false;
        this->dtr    = true;
        this->rts    = true;
        this->flowControl = false;
        this->keepOpen = true;
        this->notifier = 0;
        this->timer  = 0;
    }
    ~QSerialPortPrivate()
    {
        if ( notifier )
            delete notifier;
        if ( timer )
            delete timer;
    }

public:
    QString device;
    int     rate;
    int     fd;
    HANDLE handle;
    int     status;
    bool    track;
    bool    isTty;
    bool    dtr;
    bool    rts;
    bool    flowControl;
    bool    keepOpen;
    QSocketNotifier *notifier;
    QTimer *timer;
};

/*!
    \class QSerialPort
    \mainclass
    \brief The QSerialPort class provides a simple serial device interface.
    \ingroup io
    \ingroup telephony::serial

    This class manages a very simple serial device, which is accessed
    at a specific baud rate with no parity, 8 data bits, and 1 stop bit.
    It is intended for communicating with GSM modems and the like.

    The recommended way to create an instance of this class is to
    call the QSerialPort::create() method.

    \sa QSerialPort::create(), QSerialIODevice
*/

/*!
    Construct a new serial device handler object for the specified
    \a device at the given \a rate.  After construction, it is necessary
    to call QSerialPort::open() to complete initialization.

    If \a trackStatus is true, then the device should attempt to
    track changes in DSR, DCD, and CTS.  This may require the use
    of a regular timeout, which will be detrimental to battery life.

    Status tracking should only be enabled when absolutely required.
    It isn't required for modems that support GSM 07.10 multiplexing,
    as the multiplexing mechanism has its own method of tracking
    status changes that does not require the use of a timeout.

    The device name is usually something like \c{/dev/ttyS0}, but it can
    have the special form \c{sim:hostname}, where \c hostname is the name
    of a host running a phone simulator daemon (usually \c localhost).
    The phone simulator mode is intended for debugging purposes only.
*/
QSerialPort::QSerialPort( const QString& device, int rate, bool trackStatus )
{
    d = new QSerialPortPrivate( device, rate, trackStatus );
}

/*!
    Destruct this serial device.  If the device is currently open,
    it will be closed.
*/
QSerialPort::~QSerialPort()
{
    close();
    delete d;
}

/*!
    Returns the operating system file descriptor for this serial port,
    or -1 if the port is currently closed.

    \since 4.3
*/
int QSerialPort::fd() const
{
    return d->fd;
}

/*!
    Opens the serial device with the specified \a mode.
    Returns true if the device could be opened; false otherwise.
*/
bool QSerialPort::open( OpenMode mode )
{
    if ( isOpen() ) {
        return false;
    }
#ifdef WINCE

	HANDLE hCommPort = INVALID_HANDLE_VALUE;
	COMMTIMEOUTS ct;
	DCB dcb;

	hCommPort = CreateFile ( (LPCWSTR)d->device.utf16() ,
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		0,
		NULL);

 
	if(hCommPort == INVALID_HANDLE_VALUE) {
		return (HANDLE)-1;
	}

	ct.ReadIntervalTimeout = MAXDWORD;
	ct.ReadTotalTimeoutMultiplier = 0;
	ct.ReadTotalTimeoutConstant = 0;
	ct.WriteTotalTimeoutMultiplier = 10;
	ct.WriteTotalTimeoutConstant = 1000;
	if(!SetCommTimeouts(hCommPort, &ct)) {
		close();
		return (HANDLE)-1;
	}
	dcb.DCBlength = sizeof(DCB);
	if(!GetCommState(hCommPort, &dcb)) {
		close();
		return (HANDLE)-1;
	}

	dcb.fBinary			   = TRUE;
	dcb.BaudRate	       = d->rate;
	dcb.fOutxCtsFlow       = TRUE;
	dcb.fRtsControl        = RTS_CONTROL_DISABLE;
	dcb.fDtrControl        = DTR_CONTROL_DISABLE;
	dcb.fOutxDsrFlow       = FALSE;
	dcb.fOutX              = FALSE;
	dcb.fInX               = FALSE;
	dcb.ByteSize           = 8;
	dcb.Parity             = NOPARITY;
	dcb.StopBits           = ONESTOPBIT;
	dcb.EvtChar            = '\n';

	if(!SetCommState(hCommPort, &dcb)) {
		close();
		return (HANDLE)-1;
	}

   if (!SetCommMask(hCommPort, EV_RXCHAR | EV_RXFLAG))
      // Error setting communications mask
      qDebug()<<  "exit";
	
  d->handle=hCommPort;
	d->fd=(int)hCommPort;
#endif

    overlapThread = new Win_QextSerialThread(this);    
    
    // delayed start of the thread
    QTimer::singleShot(7000, this, SLOT(slotStartMonitor()));
    QIODevice::setOpenMode( mode | QIODevice::Unbuffered );

    return true;
}

void QSerialPort::slotWaitReady()
{
    
}

void QSerialPort::slotStartMonitor()
{
    overlapThread->start();
}

/*!
    Closes the serial device.
*/
void QSerialPort::close()
{
    if ( d->notifier ) {
        delete d->notifier;
        d->notifier = 0;
    }
    if ( d->timer ) {
        delete d->timer;
        d->timer = 0;
    }
#ifdef WINCE
	if (overlapThread->isRunning()) {
		overlapThread->stop();
		if (QThread::currentThread() != overlapThread)
			overlapThread->wait();
	}
	if (d->handle != INVALID_HANDLE_VALUE) {
		CloseHandle(d->handle);
		d->fd = -1;
	}
#endif
    setOpenMode( NotOpen );
}

/*!
    Returns true if able to flush all buffered data to the physical serial device; otherwise returns false.
*/
bool QSerialPort::flush()
{
    return true;
}

/*!
    \reimp
*/
bool QSerialPort::waitForReadyRead(int msecs)
{
    return false;                  
}

/*!
    \reimp
*/
qint64 QSerialPort::bytesAvailable() const
{
    return 0;
}

/*!
    \reimp
*/
qint64 QSerialPort::readData( char *data, qint64 maxlen )
{
#ifdef WINCE
   DWORD dwBytesRead;
   // Issue read operation.
   if (!ReadFile((HANDLE)d->handle, (void *)data, (DWORD)maxlen , &dwBytesRead, NULL)) {
         return -1;
   }
   else {    
      // read completed immediately
      return dwBytesRead;
    }
#endif
}


/*!
    \reimp
*/
qint64 QSerialPort::writeData( const char *data, qint64 len )
{
#ifdef WINCE
   DWORD dwBytesWritten;

   if(!WriteFile((HANDLE)d->handle,
                data,
                (int)len,
                &dwBytesWritten,
                NULL)) {
      return -1;
   }

   return dwBytesWritten;
#endif

}

/*!
    \reimp
*/
int QSerialPort::rate() const
{
    return d->rate;
}

/*!
    Returns the state of CTS/RTS flow control on the serial device.
    The default value is false.

    \sa setFlowControl()
*/
bool QSerialPort::flowControl() const
{
    return d->flowControl;
}

/*!
    Sets the state of CTS/RTS flow control on the serial device to \a value.
    This must be called before QSerialPort::open().  Changes to
    the value after opening will be ignored.

    \sa flowControl()
*/
void QSerialPort::setFlowControl( bool value )
{
    d->flowControl = value;
}

/*!
    Returns true if tty devices should be kept open on a zero-byte read; otherwise returns false.
    The default value is true.

    Some serial devices return zero bytes when there is no data available,
    instead of returning the system error \c EWOULDBLOCK.  When keepOpen()
    is true, a zero-byte read will be treated the same as \c EWOULDBLOCK.
    When keepOpen() is false, a zero-byte read will be interpreted as an
    unrecoverable error and the serial port will be closed.

    For Bluetooth RFCOMM sockets, keepOpen() should be false.

    The keepOpen() state is ignored if the underlying serial device is a
    socket connection to a phone simulator.

    \sa setKeepOpen()
*/
bool QSerialPort::keepOpen() const
{
    return d->keepOpen;
}

/*!
    Sets the keep open flag to \a value.  The default value is true.

    Some serial devices return zero bytes when there is no data available,
    instead of returning the system error \c EWOULDBLOCK.  When \a value
    is true, a zero-byte read will be treated the same as \c EWOULDBLOCK.
    When \a value is false, a zero-byte read will be interpreted as an
    unrecoverable error and the serial port will be closed.

    For Bluetooth RFCOMM sockets, \a value should be false.

    The state of \a value is ignored if the underlying serial device is a
    socket connection to a phone simulator.

    \sa keepOpen()
*/
void QSerialPort::setKeepOpen( bool value )
{
    d->keepOpen = value;
}

/*!
    \reimp
*/
bool QSerialPort::dtr() const
{
    return d->dtr;
}

/*!
    \reimp
*/
void QSerialPort::setDtr( bool value )
{
#ifdef USE_TERMIOS
    if ( d->fd != -1 && d->isTty ) {
        int status = TIOCM_DTR;
        if ( value )
            ::ioctl( d->fd, TIOCMBIS, &status );
        else
            ::ioctl( d->fd, TIOCMBIC, &status );
        d->dtr = value;
    }
#endif
}

/*!
    \reimp
*/
bool QSerialPort::dsr() const
{
#ifdef USE_TERMIOS
    if ( d->fd != -1 && d->isTty ) {
        int status = 0;
        ::ioctl( d->fd, TIOCMGET, &status );
        return ( ( status & TIOCM_DSR ) != 0 );
    }
#endif
    return true;
}

/*!
    \reimp
*/
bool QSerialPort::carrier() const
{
#ifdef USE_TERMIOS
    if ( d->fd != -1 && d->isTty ) {
        int status = 0;
        ::ioctl( d->fd, TIOCMGET, &status );
        return ( ( status & TIOCM_CAR ) != 0 );
    }
#endif
    return true;
}

/*!
    \reimp
*/
bool QSerialPort::rts() const
{
    return d->rts;
}

/*!
    \reimp
*/
void QSerialPort::setRts( bool value )
{
#ifdef USE_TERMIOS
    if ( d->fd != -1 && d->isTty ) {
        int status = TIOCM_RTS;
        if ( value )
            ::ioctl( d->fd, TIOCMBIS, &status );
        else
            ::ioctl( d->fd, TIOCMBIC, &status );
        d->rts = value;
    }
#endif
}

/*!
    \reimp
*/
bool QSerialPort::cts() const
{
#ifdef USE_TERMIOS
    if ( d->fd != -1 && d->isTty ) {
        int status = 0;
        ::ioctl( d->fd, TIOCMGET, &status );
        return ( ( status & TIOCM_CTS ) != 0 );
    }
#endif
    return true;
}

/*!
    \reimp
*/
void QSerialPort::discard()
{
#ifdef USE_TERMIOS
    if ( d->fd != -1 && d->isTty ) {
        ::tcflush( d->fd, TCIOFLUSH );
    }
#endif
}

/*!
    \reimp
*/
bool QSerialPort::isValid() const
{
    return QSerialIODevice::isValid();
}

/*!
    \reimp

    This class overrides QSerialIODevice::run() to run pppd directly on the
    underlying device name so that it isn't necessary to create a pseudo-tty.
    This is generally more efficient for running pppd.
*/
QProcess *QSerialPort::run( const QStringList& arguments,
                              bool addPPPdOptions )
{
    if ( addPPPdOptions && d->isTty ) {
        // Make sure we aren't using the device before handing it off to pppd.
        if ( isOpen() )
            close();

        // Build a new argument list for pppd, with the device name in place.
        QStringList newargs;
        newargs << d->device;
        newargs << QString::number( d->rate );
        for ( int index = 1; index < arguments.size(); ++index )
            newargs << arguments[index];

        // Run the process.  When it exits, open() will be called again.
        QProcess *process = new QProcess();
        process->setReadChannelMode( QProcess::ForwardedChannels );
        connect( process, SIGNAL(stateChanged(QProcess::ProcessState)),
                 this, SLOT(pppdStateChanged(QProcess::ProcessState)) );
        connect( process, SIGNAL(destroyed()), this, SLOT(pppdDestroyed()) );
        process->start( arguments[0], newargs );
        return process;
    } else {
        return QSerialIODevice::run( arguments, addPPPdOptions );
    }
}

void QSerialPort::statusTimeout()
{
}

void QSerialPort::pppdStateChanged( QProcess::ProcessState state )
{
    if ( state == QProcess::NotRunning && !isOpen() ) {
        // The process has exited, so re-open the device for our own use.
        open( QIODevice::ReadWrite );
    }
}

void QSerialPort::pppdDestroyed()
{
    if ( !isOpen() ) {
        // The process has been killed, so re-open the device for our own use.
        open( QIODevice::ReadWrite );
    }
}

/*!
    Create and open a serial device from a \a name of the form \c{device:rate}.
    Returns NULL if the device could not be opened.  The \a defaultRate
    parameter indicates the default rate to use if \c{:rate} was not included
    in the device name.  If \a flowControl is true, then CTS/RTS flow
    control should be enabled on the device.

    The \a name parameter can have the special form \c{sim:hostname},
    where \c hostname is the name of a host running a phone simulator daemon
    (usually \c localhost).  The phone simulator mode is intended for
    debugging purposes only.
*/
QSerialPort *QSerialPort::create( const QString& name, int defaultRate,
                                    bool flowControl )
{
    int rate = defaultRate;
    QString dev = name;
    if ( dev.length() > 0 && dev[0] == '/' ) {
        int index = dev.indexOf(QChar(':'));
        if ( index != -1 ) {
            rate = dev.mid( index + 1 ).toInt();
            dev = dev.left( index );
        }
    }
    qDebug() << "opening serial device " << dev << " at " << rate << "\n";
    QSerialPort *device = new QSerialPort( dev, rate );
    device->setFlowControl( flowControl );
    if ( !device->open( ReadWrite ) ) {
        qWarning() << "Failed opening " << dev;
        delete device;
        return 0;
    } else {
        qDebug() << "Opened " << dev << "\n";
        return device;
    }
}

#ifdef WINCE

void QSerialPort::monitorCommEvent()
{
	DWORD eventMask; // = 0;
	//emit message("monitoring event\n");
  SetCommMask (d->handle, EV_RXFLAG | EV_CTS | EV_DSR | EV_RLSD | EV_RING);
  if (WaitCommEvent (d->handle, &eventMask, 0) ) { 
    SetCommMask (d->handle, EV_RXFLAG | EV_CTS | EV_DSR | EV_RLSD | EV_RING);
	  if (sender() != this)
			emit readyRead();
	}
}

void QSerialPort::terminateCommWait()
{
	SetCommMask(d->handle, 0);
}


/*!
\fn void Win_QextSerialPort::setTimeout(ulong millisec);
Sets the read and write timeouts for the port to millisec milliseconds.
Setting 0 for both sec and millisec indicates that timeouts are not used for read nor
write operations. Setting -1 indicates that read and write should return immediately.

\note this function does nothing in event driven mode.
*/
/*
void QSerialPort::setTimeout(long millisec) {
    //LOCK_MUTEX();
    Settings.Timeout_Millisec = millisec;

	if (millisec == -1) {
		Win_CommTimeouts.ReadIntervalTimeout = MAXDWORD;
		Win_CommTimeouts.ReadTotalTimeoutConstant = 0;
	} else {
		Win_CommTimeouts.ReadIntervalTimeout = millisec;
		Win_CommTimeouts.ReadTotalTimeoutConstant = millisec;
	}
	Win_CommTimeouts.ReadTotalTimeoutMultiplier = 0;
	Win_CommTimeouts.WriteTotalTimeoutMultiplier = millisec;
	Win_CommTimeouts.WriteTotalTimeoutConstant = 0;
	//if (queryMode() != QextSerialBase::EventDriven)
      	SetCommTimeouts(d->handle, &Win_CommTimeouts);

    //UNLOCK_MUTEX();
}
*/

Win_QextSerialThread::Win_QextSerialThread(QSerialPort * qesp):
	QThread()
{
	this->qesp = qesp;
	terminate = false;
}

void Win_QextSerialThread::stop()
{
	terminate = true;
	qesp->terminateCommWait();
}

void Win_QextSerialThread::run()
{
  //emit message("Thread run\n");
	while (!terminate) {
		qesp->monitorCommEvent();
		msleep(250);
	}
	terminate = false;
	//emit message("Thread quit\n");
}

#endif