#include "stdafx.h"
#include "COMPort.h"
#include "Timeout.h"
#include "Timer.h"
#include "Error.h"
#include "Trace.h"

IoACOMPort::IoACOMPort( )
{
    m_nPort = 1;

    m_nInputQueueSize  = 1024;
    m_nOutputQueueSize = 1024;

    m_hCOMPort = INVALID_HANDLE_VALUE;

    ::ZeroMemory( &m_DCB, sizeof( m_DCB ) );

    m_OverlappedRead .hEvent = NULL;
    m_OverlappedWrite.hEvent = NULL;
    m_OverlappedWait .hEvent = NULL;
}

int IoACOMPort::SetPort( int Port )
{
    if ( Port < 1 || Port > 256 ) return FALSE;

    if ( IsOpen( ) ) return FALSE;

    m_nPort = Port;

    return TRUE;
}

int IoACOMPort::SetInputQueueSize( int InputQueueSize )
{
    if ( InputQueueSize <= 0 ) return FALSE;

    if ( IsOpen( ) ) return FALSE;

    m_nInputQueueSize = InputQueueSize;

    return TRUE;
}

int IoACOMPort::SetOutputQueueSize( int OutputQueueSize )
{
    if ( OutputQueueSize <= 0 ) return FALSE;

    if ( IsOpen( ) ) return FALSE;

    m_nOutputQueueSize = OutputQueueSize;

    return TRUE;
}

