/** \file
 *
 *  Contains the DAT class declaration.
 *
 *  DAT.h should only be included by DAT.cpp
 *  Other classes should include DATIF.h
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#ifndef DAT_H
#define DAT_H

#include "component/AsyncComponent.h"
#include "data/Matrix3x3.h"
#include "data/StrValue.h"
#include "io/LoadControl.h"
#include "io/SendPacket.h"
#include "io/UartStream.h"
#include "utils/RingBuffer.h"
#include "logger/Logger.h"
#include "supervisor/SendData.h"

#include <stdint.h>

#define DAT_MAX_RCV 2048UL
#define DAT_MAX_SEND 1024UL
#define MAX_DOWNLINK_DATA_SIZE ( DAT_MAX_SEND - SENDPACKET_HEADER_SIZE )

class UniversalDataReader;
class UniversalDataWriter;

/**
 *  Provides software interface to the Benthos DAT.
 *
 *  DAT.h should only be included by DAT.cpp
 *  Other classes should include DATIF.h
 *
 *  \ingroup modules_sensor
 */
class DAT: public AsyncComponent
{
public:

    DAT( const Module* module );

    virtual ~DAT();

    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;

private:

    /// Async thread cycle period
    static const Timespan PERIOD;

    static const Str ACK;

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

    void readConfig();

    ConfigReader* verbosityCfgReader_;
    ConfigReader* ignoreElevationCfgReader_;
    ConfigReader* txPowerCfgReader_;
    ConfigReader* localAddressCfgReader_;
    ConfigReader* sbdAddressCfgReader_;
    ConfigReader* transponderAddressCfgReader_;
    ConfigReader* phaseDataToDirectionCfgReader_;
    ConfigReader* surfaceThresholdCfgReader_;
    ConfigReader* sendExpressCfgReader_;
    ConfigReader* maxAckTimeoutsCfgReader_;
    ConfigReader* sendDataToShoreCfgReader_;

    // Slate inputs
    UniversalDataReader* depthReader_;
    DataReader* queryAddressRequestedReader_;
    DataReader* numberOfPingsRequestedReader_;
    DataReader* power24vConverterDataReader_;

    // universal outputs
    UniversalBlobWriter* directionToContactWriter_;
    UniversalDataWriter* contactAddressWriter_;
    UniversalDataWriter* platformCommunicationsWriter_;
    UniversalDataWriter* rangeToContactWriter_;
    UniversalDataWriter* rxTimeWriter_;
    UniversalDataWriter* txTimeWriter_;

    // Slate outputs
    DataWriter* lvl1Writer_;
    DataWriter* lvl2Writer_;
    DataWriter* lvl3Writer_;
    DataWriter* lvl4Writer_;
    DataWriter* agcWriter_;
    DataWriter* phaseAWriter_;
    DataWriter* phaseBWriter_;
    DataWriter* phaseCWriter_;
    DataWriter* rawAzimuthWriter_;
    DataWriter* rawElevationWriter_;
    DataWriter* calibratedAzimuthWriter_;
    DataWriter* calibratedElevationWriter_;
    DataWriter* rotatedAzimuthWriter_;
    DataWriter* rotatedElevationWriter_;
    DataWriter* acousticWakeupWriter_;
    DataWriter* rangeRequestReceivedWriter_;
    DataWriter* remoteAddressWriter_;
    DataWriter* localAddressWriter_;
    DataWriter* deviceEnableRequestedWriter_;
    DataWriter* rangeWriter_;
    DataWriter* onewayCmdWriter_;
    DataWriter* msgAcknowledgedWriter_;

    DataWriter* tAzimuthWriter_;
    DataWriter* tElevationWriter_;
    DataWriter* vAzimuthWriter_;
    DataWriter* vElevationWriter_;

    BlobWriter* tDirectionWriter_;
    BlobWriter* vDirectionWriter_;

    // Incoming data from Send command and SendData behavior
    RingBuffer<SendData*>* sendDataBuffer_;

    // 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_;

    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_;
    OutgoingComms* outgoingCommsNow_;

    bool debug_;
    int verbosity_;
    int datVerbose_;
    char deviceResponse_[396]; // Stores the current response from the modem

    Matrix3x3 transformationFromTetrahedronToVehicleFrame_, transformationFromVehicleToTetrahedronFrame_; // alignment matrices from vehicle to instrument frame and vice versa

    // Instance of load controller
    LoadControl loadControl_;
    Timestamp startTime_;
    Timestamp connectTime_;
    Timestamp cmdTimeStart_;
    Timestamp powerOffTimeStart_;
    Timespan powerOnTimeout_, postConnectTimeout_, cmdTimeout_, powerDownTimeout_;
    int cmdTries_, maxCmdTries_;

