
#include "io/FileInStream.h"
#include "io/FileOutStream.h"
#include "io/IOStream.h"
#include "io/SendPacket.h"
#include "io/UartStreamBase.h"
//#include "logger/LogEngine.h"
#include "logger/LoggerBase.h"
//#include "logger/LoggerRules.h"
//#include "logger/TextLogWriter.h"
#include "utils/FlexArray.h"
#include "utils/Str.h"

#include <cstdio>
#include <cstdlib>
#include <dirent.h>
#include <errno.h>
#include <ncurses.h>
#include <signal.h>

const float UPPER_WIN_FRAC = 0.75;
const float LOWER_WIN_FRAC = 1 - UPPER_WIN_FRAC;

/// provides a terminal GUI for a chat window (lower box to type and upper box to receive messages). Part of the chat.cpp example.
class ChatCurses
{
public:
    /// \name Constructors/Destructor
    //@{
    ChatCurses() {}
    ~ChatCurses()
    {
        cleanup();
    }
    //@}

    /// give the modem_id so we know how to label our messages
    void set_modem_id( unsigned id )
    {
        id_ = id;
    }

    /// start the display
    void startup();
    /// grab a character and if there's a line to return it will be returned in \c line
    void run_input( Str& line );

    /// end the display
    void cleanup();

    /// add a message to the upper window (the chat log)
    void post_message( unsigned id, const Str& line );
    void post_message( const Str& line );


private:
    void update_size();



private:
    int xmax_;
    int ymax_;

    unsigned id_;

    WINDOW* upper_win_;
    WINDOW* lower_win_;
    WINDOW* divider_win_;

    Str line_buffer_;
};

bool debug_( false );
int verbosity_( 0 );
ChatCurses curses_;
int verboseCfgSetting_( 3 );
int localAddressCfgSetting_;
int currentRemoteAddress_;
int remoteAddress_, localAddress_;
FILE* fout_;
LoggerBase logger_( "benthoschat" );
Timestamp startTime_;
Timestamp commandModeTimeStart_;
Timestamp onlineModeTimeStart_;
Timespan acousticResponseTimeout_( 20 ), commandModeTimeout_( 20 ), onlineModeTimeout_( 20 );
bool awaitingAckTransmitAck_( false );
char deviceResponse_[396]; // Stores the current response from the modem
size_t dataBytesReceiving_( 0 );
size_t dataBytesReceived_( 0 );
int incomingRemoteAddress_, incomingLocalAddress_;
#define ATM900_MAX_RCV 2048UL
#define ATM900_MAX_SEND 192UL
#define MAX_DOWNLINK_DATA_SIZE ( ATM900_MAX_SEND - DOWNLINK_DATA_OFFSET )
char commsData_[ ATM900_MAX_RCV ];
// State
bool commandModeSent_( false ), commandModeAcknowledged_( false );
bool onlineModeSent_( false ), onlineModeAcknowledged_( false );
bool verboseSettingSent_( false ), verboseSettingAcknowledged_( false );
bool localAddressSettingSent_( false ), localAddressSettingAcknowledged_( false );
bool currentRemoteAddressSent_( false ), currentRemoteAddressAcknowledged_( false );
Timespan loopPause_( 0.2 );
char outgoing_buf[ATM900_MAX_SEND + 1];
Str outgoing_dir;
Str incoming_dir;
Str incoming_exe;
//LogEngine* syslogEngine_;

// Used to save commnications state
enum CommsState
{
    SENDING_FILL_BUFFER,
    SENDING_TRANSMIT,
    SENDING_TRANSMIT_VERIFY,
    SENDING_ACK_WAITING,
    SENDING_VERIFIED,
};

// To hold our state
CommsState commsState_ = SENDING_FILL_BUFFER;

enum OutgoingCommsType
{
    OUTGOING_SBD,
    OUTGOING_DATA,
    OUTGOING_ACK
};

// Used to wrap outgoing comms
class OutgoingComms
{
private:
    Str data_;
    int address_;
    OutgoingCommsType commsType_;
public:
    OutgoingComms( const Str& data, int address, OutgoingCommsType commsType )
        : data_( data ),
          address_( address ),
          commsType_( commsType )
    {}
    OutgoingComms( const char* data, size_t length, int address, OutgoingCommsType commsType )
        : data_( data, length ),
          address_( address ),
          commsType_( commsType )
    {}
    const Str& getData() const
    {
        return data_;
    }
    int getAddress() const
    {
        return address_;
    }
    OutgoingCommsType getCommsType() const
    {
        return commsType_;
    }
};
FlexArray<OutgoingComms*> outgoingCommsBuffer_( true );
OutgoingComms* outgoingCommsNow_;