int IoACOMPort::Open( )
{
    // Make sure COM port is closed before attempting to open it
    Close( );

    m_hCOMPort = INVALID_HANDLE_VALUE;

    m_OverlappedRead .hEvent = NULL;
    m_OverlappedWrite.hEvent = NULL;
    m_OverlappedWait .hEvent = NULL;

    try
    {
        // Open COM port
        CString Port;

        Port.Format( "\\\\.\\COM%d", m_nPort );

        m_hCOMPort = ::CreateFile( Port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL );
//        m_hCOMPort = ::CreateFile( Port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL );

        if( m_hCOMPort == INVALID_HANDLE_VALUE ) ThrowIoAErrorNumber( 1 );

        IoATrace( eTraceCOMPortDiagnostic, "IoACOMPort::Open( ): COM port file %s is open\n", (LPCTSTR) Port );

        // Save current serial comms settings
        DCB dcb;

        if( ! ::GetCommState( m_hCOMPort, &dcb ) ) ThrowIoAErrorNumber( 2 );

        m_DCB = dcb;  // keep copy for restoring current settings on Close( )

        // Establish which baud rates are valid
        IoASerialLink::ClearSupportedBaudRates( );

        for ( int i = 0; i < CandidateBaudRateCount( ); i++ )
        {
            int BaudRate = CandidateBaudRate( i );

            dcb.BaudRate = BaudRate;

            if( ! ::SetCommState( m_hCOMPort, &dcb ) )
            {
                IoATrace( eTraceCOMPortDiagnostic, "IoACOMPort::Open( ): baud rate %d is not supported\n", BaudRate );
                continue;
            }

            if( ! ::GetCommState( m_hCOMPort, &dcb ) ) ThrowIoAErrorNumber( 3 );

            if ( BaudRate != (int) dcb.BaudRate )
            {
                IoATrace( eTraceCOMPortDiagnostic, "IoACOMPort::Open( ): Baud rate %d converted to %d\n", BaudRate, dcb.BaudRate );
                continue;
            }

            IoASerialLink::AddSupportedBaudRate( BaudRate );

            IoATrace( eTraceCOMPortDiagnostic, "IoACOMPort::Open( ): baud rate %d is OK\n", BaudRate );
        }

        IoATrace( eTraceCOMPortDiagnostic, "IoACOMPort::Open( ): number of valid baud rates = %d\n", SupportedBaudRateCount( ) );

        for ( int j = 0; j < SupportedBaudRateCount( ); j++ )
        {
            IoATrace( eTraceCOMPortDiagnostic, "IoACOMPort::Open( ): valid baud rate[ %2d ] = %d\n", j, SupportedBaudRate( j ) );
        }

        // Fault invalid baud rate
        if ( ! IsSupportedBaudRate( BaudRate( ) ) ) ThrowIoAErrorNumber( 4 );

        // Set up serial comms parameters
        dcb.BaudRate = BaudRate( );
        dcb.Parity   = NOPARITY;
        dcb.StopBits = ONESTOPBIT;
        dcb.ByteSize = 8;
        dcb.fOutxCtsFlow = TRUE;
        dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
        dcb.fAbortOnError = TRUE;

//        dcb.fParity = FALSE;
//        dcb.fOutxDsrFlow = FALSE;
//        dcb.fDtrControl = DTR_CONTROL_ENABLE;
//        dcb.fDsrSensitivity = FALSE;
//        dcb.fTXContinueOnXoff = FALSE;
//        dcb.fOutX = FALSE;
//        dcb.fInX = FALSE;
//        dcb.fErrorChar = FALSE;
//        dcb.fNull = FALSE;

//		dcb.EvtChar = '>';

        if( ! ::SetCommState( m_hCOMPort, &dcb ) ) ThrowIoAErrorNumber( 5 );

        // Disable all serial comms events
        if( ! ::SetCommMask( m_hCOMPort, NULL ) ) ThrowIoAErrorNumber( 6 );

        // Set up I/O buffer sizes
        if ( ! ::SetupComm( m_hCOMPort, m_nInputQueueSize, m_nOutputQueueSize ) ) ThrowIoAErrorNumber( 7 );

        // Purge I/O buffers
        if( ! ::PurgeComm( m_hCOMPort, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ) ThrowIoAErrorNumber( 8 );

        // Set up serial comms timeouts
        COMMTIMEOUTS CommTimeouts;

        CommTimeouts.ReadIntervalTimeout        = 0;  // => no input timeout
        CommTimeouts.ReadTotalTimeoutMultiplier = 0;
        CommTimeouts.ReadTotalTimeoutConstant   = 0;

        CommTimeouts.WriteTotalTimeoutMultiplier = 0;  // => no output timeout
        CommTimeouts.WriteTotalTimeoutConstant   = 0;

        if ( ! ::SetCommTimeouts( m_hCOMPort, &CommTimeouts ) ) ThrowIoAErrorNumber( 9 );

        // Create event objects to support overlapped I/O
        if ( ( m_OverlappedRead .hEvent = ::CreateEvent( NULL, TRUE, FALSE, NULL ) ) == NULL ) ThrowIoAErrorNumber( 10 );
        if ( ( m_OverlappedWrite.hEvent = ::CreateEvent( NULL, TRUE, FALSE, NULL ) ) == NULL ) ThrowIoAErrorNumber( 11 );
        if ( ( m_OverlappedWait .hEvent = ::CreateEvent( NULL, TRUE, FALSE, NULL ) ) == NULL ) ThrowIoAErrorNumber( 12 );

//        if( ! ::SetCommMask( m_hCOMPort, EV_RXFLAG ) ) ThrowIoAErrorNumber( 13 );

        // All done
        IoASerialLink::Open( );

        return TRUE;
    }
    catch( IoAError Error )
    {
        IoATrace( eTraceCOMPortError, "IoACOMPort::Open( ): error %d thrown.\n", Error.Number( ) );
        IoATrace( eTraceCOMPortError, "Error thrown in %s at line %d.\n", Error.File( ), Error.Line( ) );

        DWORD ErrorCode = ::GetLastError();

        LPVOID pErrorString;

        ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                       NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &pErrorString, 0, NULL );

        IoATrace( eTraceCOMPortError, "[0x%x] %s", ErrorCode, pErrorString );

        ::LocalFree( pErrorString );

        // Clean up system resources
        if( m_hCOMPort != INVALID_HANDLE_VALUE )
        {
            if ( m_DCB.DCBlength )
            {
                ::SetCommState( m_hCOMPort, &m_DCB ); // restore serial comms settings
            }

            ::CloseHandle( m_hCOMPort );
        }
    
        if ( m_OverlappedRead .hEvent != NULL ) ::CloseHandle( m_OverlappedRead .hEvent );
        if ( m_OverlappedWrite.hEvent != NULL ) ::CloseHandle( m_OverlappedWrite.hEvent );
        if ( m_OverlappedWait .hEvent != NULL ) ::CloseHandle( m_OverlappedWait .hEvent );

        return FALSE;
    }
}

