/** \file
 *
 *  Contains the Component class definition.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

/** \defgroup component Component
 *
 *  A Component is a chunk of code that will be run at a set time or interval, or
 *  in response to a set of conditions is considered a Component.
 *  Components are also capable of reading from and writing to the Slate.
 *  Loadable modules (see \ref module) are made up of a collection of
 *  Components.
 */

#ifndef COMPONENT_H_
#define COMPONENT_H_

#include "component/FailureMode.h"
#include "data/DataElement.h"
#include "logger/Logger.h"
#include "utils/CodedStr.h"
#include "utils/FlexArray.h"
#include "utils/Str.h"
#include "utils/Timestamp.h"

class BlobReader;
class BlobWriter;
class ConfigReader;
class Module;
class UniversalBlobReader;
class UniversalBlobWriter;
class UniversalDataReader;
class UniversalDataWriter;

/** Abstract Base class for software components.
 *
 *  \ingroup component
 *
 *  A Component is a chunk of code that will be run at a set time or interval, or
 *  in response to a set of conditions is considered a Component.
 *  Components are also capable of reading from and writing to the Slate.
 *  Loadable modules (see \ref module) are made up of a collection of
 *  Components.
 *
 *  In most instances, this class should not be extended -- hence the private
 *  constructor.  Instead, one of the SyncComponent daughter classes or
 *  the SyncComponent class should be used.
 *
 */
class Component
{
public:

    friend class AsyncComponent;
    friend class Behavior;
    friend class CBIT;
    friend class Config;
    friend class Handler;
    friend class MessagingComponent;
    friend class ScriptComponent;
    friend class SyncComponent;
    friend class ThreadHandler;

    /// Destructor cleans up readers_ and writers_
    virtual ~Component();

    void registerReader( DataReader *reader );
    void unregisterReader( DataReader *reader );
    void registerWriter( DataWriter *writer );
    void unregisterWriter( DataWriter *writer );

    void enableMetering( bool enable )
    {
        meteringEnabled_ = enable;
    }

    FlexArray<DataReader*> &getReaderList( void );
    FlexArray<DataWriter*> &getWriterList( void );

    const Module* getModule()
    {
        return module_;
    }

    /// In this model, the component has basically two states:  wanting to run, and
    /// completed (no longer needing to run).  The other states specify how
    /// the run()'ing of this class is metered -- it could be periodic,
    /// or waiting for a particular trigger (slate change).
    ///
    /// I had hoped to come up with a more elegant solution but I don't think
    /// it's worth the complexity.  Are there components that will pend on a state
    /// table change _and_ on time?  Maybe....
    enum ComponentState
    {
        /// runs continuously in its own thread.
        BLOCK_CONTINUOUS,
        /// runs periodically -- likely in its own thread.
        BLOCK_PERIODIC,
        /// runs when any of its designated slate inputs have been changed.
        BLOCK_NORMAL,
        /// completed
        BLOCK_COMPLETED,
        /// unknown
        BLOCK_UNKNOWN
    };

    /// Components can indicate how they want to be handled by their type
    enum ComponentType
    {
        COMPONENT_ASYNC,
        COMPONENT_CONFIG,
        COMPONENT_BEHAVIOR,
        COMPONENT_SYNC
    };

    enum RunState
    {
        NO_RUN_STATE = 0, // Do not use runState -- use initialize, run, uninitialize
        START,        // Do what needs to be done to run
        STARTING,     // Might follow a STOP...START sequence
        RUNNABLE,     // Should eventually follow a START request or RESETTING
        SATISFIED,    // Possible return value of a Behavior with isSequence()=true.
        PAUSE,        // Pause for a short period (indicated by pauseTime)
        PAUSING,      // Might follow a PAUSE request
        PAUSED,       // Should eventually follow a PAUSE request, should set continueTime
        RESUME,     // Resume from PAUSE
        RESUMING,   // Might follow a PAUSE...RESUME sequence
        RESETTING,    // Might occur in case of Error
        STOP,         // Stop the execution of the component indefinitely
        STOPPING,     // Might follow a STOP request
        STOPPED,      // Should eventually follow a STOP request
    };