// Their state of needing to be run/completion are handled by *Acknowledged_ members
bool enterCommandMode( UartStreamBase& uart );
bool enterOnlineMode( UartStreamBase& uart );
bool sendVerboseCfgSetting( UartStreamBase& uart );
bool sendLocalAddressFromCfg( UartStreamBase& uart );
bool sendCurrentRemoteAddress( UartStreamBase& uart );

// Sends data
void sendingFillBuffer( UartStreamBase& uart );
void sendingTransmit( UartStreamBase& uart );
void sendingTransmitVerify( UartStreamBase& uart );
void sendingAckWaiting( UartStreamBase& uart );
void sendingVerified( UartStreamBase& uart );

// Receives data
int clearUserPrompt( UartStreamBase& uart );
void readAndParseResponses( UartStreamBase& uart );
void parseResponses( UartStreamBase& uart );
void cleanResponse( void );

// Handles ctrl-c
void sigIntHandler( int sig );

int startup_failure()
{
    fprintf( stderr, "usage: benthoschat serial_port my_id buddy_id [log_file] [outgoing_dir] [incoming_dir] [incoming_exe]\n" );
    fprintf( stderr, " serial_port: something like /dev/tty_modem\n" );
    fprintf( stderr, "       my_id: integer between 0 and 254\n" );
    fprintf( stderr, "    buddy_id: integer between 0 and 254, or 255 to broadcast (does not wait for acks)\n" );
    fprintf( stderr, "    log_file: LRAUV-style logs written here\n" );
    fprintf( stderr, "outgoing_dir: files in this dir are forwarded (in alpha order), then deleted\n" );
    fprintf( stderr, "incoming_dir: incoming packets are put here, named YYYYMMDD'T'HHMMSS.pkt\n" );
    fprintf( stderr, "incoming_exe: script to run when incoming packets are received\n" );
    return 1;
}