void IoACOMPort::Close( )
{
    if ( IsOpen( ) )
    {
        // Notify serial link object
        IoASerialLink::Close( );  // must do this before shutting down COM port so that any attached devices can be shut down in an orderly way

        if( m_hCOMPort != INVALID_HANDLE_VALUE )
        {
            if ( m_DCB.DCBlength )
            {
                ::SetCommState( m_hCOMPort, &m_DCB ); // restore serial comms settings
            }

            ::CloseHandle( m_hCOMPort );
        }

        if ( m_OverlappedRead .hEvent != NULL ) ::CloseHandle( m_OverlappedRead .hEvent );
        if ( m_OverlappedWrite.hEvent != NULL ) ::CloseHandle( m_OverlappedWrite.hEvent );
        if ( m_OverlappedWait .hEvent != NULL ) ::CloseHandle( m_OverlappedWait .hEvent );
    }
}

int IoACOMPort::ReadChar( unsigned char& Cha, DWORD TimeoutInMilliseconds )
{
    if ( ! IsOpen( ) ) return FALSE;

    DWORD nBytes;

    // Start asynchronous read
	m_OverlappedRead.Offset = m_OverlappedRead.OffsetHigh = 0;  // must zero file offset

    if ( ::ReadFile( m_hCOMPort, &Cha, 1, &nBytes, &m_OverlappedRead ) )
    {
        // Immediate completion of read operation
        if ( nBytes == 1 ) return TRUE;
    }
    else if ( ::GetLastError( ) == ERROR_IO_PENDING )  // read operation still in progress
    {
        // Wait for read operation to complete
        switch( ::WaitForSingleObject( m_OverlappedRead.hEvent, TimeoutInMilliseconds ) )
        {
            // Delayed completion of read operation
            case WAIT_OBJECT_0:
            {
                if ( ::GetOverlappedResult( m_hCOMPort, &m_OverlappedRead, &nBytes, FALSE ) )
                {
                    if ( nBytes == 1 ) return TRUE;
                }
                else
                {
                    ClearCommError( "ReadChar" );  // read opertaion has failed
                }
            }
            break;

            // Read operation has exceeded time limit
            case WAIT_TIMEOUT:
            {
                ::CancelIo( m_hCOMPort );

                IoATrace( eTraceCOMPortError, "IoACOMPort::ReadChar( ) timed out.\n" );
            }
        }
    }
    else
    {
        ClearCommError( "ReadChar" );  // read opertaion has failed
    }

    return FALSE;
}

int IoACOMPort::ReadChar( unsigned char& Cha, const IoATimer& Timeout )
{
    if ( ! Timeout.IsActive( ) ) return FALSE;

    if ( ! IsOpen( ) ) return FALSE;

    DWORD nBytes;

    // Start asynchronous read
	m_OverlappedRead.Offset = m_OverlappedRead.OffsetHigh = 0;  // must zero file offset

    if ( ::ReadFile( m_hCOMPort, &Cha, 1, &nBytes, &m_OverlappedRead ) )
    {
        // Immediate completion of read operation
        if ( nBytes == 1 ) return TRUE;
    }
    else if ( ::GetLastError( ) == ERROR_IO_PENDING )  // read operation still in progress
    {
        // Wait for read operation to complete
        HANDLE ObjectHandles[ 2 ];

        ObjectHandles[ 0 ] = m_OverlappedRead.hEvent;
        ObjectHandles[ 1 ] = Timeout.Handle( );

        switch( ::WaitForMultipleObjects( 2, ObjectHandles, FALSE, INFINITE ) )
        {
            // Delayed completion of read operation
            case WAIT_OBJECT_0:
            {
                if ( ::GetOverlappedResult( m_hCOMPort, &m_OverlappedRead, &nBytes, FALSE ) )
                {
                    if ( nBytes == 1 ) return TRUE;
                }
                else
                {
                    ClearCommError( "ReadChar" );  // read opertaion has failed
                }
            }
            break;

            // Read operation has exceeded time limit
            case WAIT_OBJECT_0 + 1:
            {
                ::CancelIo( m_hCOMPort );

                IoATrace( eTraceCOMPortError, "IoACOMPort::ReadChar( ) timed out.\n" );
            }
        }
    }
    else
    {
        ClearCommError( "ReadChar" );  // read opertaion has failed
    }

    return FALSE;
}