    /// Provide a space for initialization before being run.
    virtual void initialize()
    {}

    /// Read (or re-read!) configuration settings
    virtual void configure()
    {}

    virtual void executeRunState( void );

    virtual void execute( void );

    /// The actual "payload" of the component
    virtual void run()
    {}

    /// Provide a space for uninitialization
    virtual void uninitialize()
    {}

    //== Accessors ==

    /// Indicates if the component is active.
    /// Unless runStates are used, will generally return true
    bool isActive() const;

    /// Returns the name
    const CodedStr& getName( void ) const
    {
        return name_;
    }

    const Timestamp& getTimeOfLastRun( void ) const
    {
        return timeOfLastRun_;
    }

    const Timestamp& getTimeOfRun( void ) const
    {
        return timeOfRun_;
    }

    /// Return the component's current state
    ComponentState getState( void )
    {
        return state_;
    };

    // Change the component's current state
    void setState( ComponentState state )
    {
        state_ = state;
    }

    /// Return the component's run state
    RunState getRunState( void )
    {
        return runState_;
    };

    /// Return true if using runState
    bool isUsingRunState()
    {
        return runState_ != NO_RUN_STATE;
    }

    /// Tell the component to start (or stop) sending data
    void requestData( bool requestingData );

    /// Tell the component to start runnning
    void requestStart();

    /// Tell the component to pause runnning
    void requestPause( const Timespan expectedPauseTime );

    /// Tell the component resume from pause
    void requestResume();

    /// Tell the component to stop runnning
    void requestStop();

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

    /// Might follow a STOP...START sequence
    /// Subseqent
    virtual RunState starting()
    /// Similar to initialize, in old init/run/uninit sequence
    {
        return RUNNABLE;
    }

    /// Should eventually follow a START request or RESETTING
    virtual RunState runnable()
    {
        return RUNNABLE;
    }

    /// Possible return value of a Behavior.
    virtual RunState satisfied()
    {
        return runnable();
    }

    /// Pause for a short period (indicated by pauseTime)
    virtual RunState pause()
    {
        return PAUSED;
    }

    /// Might follow a PAUSE request
    virtual RunState pausing()
    {
        return PAUSED;
    }

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

    /// Resume from PAUSE
    virtual RunState resume()
    {
        return RUNNABLE;
    }

    /// Might follow a PAUSE...RESUME sequence
    virtual RunState resuming()
    {
        return RUNNABLE;
    }

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

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

    /// Might follow a STOP request
    virtual RunState stopping()
    {
        return STOPPED;
    }

    /// Should eventually follow a STOP request
    virtual RunState stopped()
    {
        return STOPPED;
    }

    void setCriticalFailureReported( bool boolVal )
    {
        criticalFailureReported_ = boolVal;
    }

    void setRecoveringFromCritical( bool boolVal )
    {
        recoveringFromCritical_ = boolVal;
    }

    void setFailureReported( bool boolVal )
    {
        failureReported_ = boolVal;
    }

    bool isFailureUninitialized()
    {
        return failureUninitialized_;
    }

    void setFailureUninitialized( bool boolVal )
    {
        failureUninitialized_ = boolVal;
    }

    void setRetryTimeout( const Timespan& timeout )
    {
        retryTimeout_ = timeout;
    }

    /// Query if this component is completed
    virtual bool isCompleted( void )
    {
        return state_ == BLOCK_COMPLETED;
    }

    /// Set the period of this component... used if it is periodic
    void setPeriod( const Timespan &period )
    {
        desiredPeriod_ = period;
    }

    /// Returns the component's period (regardless of whether it is periodic)
    const Timespan& getPeriod( void )
    {
        return desiredPeriod_;
    }

    /// Getter function for ComponentType
    ComponentType getComponentType()
    {
        return componentType_;
    }

    virtual bool isFailed( void ) const
    {
        return failureType_ != FailureMode::NONE;
    }

    virtual FailureMode::FailType getFailureType( void ) const
    {
        return failureType_;
    }

    virtual bool getCriticalFailureReported( void ) const
    {
        return criticalFailureReported_;
    }