int main( int argc, char **argv )
{
    signal( SIGINT, sigIntHandler );

    if( argc < 4 || argc > 8 || strcmp( argv[2], "-h" ) == 0 || strcmp( argv[2], "--help" ) == 0 )
    {
        return startup_failure();
    }

    Str serial_port = argv[1];

    char* localAddressCfgSetting_end;
    char* currentRemoteAddress_end;
    localAddressCfgSetting_ = strtol( argv[2], &localAddressCfgSetting_end, 10 );
    currentRemoteAddress_ = strtol( argv[3], &currentRemoteAddress_end, 10 );

    if( localAddressCfgSetting_end == argv[2] || currentRemoteAddress_end == argv[3] || localAddressCfgSetting_ < 0 || currentRemoteAddress_ < 0 )
    {
        fprintf( stderr, "bad value for my_id: %s or buddy_id: %s. these must be unsigned integers.\n", argv[2], argv[3] );
        return startup_failure();
    }

    fout_ = NULL;
    if( argc > 4 )
    {
        fout_ = fopen( argv[4], "w" );
        if( fout_ == NULL )
        {
            fprintf( stderr, "bad value for log_file: %s\n", argv[4] );
            return startup_failure();
        }
    }

    outgoing_dir = Str::EMPTY_STR;
    if( argc > 5 )
    {
        outgoing_dir = argv[5];
    }

    incoming_dir = Str::EMPTY_STR;
    if( argc > 6 )
    {
        incoming_dir = argv[6];
    }

    incoming_exe = Str::EMPTY_STR;
    if( argc > 7 )
    {
        incoming_exe = argv[7];
    }

    //syslogEngine_ = new LogEngine();
    FileOutStream outStream( fout_ == NULL ? stdout : fout_ );
    //syslogEngine_->addAssociation( new TextLogWriter( outStream ),
    //                               new TypeRule( LogEntry::SYSLOG_LOG_ENTRY ) );
    logger_.setOutStream( outStream );

    UartStreamBase uart( argv[1], UartStreamBase::B_9600, 0.5, logger_, 4096, false, debug_ );
    uart.open();
    if( !uart.isReadable() && !uart.isWritable() )
    {
        //syslogEngine_->processQueue( Timespan::ZERO_TIMESPAN );
        fprintf( stderr, "error opening serial port: %s\n", argv[1] );
        return startup_failure();
    }

    curses_.set_modem_id( localAddressCfgSetting_ );
    curses_.startup();

    //
    // Loop until terminated (CTRL-C)
    //
    for( ;; )
    {
        Str line;
        curses_.run_input( line );

        if( line != Str::EMPTY_STR )
        {
            outgoingCommsBuffer_.push( new OutgoingComms( line, currentRemoteAddress_, OUTGOING_DATA ) );
        }

        if( outgoing_dir != Str::EMPTY_STR )
        {
            struct dirent **namelist;
            int n;
            n = scandir( outgoing_dir.cStr(), &namelist, 0, alphasort );

            if( n < 0 )
            {
                logger_.syslog( "scandir error: ", strerror( errno ), Syslog::ERROR );
                return 1;
            }
            else
            {
                while( n-- )
                {
                    if( namelist[n]->d_type == DT_REG )
                    {
                        Str filename = outgoing_dir + "/" + namelist[n]->d_name;
                        FileInStream fileIn( filename.cStr() );
                        fileIn.read( outgoing_buf, ATM900_MAX_SEND );
                        size_t bytesRead = fileIn.bytesRead();
                        if( bytesRead > 0 )
                        {
                            outgoing_buf[bytesRead] = 0;
                            line = Str( outgoing_buf, bytesRead );
                            outgoingCommsBuffer_.push( new OutgoingComms( line, currentRemoteAddress_, OUTGOING_SBD ) );
                            line = Str( ( int )bytesRead ) + " bytes from " + filename;
                            curses_.post_message( localAddressCfgSetting_, line );
                        }
                        fileIn.close();
                        remove( filename.cStr() );
                    }
                    free( namelist[n] );
                }
                free( namelist );
            }
        }

        if( NULL == outgoingCommsNow_ && !outgoingCommsBuffer_.isEmpty() )
        {
            outgoingCommsNow_ = outgoingCommsBuffer_.pop( 0 );
        }

        if( !verboseSettingAcknowledged_ )
        {
            sendVerboseCfgSetting( uart );
        }
        else if( !localAddressSettingAcknowledged_ )
        {
            sendLocalAddressFromCfg( uart );
        }
        else if( !currentRemoteAddressAcknowledged_ )
        {
            sendCurrentRemoteAddress( uart );
        }
        else if( !onlineModeAcknowledged_ )
        {
            enterOnlineMode( uart );
        }
        else
        {
            readAndParseResponses( uart );

            if( NULL != outgoingCommsNow_ )
            {
                switch( commsState_ )
                {
                case SENDING_FILL_BUFFER:
                    if( debug_ ) logger_.syslog( Str( "************** SENDING_FILL_BUFFER **************\n" ), Syslog::INFO );
                    sendingFillBuffer( uart );
                    break;
                case SENDING_TRANSMIT:
                    if( debug_ ) logger_.syslog( Str( "************** SENDING_TRANSMIT **************\n" ), Syslog::INFO );
                    sendingTransmit( uart );
                    break;
                case SENDING_TRANSMIT_VERIFY:
                    if( debug_ ) logger_.syslog( Str( "************** SENDING_TRANSMIT_VERIFY **************\n" ), Syslog::INFO );
                    sendingTransmitVerify( uart );
                    break;
                case SENDING_ACK_WAITING:
                    if( debug_ ) logger_.syslog( Str( "************** SENDING_ACK_WAITING **************\n" ), Syslog::INFO );
                    sendingAckWaiting( uart );
                    break;
                case SENDING_VERIFIED:
                    if( debug_ ) logger_.syslog( Str( "************** SENDING_VERIFIED **************\n" ), Syslog::INFO );
                    sendingVerified( uart );
                    break;
                }
            }
        }

        //syslogEngine_->processQueue( Timespan::ZERO_TIMESPAN );
        loopPause_.sleepFor();
    }

    return 0;

}

void sigIntHandler( int sig )
{
    signal( sig, SIG_IGN );
    //syslogEngine_->processQueue( Timespan::ZERO_TIMESPAN );
    if( NULL != fout_ )
    {
        fclose( fout_ );
    }
    exit( 1 );
}