int IoACOMPort::WriteChar( unsigned char Cha, DWORD TimeoutInMilliseconds )
{
    if ( ! IsOpen( ) ) return FALSE;

    DWORD nBytes;

    // Start asynchronous write
	m_OverlappedWrite.Offset = m_OverlappedWrite.OffsetHigh = 0;  // must zero file offset

    if ( ::WriteFile( m_hCOMPort, &Cha, 1, &nBytes, &m_OverlappedWrite ) )
    {
        // Immediate completion of write operation
        if ( nBytes == 1 ) return TRUE;
    }
    else if ( ::GetLastError( ) == ERROR_IO_PENDING )  // write operation still in progress
    {
        // Wait for write operation to complete
        switch( ::WaitForSingleObject( m_OverlappedWrite.hEvent, TimeoutInMilliseconds ) )
        {
            // Delayed completion of write operation
            case WAIT_OBJECT_0:
            {
                if ( ::GetOverlappedResult( m_hCOMPort, &m_OverlappedWrite, &nBytes, FALSE ) )
                {
                    if ( nBytes == 1 ) return TRUE;
                }
                else
                {
                    ClearCommError( "WriteChar" );  // write operation has failed
                }
            }
            break;

            // Write operation has exceeded time limit
            case WAIT_TIMEOUT:
            {
                ::CancelIo( m_hCOMPort );

                IoATrace( eTraceCOMPortError, "IoACOMPort::WriteChar( ) timed out.\n" );
            }
        }
    }
    else
    {
        ClearCommError( "WriteChar" );  // write operation has failed
    }

    return FALSE;
}

int IoACOMPort::WriteChar( unsigned char Cha, const IoATimer& Timeout )
{
    if ( ! Timeout.IsActive( ) ) return FALSE;

    if ( ! IsOpen( ) ) return FALSE;

    DWORD nBytes;

    // Start asynchronous write
	m_OverlappedWrite.Offset = m_OverlappedWrite.OffsetHigh = 0;  // must zero file offset

    if ( ::WriteFile( m_hCOMPort, &Cha, 1, &nBytes, &m_OverlappedWrite ) )
    {
        // Immediate completion of write operation
        if ( nBytes == 1 ) return TRUE;
    }
    else if ( ::GetLastError( ) == ERROR_IO_PENDING )  // write operation still in progress
    {
        // Wait for write operation to complete
        HANDLE ObjectHandles[ 2 ];

        ObjectHandles[ 0 ] = m_OverlappedWrite.hEvent;
        ObjectHandles[ 1 ] = Timeout.Handle( );

        switch( ::WaitForMultipleObjects( 2, ObjectHandles, FALSE, INFINITE ) )
        {
            // Delayed completion of write operation
            case WAIT_OBJECT_0:
            {
                if ( ::GetOverlappedResult( m_hCOMPort, &m_OverlappedWrite, &nBytes, FALSE ) )
                {
                    if ( nBytes == 1 ) return TRUE;
                }
                else
                {
                    ClearCommError( "WriteChar" );  // write operation has failed
                }
            }
            break;

            // Write operation has exceeded time limit
            case WAIT_OBJECT_0 + 1:
            {
                ::CancelIo( m_hCOMPort );

                IoATrace( eTraceCOMPortError, "IoACOMPort::WriteChar( ) timed out.\n" );
            }
        }
    }
    else
    {
        ClearCommError( "WriteChar" );  // write operation has failed
    }

    return FALSE;
}