    UartStream uart_; // Holds the communications device

    // Cfg Settings
    StrValue keyText_;
    StrValue vehicleNameCfg_;
    int phaseToDirectionCfgSetting_;
    int verboseCfgSetting_, txPowerCfgSetting_, localAddressCfgSetting_, sbdAddressCfgSetting_, transponderAddressCfgSetting_;
    int ignoreElevationSetting_;
    float surfaceThresholdCfgSetting_;
    int sendExpressCfgSetting_;
    int maxAckTimeoutsCfgSetting_;
    bool sendDataToShoreCfgSetting_;

    // State
    bool commandModeSent_, commandModeAcknowledged_;
    bool onlineModeSent_, onlineModeAcknowledged_, skipNextError_;
    bool verboseSettingSent_, verboseSettingAcknowledged_;
    bool datVerboseSent_, datVerboseAcknowledged_;
    bool txPowerSettingSent_, txPowerSettingAcknowledged_;
    bool localAddressSettingSent_, localAddressSettingAcknowledged_;
    bool currentRemoteAddressSent_, currentRemoteAddressAcknowledged_;
    bool localTimeSent_, localTimeSetAcknowledged_;

    Timespan acousticResponseTimeout_, remoteResponseTime_;
    size_t dataBytesReceiving_, dataBytesReceived_;
    int remoteAddressRequested_, numPingsRequested_; // The query from another component, consisting of the address of the modem from which the DAT is requesting data and the number of return pings it is requesting
    int numPingsReceived_;
    bool gotAcousticWakeup_, gotResponseNotReceived_, gotDebugRxMessage_, gotDebugTxMessage_, gotRangeRequestMessage_, gotRangeMessage_, gotDirectionMessage_, gotAckMessage_;
    bool awaitingAckTransmitAck_;
    int commRate_;
    bool commRateReported_;
    int ackTimeouts_;

    bool rangeRequestPending_; // Has info from the DAT been requested?
    bool oneWayRequested_; // Has oneway mode has been requested of the homing target?
    double range_, phaseA_, phaseB_, phaseC_;
    Timestamp dataTimestamp_;
    Timestamp transmitPingTime_, receivePingTime_;
    int levelA_, levelB_, levelC_, levelD_, agc_;
    float rawAzimuth_, rawElevation_, calibratedAzimuth_, calibratedElevation_, rotatedAzimuth_, rotatedElevation_;
    int remoteAddress_, localAddress_;
    bool deviceEnableRequested_;
    int currentRemoteAddress_, incomingRemoteAddress_, incomingLocalAddress_;

    char commsData_[ DAT_MAX_RCV ];
    SendPacket sendPacket_;

    Point3D directionInTetrahedronFrame_, directionInVehicleFrame_;
    float azimuthInTetrahedronFrame_, elevationInTetrahedronFrame_;
    float azimuthInVehicleFrame_, elevationInVehicleFrame_;

    // These methods return true if OK, false if an error encountered.
    // Their state of needing to be run/completion are handled by *Acknowledged_ members
    bool enterCommandMode();
    bool enterOnlineMode();
    bool sendVerboseCfgSetting();
    bool sendDatVerbose();
    bool sendTxPowerFromCfg();
    bool sendLocalAddressFromCfg();
    bool sendCurrentRemoteAddress();

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

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

    void logVoltageAndCurrent();

    // returns true if power should stay on
    bool keepPowerOn();

    // returns true if platform communications (sbd send) is requested
    bool isCommsRequested();

    // returns true if Send data is available
    bool isSendDataAvailable();

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

    // returns true if device enable is requested to be sent to the remote
    bool isDeviceEnableRequested();

    bool gotNewQuery( void );

    void requestRange( const int remoteAddress, const int numPings );

    void requestDeviceEnableSet( void );
    void requestDeviceEnableClr( void );

    bool readAndParseResponses( void );

    void parseResponses( void );

    void cleanResponse( void );

    bool parseDirectionMessage( void );

    bool parseLocalAzimuthMessage( void );

    bool parseRangeMessage( void );

    bool parseDebugRxMessage( void );

    bool parseDebugTxMessage( void );

    void publishData( void );

    Point3D phaseToUnitVector( const double phaseA, const double phaseB, const double phaseC, const double phaseD );

    void assembleDirectionInVehicleFrame( void );

    void directionsToAngles( void );

    bool getSimulatedMeasurements( void ); // TODO fix simulator where appropriate so that this method just reads sim outputs and rewrites them to the slate in the appropriate places

    bool setTime(); // Sets the time of the DAT to local system time.

};

#endif /*DAT_H*/
