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

#include "ReadDataComponent.h"

#include "component/ComponentRegistry.h"
#include "data/Slate.h"
#include "data/StrValue.h"
#include "data/UniversalDataReader.h"
#include "missionScript/MissionItem.h"
#include "missionScript/MissionNode.h"
#include "missionScript/ValueClause.h"
#include "units/Units.h"

const StrValue ReadDataComponent::NO_VALUE( "no_value" );

ReadDataComponent* ReadDataComponent::Instance( MissionNode* node, const Str& itemName,
        MissionItem* parent, const Module* module, Logger& logger )
{
    ReadDataComponent* readData = new ReadDataComponent( itemName );
    parent->setComponent( readData ); // temporary assigment for configuring DataReaders

    const char* strategyStr = node->getAttribute( "Strategy" );
    if( NULL != strategyStr )
    {
        DataAccessor::RequestStrategy requestStrategy =
            DataAccessor::Str2RequestStrategy( strategyStr );
        if( requestStrategy != DataAccessor::NO_STRATEGY )
        {
            readData->requestStrategy_ = requestStrategy;
        }
    }

    MissionNode* partNode = node->getFirstChild( true );
    while( NULL != partNode && partNode->isElement() )
    {
        bool isUri = ( strncmp( partNode->getName(), "Universal:", 10 ) == 0 );
        Str name = ValueClause::StripPastColon( partNode->getName() );
        isUri |= name.find( '.' ) != Str::NO_POS;

        if( name == "Timeout"
                || name == "Sequence"
                || name == "Parallel"
                || name == "Progression"
                || name == "When"
                || name == "While"
                || name == "Break"
                || name == "Preemptive" )
        {
            // Ignore the node -- it will be handled by MissionItem
        }
        else if( name == "Period" )
        {
            readData->intervalSettingClause_ = ValueClause::Instance( partNode, true, parent, logger );
        }
        else if( name == "Interval" )
        {
            MissionNode* childNode = partNode->getFirstChild( true );
            if( NULL == childNode )
            {
                logger.syslog( "Interval element must specify an Interval!", Syslog::CRITICAL );
            }
            else
            {
                const char* uriChars;
                if( strncmp( childNode->getName(), "CustomUri", 9 ) == 0 )
                {
                    uriChars = childNode->getAttribute( "Uri" );
                }
                else
                {
                    uriChars = childNode->getName();
                }
                Str uriStr;
                uriStr = ValueClause::StripPastColon( uriChars );

                ElementURI* elementURI = Slate::FindElementURI( uriStr );
                if( NULL == elementURI )
                {
                    logger.syslog( "Could not find Interval variable " + uriStr, Syslog::ERROR );
                }
                else
                {
                    if( elementURI->isUniversal() )
                    {
                        UniversalURI* universalURI = static_cast<UniversalURI*>( elementURI );
                        readData->intervalValueReader_ = Slate::NewUniversalReader( *universalURI,
                                                         readData, & NO_VALUE, false );
                    }
                    else
                    {
                        readData->intervalValueReader_ = Slate::NewReader( *elementURI,
                                                         readData, & NO_VALUE, false );
                    }

                    if( NULL == readData->intervalValueReader_ )
                    {
                        logger.syslog( "Invalid Interval source for " + itemName + ": " +  uriStr, Syslog::CRITICAL );
                        break;
                    }
                    childNode = childNode->getNextSibling( true );
                    readData->intervalSettingClause_ = ValueClause::Instance( childNode, false, parent, logger );
                }
            }
        }
        else if( isUri )
        {
            readData->readerNames_.push( new Str( name ) );
        }
        else
        {
            Component* sensor = ComponentRegistry::Find( name );
            if( NULL == sensor )
            {
                logger.syslog( "No sensor named " + name, Syslog::CRITICAL );
            }
            readData->sensors_.push( sensor );

            logger.syslog( "Initialize ReadDataComponent with component " + name );
        }
        partNode = partNode->getNextSibling( true );
    }
    return readData;
}

ReadDataComponent::ReadDataComponent( const Str& name )
    : Behavior( name,  NULL, true, true ),
      sensors_( false ),
      readerNames_( true ),
      varReaders_( false ),
      intervalValueReader_( NULL ),
      lastIntervalValue_( nan( "" ) ),
      intervalSettingClause_( NULL ),
      intervalTriggered_( false ),
      initializedReaders_( false ),
      requestStrategy_( DataAccessor::MIN_POWER )
{
    // debugLevel_ = Syslog::INFO;
}

ReadDataComponent::~ReadDataComponent()
{
    if( NULL != intervalSettingClause_ )
    {
        delete intervalSettingClause_;
    }
}

/// Initialize function
void ReadDataComponent::initialize( void )
{
    DEBUG_LOG( "initialize" )
    setTimeOfLastRun( Timestamp::Now() );
}

