/** \file
 *
 * ESPClient class declaration.
 *
 *  Copyright (c) 2015 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#ifndef ESPCLIENT_H_
#define ESPCLIENT_H_

#include "ESPComm.h"


/**
 * Core logic for interactions with the ESP.
 * Serves as a supporting class for ESPComponent.
 *
 * ESPClient basically takes care of a complete sampling phase.
 * The main methods are:
 *   - startSampling() to start a sampling phase
 *   - isSampling() to advance the interactions with the ESP until it returns false
 *   - isSuccessfulSampling() to verify the sampling has completed successfully.
 *
 * See ESPClientTest.cpp for a demo program that exercises this client
 * outside of the regular LRAUV framework.
 */
class ESPClient
{
public:

    ///
    /// Constructor.
    /// Note that the filtering and processing completion timeouts
    /// are indicated dynamically by the ESP.
    ///
    /// \param espComm ESPComm object.
    /// \param logger
    /// \param initialPromptTimeout
    /// \param loadCartridgeTimeout
    /// \param filterResultTimeout
    /// \param processResultTimeout
    /// \param stopResultTimeout
    ///
    ESPClient( ESPComm& espComm,
               Logger& logger,
               Timespan initialPromptTimeout,
               Timespan loadCartridgeTimeout,
               Timespan filterResultTimeout,
               Timespan processResultTimeout,
               Timespan stopResultTimeout
             );

    /// Destructor
    ~ESPClient();

    ///
    /// The states considered to be part of a whole sampling phase.
    ///
    enum SamplingState
    {
        S_NONE,
        S_WAITING_INITIAL_PROMPT,
        S_PREPARING_SHOW_LOG,
        S_PREPARING_SHOW_STATUS,
        S_LOADING_CARTRIDGE,
        S_WAITING_FOR_PRIMING,
        S_PRIMING,
        S_WAITING_FOR_FILTERING,
        S_FILTERING,
        S_WAITING_FOR_PAUSED,
        S_PAUSED,
        S_PROCESSING,
        S_WAITING_FOR_PROCESSED,
        S_STOPPING,
        S_STOPPED,
        S_ERROR
    };

    const char* samplingStateName( SamplingState samplingState )
    {
        switch( samplingState )
        {
        case S_NONE:
            return "S_NONE";
        case S_WAITING_INITIAL_PROMPT:
            return "S_WAITING_INITIAL_PROMPT";
        case S_PREPARING_SHOW_LOG:
            return "S_PREPARING_SHOW_LOG";
        case S_PREPARING_SHOW_STATUS:
            return "S_PREPARING_SHOW_STATUS";
        case S_LOADING_CARTRIDGE:
            return "S_LOADING_CARTRIDGE";
        case S_WAITING_FOR_PRIMING:
            return "S_WAITING_FOR_PRIMING";
        case S_PRIMING:
            return "S_PRIMING";
        case S_WAITING_FOR_FILTERING:
            return "S_WAITING_FOR_FILTERING";
        case S_FILTERING:
            return "S_FILTERING";
        case S_WAITING_FOR_PAUSED:
            return "S_WAITING_FOR_PAUSED";
        case S_PAUSED:
            return "S_PAUSED";
        case S_PROCESSING:
            return "S_PROCESSING";
        case S_WAITING_FOR_PROCESSED:
            return "S_WAITING_FOR_PROCESSED";
        case S_STOPPING:
            return "S_STOPPING";
        case S_STOPPED:
            return "S_STOPPED";
        case S_ERROR:
            return "S_ERROR";
        default:
            return "(invalid SamplingState)";
        }
    }

    SamplingState currentSamplingState()
    {
        return samplingState_;
    }

    const char* currentSamplingStateName()
    {
        return samplingStateName( samplingState_ );
    }

    ///
    /// Starts the sampling phase.
    /// Returns true if sampling phase started OK.  False otherwise.
    ///
    /// \param cartridge  Optional argument for the `Cmd.loadCartridge` call.
    ///                   By default, no argument is passed.
    ///
    bool startSampling( Str cartridge = "" );

