/** \file
 *
 *  Contains the Micromodem class declaration.
 *
 *  Micromodem.h should only be included by Micromodem.cpp
 *  Other classes should include MicromodemIF.h
 *
 *  Copyright (c) 2019 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#ifndef MICROMODEM_H
#define MICROMODEM_H

#include "MicromodemIF.h"

#include "component/SyncComponent.h"
#include "data/StrValue.h"
#include "io/LoadControl.h"
#include "io/SendPacket.h"
#include "io/UartStream.h"
#include "logger/Logger.h"
#include "navigationModule/LBLNavigationIF.h"
#include "utils/RingBuffer.h"

#define MAX_DEVICE_RESPONSE 395
#define MAX_UMODEM_DOWNLINK_MSG_BYTES 192L

class UniversalDataReader;
class UniversalDataWriter;

/**
 *  Provides software interface to the WHOI Micromodem.
 *
 *  Micromodem.h should only be included by Micromodem.cpp
 *  Other classes should include MicromodemIF.h
 *
 *  \ingroup modules_sensor
 */
class Micromodem : public SyncSensorComponent
{
public:

    Micromodem( const Module* module );

    virtual ~Micromodem();

    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 stop();
    }

    /// 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();

    virtual void uninitialize();

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

    static const int FrameCounts_[];
    static const int FrameSizes_[];
    static const char* const FrameFmts_[];

