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

#include "Component.h"
#include "data/BlobReader.h"
#include "data/BlobWriter.h"
#include "data/ConfigDataElement.h"
#include "data/ConfigReader.h"
#include "data/Slate.h"
#include "data/UniversalDataElement.h"
#include "data/UniversalDataReader.h"
#include "data/UniversalDataWriter.h"
#include "units/Units.h"

/// Constructor
Component::Component( const Str& name, ComponentType componentType, const Module* module )
    : logger_( name ),
      debugLevel_( Syslog::NONE ),
      state_( BLOCK_COMPLETED ),
      desiredPeriod_( 5.0 ),
      name_( Slate::GetCodedName( name ) ),
      timeOfRun_( Timestamp::NOT_SET_TIME ),
      timeOfLastRun_( Timestamp::NOT_SET_TIME ),
      durationOfLastRun_( Timespan::INVALID_TIMESPAN ),
      dt_( Timespan::INVALID_TIMESPAN ),
      retryTimeout_( 900 ),
      enabled_( true ),
      failureReported_( false ),
      criticalFailureReported_( false ),
      recoveringFromCritical_( false ),
      failureUninitialized_( false ),
      failCount_( 0 ),
      failureMissionCritical_( true ),
      allowableFailures_( 0 ),
      skippedRunsSinceFail_( 0 ),
      failureType_( FailureMode::NONE ),
      writeTimestamp_( Timestamp::NOT_SET_TIME ),
      readers_( true ),
      writers_( true ),
      module_( module ),
      pauseTime_( Timespan::ZERO_TIMESPAN ),
      meteringEnabled_( false ),
      //timeOfLastRunWriter_( NULL ),
      //dtWriter_( NULL ),
      durationOfLastRunWriter_( NULL ),
      persistentPause_( false ),
      componentType_( componentType ),
      runState_( NO_RUN_STATE ),
      missionCriticalReader_( NULL ),
      simulateHardwareReader_( NULL ),
      simulateHardware_( NOT_CHECKED ),
      startRequested_( false ),
      segfaultRequested_( false )
{}

/// Destructor cleans up readers_ and writers_
Component::~Component( void )
{
    // Someone needs to delete those darn readers & writers!
    // Note that unregister is called each time one is deleted
    // The readers_ and writers_ destructors should handle this,
    // but for some reason, they are not!
    while( !readers_.isEmpty() )
    {
        delete readers_.pop();
    }
    while( !writers_.isEmpty() )
    {
        delete writers_.pop();
    }
}

//==================================================================
// Functions for dealing with reader and writer lists
FlexArray<DataReader*> &Component::getReaderList( void )
{
    return readers_;
}
FlexArray<DataWriter*> &Component::getWriterList( void )
{
    return writers_;
}

void Component::registerReader( DataReader *reader )
{
    readers_.push( reader );
}
void Component::unregisterReader( DataReader *reader )
{
    for( int i = readers_.getMaxIndex(); i >= 0; --i )
    {
        if( readers_[i] == reader )
        {
            readers_.pop( i );
            break;
        }
    }
}

void Component::registerWriter( DataWriter *writer )
{
    writers_.push( writer );
}
void Component::unregisterWriter( DataWriter *writer )
{
    for( int i = writers_.getMaxIndex(); i >= 0; --i )
    {
        if( writers_[i] == writer )
        {
            writers_.pop( i );
            break;
        }
    }
}

