/** \file
 *
 * Standalone application written during the implementation and testing of the ESPComponent.
 * This program exercises direct interactions with the ESP as an ESP client.
 * It uses the ESPClient supporting class, which is also used by ESPComponent.
 *
 *  Copyright (c) 2015 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

/*
 * Usage:
 *      $ bin/ESPClientTest --help
 *      USAGE: ESPClientTest [options]
 *      -h/--host <name>   indicate ESP server hostname (default: bufflehead)
 *      -p/--port <port>   indicate ESP server port (default: 7777)
 *      -l/--log <file>    indicate log filename (default: none)
 *      -o/--onlyLog       include only <log> lines in log file (default: false)
 *      -c/--cartridge <c> set loadCartridge argument for automated mode (default: none)
 *      -i/--interactive   run in interactive mode (default: false)
 *      --help             show this message
 *
 * A complete example of running ESPClientTest against ESP server on bufflehead port 7777
 * is described at https://bitbucket.org/carueda/workspace/snippets/ynbdqq
 */

#include "scienceModule/ESPClient.h"

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <time.h>

using namespace std;

static int automated( ESPComm& espComm, Str cartridge, Logger& logger );
static int interactive( ESPComm& espComm );

static void usage()
{
    cout << "USAGE: ESPClientTest [options]" << endl;
    cout << "-h/--host <name>   indicate ESP server hostname (default: bufflehead)" << endl;
    cout << "-p/--port <port>   indicate ESP server port (default: 7777)" << endl;
    cout << "-l/--log <file>    indicate log filename (default: none)" << endl;
    cout << "-o/--onlyLog       include only <log> lines in log file (default: false)" << endl;
    cout << "-c/--cartridge <c> set loadCartridge argument for automated mode (default: none)" << endl;
    cout << "-i/--interactive   run in interactive mode (default: false)" << endl;
    cout << "--help             show this message" << endl;
    exit( 0 );
}


int main( int argc, char **argv )
{
    const char* serverHost = "bufflehead.shore.mbari.org";
    int serverPort = 7777;
    bool do_automated = true;
    const char* cartridge = "";
    const char* espLogFilename = 0;
    bool onlyLog = false;

    for( int ii = 1; ii < argc; ++ii )
    {
        const char* opt = argv[ii];
        if( strcmp( "-h", opt ) == 0 || strcmp( "--host", opt ) == 0 )
        {
            serverHost = argv[++ii];
        }
        else if( strcmp( "-p", opt ) == 0 || strcmp( "--port", opt ) == 0 )
        {
            serverPort = atoi( argv[++ii] );
        }
        else if( strcmp( "-l", opt ) == 0 || strcmp( "--log", opt ) == 0 )
        {
            espLogFilename = argv[++ii];
        }
        else if( strcmp( "-o", opt ) == 0 || strcmp( "--onlyLog", opt ) == 0 )
        {
            onlyLog = true;
        }
        else if( strcmp( "-c", opt ) == 0 || strcmp( "--cartridge", opt ) == 0 )
        {
            cartridge = argv[++ii];
        }
        else if( strcmp( "-i", opt ) == 0 || strcmp( "--interactive", opt ) == 0 )
        {
            do_automated = false;
        }
        else if( strcmp( "--help", opt ) == 0 )
        {
            usage();
        }
        else
        {
            cerr << "unrecognized option: " << opt << endl;
            exit( 1 );
        }
    }

    const char* myname = "LRAUV-ESPClientTest";

    cout << "Connecting to " << serverHost << ":" << serverPort << endl;

    Logger logger( "FOO" );
    ESPComm espComm( myname, serverHost, serverPort, logger );
    espComm.setDebug( true );
    espComm.setEspLogFile( espLogFilename, onlyLog );

    espComm.open();
    if( espComm.hasError() != 0 )
    {
        cerr << "ESPComm error in open: " << espComm.getError() << endl;
        return 3;
    }

    espComm.acceptClient();
    if( espComm.hasError() != 0 )
    {
        cerr << "ESPComm error in acceptClient: " << espComm.getError() << endl;
        return 3;
    }

    cout << "Connected to " << serverHost << ":" << serverPort << endl;

    if( do_automated )
    {
        return automated( espComm, cartridge, logger );
    }
    else
    {
        return interactive( espComm );
    }
}

/////////////////////////////////////////////////////////////////////////////////////////////
// automated program

static string elapsed()
{
    static double startMs = -1L;
    struct timespec spec;
    clock_gettime( CLOCK_MONOTONIC, &spec );
    double ms = 1000 * spec.tv_sec + spec.tv_nsec / 1e6;

    if( startMs < 0 )
        startMs = ms;

    double elapsedSecs = ( ms - startMs ) / 1e3;
    char result[64];
    sprintf( result, "%08.3f  ", elapsedSecs );
    return string( result );
}

static Timespan loopWait_g = Timespan::FromMillis( 700 );

