/** \file
 *
 *  ESPComponent class declaration.
 *
 *  ESPComponent.h should only be included by ESPComponent.cpp.
 *  Other classes should include ESPComponentIF.h
 *
 *  Copyright (c) 2015 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#ifndef ESPCOMPONENT_H_
#define ESPCOMPONENT_H_

#include "ESPClient.h"

#include "component/SyncComponent.h"
#include "io/LoadControl.h"
#include "data/StrValue.h"
#include "logger/Logger.h"
#include "io/UartStream.h"


/**
 * Sensor component to interface with the ESP.
 *
 * Uses ESPComm for the base communication layer with the ESP.
 * Uses ESPClient for the high-level logic for interactions with the ESP.
 *
 *  \ingroup modules_science
 */

class ESPComponent : public SyncSensorComponent
{
public:

    ESPComponent( const Module* module );

    virtual ~ESPComponent();

    virtual void run();

    /// Do what needs to be done to run
    /// Similar to initialize, in old init/run/uninit sequence
    virtual RunState start();

    /// Might follow a STOP...START sequence
    virtual RunState starting();

    /// Pause for a short period (indicated by pauseTime)
    virtual RunState pause();

    /// Should eventually follow a PAUSE request: should set continueTime
    virtual RunState paused();

    /// Resume from PAUSE
    virtual RunState resume();

    /// Might follow a PAUSE...RESUME sequence
    virtual RunState resuming();

    /// Should eventually follow a START request or RESETTING
    virtual RunState runnable();

    /// Might occur in case of Error
    virtual RunState resetting()
    {
        return start();
    }

    /// Initial state -- can be later followed by START
    virtual RunState stop();

    virtual RunState stopping();

    /// Initial state -- can be later followed by START
    virtual RunState stopped();

    void uninitialize();

    /// Should return [myNamespace]::SIMULATE_HARDWARE, or [myNamespace]::POWER, etc
    virtual ConfigURI getConfigURI( ConfigOption configOption ) const;

private:

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

    ///
    /// Sub-states while in LRAUV framework's STARTING state.
    /// We transition through these sub-states until we are connected to the ESP,
    /// at which point we transition to the LRAUV framework's RUNNABLE state
    /// where an ESP sampling phase is performed.
    ///
    enum StartState
    {
        SS_INIT,
        SS_CONNECTING_AS_CLIENT,
        SS_WAITING_FOR_ESP_TO_CONNECT,
        SS_WAITING_FOR_PORT_NUMBER,
        SS_CONNECTED
    };
    StartState startState_;

    const char* startStateName( StartState startState )
    {
        switch( startState )
        {
        case SS_INIT:
            return "SS_INIT";
        case SS_CONNECTING_AS_CLIENT:
            return "SS_CONNECTING_AS_CLIENT";
        case SS_WAITING_FOR_ESP_TO_CONNECT:
            return "SS_WAITING_FOR_ESP_TO_CONNECT";
        case SS_WAITING_FOR_PORT_NUMBER:
            return "SS_WAITING_FOR_PORT_NUMBER";
        case SS_CONNECTED:
            return "SS_CONNECTED";
        default:
            return "(invalid StartState)";
        }
    };

    ///
    /// The base communications interface to the ESP
    ///
    ESPComm* espComm_;

    ///
    /// High-level logic for interactions with the ESP
    ///
    ESPClient* espClient_;

    ESPClient::SamplingState samplingState_;

    ///
    /// Creates espComm_ in specific mode (LRAUV either as server or client)
    /// depending on espServerHost_ (and using socketServerPort_ if LRAUV is to be server).
    /// Also sets espAlwaysServer_ accordingly.
    ///
    void createESPComm();

    ///
    /// Creates espClient_
    ///
    void createESPClient();

    ///
    /// opens server socket to listen for ESP connection
    ///
    bool openServerSocket();

    ///
    /// powers up ESP and initiates PPP link
    ///
    bool powerUpESP();

    ///
    /// powers down ESP and brings down PPP link.
    ///
    /// \param setFailure
    ///         true (the default) to report problem with powerDown call to loadControl as
    ///         a FAULT and set failure to HARDWARE;
    ///         otherwise, such problem is just reported as a DEBUG message.
    ///
    bool powerDownESP( bool setFailure = true );

    ///
    /// Once the ESP is powered up, we wait for the ESP to connect
    ///
    Component::RunState waitForESPConnection();

    ///
    /// Once the ESP is connected as a client, we expect a first line indicating the port it is
    /// listening on as a server.
    /// This port, along with the peer address, is captured in case we need to connect as a client
    /// later on (specifically for the submitAbort operation).
    ///
    bool scanEspServerPort( Str& line, int *espServerPort );

    ///
    /// ESP connection ready
    ///
    void espReady();

    ///
    /// Starts a sampling phase
    ///
    Component::RunState startSamplingPhase();

    ///
    /// Expects next line from the ESP.
    ///
    /// NOTE: This method is actually only called while in SS_WAITING_FOR_PORT_NUMBER.
    /// The getLine logic here is similar to that of the getLine method in ESPClient.
    /// This could be factored out at some point.
    ///
    /// \param line   the receive line is reported here. Trailing \n NOT included.
    /// \return
    ///      +1:  a line was actually received in this call
    ///       0:  still waiting for line
    ///      <0:  some error (in readLine)
    ///
    int getLine( Str& line );