void Component::executeRunState( void )
{
    switch( runState_ )
    {
    case NO_RUN_STATE: // Do not use runState -- use initialize, run, uninitialize
        run();
        break;
    case START:        // Do what needs to be done to run
        setRunState( start() );
        break;
    case STARTING:     // Might follow a STOP...START sequence
        setRunState( starting() );
        break;
    case RUNNABLE:     // Should eventually follow a START request or RESETTING
        setRunState( runnable() );
        break;
    case SATISFIED:    // Possible return value of a Behavior with isSequence()=true.
        setRunState( satisfied() );
        break;
    case PAUSE:        // Pause for a short period (indicated by pauseTime)
        setRunState( pause() );
        break;
    case PAUSING:      // Might follow a PAUSE request
        setRunState( pausing() );
        break;
    case PAUSED:       // Should eventually follow a PAUSE request: should set continueTime
        setRunState( paused() );
        break;
    case RESUME:       // Resume from PAUSE
        setRunState( resume() );
        break;
    case RESUMING:     // Might follow a PAUSE...RESUME sequence
        setRunState( resuming() );
        break;
    case RESETTING:    // Might occur in case of Error
        setRunState( resetting() );
        break;
    case STOP:         // Initial state -- can be later followed by START
        setRunState( stop() );
        break;
    case STOPPING:     // Might follow a STOP request
        setRunState( stopping() );
        break;
    case STOPPED:      // Should eventually follow a STOP request
        if( startRequested_ )
        {
            startRequested_ = false;
            setRunState( START );
            setRunState( start() );
        }
        else
        {
            setRunState( stopped() );
        }
        break;
    }
}

void Component::execute( void )
{
    if( segfaultRequested_ )
    {
        segfaultImpl();
    }
    if( true == enabled_ )
    {
        // Now run it.
        Timestamp beforeRun, afterRun;
        Timespan runTime, dt;

        beforeRun = Timestamp::Now();
        setTimeOfRun( beforeRun );

        if( timeOfLastRun_ == Timestamp::NOT_SET_TIME )
        {
            dt = Timespan::INVALID_TIMESPAN;
        }
        else
        {
            dt = beforeRun - timeOfLastRun_;
        }
        setDt( dt );

        // Execute the component's run state
        executeRunState();

        afterRun = Timestamp::Now();

        runTime = afterRun - beforeRun;

        setTimeOfLastRun( beforeRun );
        setDurationOfLastRun( runTime );

    }
    else
    {
        if( runState_ != STOPPED && runState_ != STOPPING )
        {
            setRunState( stop() );
        }
        else if( runState_ == STOPPING )
        {
            setRunState( stopping() );
        }
        else
        {
            setRunState( stopped() );
        }
    }
}

FailureMode::FailType Component::setFailure( FailureMode::FailType failureType )
{
    if( failureType_ == FailureMode::NONE && failureType != FailureMode::NONE )
    {
        incrementFailCount();
        timeOfFailure_ = Timestamp::Now();
        skippedRunsSinceFail_ = 0;
        failureReported_ = false;
    }

    if( failureType != failureType_ )
    {
        failureType_ = failureType;
        logger_.logComponentFailure( this, failureType, failCount_ );
        if( failureType != FailureMode::NONE )
        {
            logger_.syslog( FailureMode::ToString( failureType ), Syslog::ERROR );
        }
        for( unsigned int i = 0; i < writers_.size(); ++ i )
        {
            DataWriter* writer = writers_[i];
            if( NULL != writer )
            {
                writer->registerChange();
            }
        }
    }
    return failureType_;
}


void Component::resetFailCount( void )
{
    // We want to know if we're coming into this function following a repeat failure or after a component has run and called resetFailCount itself.
    // If there is an existing critical when it's called, it won't be the component.
    if( recoveringFromCritical_ && !criticalFailureReported_ && failCount_ == 0 )
    {
        logger_.syslog( "Failure count cleared after critical for " + name_, Syslog::INFO );
        recoveringFromCritical_ = false;
    }
    failCount_ = 0;
}


void Component::setTimeOfLastRun( const Timestamp &time )
{
    timeOfLastRun_ = time;
//    if ( meteringEnabled_ )
//    {
//        if( NULL == timeOfLastRunWriter_ )
//        {
//            timeOfLastRunWriter_ = Slate::NewOutputWriter( Str( "timeOfLastRun" ), this, Units::SECOND( 0.0, DOUBLE8 ) );
//        }
//    }
//    timeOfLastRunWriter_->write( Units::second_s, time.asDouble() );
}