private:

    // Note that the copy constructor below is private and not given a body.
    // Any attempt to call it will return a compiler error.
    Micromodem( const Micromodem& old ); // disallow copy constructor

    void readConfig();

    // Config inputs
    ConfigReader* verbosityCfgReader_;
    ConfigReader* localAddressCfgReader_;
    ConfigReader* destinationAddressCfgReader_;
    ConfigReader* dataRateCfgReader_;
    ConfigReader* sendExpressCfgReader_;
    ConfigReader* acousticResponseTimeoutCfgReader_;
    ConfigReader* resendPeriodCfgReader_;
    ConfigReader* dusblPingCodeCfgReader_;
    ConfigReader* rangeTXSeqCodeCfgReader_;
    ConfigReader* rangeTxFreqCfgReader_;
    ConfigReader* rangeTxTimeCfgReader_;
    ConfigReader* rangeRxTimeCfgReader_;
    ConfigReader* rangeTATCfgReader_;
    ConfigReader* surfaceThresholdCfgReader_;
    ConfigReader* pwrampTXLevelCfgReader_;
    ConfigReader* centerFrequencyCfgReader_;
    ConfigReader* bandwidthCfgReader_;
    ConfigReader* sendDataToShoreCfgReader_;
    ConfigReader* transChannelCfgReaders_[MicromodemIF::NUM_TRANS];

    // Slate inputs
    UniversalDataReader* depthReader_;
    DataReader *queryAddressRequestedReader_;
    DataReader *pingsRequestedReader_;
    DataReader* dusblPingCodeReader_;
    DataReader* power24vConverterDataReader_;

    // Slate outputs
    DataWriter* acousticWakeupWriter_;
    UniversalDataWriter* rxTimeWriter_;
    UniversalDataWriter* txTimeWriter_;
    DataWriter* rangeRequestReceivedWriter_;
    DataWriter* remoteAddressWriter_;
    DataWriter* localAddressWriter_;
    DataWriter* rangeWriter_;
    UniversalDataWriter* platformCommunicationsWriter_;
    DataWriter* pingTimeOfFightWriters_[LBLNavigationIF::NUM_PINGS];

    // Used to save state since there are long potential waiting periods for this device to respond
    enum CommsState
    {
        //SLEEPING,
        //WAKING,
        //CMDMODE,
        //AWAKE,
        //NEED_FIX,
        //NEED_GSV,
        //GET_SIG_QUALITY,
        SENDING_FILL_BUFFER,
        SENDING_TRANSMIT,
        //SENDING_VERIFY,
        SENDING_VERIFIED,
        SENDING_WAIT_FOR_SIGNAL,
        //RECEIVING_1,
        //RECEIVING_2,
        //MTQUEUE_QUALITY,
        //MTQUEUE_SESSION,
        //GO_TO_SLEEP
    };

    enum TransChannelType
    {
        TRANS_CHANNEL_NONE,
        TRANS_CHANNEL_ID,
        TRANS_CHANNEL_FREQENCY,
        TRANS_CHANNEL_CODE
    };

    // To hold our state
    CommsState commsState_;

    int verbosity_;
    char deviceResponse_[MAX_DEVICE_RESPONSE + 1]; // Stores the current response from the modem

    // Instance of load controller
    LoadControl loadControl_;

    Timestamp startTime_, sendTime_, caRevTime_;
    Timestamp powerOffTimeStart_;
    Timespan poPause_, poTimeout_, powerDownTimeout_, caRevTimeout_;

    UartStream uart_; // Holds the communications device

    // Config settings
    int localAddressCfgSetting_;
    int destinationAddressCfgSetting_;
    int dataRateCfgSetting_;
    int sendExpressCfgSetting_;
    double acousticResponseTimeoutCfgSetting_;
    double resendPeriodCfgSetting_;
    StrValue dusblPingCodeCfgSetting_;
    StrValue rangeTXSeqCodeCfgSetting_;
    float rangeTxFreqCfgSetting_;
    float rangeTxTimeCfgSetting_;
    float rangeRxTimeCfgSetting_;
    float rangeTATCfgSetting_;
    float surfaceThresholdCfgSetting_;
    int pwrampTXLevelCfgSetting_;
    int centerFrequencyCfgSetting_;
    int bandwidthCfgSetting_;
    int transChannelCfgSetting_[MicromodemIF::NUM_TRANS];
    TransChannelType transChannelType_[MicromodemIF::NUM_TRANS];
    bool transChannelPinged_[MicromodemIF::NUM_TRANS];
    bool sendDataToShore_;
    int maxDownlinkMsgSize_, maxDownlinkDataSize_;
    StrValue keyText_;
    int majorRevision_;

    typedef enum
    {
        NOT_STARTING = 0,
        STARTING_POWER_ON,
        STARTING_POWER_WAIT,
        STARTING_WRITE_CFG,
        STARTING_WRITE_CFG_WAIT,
        STARTING_SET_CLOCK,
        STARTING_SET_CLOCK_WAIT,
        STARTING_DONE
    } StartingStates;
    StartingStates startingState_;
    RingBuffer<Str*> outbuffer_;
    RingBuffer<Str*> simbuffer_;
    Timestamp cmdSentTime_;
    Timespan cmdResponseWait_;

    // The query from another component, consisting of the address of the modem
    // from which the Micromodem is requesting data and the number of return
    // pings it is requesting
    int remoteAddressRequested_, pingsRequested_, pingsSent_;

    bool wasDusblRequested_, gotAcousticWakeup_, gotResponseNotReceived_, gotRangeRequestMessage_, gotRangeMessage_;
    int commRate_;

    // Keeps track of what channels we are waiting for in SNTTA response
    int pingChannels_[4];

    // Lets us know when we've got nothing left to send back to shore
    bool communicationsDone_;

    double range_;
    Timestamp dataTimestamp_;
    Timestamp transmitPingTime_, receivePingTime_;
    Timestamp modemTransmitPingTime_, modemReceivePingTime_;
    double modemTransmitPingEpochSeconds_, modemReceivePingEpochSeconds_;
    int remoteAddress_, localAddress_;
    double maxSimAcousticDistance_;

    // Contains a SBD-like data structure
    SendPacket sendPacket_;