// Initialize DataReaders
void ReadDataComponent::initializeReaders( void )
{
    initializedReaders_ = true;
    for( unsigned int i = 0; i < readerNames_.size(); ++i )
    {
        Str& name = *readerNames_.get( i );

        unsigned short code( Slate::GetElementURICode( name.cStr() ) );
        if( code == ElementURI::NO_CODE )
        {
            logger_.syslog( "Unable to read non-existent URI:" + name, Syslog::CRITICAL );
            initializedReaders_ = false;
        }
        DataElement* dataElement = Slate::GetElement( code );
        if( NULL == dataElement )
        {
            logger_.syslog( "Unable to read non-existent data element: " + name, Syslog::CRITICAL );
            initializedReaders_ = false;
        }
        else
        {
            // get the data reader if there is a dataElement
            DataReader* reader = new DataReader( this, *dataElement, &NO_VALUE, false );
            if( NULL != reader )
            {
                varReaders_.push( reader );
                logger_.syslog( "Initialize ReadDataComponent to sense " + name );
                // if(reader->getUri() == UniversalURI::PLATFORM_COMMUNICATIONS) debugLevel_=Syslog::IMPORTANT;
            }
        }

    }
}

/// Just do the run: ignore the results of the satisfied
void ReadDataComponent::run()
{
    DEBUG_LOG( "run" )
    requestData( true );
}

/// Just do the satisfied: return true if envelope "satisfied"
bool ReadDataComponent::isSatisfied()
{
    DEBUG_LOG( "isSatisfied" )
    if( !initializedReaders_ )
    {
        initializeReaders();
    }
    if( !initializedReaders_ )
    {
        return false;
    }
    if( dt_ == Timespan::INVALID_TIMESPAN )
    {
        return false;
    }
    bool satisfied( true );
    for( unsigned int i = 0; i < sensors_.size(); ++i )
    {
        satisfied &= sensors_.get( i )->getWriteTimestamp().elapsed() < dt_;
    }
    for( unsigned int i = 0; i < varReaders_.size(); ++i )
    {
        satisfied &= varReaders_.get( i )->getTimestamp().elapsed() < dt_;
    }
    return satisfied;
}

/// Do the run, and return true if envelope "satisfied"
bool ReadDataComponent::runIfUnsatisfied()
{
    DEBUG_LOG( "runIfUnsatisfied" )
    if( !isSatisfied() )
    {
        requestData( true );
        return false;
    }
    requestData( false );
    return true;
}

void ReadDataComponent::requestData( bool requestingData )
{
    DEBUG_LOG( "requestData(", requestingData )
    if( !initializedReaders_ )
    {
        initializeReaders();
    }
    if( !initializedReaders_ )
    {
        return;
    }

    if( requestingData )
    {
        float intervalSetting( nan( "" ) );

        if( NULL != intervalSettingClause_ && NULL != intervalSettingClause_->getDataValue() )
        {
            if( intervalSettingClause_->eval() )
            {
                DataValue* value = intervalSettingClause_->getDataValue();
                value->copyTo( value->getUnit().getBaseUnit(), intervalSetting );
            }
        }

        if( isnan( intervalSetting ) )
        {
            requestingData = true;
        }
        else
        {
            double intervalValue;
            if( NULL == intervalValueReader_ )
            {
                intervalValue = Timestamp::Now().asDouble();
            }
            else
            {
                intervalValueReader_->read( intervalValueReader_->getDefaultUnit()->getBaseUnit(), intervalValue );
            }
            if( isnan( lastIntervalValue_ ) || fabs( intervalValue - lastIntervalValue_ ) > intervalSetting )
            {
                lastIntervalValue_ = intervalSetting * ( round( intervalValue / intervalSetting ) );
                intervalTriggered_ = true;
            }
            else if( intervalTriggered_ )
            {
                requestingData = intervalTriggered_ = !isSatisfied();
            }
            else
            {
                requestingData = false;
            }
            //printf( "intervalSetting=%g,intervalValue=%g,requestingData=%d\n", intervalSetting, intervalValue, requestingData );
        }
    }

    for( unsigned int i = 0; i < sensors_.size(); ++i )
    {
        sensors_.get( i )->requestData( requestingData );
    }
    for( unsigned int i = 0; i < varReaders_.size(); ++i )
    {
        varReaders_.get( i )->requestData( requestingData ? requestStrategy_ : DataAccessor::NO_STRATEGY );
    }
}

/// Called when the mission component becomes preempted.
void ReadDataComponent::preempted()
{
    DEBUG_LOG( "preempted" )
    if( initializedReaders_ )
    {
        requestData( false );
    }
}

/// Uninit function
void ReadDataComponent::uninitialize( void )
{
    DEBUG_LOG( "uninitialize" )
    if( initializedReaders_ )
    {
        requestData( false );
    }
}