void Component::setDt( const Timespan &dt )
{
    dt_ = dt;
//    if ( meteringEnabled_ )
//    {
//        if( NULL == dtWriter_ )
//        {
//            dtWriter_ = Slate::NewOutputWriter( Str( "dt" ), this, Units::SECOND( 0.0, FLOAT2 ) );
//        }
//    }
//    dtWriter_->write( Units::second_s, dt.asDouble() );
}

void Component::setDurationOfLastRun( const Timespan &span )
{
    durationOfLastRun_ = span;
    if( meteringEnabled_ )
    {
        if( NULL == durationOfLastRunWriter_ )
        {
            durationOfLastRunWriter_ = Slate::NewOutputWriter( Str( "durationOfLastRun" ), this, Units::SECOND( 0.0, FLOAT2 ) );
        }
        durationOfLastRunWriter_->write( Units::SECOND, span.asDouble() );
    }
}

/// Tell the component to start (or stop) sending data
void Component::requestData( bool requestingData )
{
    for( int i = writers_.getMaxIndex(); i >= 0; --i )
    {
        writers_[i]->componentRequestData( requestingData );
    }

}

// Tell the component to start runnning
void Component::requestStart()
{
    switch( runState_ )
    {
    case NO_RUN_STATE: // Do not use runState -- use initialize, run, uninitialize
        break;
    case START:        // Do what needs to be done to run
    case STARTING:     // Might follow a STOP...START sequence
    case RUNNABLE:     // Should eventually follow a START request or RESETTING
    case SATISFIED:    // Possible return value of a Behavior with isSequence()=true.
    case PAUSE:        // Pause for a short period (indicated by pauseTime)
    case PAUSING:      // Might follow a PAUSE request
    case PAUSED:       // Should eventually follow a PAUSE request: should set continueTime
    case RESUME:       // Resume from PAUSE
    case RESUMING:     // Might follow a PAUSE...RESUME sequence
    case RESETTING:    // Might occur in case of Error
        break;
    case STOP:         // Initial state -- can be later followed by START
    case STOPPING:     // Might follow a STOP request
        startRequested_ = true;
        break;
    case STOPPED:      // Should eventually follow a STOP request
        setRunState( START );
        break;
    }
}

// Tell the component to pause runnning
void Component::requestPause( const Timespan expectedPauseTime )
{
    switch( runState_ )
    {
    case NO_RUN_STATE: // Do not use runState -- use initialize, run, uninitialize
        break;
    case START:        // Do what needs to be done to run
    case STARTING:     // Might follow a STOP...START sequence
    case RUNNABLE:     // Should eventually follow a START request or RESETTING
    case SATISFIED:    // Possible return value of a Behavior with isSequence()=true.
        pauseTime_ = expectedPauseTime;
        setRunState( PAUSE );
        break;
    case PAUSE:        // Pause for a short period (indicated by pauseTime)
    case PAUSING:      // Might follow a PAUSE request
    case PAUSED:       // Should eventually follow a PAUSE request: should set continueTime
        break;
    case RESUME:       // Resume from PAUSE
    case RESUMING:     // Might follow a PAUSE...RESUME sequence
    case RESETTING:    // Might occur in case of Error
        pauseTime_ = expectedPauseTime;
        setRunState( PAUSE );
        break;
    case STOP:         // Initial state -- can be later followed by START
    case STOPPING:     // Might follow a STOP request
    case STOPPED:      // Should eventually follow a STOP request
        break;
    }
}