#define MICROMODEM_MAX_FRAMESIZE 256L
#define MICROMODEM_MAX_FRAMECOUNT 8L
    typedef struct
    {
        int source_;
        int destination_;
        int rate_;
        int frameCount_; // Max value from rate_;
        int frameToSend_;
        int frameSize_[ MICROMODEM_MAX_FRAMECOUNT ];
        uint8_t hexData_[ MICROMODEM_MAX_FRAMECOUNT ][ MICROMODEM_MAX_FRAMESIZE * 2 + 1 ];
        int binSize_;
        uint8_t binData_[ MICROMODEM_MAX_FRAMECOUNT * MICROMODEM_MAX_FRAMESIZE ];
        bool ack_[ MICROMODEM_MAX_FRAMECOUNT ];
    } MicromodemPacket;

    MicromodemPacket outgoingPacket_;
    MicromodemPacket incomingPacket_;

    void initUmodemPacket( MicromodemPacket& packet, int rate = 5 );
    void prepIncomingPacket( MicromodemPacket& packet, int src, int dst, int rate );
    void deserializeIncomingPacket( MicromodemPacket& packet );
    void fillOutgoingPacket( MicromodemPacket& packet, const unsigned char* msg, int nMsgBytes );

    uint8_t hex2uint8_t( uint8_t hex );

    // Sends range ping(s)
    void sendRangePings();

    // Sends range pings to
    void sendRangePingsById();
    void sendRangePingsByFreq();
    void sendRangePingsByCode();

    // Sends ping to DUSBL
    void sendDUSBLPing();

    // Sends data
    void sendingFillBuffer();
    void sendingTransmit();
    void sendingVerified();

    // If no data Ack, wait for ping response
    void sendingWaitForSignal();

    // Fills the buffer for next time a SBD session is initiated
    bool fillBuffer( const unsigned char *msg, size_t nMsgBytes );

    // Returns true if below surface (as defined in Vertical Control)
    bool belowSurfaceThreshold();

    void logVoltageAndCurrent();

    // returns true if modem should be kept powered on
    bool keepPowerOn();

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

    // returns pingsRequestedReader_ value if touched, otherwise 0
    int pingsRequested();

    // returns true if dusblPingCodeReader_ has been touched
    bool isDusblRequested();

    Str* createNMEA( const char* nmeaCmd, size_t cmdLength = 0,
                     const char* nmeaArg1 = NULL, size_t length1 = 0,
                     const char* nmeaArg2 = NULL, size_t length2 = 0,
                     const char* nmeaArg3 = NULL, size_t length3 = 0,
                     const char* nmeaArg4 = NULL, size_t length4 = 0,
                     const char* nmeaArg5 = NULL, size_t length5 = 0,
                     const char* nmeaArg6 = NULL, size_t length6 = 0,
                     const char* nmeaArg7 = NULL, size_t length7 = 0,
                     const char* nmeaArg8 = NULL, size_t length8 = 0,
                     const char* nmeaArg9 = NULL, size_t length9 = 0,
                     const char* nmeaArg10 = NULL, size_t length10 = 0,
                     const char* nmeaArg11 = NULL, size_t length11 = 0,
                     const char* nmeaArg12 = NULL, size_t length12 = 0,
                     const char* nmeaArg13 = NULL, size_t length13 = 0,
                     const char* nmeaArg14 = NULL, size_t length14 = 0 );

    void bufferNMEA( const char* nmeaCmd, size_t cmdLength = 0,
                     const char* nmeaArg1 = NULL, size_t length1 = 0,
                     const char* nmeaArg2 = NULL, size_t length2 = 0,
                     const char* nmeaArg3 = NULL, size_t length3 = 0,
                     const char* nmeaArg4 = NULL, size_t length4 = 0,
                     const char* nmeaArg5 = NULL, size_t length5 = 0,
                     const char* nmeaArg6 = NULL, size_t length6 = 0,
                     const char* nmeaArg7 = NULL, size_t length7 = 0,
                     const char* nmeaArg8 = NULL, size_t length8 = 0,
                     const char* nmeaArg9 = NULL, size_t length9 = 0,
                     const char* nmeaArg10 = NULL, size_t length10 = 0,
                     const char* nmeaArg11 = NULL, size_t length11 = 0,
                     const char* nmeaArg12 = NULL, size_t length12 = 0,
                     const char* nmeaArg13 = NULL, size_t length13 = 0,
                     const char* nmeaArg14 = NULL, size_t length14 = 0 );

    Str* createNMEAStrs( const Str& nmeaCmd,
                         const Str& nmeaArg1 = Str::EMPTY_STR,
                         const Str& nmeaArg2 = Str::EMPTY_STR,
                         const Str& nmeaArg3 = Str::EMPTY_STR,
                         const Str& nmeaArg4 = Str::EMPTY_STR,
                         const Str& nmeaArg5 = Str::EMPTY_STR,
                         const Str& nmeaArg6 = Str::EMPTY_STR,
                         const Str& nmeaArg7 = Str::EMPTY_STR,
                         const Str& nmeaArg8 = Str::EMPTY_STR,
                         const Str& nmeaArg9 = Str::EMPTY_STR,
                         const Str& nmeaArg10 = Str::EMPTY_STR,
                         const Str& nmeaArg11 = Str::EMPTY_STR,
                         const Str& nmeaArg12 = Str::EMPTY_STR,
                         const Str& nmeaArg13 = Str::EMPTY_STR,
                         const Str& nmeaArg14 = Str::EMPTY_STR )
    {
        return
            createNMEA( nmeaCmd.cStr(), nmeaCmd.length(),
                        nmeaArg1.cStr(), nmeaArg1.length(),
                        nmeaArg2.cStr(), nmeaArg2.length(),
                        nmeaArg3.cStr(), nmeaArg3.length(),
                        nmeaArg4.cStr(), nmeaArg4.length(),
                        nmeaArg5.cStr(), nmeaArg5.length(),
                        nmeaArg6.cStr(), nmeaArg6.length(),
                        nmeaArg7.cStr(), nmeaArg7.length(),
                        nmeaArg8.cStr(), nmeaArg8.length(),
                        nmeaArg9.cStr(), nmeaArg9.length(),
                        nmeaArg10.cStr(), nmeaArg10.length(),
                        nmeaArg11.cStr(), nmeaArg11.length(),
                        nmeaArg12.cStr(), nmeaArg12.length(),
                        nmeaArg13.cStr(), nmeaArg13.length(),
                        nmeaArg14.cStr(), nmeaArg14.length() );
    }

    void bufferNMEAStrs( const Str& nmeaCmd,
                         const Str& nmeaArg1 = Str::EMPTY_STR,
                         const Str& nmeaArg2 = Str::EMPTY_STR,
                         const Str& nmeaArg3 = Str::EMPTY_STR,
                         const Str& nmeaArg4 = Str::EMPTY_STR,
                         const Str& nmeaArg5 = Str::EMPTY_STR,
                         const Str& nmeaArg6 = Str::EMPTY_STR,
                         const Str& nmeaArg7 = Str::EMPTY_STR,
                         const Str& nmeaArg8 = Str::EMPTY_STR,
                         const Str& nmeaArg9 = Str::EMPTY_STR,
                         const Str& nmeaArg10 = Str::EMPTY_STR,
                         const Str& nmeaArg11 = Str::EMPTY_STR,
                         const Str& nmeaArg12 = Str::EMPTY_STR,
                         const Str& nmeaArg13 = Str::EMPTY_STR,
                         const Str& nmeaArg14 = Str::EMPTY_STR )
    {
        bufferNMEA( nmeaCmd.cStr(), nmeaCmd.length(),
                    nmeaArg1.cStr(), nmeaArg1.length(),
                    nmeaArg2.cStr(), nmeaArg2.length(),
                    nmeaArg3.cStr(), nmeaArg3.length(),
                    nmeaArg4.cStr(), nmeaArg4.length(),
                    nmeaArg5.cStr(), nmeaArg5.length(),
                    nmeaArg6.cStr(), nmeaArg6.length(),
                    nmeaArg7.cStr(), nmeaArg7.length(),
                    nmeaArg8.cStr(), nmeaArg8.length(),
                    nmeaArg9.cStr(), nmeaArg9.length(),
                    nmeaArg10.cStr(), nmeaArg10.length(),
                    nmeaArg11.cStr(), nmeaArg11.length(),
                    nmeaArg12.cStr(), nmeaArg12.length(),
                    nmeaArg13.cStr(), nmeaArg13.length(),
                    nmeaArg14.cStr(), nmeaArg14.length() );
    }

    void trySendNMEA();

    void tryRcvNMEA();

    void receivedNMEA();

    // Modem Response handlers, alphabetical for now
    void simCAACK();
    void receivedCAACK();
    void receivedCACFG();
    void receivedCACLK();
    void receivedCACYC();
    void simCADRQ();
    void receivedCADRQ();
    void receivedCAERR( const Str* cmdStr );
    void receivedCAMPC();
    void simCAMPR();
    void receivedCAMPR();
    void receivedCAPGTorCAPNT();
    void receivedCAREV();
    void receivedCARXD();
    void receivedCATMS();
    void receivedCATMQ();
    void receivedCATXD();
    void receivedSNPGT();
    void simSNTTA();
    void receivedSNTTA();

    void publishData( void );

};

#endif /*MICROMODEM_H*/