    virtual bool getFailureReported( void ) const
    {
        return failureReported_;
    }

    virtual Timespan getRetryTimeout( void ) const
    {
        return retryTimeout_;
    }

    virtual FailureMode::FailType setFailure( FailureMode::FailType failureType );

    virtual int getFailCount( void ) const
    {
        return failCount_;
    }

    virtual bool isFailureMissionCritical()
    {
        return failureMissionCritical_;
    }

    virtual int getAllowableFailures( void ) const
    {
        return allowableFailures_;
    }

    virtual void setAllowableFailures( int fails )
    {
        allowableFailures_ = fails;
    }

    Logger &getLogger( void )
    {
        return logger_;
    }

    virtual int incrementFailCount( void )
    {
        return ++failCount_;
    }

    virtual void resetFailCount( void );

    virtual int getSkippedRunsSinceFail( void ) const
    {
        return skippedRunsSinceFail_;
    }

    virtual int incrementSkippedRunsSinceFail( void )
    {
        return ++skippedRunsSinceFail_;
    }

    const Timestamp& getWriteTimestamp( void )
    {
        return writeTimestamp_;
    }

    void setWriteTimestamp( const Timestamp& writeTimestamp )
    {
        writeTimestamp_ = writeTimestamp;
    }

    BlobReader* newBlobReader( const BlobURI& blobURI );

    BlobReader* newBlobReaderFromUniversal( const Str& componentName, const UniversalBlobURI& blobURI );

    BlobWriter* newBlobWriter( const BlobURI& blobURI,
                               Logger::TimePrecisionType timePrecision = Logger::TIME_PRECISION_MILLIS );

    ConfigReader* newConfigReader( const ConfigURI& configURI );

    DataReader* newDataReader( const DataURI& dataURI );

    DataReader* newDataReaderFromUniversal( const Str& componentName, const UniversalURI& universalURI );

    virtual DataWriter* newDataWriter( const DataURI& dataURI,
                                       Logger::TimePrecisionType timePrecision = Logger::TIME_PRECISION_MILLIS );

    UniversalBlobReader* newUniversalBlobReader( const UniversalBlobURI& universalURI );

    UniversalBlobWriter* newUniversalBlobWriter( const UniversalBlobURI& universalURI,
            const Unit& accuracyUnit = Units::NONE,
            const float accuracy = DataElement::NO_ACCURACY,
            Logger::TimePrecisionType timePrecision = Logger::TIME_PRECISION_MILLIS );

    UniversalDataReader* newUniversalReader( const UniversalURI& universalURI );

    UniversalDataWriter* newUniversalWriter( const UniversalURI& universalURI,
            const Unit& accuracyUnit = Units::NONE,
            const float accuracy = DataElement::NO_ACCURACY,
            Logger::TimePrecisionType timePrecision = Logger::TIME_PRECISION_MILLIS );

    virtual const bool broadcastEnabled( void ) const
    {
        return false;
    }

    virtual bool setMessage( const Str& channel, const DataElement& dataElement )
    {
        return false;
    }

protected:

    enum ConfigOption
    {
        CONFIG_SIMULATE_HARDWARE,
        CONFIG_POWER,
        CONFIG_FREQUENCY,
        CONFIG_LATENCY,
        CONFIG_MISSION_CRITICAL
    };

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

    /// Should return [myNamespace]::POWER_[forElement], [myNamespace]::LATENCY_[forElement], etc
    virtual ConfigURI getConfigURI( ConfigOption configOption, ElementURI forElement ) const
    {
        return ConfigURI::NO_CONFIG_URI;
    }

    Logger logger_;

    // Set above Syslog::NONE to enable debug logging
    Syslog::Severity debugLevel_;

#define DEBUG_LOG(...) if(debugLevel_>Syslog::NONE){logger_.syslog(__VA_ARGS__,debugLevel_);}

    /// Keep track of whether the component has "completed" or should be run again
    ComponentState state_;
    Timespan desiredPeriod_;

    /// Store the component's name (used for logging purposes)
    CodedStr& name_;

    Timestamp timeOfRun_;
    Timestamp timeOfLastRun_;
    Timespan durationOfLastRun_;
    Timespan dt_;