/// Tell the component resume from pause
void Component::requestResume()
{
    switch( runState_ )
    {
    case NO_RUN_STATE: // Do not use runState -- use initialize, run, uninitialize
    case START:        // Do what needs to be done to run
    case STARTING:     // Might follow a STOP...START sequence
    case RUNNABLE:     // Should eventually follow a START request or RESETTING
    case SATISFIED:    // Possible return value of a Behavior with isSequence()=true.
        break;
    case PAUSE:        // Pause for a short period (indicated by pauseTime)
    case PAUSING:      // Might follow a PAUSE request
    case PAUSED:       // Should eventually follow a PAUSE request: should set continueTime
        setRunState( RESUME );
        break;
    case RESUME:       // Resume from PAUSE
    case RESUMING:     // Might follow a PAUSE...RESUME sequence
    case RESETTING:    // Might occur in case of Error
    case STOP:         // Initial state -- can be later followed by START
    case STOPPING:     // Might follow a STOP request
    case STOPPED:      // Should eventually follow a STOP request
        break;
    }
}

// Tell the component to stop runnning
void Component::requestStop()
{
    switch( runState_ )
    {
    case NO_RUN_STATE: // Do not use runState -- use initialize, run, uninitialize
        break;
    case START:        // Do what needs to be done to run
    case STARTING:     // Might follow a STOP...START sequence
    case RUNNABLE:     // Should eventually follow a START request or RESETTING
    case SATISFIED:    // Possible return value of a Behavior with isSequence()=true.
    case PAUSE:        // Pause for a short period (indicated by pauseTime)
    case PAUSING:      // Might follow a PAUSE request
    case PAUSED:       // Should eventually follow a PAUSE request: should set continueTime
    case RESUME:       // Resume from PAUSE
    case RESUMING:     // Might follow a PAUSE...RESUME sequence
    case RESETTING:    // Might occur in case of Error
        setRunState( STOP );
        break;
    case STOP:         // Initial state -- can be later followed by START
    case STOPPING:     // Might follow a STOP request
    case STOPPED:      // Should eventually follow a STOP request
        break;
    }
}

/// Indicates if the component is active.
/// Unless runStates are used, will generally return true
bool Component::isActive() const
{
    switch( runState_ )
    {
    case NO_RUN_STATE: // Do not use runState -- use initialize, run, uninitialize
        return true;
    case START:        // Do what needs to be done to run
    case STARTING:     // Might follow a STOP...START sequence
        return false;
    case RUNNABLE:     // Should eventually follow a START request or RESETTING
    case SATISFIED:    // Possible return value of a Behavior with isSequence()=true.
        return true;
    case PAUSE:        // Pause for a short period (indicated by pauseTime)
    case PAUSING:      // Might follow a PAUSE request
    case PAUSED:       // Should eventually follow a PAUSE request: should set continueTime
    case RESUME:       // Resume from PAUSE
    case RESUMING:     // Might follow a PAUSE...RESUME sequence
        return persistentPause_;
    case RESETTING:    // Might occur in case of Error
    case STOP:         // Initial state -- can be later followed by START
    case STOPPING:     // Might follow a STOP request
    case STOPPED:      // Should eventually follow a STOP request
        return false;
    default:
        return true;
    }
}


// Protected set the run state
void Component::setRunState( const RunState runState )
{
    bool startingRun = ( runState_ != RUNNABLE && runState_ != SATISFIED )
                       && ( runState == RUNNABLE || runState == SATISFIED );
    bool endingRun = ( runState_ == RUNNABLE || runState_ == SATISFIED )
                     && ( runState != RUNNABLE && runState != SATISFIED );

    runState_ = runState;

    if( startingRun || endingRun )
    {
        for( int i = writers_.getMaxIndex(); i >= 0; --i )
        {
            DataWriter* writer = writers_[i];
            if( NULL != writer->getElement().getUniversal() )
            {
                writer->getElement().getUniversal()->recalculate();
            }
        }
    }
}

bool Component::configSetFailureMissionCritical()
{
    ConfigURI missionCriticalUri = getConfigURI( CONFIG_MISSION_CRITICAL );
    if( ConfigURI::NO_CONFIG_URI != missionCriticalUri )
    {
        missionCriticalReader_ = newConfigReader( missionCriticalUri );
        if( NULL != missionCriticalReader_ )
        {
            int missionCritical;
            if( missionCriticalReader_->read( Units::BOOL, missionCritical ) )
            {
                setFailureMissionCritical( missionCritical );
                return true;
            }
        }
    }
    return false;
}