bool enterCommandMode( UartStreamBase& uart_ )
{
    if( !commandModeSent_ ) // you've seen the CONNECT message, one cycle ago...
    {
        uart_ << "+++\r"; // enter command mode
        commandModeSent_ = true;
        logger_.syslog( "entering command mode", Syslog::INFO );
        onlineModeSent_ = onlineModeAcknowledged_ = commandModeAcknowledged_ = false;
        commandModeTimeStart_ = Timestamp::Now();
        return true;
    }
    if( commandModeSent_ && !commandModeAcknowledged_ )
    {
        logger_.syslog( "checking for command mode acknowledgment", Syslog::DEBUG );
        if( clearUserPrompt( uart_ ) > 0 )
        {
            commandModeAcknowledged_ = true;
            logger_.syslog( "command mode acknowledged", Syslog::INFO );
        }
        else if( commandModeTimeStart_.elapsed() > commandModeTimeout_ )
        {
            logger_.syslog( "failed to enter command mode", Syslog::FAULT );
            return false;
        }
    }
    return true;
}

bool enterOnlineMode( UartStreamBase& uart_ )
{
    if( !onlineModeSent_ ) // you've seen the CONNECT message, one cycle ago...
    {
        uart_ << "ATO\r"; // enter online mode
        onlineModeSent_ = true;
        logger_.syslog( "entering online mode", Syslog::INFO );
        commandModeSent_ = commandModeAcknowledged_ = onlineModeAcknowledged_ = false;
        onlineModeTimeStart_ = Timestamp::Now();
        return true;
    }
    if( onlineModeSent_ && !onlineModeAcknowledged_ )
    {
        logger_.syslog( "checking for online mode acknowledgment", Syslog::DEBUG );
        if( uart_.canReadUntil( "CONNECT" ) && uart_.canReadUntil( "bits/sec" ) )
        {
            uart_.readUntil( deviceResponse_, sizeof( deviceResponse_ ), "bits/sec", 8 );
            uart_.flush(); // TODO: come back and read the rest ( 1 of 4, Rate 1/2 CC 12.50ms MGP)
            onlineModeAcknowledged_ = true;
            logger_.syslog( "online mode acknowledged", Syslog::INFO );
        }
        else if( onlineModeTimeStart_.elapsed() > onlineModeTimeout_ )
        {
            logger_.syslog( "failed to enter online mode", Syslog::FAULT );
            return false;
        }
    }
    return true;
}

bool sendVerboseCfgSetting( UartStreamBase& uart_ )
{
    if( !commandModeAcknowledged_ )
    {
        return enterCommandMode( uart_ );
    }
    if( !verboseSettingSent_ )
    {
        logger_.syslog( "setting verbose to ", verboseCfgSetting_, Syslog::INFO );
        uart_ << "@verbose=" << verboseCfgSetting_ << "\r";
        verboseSettingSent_ = true;
        verboseSettingAcknowledged_ = false;
        return true;
    }
    else if( verboseSettingSent_ && !verboseSettingAcknowledged_ )
    {
        logger_.syslog( "checking for verbose setting acknowledgment", Syslog::DEBUG );
        if( uart_.flushCRLF().canReadUntil( '\n' ) )
        {
            int verboseRead;
            uart_.readUntil( deviceResponse_, sizeof( deviceResponse_ ), '\n' );
            if( sscanf( deviceResponse_, "Verbose         | %d", &verboseRead ) == 1 )
            {
                if( verboseCfgSetting_ == verboseRead )
                {
                    logger_.syslog( "set verbose to ", verboseRead, Syslog::INFO );
                    verboseSettingAcknowledged_ = true;
                    return true;
                }
                else
                {
                    logger_.syslog( "failed to set verbose to " + Str( verboseCfgSetting_ ) + ", device returned " + Str( verboseRead ) + " instead.", Syslog::ERROR );
                    //this->setFailure( FailureMode::COMMUNICATIONS );
                    return false;
                }
            }
            else if( NULL == strstr( deviceResponse_, "user:" ) )
            {
                logger_.syslog( "failed to set verbose; deviceResponse_: ", deviceResponse_, Syslog::ERROR );
                //this->setFailure( FailureMode::COMMUNICATIONS );
                return false;
            }
        }
    }
    return true;
}