    // How long to wait prior to re-trying to run the component after failure
    Timespan retryTimeout_;

    bool enabled_;

    /// Indicates if the failure has been reported -- cuts down on syslog.
    bool failureReported_;

    /// Indicates if a "Critical" failure has been reported -- cuts down on syslog.
    bool criticalFailureReported_;

    /// Indicates that a component had a previous critical failure that has been cleared temporarily awaiting final word from the component itself.
    bool recoveringFromCritical_;

    /// Indicates if the failure has resulted in an uninitialize call
    bool failureUninitialized_;

    /// Number of failures logged by PCaller
    int failCount_;

    /// If true (default), complete failure ends mission
    bool failureMissionCritical_;

    /// Number of failures allowed to occur
    int allowableFailures_;

    /// Number of skipped runs since failureMode_ set by failCount_
    int skippedRunsSinceFail_;

    /// Failed status.  If failCount_ > 0, then this may be reset.
    FailureMode::FailType failureType_;

    /// Time of last failure.  Relevant only if failed=true
    Timestamp timeOfFailure_;

    /// Time of last write
    Timestamp writeTimestamp_;

    // I'm not sure this is the right way to go (embedding info in the Components)
    // as opposed to tracking elsewhere (i.e. some data structure in Slate)
    // but we'll start with this and see how Gordian things get
    FlexArray<DataReader*> readers_;
    FlexArray<DataWriter*> writers_;

    // The module that contains this component.
    // Can be NULL if no module contains the component.
    const Module* module_;

    Timespan pauseTime_;  // indication of how long to expect to be paused

    /// If true, metering is enabled
    bool meteringEnabled_;

    //DataWriter *timeOfLastRunWriter_;        ///< Writer for timeOfLastRun
    //DataWriter *dtWriter_;                   ///< Writer for dt (time between runs)
    DataWriter *durationOfLastRunWriter_;      ///< Writer for durationOfLastRun

    /// True if data is persistent while component is paused.
    bool persistentPause_;

    virtual void setDurationOfLastRun( const Timespan &span );

    virtual void setTimeOfRun( const Timestamp &time )
    {
        timeOfRun_ = time;
    }

    virtual void setTimeOfLastRun( const Timestamp &time );

    virtual void setDt( const Timespan &dt );

    virtual bool setEnabled( bool enabled = true )
    {
        enabled_ = enabled;
        return enabled_;
    }

    // Protected set the run state
    void setRunState( const RunState runState );

    // Protected read pauseTime
    const Timespan getPauseTime() const
    {
        return pauseTime_;
    }

    virtual void setFailureMissionCritical( bool failureMissionCritical )
    {
        failureMissionCritical_ = failureMissionCritical;
    }

    /// If config option present, set mission criticality from config
    /// @return true if set, false if not set (not present in config)
    bool configSetFailureMissionCritical();

    bool simulateHardware();

private:

    /// This is private, since it should not change.
    ComponentType componentType_;

    RunState runState_;  // indication of what state we are running in.

    ConfigReader* missionCriticalReader_;

    ConfigReader* simulateHardwareReader_;

    typedef enum
    {
        USE_HARDWARE = 0,
        SIMULATE_HARDWARE = 1,
        NOT_CHECKED = 2
    } SimulateHardware;

    SimulateHardware simulateHardware_;

    bool startRequested_; // true if a start is requested, but not yet possible.

    bool segfaultRequested_; // true if a segfault has been requested

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

    /// Private constructor for use only by AsynComponent and SyncComponent
    Component( const Str& name, ComponentType componentType, const Module* module );

    const Timestamp& getTimeOfFailure( void ) const
    {
        return timeOfFailure_;
    }

    virtual void setFailTime( Timestamp t )
    {
        timeOfFailure_ = t;
    }

    friend class CommandExec;

    void segfault()
    {
        segfaultRequested_ = true;
    }

    void segfaultImpl();

};

/// Define a type for a "list of component pointers".  Used in a number of
/// different places.
typedef FlexArray<Component *> ListOfComponents;

#endif /* COMPONENT_H_ */