bool Component::simulateHardware()
{
#ifdef __FASTSIM
    // Simulate the hardware when running on a host.
    return true;
#else
    if( simulateHardware_ == NOT_CHECKED )
    {
        // Assume hardware is present when running on the target.
        simulateHardware_ = USE_HARDWARE;

        ConfigURI simulateHarwareUri = getConfigURI( CONFIG_SIMULATE_HARDWARE );
        if( ConfigURI::NO_CONFIG_URI != simulateHarwareUri )
        {
            simulateHardwareReader_ = newConfigReader( simulateHarwareUri );
            if( NULL != simulateHardwareReader_ )
            {
                int simHardware = ( int )USE_HARDWARE;
                if( simulateHardwareReader_->read( Units::BOOL, simHardware ) )
                {
                    simulateHardware_ = ( SimulateHardware )simHardware;
                }
            }
        }
    }

    return simulateHardware_ == SIMULATE_HARDWARE;
#endif
}

BlobReader* Component::newBlobReader( const BlobURI& blobURI )
{
    DataElement* newElement = Slate::GetElement( blobURI );
    if( NULL == newElement )
    {
        newElement = Slate::NewDataElement( blobURI, this, blobURI.getUnit(), blobURI.getBinaryType() );
    }
    BlobReader* reader = new BlobReader( this, *newElement, blobURI.getUnit() );
    return reader;
}

BlobReader* Component::newBlobReaderFromUniversal( const Str& componentName, const UniversalBlobURI& universalBlobURI )
{
    BlobURI blobUri( componentName, universalBlobURI.cStr(), universalBlobURI.getUnit(),
                     universalBlobURI.getBlobType(), universalBlobURI.getDimension1(),
                     universalBlobURI.getDimension2(), universalBlobURI.getDimension3() );
    return newBlobReader( blobUri );
}

BlobWriter* Component::newBlobWriter( const BlobURI& blobURI,
                                      Logger::TimePrecisionType timePrecision )
{
    DataElement* newElement = Slate::GetElement( blobURI );
    if( NULL == newElement )
    {
        newElement = Slate::NewDataElement( blobURI, this, blobURI.getUnit(), blobURI.getBinaryType() );
    }
    BlobWriter* writer = new BlobWriter( this, *newElement, NULL, timePrecision );
    return writer;
}

ConfigReader* Component::newConfigReader( const ConfigURI& configURI )
{
    bool found = true;

    ConfigDataElement* newElement = dynamic_cast<ConfigDataElement*>( Slate::GetElement( configURI ) );
    if( NULL == newElement )
    {
        logger_.syslog( "No configuration setting for: " + configURI, Syslog::CRITICAL );
        found = false;
        newElement = new ConfigDataElement( configURI, configURI.getUnit().dataValue( configURI.getBinaryType() ) );
        Slate::MapDataElement( ( ConfigURI* )&configURI, newElement );
    }
    newElement->setRequiresRestart( configURI.requiresRestart() );
    ConfigReader* reader = new ConfigReader( this, *newElement, configURI.getUnit() );
    reader->setConfigured( found );
    return reader;
}

DataReader* Component::newDataReader( const DataURI& dataURI )
{
    DataElement* newElement = Slate::GetElement( dataURI );
    if( NULL == newElement )
    {
        newElement = Slate::NewDataElement( dataURI, this, dataURI.getUnit(), dataURI.getBinaryType() );
    }
    DataReader* reader = new DataReader( this, *newElement, dataURI.getUnit() );
    return reader;
}