bool sendLocalAddressFromCfg( UartStreamBase& uart_ )
{
    if( !commandModeAcknowledged_ )
    {
        return enterCommandMode( uart_ );
    }
    if( !localAddressSettingSent_ )
    {
        logger_.syslog( "setting local address to ", localAddressCfgSetting_, Syslog::INFO );
        uart_ << "@localaddr=" << localAddressCfgSetting_ << "\r";
        localAddressSettingSent_ = true;
        localAddressSettingAcknowledged_ = false;
        return true;
    }
    else if( localAddressSettingSent_ && !localAddressSettingAcknowledged_ )
    {
        logger_.syslog( "checking for local address setting acknowledgment", Syslog::DEBUG );
        if( uart_.flushCRLF().canReadUntil( '\n' ) )
        {
            uart_.readUntil( deviceResponse_, sizeof( deviceResponse_ ), '\n' );
            if( sscanf( deviceResponse_, "LocalAddr       | %d", &localAddress_ ) == 1 )
            {
                if( localAddressCfgSetting_ == localAddress_ )
                {
                    logger_.syslog( "set local address to ", localAddress_, Syslog::INFO );
                    localAddressSettingAcknowledged_ = true;
                    return true;
                }
                else
                {
                    logger_.syslog( "failed to set local address to " + Str( localAddressCfgSetting_ ) + ", device returned " + Str( localAddress_ ) + " instead.", Syslog::ERROR );
                    //this->setFailure( FailureMode::COMMUNICATIONS );
                    return false;
                }
            }
            else if( NULL == strstr( deviceResponse_, "user:" ) )
            {
                logger_.syslog( "failed to set local address; deviceResponse_: ", deviceResponse_, Syslog::ERROR );
                //this->setFailure( FailureMode::COMMUNICATIONS );
                return false;
            }
        }
    }
    return true;
}

bool sendCurrentRemoteAddress( UartStreamBase& uart_ )
{
    if( !commandModeAcknowledged_ )
    {
        return enterCommandMode( uart_ );
    }
    if( !currentRemoteAddressSent_ )
    {
        logger_.syslog( "setting remote address to ", currentRemoteAddress_, Syslog::INFO );
        uart_ << "@remoteaddr=" << currentRemoteAddress_ << "\r";
        currentRemoteAddressSent_ = true;
        currentRemoteAddressAcknowledged_ = false;
        return true;
    }
    if( currentRemoteAddressSent_ && !currentRemoteAddressAcknowledged_ )
    {
        logger_.syslog( "checking for remote address setting acknowledgment", Syslog::DEBUG );
        if( uart_.flushCRLF().canReadUntil( '\n' ) )
        {
            uart_.readUntil( deviceResponse_, sizeof( deviceResponse_ ), '\n' );
            if( sscanf( deviceResponse_, "RemoteAddr      | %d", &remoteAddress_ ) == 1 )
            {
                if( currentRemoteAddress_ == remoteAddress_ )
                {
                    logger_.syslog( "set remote address to ", remoteAddress_, Syslog::INFO );
                    currentRemoteAddressAcknowledged_ = true;
                    return true;
                }
                else
                {
                    logger_.syslog( "failed to set remote address to " + Str( currentRemoteAddress_ ) + ", device returned " + Str( remoteAddress_ ) + " instead.", Syslog::ERROR );
                    //this->setFailure( FailureMode::COMMUNICATIONS );
                    return false;
                }
            }
            else if( NULL == strstr( deviceResponse_, "user:" ) )
            {
                logger_.syslog( "failed to set remote address; deviceResponse_: ", deviceResponse_, Syslog::ERROR );
                //this->setFailure( FailureMode::COMMUNICATIONS );
                return false;
            }
        }
    }
    return true;
}


void sendingFillBuffer( UartStreamBase& uart )
{
    // In this simple application, the buffer is already full
    if( NULL != outgoingCommsNow_ )
    {
        commsState_ = SENDING_TRANSMIT;
        logger_.syslog( "commsState_ = SENDING_TRANSMIT in sendingFillBuffer", Syslog::DEBUG );
        sendingTransmit( uart );
    }
}

void sendingTransmit( UartStreamBase& uart )
{
    if( NULL == outgoingCommsNow_ )
    {
        commsState_ = SENDING_FILL_BUFFER;
        logger_.syslog( "commsState_ = SENDING_FILL_BUFFER in sendingTransmit", Syslog::DEBUG );
        return;
    }

    if( awaitingAckTransmitAck_ )
    {
        return;
    }

    uart << outgoingCommsNow_->getData();
    logger_.syslog( "Out: " + outgoingCommsNow_->getData(), Syslog::IMPORTANT );
    startTime_ = Timestamp::Now();
    commsState_ = SENDING_TRANSMIT_VERIFY;
    logger_.syslog( "commsState_ = SENDING_TRANSMIT_VERIFY in sendingTransmit", Syslog::DEBUG );
}