int IoACOMPort::ReadBytes( unsigned char *Buffer, int Size, DWORD TimeoutInMilliseconds )
{
    if ( Buffer == NULL ) return FALSE;
    if ( Size <= 0 ) return FALSE;
    if ( TimeoutInMilliseconds < 0 ) return FALSE;

    if ( ! IsOpen( ) ) return FALSE;

    DWORD nBytes;

    // Start asynchronous read
	m_OverlappedRead.Offset = m_OverlappedRead.OffsetHigh = 0;  // must zero file offset

    if ( ::ReadFile( m_hCOMPort, Buffer, Size, &nBytes, &m_OverlappedRead ) )
    {
        // Immediate completion of read operation
        if ( (int) nBytes == Size ) return TRUE;
    }
    else if ( ::GetLastError( ) == ERROR_IO_PENDING )  // read operation still in progress
    {
        // Wait for read operation to complete
        switch( ::WaitForSingleObject( m_OverlappedRead.hEvent, TimeoutInMilliseconds ) )
        {
            // Delayed completion of write operation
            case WAIT_OBJECT_0:
            {
                if ( ::GetOverlappedResult( m_hCOMPort, &m_OverlappedRead, &nBytes, FALSE ) )
                {
                    if ( (int) nBytes == Size ) return TRUE;
                }
                else
                {
                    ClearCommError( "ReadBytes" );  // read operation has failed
                }
            }
            break;

            // Read operation has exceeded time limit
            case WAIT_TIMEOUT:
            {
                ::CancelIo( m_hCOMPort );

                IoATrace( eTraceCOMPortError, "IoACOMPort::ReadChar( ) timed out.\n" );
            }
        }
    }
    else
    {
        ClearCommError( "ReadBytes" );  // read operation has failed
    }

    return FALSE;
}

int IoACOMPort::ReadBytes( unsigned char *Buffer, int Size, const IoATimer& Timeout )
{
    if ( Buffer == NULL ) return FALSE;
    if ( Size <= 0 ) return FALSE;
    if ( ! Timeout.IsActive( ) ) return FALSE;

    if ( ! IsOpen( ) ) return FALSE;

    DWORD nBytes;

    // Start asynchronous read
	m_OverlappedRead.Offset = m_OverlappedRead.OffsetHigh = 0;  // must zero file offset

    if ( ::ReadFile( m_hCOMPort, Buffer, Size, &nBytes, &m_OverlappedRead ) )
    {
        // Immediate completion of read operation
        if ( (int) nBytes == Size ) return TRUE;
    }
    else if ( ::GetLastError( ) == ERROR_IO_PENDING )  // read operation still in progress
    {
        // Wait for read operation to complete
        HANDLE ObjectHandles[ 2 ];

        ObjectHandles[ 0 ] = m_OverlappedRead.hEvent;
        ObjectHandles[ 1 ] = Timeout.Handle( );

        switch( ::WaitForMultipleObjects( 2, ObjectHandles, FALSE, INFINITE ) )
        {
            // Delayed completion of write operation
            case WAIT_OBJECT_0:
            {
                if ( ::GetOverlappedResult( m_hCOMPort, &m_OverlappedRead, &nBytes, FALSE ) )
                {
                    if ( (int) nBytes == Size ) return TRUE;
                }
                else
                {
                    ClearCommError( "ReadBytes" );  // read operation has failed
                }
            }
            break;

            // Read operation has exceeded time limit
            case WAIT_OBJECT_0 + 1:
            {
                ::CancelIo( m_hCOMPort );

                IoATrace( eTraceCOMPortError, "IoACOMPort::ReadChar( ) timed out.\n" );
            }
        }
    }
    else
    {
        ClearCommError( "ReadBytes" );  // read operation has failed
    }

    return FALSE;
}

int IoACOMPort::WriteBytes( const unsigned char *Buffer, int Size, DWORD TimeoutInMilliseconds )
{
    if ( Buffer == NULL ) return FALSE;
    if ( Size <= 0 ) return FALSE;
    if ( TimeoutInMilliseconds < 0 ) return FALSE;

    if ( ! IsOpen( ) ) return FALSE;

    DWORD nBytes;

	int Status = 0;

    // Start asynchronous write
	m_OverlappedWrite.Offset = m_OverlappedWrite.OffsetHigh = 0;  // must zero file offset

    if ( ::WriteFile( m_hCOMPort, Buffer, Size, &nBytes, &m_OverlappedWrite ) )
    {
        // Immediate completion of write operation
        if ( (int) nBytes == Size ) return TRUE;
    }
    else if ( ( Status = ::GetLastError( ) ) == ERROR_IO_PENDING )  // write operation still in progress
    {
        // Wait for write operation to complete
        switch( ::WaitForSingleObject( m_OverlappedWrite.hEvent, TimeoutInMilliseconds ) )
        {
            // Delayed completion of write operation
            case WAIT_OBJECT_0:
            {
                if ( ::GetOverlappedResult( m_hCOMPort, &m_OverlappedWrite, &nBytes, FALSE ) )
                {
                    if ( (int) nBytes == Size ) return TRUE;
                }
                else
                {
                    ClearCommError( "WriteBytes" );  // write operation has failed
                }
            }
            break;

            // Write operation has exceeded time limit
            case WAIT_TIMEOUT:
            {
                ::CancelIo( m_hCOMPort );

                IoATrace( eTraceCOMPortError, "IoACOMPort::WriteChar( ) timed out.\n" );
            }
        }
    }
    else
    {
        ClearCommError( "WriteBytes" );  // write operation has failed
    }

    return FALSE;
}