DataReader* Component::newDataReaderFromUniversal( const Str& componentName, const UniversalURI& universalURI )
{
    DataURI dataUri( componentName, universalURI.cStr(), universalURI.getUnit(), universalURI.getBinaryType() );
    return newDataReader( dataUri );
}


DataWriter* Component::newDataWriter( const DataURI& dataURI,
                                      Logger::TimePrecisionType timePrecision )
{
    DataElement* newElement = Slate::GetElement( dataURI );
    if( NULL == newElement )
    {
        newElement = Slate::NewDataElement( dataURI, this, dataURI.getUnit(), dataURI.getBinaryType() );
    }
    DataWriter* writer = new DataWriter( this, *newElement, NULL, timePrecision );
    return writer;
}

UniversalBlobReader* Component::newUniversalBlobReader( const UniversalBlobURI& universalURI )
{
    UniversalDataElement* newUniversal = Slate::NewUniversalElement( universalURI, this, universalURI.getUnit(), universalURI.getBinaryType() );
    UniversalBlobReader* reader = new UniversalBlobReader( this, *newUniversal, universalURI.getUnit() );
    return reader;
}

UniversalBlobWriter* Component::newUniversalBlobWriter( const UniversalBlobURI& universalURI,
        const Unit& accuracyUnit, const float accuracy,
        Logger::TimePrecisionType timePrecision )
{
    UniversalDataElement*  newUniversal = Slate::NewUniversalElement( universalURI, this, universalURI.getUnit(), universalURI.getBinaryType() );
    BlobURI elementURI( getName(), universalURI.cStr(), universalURI.getUnit(),
                        universalURI.getBlobType(), universalURI.getDimension1(), universalURI.getDimension2(),
                        universalURI.getDimension3() );
    DataElement* newElement = Slate::NewDataElement( elementURI, this, universalURI.getUnit(), universalURI.getBinaryType(), accuracy );
    UniversalBlobWriter* newWriter = new UniversalBlobWriter( this, *newElement, newUniversal, timePrecision );
    newElement->registerUniversal( newUniversal );
    newUniversal->addElement( *newElement );
    newUniversal->notifyChangeDone( newWriter );
    if( accuracy != DataElement::NO_ACCURACY )
    {
        newWriter->setAccuracy( accuracyUnit, accuracy );
    }
    return newWriter;
}

UniversalDataReader* Component::newUniversalReader( const UniversalURI& universalURI )
{
    UniversalDataElement* newUniversal = Slate::NewUniversalElement( universalURI, this, universalURI.getUnit(), universalURI.getBinaryType() );
    UniversalDataReader* reader = new UniversalDataReader( this, *newUniversal, universalURI.getUnit() );
    return reader;
}

UniversalDataWriter* Component::newUniversalWriter( const UniversalURI& universalURI,
        const Unit& accuracyUnit, const float accuracy,
        Logger::TimePrecisionType timePrecision )
{
    UniversalDataElement*  newUniversal = Slate::NewUniversalElement( universalURI, this, universalURI.getUnit(), universalURI.getBinaryType() );
    ElementURI elementURI( getName(), universalURI );
    DataElement* newElement = Slate::NewDataElement( elementURI, this, universalURI.getUnit(), universalURI.getBinaryType(), accuracy );
    UniversalDataWriter* newWriter = new UniversalDataWriter( this, *newElement, newUniversal, timePrecision );
    newElement->registerUniversal( newUniversal );
    newUniversal->addElement( *newElement );
    newUniversal->notifyChangeDone( newWriter );
    if( accuracy != DataElement::NO_ACCURACY )
    {
        newWriter->setAccuracy( accuracyUnit, accuracy );
    }
    return newWriter;
}

void Component::segfaultImpl()
{
    logger_.syslog( "Segfault Requested!", Syslog::FAULT );
    segfaultRequested_ = false;
    size_t address( 0 );
    for( int i = 0; i < 10; ++i )
    {
        address += i * 0;
    }
    int value = *( int* )address;
    printf( "Value at address %08ZX is %d\n", address, value );
}