void sendingTransmitVerify( UartStreamBase& uart )
{
    if( startTime_.elapsed() > acousticResponseTimeout_ )
    {
        logger_.syslog( Str( "Buffer send receipt timeout failure." ), Syslog::FAULT );
        commsState_ = SENDING_FILL_BUFFER;
        logger_.syslog( "commsState_ = SENDING_FILL_BUFFER in sendingTransmitVerify", Syslog::DEBUG );
    }
}

void sendingAckWaiting( UartStreamBase& uart )
{
    if( startTime_.elapsed() > acousticResponseTimeout_ )
    {
        logger_.syslog( Str( "Ack receipt timeout failure." ), Syslog::FAULT );
        commsState_ = SENDING_FILL_BUFFER;
        logger_.syslog( "commsState_ = SENDING_FILL_BUFFER in sendingAckWaiting", Syslog::DEBUG );
    }
}

void sendingVerified( UartStreamBase& uart )
{
    // Not much to do when sending ordinary data
    commsState_ = SENDING_FILL_BUFFER;
    logger_.syslog( "commsState_ = SENDING_FILL_BUFFER in sendingVerified", Syslog::DEBUG );

    if( commsState_ == SENDING_FILL_BUFFER )
    {
        delete outgoingCommsNow_;
        outgoingCommsNow_ = NULL;
    }

}

int clearUserPrompt( UartStreamBase& uart )
{
    int a_prompt_number( -1 ), prompt_number( -1 );
    uart.flushCRLF(); // flush any empty lines
    if( uart.canReadUntil( "UART Wakeup" ) )
    {
        commandModeSent_ = false;
    }
    while( uart.canReadUntil( "user:", 5 ) && uart.canReadUntil( '>' ) )
    {
        if( uart.canReadUntil( '\n' ) )
        {
            uart.readUntil( deviceResponse_, sizeof( deviceResponse_ ), '\n' );
        }
        else
        {
            uart.readUntil( deviceResponse_, sizeof( deviceResponse_ ), '>' );
        }
        if( sscanf( deviceResponse_, "user:%d>", &a_prompt_number ) == 1 )
        {
            prompt_number = a_prompt_number;
            commandModeSent_ = true;
        }
        if( debug_ ) logger_.syslog( "read user prompt " + Str( prompt_number ) + ": " + deviceResponse_, Syslog::DEBUG );
    }
    return prompt_number;
}


void readAndParseResponses( UartStreamBase& uart )
{
    while( uart.canReadUntil( '\n' ) )
    {
        uart.readUntil( deviceResponse_, sizeof( deviceResponse_ ), '\n' );

        if( verbosity_ > 0 )
        {
            logger_.syslog( "DAT read: ", deviceResponse_, Syslog::INFO );
        }

        if( uart.hasError() )
        {
            logger_.syslog( "DAT uart error: ", uart.errorString(), Syslog::FAULT );
            //this->setFailure( FailureMode::COMMUNICATIONS );
        }
        parseResponses( uart );
    }
    clearUserPrompt( uart );
}