int IoACOMPort::WriteBytes( const unsigned char *Buffer, int Size, const IoATimer& Timeout )
{
    if ( Buffer == NULL ) return FALSE;
    if ( Size <= 0 ) return FALSE;
    if ( ! Timeout.IsActive( ) ) return FALSE;

    if ( ! IsOpen( ) ) return FALSE;

    DWORD nBytes;

    // Start asynchronous write
	m_OverlappedWrite.Offset = m_OverlappedWrite.OffsetHigh = 0;  // must zero file offset

    if ( ::WriteFile( m_hCOMPort, Buffer, Size, &nBytes, &m_OverlappedWrite ) )
    {
        // Immediate completion of write operation
        if ( (int) nBytes == Size ) return TRUE;
    }
    else if ( ::GetLastError( ) == ERROR_IO_PENDING )  // write operation still in progress
    {
        // Wait for write operation to complete
        HANDLE ObjectHandles[ 2 ];

        ObjectHandles[ 0 ] = m_OverlappedWrite.hEvent;
        ObjectHandles[ 1 ] = Timeout.Handle( );

        switch( ::WaitForMultipleObjects( 2, ObjectHandles, FALSE, INFINITE ) )
        {
            // Delayed completion of write operation
            case WAIT_OBJECT_0:
            {
                if ( ::GetOverlappedResult( m_hCOMPort, &m_OverlappedWrite, &nBytes, FALSE ) )
                {
                    if ( (int) nBytes == Size ) return TRUE;
                }
                else
                {
                    ClearCommError( "WriteBytes" );  // write operation has failed
                }
            }
            break;

            // Write operation has exceeded time limit
            case WAIT_OBJECT_0 + 1:
            {
                ::CancelIo( m_hCOMPort );

                IoATrace( eTraceCOMPortError, "IoACOMPort::WriteChar( ) timed out.\n" );
            }
        }
    }
    else
    {
        ClearCommError( "WriteBytes" );  // write operation has failed
    }

    return FALSE;
}

int IoACOMPort::QueuedInput( ) const
{
    int nQueued = 0;

    if ( IsOpen( ) )
    {
        DWORD dwErrorFlags;
        COMSTAT ComStat;

        if ( ::ClearCommError( m_hCOMPort, &dwErrorFlags, &ComStat ) )
        {
            nQueued = ComStat.cbInQue;
        }
    }

    return nQueued;
}

int IoACOMPort::QueuedOutput( ) const
{
    int nQueued = 0;

    if ( IsOpen( ) )
    {
        DWORD dwErrorFlags;
        COMSTAT ComStat;

        if ( ::ClearCommError( m_hCOMPort, &dwErrorFlags, &ComStat ) )
        {
            nQueued = ComStat.cbOutQue;
        }
    }

    return nQueued;
}

int IoACOMPort::WaitForInput( double Timeout, int ByteCount )
{
    if ( ByteCount < 0 ) return FALSE;

    if ( IsOpen( ) )
    {
        for ( IoATimeout Timeout( Timeout ); Timeout.IsActive( ); ::Sleep( 1 ) )
        {
            if ( QueuedInput( ) >= ByteCount ) return TRUE;
        }

        IoATrace( eTraceCOMPortError, "IoACOMPort::WaitForInput( ) timed out.\n" );
    }

    return FALSE;
}