    // any partial line received so far from the ESP. Updated by getLine.
    Str partialLine_;

    // returns true if data is requested from one of the readers
    bool isDataRequested();

    // Queries the LCBs for status
    bool logVoltageAndCurrent();

    // Get simulation data
    bool getSimulatedData();

    // Write sample volume to the slate
    void writeSampleVolume( float sampleVolume );

    // Write data to the slate
    void writeData();

#define MAX_DEVICE_RESPONSE ( 255U )

    // Stores the current response from the device
    char deviceResponse_[MAX_DEVICE_RESPONSE + 1];

    /// Power and comms in this case
    UartStream uart_, consoleUart_;

    LoadControl loadControl_, loadControl2_; // secondary LCB used only for isolation for comms on the PPP link. We need relay control to close the circuit though.

    // config readers
    ConfigReader* connectTimeoutCfgReader_;
    ConfigReader* debugCfgReader_;
    ConfigReader* espLogFilterRegexReader_;
    ConfigReader* espServerHostCfgReader_;
    ConfigReader* filterResultTimeoutCfgReader_;
    ConfigReader* initialPromptTimeoutCfgReader_;
    ConfigReader* loadCartridgeTimeoutCfgReader_;
    ConfigReader* poTimeoutCfgReader_;
    ConfigReader* processResultTimeoutCfgReader_;
    ConfigReader* sampleTimeoutCfgReader_;
    ConfigReader* socketServerPortCfgReader_;
    ConfigReader* stopResultTimeoutCfgReader_;
    ConfigReader* upsyncTimeoutCfgReader_;

    DataReader *cartridgeArgumentReader_;

    // port to listen for ESP when we start as a TCP server
    int socketServerPort_;

    /// String with a regex to select lines from the ESP log during a sampling
    /// exercise. These lines will be all reported at the end of the exercise.
    /// If empty, no such handling is performed.
    StrValue espLogFilterRegexStr_;

    /// Typically this will the empty string from configuration, meaning that the LRAUV
    /// will power-up and down the ESP as part of an overall sampling sequence.
    ///
    /// If non-empty, must have the format "hostname:portNumber" (with portNumber > 0),
    /// indicating the host and port of the ESP server. In this case, the LRAUV will
    /// always assume the ESP is already running and accessible at hostname:portNumber.
    /// No ESP power cycling at all will be performed.
    StrValue espServerHost_;

    /// true if and only if espServerHost_ is non-empty and has the format "hostname:portNumber"
    bool espAlwaysServer_;

    /// Timeouts
    Timespan poTimeout_;      // powering up, including ESP connection
    Timespan poRetryWait_;    // Wait time before trying general power-up/connection
    Timespan connectTimeout_; // when connecting as client to the ESP
    Timespan sampleTimeout_;  // complete sample sequence

    // timeouts for main steps within a complete sample sequence:
    Timespan initialPromptTimeout_;
    Timespan loadCartridgeTimeout_;
    Timespan filterResultTimeout_;
    Timespan processResultTimeout_;

    Timespan stopResultTimeout_;

    Timespan lineTimeout_;

    Timespan upsyncTimeout_;

    /// Time prior to accept ESP client connection:
    Timestamp startTimeAccept_;

    /// Time prior to expecting a line from ESP:
    Timestamp startTimeForLine_;

    /// Time prior to connecting as client to the ESP:
    Timestamp startTimeConnecting_;

    /// If not null, time prior to the wait before trying power-up.
    Timestamp* startPoRetryWait_;

    /// Time when sampling started
    Timestamp sampleTime_;

    /// Time when esp file sync started
    Timestamp upsyncTime_;

    // Indicates sampling is requested
    bool upsyncRequested_;

    // Indicates sampling is going on
    bool samplingActive_;
    float sampleVolume_;
    int sampleNumber_;

    // Starts and stops pppd
    void startPPP( void );
    void stopPPP( void );

    // Starts ESP log rsync
    bool rsyncESPlogs( void );

    /// Debugging outputs
    bool debug_;

    // PPP connect/disconnect execution strings
    StrValue connectExec_;
    StrValue pppFlow_;

    StrValue uartName_;

    // Command to start PPPD
    Str pppCmd_;

    // Outputs to slate
    DataWriter* samplingActiveWriter_;
    DataWriter* sampleVolumeWriter_;
    DataWriter* sampleNumberWriter_;

    DataReader* stopSamplingReader_;
    DataWriter* stopSamplingWriter_;

    ///
    /// Handling of the stopSampling request, especially for the initial
    /// LRAUV framework states of the component: START, STARTING, and
    /// RUNNABLE (when in simulateHardware condition). All other cases
    /// are handled in ::runnable along with support from ESPClient.
    ///
    /// @returns true only when the method determines the next run state
    ///          to be triggered, which is stored in nextRunState.
    ///
    bool handleStopSamplingRequest( RunState &nextRunState );

    /// Helper to reset the stopSampling flag to false
    void resetStopSamplingIfSet();

    // Reads all configs
    bool readConfigs();

    // Re-reads the config variables that are allowed to change without restarting the application.
    bool readAdjustableConfigs();

    bool espLogSummaryReported_;
    void reportEspLogSummary();

    void logDebug( Str msg );

};
#endif /*ESPCOMPONENT_H_*/