void parseResponses( UartStreamBase& uart )
{
    size_t data_bytes = 0; // #comms bytes expected
    if( dataBytesReceiving_ > 0 || 1 == sscanf( deviceResponse_, "DATA(%4zu):", &data_bytes ) )
    {
        logger_.syslog( "Got DATA ", ( int )data_bytes, Syslog::DEBUG );

        if( data_bytes > 0 )
        {
            dataBytesReceiving_ = data_bytes;
            dataBytesReceived_ = 0;
        }

        // Ignore extra bytes in pkt (typically trailing "\n")
        size_t pktDataBytes = AuvMath::Min( dataBytesReceiving_, uart.bytesRead() - ( dataBytesReceived_ ? 0 : sizeof( "DATA(XXXX)" ) ) );

        // Don't overflow comms buffer
        pktDataBytes = AuvMath::Min( pktDataBytes, ( size_t )( ATM900_MAX_RCV - dataBytesReceived_ ) );

        // Move received data into comms buffer
        memmove( commsData_ + dataBytesReceived_, deviceResponse_ + sizeof( "DATA(XXXX)" ), pktDataBytes );

        dataBytesReceived_ += pktDataBytes;
        dataBytesReceiving_ -= pktDataBytes;

        incomingRemoteAddress_ = incomingLocalAddress_ = -1;

        logger_.syslog( Str( "data bytes Received=" ) + ( int ) dataBytesReceived_ + ", Receiving=" + ( int ) dataBytesReceiving_ );

        return;
    }
    if( strstr( deviceResponse_, "Source:" ) && strstr( deviceResponse_, "Destination:" ) )
    {
        if( verbosity_ > 0 )
        {
            logger_.syslog( "Got Src/Dest after DATA ", Syslog::INFO );
        }

        if( 2 == sscanf( deviceResponse_, "Source:%3d  Destination:%3d:", &incomingRemoteAddress_, &incomingLocalAddress_ ) )
        {
            if( verbosity_ > 0 )
            {
                logger_.syslog( Str( "DATA Src=" ) + incomingRemoteAddress_ + ", Dst=" + incomingLocalAddress_, Syslog::INFO );
            }
            return;
        }
        else
        {
            logger_.syslog( "Could not parse Src/Dst in ", deviceResponse_, Syslog::FAULT );
        }
    }
    if( strstr( deviceResponse_, "CRC:Pass" ) )
    {
        if( verbosity_ > 0 )
        {
            logger_.syslog( "Got CRC:Pass", Syslog::INFO );
        }
        if( incomingLocalAddress_ == localAddressCfgSetting_ || 255 == incomingLocalAddress_ )
        {
            if( verbosity_ > 0 )
            {
                logger_.syslog( "Got CRC:Pass", Syslog::INFO );
            }
            if( ( dataBytesReceiving_ == 0 && dataBytesReceived_ > 0 ) || dataBytesReceived_ == ATM900_MAX_RCV )
            {
                if( verbosity_ > 0 )
                {
                    logger_.syslog( "Incoming data is intended for us", Syslog::INFO );
                }

                while( dataBytesReceived_ >= 2 &&
                        ( 0 == strncmp( commsData_, "~~", 2 ) || 0 == strncmp( commsData_ + dataBytesReceived_ - 2, "~~", 2 ) ) )
                {
                    if( SENDING_ACK_WAITING == commsState_ || SENDING_TRANSMIT_VERIFY == commsState_ )
                    {
                        commsState_ = SENDING_VERIFIED;
                        logger_.syslog( "commsState_ = SENDING_VERIFIED in parseResponses", Syslog::DEBUG );
                    }
                    curses_.post_message( currentRemoteAddress_, "[Got Ack]" );
                    logger_.syslog( "Got Ack", Syslog::INFO );
                    dataBytesReceived_ -= 2;
                    if( 0 == strncmp( commsData_, "~~", 2 ) )
                    {
                        memmove( commsData_, commsData_ + 2, dataBytesReceived_ );
                    }
                }

                if( dataBytesReceived_ > 0 )
                {
                    // This is a non-SBD packet
                    //DataReceiver::Receive( ( const char* )commsData_, dataBytesReceived_, logger_ );
                    Str line( ( const char* )commsData_, dataBytesReceived_ );
                    curses_.post_message( incomingRemoteAddress_, line );
                    logger_.syslog( Str( "In: " ) + line, Syslog::IMPORTANT );

                    if( incomingLocalAddress_ == localAddressCfgSetting_ )
                    {
                        // Send acknowledgement
                        if( onlineModeAcknowledged_ )
                        {
                            uart << "~~";
                            awaitingAckTransmitAck_ = true;
                        }
                        else
                        {
                            outgoingCommsBuffer_.insert( 0, new OutgoingComms( "~~", 2, incomingRemoteAddress_, OUTGOING_ACK ) );
                        }
                        curses_.post_message( localAddressCfgSetting_, "[Sent Ack]" );
                        logger_.syslog( "Sent Ack", Syslog::INFO );
                    }

                    if( Str::EMPTY_STR != incoming_dir )
                    {
                        Str filename = incoming_dir + "/" + Timestamp::Now().toSmallString() + ".pkt";
                        FileOutStream fos( filename.cStr() );
                        fos.write( line.cStr(), line.size() );
                        fos.close();

                        if( Str::EMPTY_STR != incoming_exe )
                        {
                            Str cmd = incoming_exe + " " + filename;
                            int retval = system( cmd.cStr() );
                            logger_.syslog( cmd + " ==> " + retval, Syslog::INFO );
                        }
                    }
                }
            }
            else
            {
                logger_.syslog( "Got message confirmation too early", Syslog::ERROR );
            }
        }
        else if( verbosity_ > 1 )
        {
            logger_.syslog( Str( "Ignoring message sent to address" ) + incomingLocalAddress_, Syslog::INFO );
        }
        dataBytesReceiving_ = dataBytesReceived_ = 0;
        return;
    }


    // TODO: Record full raw response for debugging.
    cleanResponse();
    if( strstr( deviceResponse_, "user:" ) )
    {
        logger_.syslog( "unexpected user prompt in deviceResponse_: ", deviceResponse_, Syslog::INFO ); // should not get this
    }
    // Check for message transmitted
    else if( ( awaitingAckTransmitAck_ || commsState_ == SENDING_TRANSMIT_VERIFY ) && strstr( deviceResponse_, "Forwarding Delay UpTx" ) )
    {
        if( awaitingAckTransmitAck_ )
        {
            awaitingAckTransmitAck_ = false;
        }
        else if( currentRemoteAddress_ == 255 )
        {
            commsState_ = SENDING_VERIFIED;
            logger_.syslog( "In parseResponses, remote == 255 so set commsState_ = SENDING_VERIFIED", Syslog::DEBUG );
        }
        else if( outgoingCommsNow_->getCommsType() == OUTGOING_ACK )
        {
            commsState_ = SENDING_VERIFIED;
            logger_.syslog( "In parseResponses, sent ack so set commsState_ = SENDING_VERIFIED", Syslog::DEBUG );
        }
        else
        {
            commsState_ = SENDING_ACK_WAITING;
            logger_.syslog( "commsState_ = SENDING_ACK_WAITING in parseResponses", Syslog::DEBUG );
        }
    }
}