static int automated( ESPComm& espComm, Str cartridge, Logger& logger )
{
    // NOTE: reflect the following timeout values according to corresponding
    // entries for ESPComponent in Config/Science.cfg
    Timespan initialPromptTimeout = 20;
    Timespan loadCartridgeTimeout = 10 * 60;
    Timespan filterResultTimeout = 30;
    Timespan processResultTimeout = 30;
    Timespan stopResultTimeout = 2 * 60;

    ESPClient espClient( espComm, logger,
                         initialPromptTimeout,
                         loadCartridgeTimeout,
                         filterResultTimeout,
                         processResultTimeout,
                         stopResultTimeout
                       );

    cout << "ESPClient created" << endl;
    cout << "Sampling state transitions:" << endl;

    elapsed();  // to capture start time

    espClient.startSampling( cartridge );

    ESPClient::SamplingState samplingState = espClient.currentSamplingState();
    cout << elapsed() << "  " << espClient.samplingStateName( samplingState ) << endl;

    while( espClient.isSampling() )
    {
        ESPClient::SamplingState newSamplingState = espClient.currentSamplingState();
        if( newSamplingState != samplingState )
        {
            samplingState = newSamplingState;
            cout << elapsed() << "  " << espClient.samplingStateName( samplingState ) << endl;
        }

        loopWait_g.sleepFor();
    }

    if( espClient.isSuccessfulSampling() )
    {
        cout << elapsed() << "OK: ESP sampling sequence completed normally" << endl;
        return 0;
    }
    else
    {
        Str error = espClient.getSamplingError();
        cerr << elapsed() << "ERROR: ESP sampling sequence terminated with error: " << error.cStr() << endl;
        return 1;
    }
}

/////////////////////////////////////////////////////////////////////////////////////////////
// interactive program

static void send_line( ESPComm& espComm, const char* buffer, size_t bufferSize = 0xFFFFFFFF );
static string create_string( const char* buffer, size_t bufferSize );
static string escape( const char* buffer, size_t bufferSize )  ;

static int interactive( ESPComm& espComm )
{

    string sockPartialLine;  // any partial contents for ongoing line
    char sockBuffer[1024];

    LineReader* stdIn = new LineReader( STDIN_FILENO );
    string stdInPartialLine;  // any partial contents for ongoing line
    char stdInBuffer[1024];

    while( 1 )
    {
        ESPComm::EspStream stream = ESPComm::UNKNOWN;

        // Read from ESP:
        int sockLen = espComm.readLine( sockBuffer, sizeof( sockBuffer ), &stream );
        if( sockLen > 0 )
        {
            if( sockBuffer[sockLen - 1] == '\n' )
            {
                sockPartialLine = "";
            }
            else
            {
                sockPartialLine += escape( sockBuffer, sockLen );
            }
        }
        if( stream == ESPComm::RESULT )
        {
            Str* completeResult = espComm.getLastResult();
            if( completeResult != 0 )
            {
                printf( "GOT complete result: <<%s>>\n", espComm.escape( completeResult->cStr() ).cStr() );
                delete completeResult;
            }
        }

        // Read from stdin:
        int stdInLen = stdIn->readLine( stdInBuffer, sizeof( stdInBuffer ) - 1 );
        if( stdInLen > 0 )
        {
            stdInPartialLine += create_string( stdInBuffer, stdInLen );

            if( stdInBuffer[stdInLen - 1] == '\n' )
            {
                char line[stdInPartialLine.length()];
                strcpy( line, stdInPartialLine.c_str() );
                stdInPartialLine = "";

                //cout << "[STDIN: \"" << line << "\"]" << endl;

                if( strcmp( line, "sl\n" ) == 0 )  // showlog nil, true
                {
                    send_line( espComm, "\tshowlog nil, true" );
                }
                else if( strcmp( line, "ss\n" ) == 0 )  // showStatus
                {
                    send_line( espComm, "showStatus" );
                }
                else if( strcmp( line, "ps\n" ) == 0 )  // prepareSimulation
                {
                    send_line( espComm, "Cmd.slot All=>:dry" );
                }
                else if( strcmp( line, "lc\n" ) == 0 )  // loadCartrige
                {
                    send_line( espComm, "Cmd.loadCartridge" );
                }
                else if( strcmp( line, "sf\n" ) == 0 )  // startFiltering
                {
                    send_line( espComm, "Cmd.startFiltering" );
                }
                else if( strcmp( line, "sp\n" ) == 0 )  // startProcessing
                {
                    send_line( espComm, "Cmd.startProcessing" );
                }
                else if( strcmp( line, "s\n" ) == 0 )  // status
                {
                    send_line( espComm, "Cmd.status" );
                }
                else if( strcmp( line, "!\n" ) == 0 )  // stop
                {
                    send_line( espComm, "Cmd.stop" );
                }
                else if( strcmp( line, "abort\n" ) == 0 )
                {
                    espComm.submitAbort();
                }
                else if( strcmp( line, "exit\n" ) == 0 || strcmp( line, "quit\n" ) == 0 )
                {
                    send_line( espComm, line, strlen( line ) );
                    break;
                }
                else if( strlen( line ) > 1 )   // non-empty
                {
                    send_line( espComm, line, strlen( line ) );
                }
            }
        }
    }

    return 0;
}

static void send_line( ESPComm& espComm, const char* buffer, size_t bufferSize )
{
    espComm.sendLine( buffer, bufferSize );
}

static string create_string( const char* buffer, size_t bufferSize )
{
    string result = "";

    for( int i = 0; i < bufferSize; ++i )
    {
        result += buffer[i];
    }

    return result;
}

// very ad hoc but enough for the purposes at hand
static string escape( const char* buffer, size_t bufferSize )
{
    string result;

    for( int i = 0; i < bufferSize; ++i )
    {
        if( buffer[i] == '\n' )
        {
            result += "\\n";
        }
        else if( buffer[i] == '\r' )
        {
            result += "\\r";
        }
        else if( buffer[i] == '\t' )
        {
            result += "\\t";
        }
        else if( buffer[i] == '"' )
        {
            result += "\\\"";
        }
        else if( buffer[i] == '\0' || ( ( unsigned char ) buffer[i] ) & 0x80 )
        {
            char octal[64];
            sprintf( octal, "\\%03o", ( unsigned char ) buffer[i] );
            result += octal;
        }
        else
        {
            result += buffer[i];
        }
    }

    return result;
}