int IoACOMPort::WaitForOutput( double Timeout, int ByteCount )
{
    if ( ByteCount < 0 ) return FALSE;

    if ( IsOpen( ) )
    {
        for ( IoATimeout Timeout( Timeout ); Timeout.IsActive( ); ::Sleep( 1 ) )
        {
            if ( QueuedOutput( ) <= ByteCount ) return TRUE;
        }

        IoATrace( eTraceCOMPortError, "IoACOMPort::WaitForOutput( ) timed out.\n" );
    }

    return FALSE;
}

int IoACOMPort::Purge( )
{
    if ( IsOpen( ) )
    {
        if ( ::PurgeComm( m_hCOMPort, PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR ) ) return TRUE;
    }

    return FALSE;
}

int IoACOMPort::PurgeInput( )
{
    if ( IsOpen( ) )
    {
        if ( ::PurgeComm( m_hCOMPort, PURGE_RXABORT | PURGE_RXCLEAR ) ) return TRUE;
    }

    return FALSE;
}

int IoACOMPort::PurgeOutput( )
{
    if ( IsOpen( ) )
    {
        if ( ::PurgeComm( m_hCOMPort, PURGE_TXABORT | PURGE_TXCLEAR ) ) return TRUE;
    }

    return FALSE;
}

int IoACOMPort::Break( int On )
{
    if ( ! IsOpen( ) ) return FALSE;

    if ( On )
    {
        if ( ! ::SetCommBreak( m_hCOMPort ) ) return FALSE;
    }
    else
    {
        if ( ! ::ClearCommBreak( m_hCOMPort ) ) return FALSE;
    }

    return TRUE;
}

int IoACOMPort::Break( double Duration )
{
    IoATimer Timer;

    if ( ! Timer.IsCreated( ) ) return FALSE;

    if ( ! Break( TRUE ) ) return FALSE;

    if ( ! Timer.Wait( Duration ) ) return FALSE;

    if ( ! Break( FALSE ) ) return FALSE;

    return TRUE;
}

int IoACOMPort::SetBaudRate( int BaudRate )
{
    if ( IsOpen( ) )
    {
        // Check baud rate
        if ( ! IsSupportedBaudRate( BaudRate ) )
        {
            IoATrace( eTraceCOMPortError, "IoACOMPort::SetBaudRate( ): invalid baud rate = %d\n" ); return FALSE;
        }

        DCB dcb;

        // Fetch current UART settings
        if( ! ::GetCommState( m_hCOMPort, &dcb ) )
        {
            IoATrace( eTraceCOMPortError, "IoACOMPort::SetBaudRate( ): call to ::GetCommState( ) failed\n" ); return FALSE;
        }

        IoATrace( eTraceCOMPortDiagnostic, "IoACOMPort::SetBaudRate( ): Old baud rate = %d\n", dcb.BaudRate );

        // Set UART to new baud rate
        dcb.BaudRate = BaudRate;

        if( ! ::SetCommState( m_hCOMPort, &dcb ) )
        {
            IoATrace( eTraceCOMPortError, "IoACOMPort::SetBaudRate( ): call to ::SetCommState( ) failed\n" ); return FALSE;
        }

        IoATrace( eTraceCOMPortDiagnostic, "IoACOMPort::SetBaudRate( ): New baud rate = %d\n", BaudRate );
    }

    IoASerialLink::SetBaudRate( BaudRate );

    return TRUE;
}

void IoACOMPort::ClearCommError( const char *Function )
{
    DWORD dwErrorFlags;
    COMSTAT ComStat;

    if ( ::ClearCommError( m_hCOMPort, &dwErrorFlags, &ComStat ) )
    {
        if ( dwErrorFlags & CE_FRAME )
        {
            IoATrace( eTraceCOMPortError, "IoACOMPort::%s( ) detected framing error.\n", Function );
        }
        
        if ( dwErrorFlags & CE_BREAK )
        {
            IoATrace( eTraceCOMPortError, "IoACOMPort::%s( ) detected break condition.\n", Function );
        }
        
        if ( dwErrorFlags & CE_IOE )
        {
            IoATrace( eTraceCOMPortError, "IoACOMPort::%s( ) detected I/O error.\n", Function );
        }
    }
}