void cleanResponse()
{
    char * i;
    while( ( i = strstr( deviceResponse_, "- " ) ) != NULL )
    {
        strncpy( i, " -", 2 );
    }
}

#include <cctype>

void ChatCurses::startup()
{
    initscr();
    start_color();

    refresh();
    update_size();
    keypad( stdscr, TRUE );
    curs_set( false );

    // tenths of seconds pause waiting for character input (getch)
    nodelay( stdscr, TRUE );
    halfdelay( 1 );

    noecho();
}

void ChatCurses::update_size()
{
    getmaxyx( stdscr, ymax_, xmax_ );

    delwin( upper_win_ );
    delwin( lower_win_ );
    delwin( divider_win_ );

    upper_win_ = newwin( ymax_ * UPPER_WIN_FRAC - 1, xmax_, 0, 0 );
    divider_win_ = newwin( 1, xmax_, ymax_ * UPPER_WIN_FRAC - 1, 0 );
    lower_win_ = newwin( ymax_ * LOWER_WIN_FRAC, xmax_, ymax_ * UPPER_WIN_FRAC, 0 );

    scrollok( upper_win_, true );

    mvwhline( divider_win_, 0, 0, 0, xmax_ );
    wrefresh( divider_win_ );
}

void ChatCurses::run_input( Str& line )
{
    chtype k = getch();

    if( k == KEY_DC || k == KEY_BACKSPACE )
    {
        if( line_buffer_ != Str::EMPTY_STR )
        {
            line_buffer_ = line_buffer_.substr( 0, line_buffer_.length() - 1 );
        }
        wclear( lower_win_ );
        waddstr( lower_win_, line_buffer_.cStr() );
    }
    else if( ( k == KEY_ENTER || k == '\n' ) && line_buffer_ != Str::EMPTY_STR )
    {
        line = line_buffer_;
        wclear( lower_win_ );
        post_message( id_, line_buffer_ );
        line_buffer_ = Str::EMPTY_STR;
    }
    else if( k == KEY_RESIZE )
    {
        update_size();
    }
    else if( isprint( k ) )
    {
        waddch( lower_win_, k );
        line_buffer_ += ( const unsigned char ) k;
    }

    wrefresh( lower_win_ );
}

void ChatCurses::cleanup()
{
    endwin();
}

void ChatCurses::post_message( unsigned id, const Str& line )
{
    post_message( Str( "[" ) + ( int )id + "]: " + line );
}

void ChatCurses::post_message( const Str& line )
{
    Str line_plus_end = line + "\n";
    waddstr( upper_win_, line_plus_end.cStr() );
    wrefresh( upper_win_ );
}