    ///
    /// Once startSampling has been called, isSampling is to be called repeatedly to
    /// advance the sampling interactions until it returns false.
    /// Returns false upon completion (either successful or not).
    ///
    bool isSampling();

    ///
    /// Requests that the ongoing sampling be ended.
    /// See TETHYS-583.
    ///
    void stopSampling()
    {
        stopSamplingRequested_ = true;
    }

    ///
    /// True only if stopSampling has been called and action on
    /// it is still pending.
    ///
    bool stopSamplingRequested()
    {
        return stopSamplingRequested_;
    }

    ///
    /// True only if stopSampling has been called, the command
    /// "Cmd.pauseFiltering" has been issued, and we are still
    /// awaiting the corresponding "filtering paused" confirmation.
    ///
    bool pauseFilteringIssued()
    {
        return pauseFilteringIssued_;
    }

    ///
    /// Checks for successful completion once isSampling returns false.
    ///
    bool isSuccessfulSampling();

    ///
    /// Returns the error when startSampling or isSuccessfulSampling() returns false.
    ///
    Str getSamplingError();

    void setDebug( bool debugOn )
    {
        debug_ = debugOn;
    }

private:

    // disallow copy constructor
    ESPClient( const ESPClient& old );

    ESPComm& espComm_;

    Logger logger_;

    const Timespan initialPromptTimeout_;
    const Timespan loadCartridgeTimeout_;
    const Timespan filterResultTimeout_;
    const Timespan processResultTimeout_;
    const Timespan stopResultTimeout_;

    bool stopSamplingRequested_;
    bool pauseFilteringIssued_;

    bool debug_;

    SamplingState samplingState_;

    Timestamp startTimeForInitialPrompt_;

    Str issuedCmd_;
    Timestamp cmdIssueTime_;
    Timespan cmdResultTimeout_;
    Timespan cmdCompleteTimeout_;

    Str cartridge_;

    Str partialLine_;

    Str samplingError_;

    // the latest received complete result
    Str* latestResult_;

    // the latest received complete exception
    Str* latestException_;

    // advances the state machine
    void advanceInteraction();

    ///
    /// Issues a command to the ESP, setting a timeout for the result and, optionally,
    /// a timeout for the "completion" of the command.
    ///
    /// NOTE: not all commands use completeTimeout, e.g., "Cmd.stop".
    /// Only "Cmd.filter" and "Cmd.process" do.
    ///
    /// \param cmd                the command
    /// \param resultTimeout      maximum time to wait for corresponding result
    /// \param completionTimeout  maximum time to wait for corresponding state transition indicating completion
    ///
    void issueCommand( Str cmd, Timespan resultTimeout, Timespan completionTimeout = 10 );

    void consumeResult();

    // for reception of result of command
    void checkCmdResultTimeout( bool issueStop = true );

    // for state transition indicating command completion
    void checkCmdCompleteTimeout( bool issueStop = true );

    void handleException( bool awaitingResult, bool issueStop = true );

    void issueStopCommand();

    void setError( Str error );

    ///
    /// Expects next line from the ESP.
    /// It internally handles possible reception of complete result.
    ///
    /// \param line   the receive line is reported here. Trailing \n NOT included.
    /// \param stream the current stream is reported here.
    ///               The stream may be updated even when no new line
    ///               is reported (this is the case when only an stream indicator
    ///               is received internally).
    /// \return
    ///      +1:  a line was actually received in this call
    ///       0:  still waiting for line
    ///      <0:  some error (in readLine)
    ///
    int getLine( Str& line, ESPComm::EspStream& stream );

    // convenience methods to facilitate testing outside of the regular LRAUV framework
    void logDebug( Str msg );
    void logError( Str msg );
    void logFault( Str msg );

};

#endif /*ESPCLIENT_H_*/
