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

#include "Aggregate.h"

#include <ctype.h> // for isalnum

#include "data/ArgDataElement.h"
#include "data/Slate.h"
#include "logger/MissionStateLogger.h"
#include "missionScript/Assign.h"
#include "missionScript/MissionItem.h"
#include "missionScript/MissionNode.h"

Aggregate::~Aggregate()
{
    if( NULL != argMap_ )
    {
        delete argMap_;
    }
    if( NULL != redefines_ )
    {
        delete redefines_;
    }
}

/// Standard component init code
void Aggregate::initialize()
{
    logger_.syslog( "Aggregate::initialize " + name_, Syslog::DEBUG );

    if( NULL != ref_ )
    {
        ref_->addCall();
        setBehaviorState( BEHAVIOR_ACTIVE );
        return;
    }

    nonParallelChildren_ = 0;
    for( unsigned int i = 0; i < behaviors_.size(); ++i )
    {
        MissionItem* item( behaviors_[ i ] );
        item->getComponent()->setBehaviorState( BEHAVIOR_INACTIVE );
        if( NULL != item && item-> isParallel() )
        {
            item->initialize();
        }
        if( item->isSequence() && NULL == item->getWhenClause() )
        {
            ++nonParallelChildren_;
        }
    }
    if( NULL == resumeItem_ )
    {
        activeItem_ = NULL;
        gotoNextItem();
    }
    else
    {
        activeItem_ = resumeItem_;
        resumeItem_ = NULL;
    }

    if( NULL != activeItem_ || parallel_
            || ( 0 == nonParallelChildren_
                 && ( behaviors_.size() > 0
                      || timeout_ != Timespan::INVALID_TIMESPAN ) ) )
    {
        // Initialize already called if parallel
        if( NULL != activeItem_ && !activeItem_->isInitialized() )
        {
            activeItem_->initialize();
        }
        setBehaviorState( BEHAVIOR_ACTIVE );
    }
}

/// Display the currently running stack to Syslog
void Aggregate::logStack( int& priority )
{
    unsigned int maxStackLocation = behaviors_.size() - 1;
    bool active[behaviors_.size()];
    for( unsigned int i = 0; i < behaviors_.size(); ++i )
    {
        MissionItem* item = behaviors_ [ i ];
        if( NULL != item )
        {
            active[ i ] = item->isActive( activeItem_ );
        }
        else
        {
            active[ i ] = false;
            continue;
        }
        bool isPreemptive = item->isPreemptive();

        if( active[ i ] && isPreemptive )
        {
            maxStackLocation = i;
            break;
        }
    }
    for( unsigned int i = 0; behaviors_.size() > 0 && i <= maxStackLocation; ++i )
    {
        if( active[ i ] )
        {
            if( behaviors_[ i ]->getAggregate() != NULL )
            {
                behaviors_[ i ]->getAggregate()->logStack( priority );
            }
            else if( behaviors_[ i ]->getComponent() != NULL )
            {
                logger_.syslog( "Priority " + Str( priority ) + ": " + behaviors_ [ i ]->getComponent()->getName(), Syslog::IMPORTANT );
                ++priority;
            }
        }
    }
}

/// Called when the mission component becomes preempted.
void Aggregate::preempted()
{
    for( unsigned int i = 0; i < behaviors_.size(); ++i )
    {
        MissionItem* item = behaviors_ [ i ];
        if( NULL != item )
        {
            item->preempted();
        }
    }
}

/// Standard component uninit code
void Aggregate::uninitialize()
{
    logger_.syslog( "Aggregate::uninitialize " + name_, Syslog::DEBUG );
    if( NULL != ref_ )
    {
        ref_->removeCall();
    }

    for( unsigned int i = 0; i < behaviors_.size(); ++i )
    {
        if( NULL != behaviors_[ i ] )
        {
            behaviors_[ i ]->uninitialize();
        }
    }
    setBehaviorState( BEHAVIOR_INACTIVE );
}

/// Returns true if the aggregate contains an active preemptive clause
bool Aggregate::containsPreemptive()
{
    for( unsigned int i = 0; i < behaviors_.size(); ++i )
    {
        MissionItem* item = behaviors_ [ i ];
        if( NULL != item )
        {
            if( item->isPreemptive() && item->isActive( activeItem_ ) )
            {
                return true;
            }
        }
    }
    return false;
}

/// Actual "payload" of the component
void Aggregate::run()
{
    //logger_.syslog("Run, BehaviorState=", getBehaviorState(),Syslog::INFO);
    if( NULL != ref_ )
    {
        if( ref_->satisfied() )
        {
            setBehaviorState( BEHAVIOR_COMPLETED );
            logger_.syslog( "Completed " + getName(), Syslog::INFO );
        }
        return;
    }

    for( unsigned int i = 0; NULL != redefines_ && i < redefines_->size(); ++i )
    {
        redefines_->get( i )->execute();
    }

    // Run though the behaviors forward, to see if there are any preemptive when clauses
    const unsigned int bsize( behaviors_.size() );
    unsigned int maxBehaviorLocation = bsize - 1;
    bool active[bsize];
    memset( active, 0, sizeof active );
    for( unsigned int i = 0; i < bsize; ++i )
    {
        MissionItem* item = behaviors_ [ i ];
        if( NULL != item )
        {
            active[ i ] = item->isActive( activeItem_ );
        }
        else
        {
            active[ i ] = false;
            continue;
        }
        bool isPreemptive = item->isPreemptive();
        //logger_.syslog(item->getName() + " active = " + Str(active[ i ]) + " isPreemptive=" + Str( isPreemptive ));
        if( active[ i ] && isPreemptive )
        {
            maxBehaviorLocation = i;
            break;
        }
    }
    for( unsigned int i = bsize - 1; i > maxBehaviorLocation; --i )
    {
        active[ i ] = false;
        MissionItem* item = behaviors_ [ i ];
        item->preempted();
    }

    // Run through items in reverse order
    for( unsigned int i = maxBehaviorLocation; ( int )i >= 0; --i )
    {
        MissionItem* item( behaviors_[ i ] );
        bool itemActive( active[ i ] );
        if( NULL != item )
        {
            bool satisfied( false );

            if( itemActive )
            {
                if( !item->isInitialized() )
                {
                    item->initialize();
                }
                if( item->isSequence()  && item->getComponent()->getBehaviorState() == BEHAVIOR_ACTIVE )
                {
                    //logger_.syslog( "runIfUnsatisfied " + item->getName(), Syslog::INFO );
                    satisfied = item->runIfUnsatisfied();
                }
                // Always run behaviors
                else if( item->isParallel() )
                {
                    // fire off behavior
                    item->run();
                }
                // Run some other items
                else
                {
                    satisfied = item->runIfUnsatisfied();
                }
            }
            // Deal with done items
            bool inactive = item->getComponent()->getBehaviorState() != BEHAVIOR_ACTIVE;
            bool itemDone = !itemActive && !inactive;
            if( satisfied || itemDone )
            {
                // Don't uninit progression items unless they are done
                if( itemDone || !item->isParallel() )
                {
                    item->uninitialize();
                }
                if( item->getRepeatLimit() > 1 )
                {
                    item->setRepeatIndex( item->getRepeatIndex() + 1 );
                    if( item->getRepeatIndex() < item->getRepeatLimit() )
                    {
                        item->getComponent()->getLogger().syslog( "Running loop #", item->getRepeatIndex() + 1,
                                Syslog::INFO );
                        item->initialize();
                    }
                }
                if( item == activeItem_ )
                {
                    // This will set the completed flag if there are no active items
                    if( item->getRepeatLimit() <= 1 || item->getRepeatIndex() >= item->getRepeatLimit() )
                    {
                        gotoNextItem();
                        if( NULL != activeItem_ && !activeItem_->isInitialized() )
                        {
                            activeItem_->initialize();
                        }
                    }
                }
            }
        }
    }
    if( isCompleted() )
    {
        logger_.syslog( "Completed " + getName(), Syslog::INFO );
    }
}

MissionItem* Aggregate::getItemByRefId( const Str& refId )
{
    for( unsigned int i = 0; i < behaviors_.size(); ++i )
    {
        MissionItem* item( behaviors_[ i ] );
        // We are looking for behaviors with full names that end with refId
        size_t findLoc( item->getComponent()->getName().findLastOf( refId ) );
        if( findLoc != Str::NO_POS )
        {
            if( findLoc == item->getComponent()->getName().length() - refId.length() )
            {
                // But not those where refId is part of a longer alphanumeric string
                if( findLoc == 0 || !isalnum( item->getComponent()->getName().cStr()[ findLoc - 1 ] ) )
                {
                    return item;
                }
            }
        }
    }
    return NULL;
}

// Set output to true if this is a DefineOutput
void Aggregate::defineArg( const Str& argName, DataValue* dataValue, bool output )
{
    if( NULL == argMap_ )
    {
        argMap_ = new FastMap<const Str, DataReader*>();
    }
    DataReader* dataReader( NULL );
    if( !output )
    {
        ElementURI elementUri = ElementURI::Create( getName(), argName );
        DataElement* dataElement = Slate::GetElement( elementUri );
        if( NULL == dataElement )
        {
            dataElement = new ArgDataElement( elementUri, dataValue->copy() );
            Slate::MapDataElement( &elementUri, dataElement );
        }
        dataReader = new DataReader( this, *dataElement, dataValue->copy(), true );
        //dataReader = Slate::NewInputReader( argName, this, dataValue->copy() );
        if( NULL != dataReader )
        {
            argMap_->put( argName, dataReader );
        }
    }
    if( NULL != dataReader )
    {
        Slate::WriteOnce( dataReader->getUri().getCode(), this, *dataValue );
    }
    if( output )
    {
        ElementURI elementUri = ElementURI::Create( getName(), argName );
        DataElement* dataElement = Slate::GetElement( elementUri );
        if( NULL == dataElement )
        {
            dataElement = new ArgDataElement( elementUri, dataValue->copy() );
            Slate::MapDataElement( &elementUri, dataElement );
        }
        Slate::WriteOnce( argName, this, *dataValue );
        dataReader = new DataReader( this, *dataElement, dataValue->copy(), true );
        //dataReader = Slate::NewInputReader( argName, this, dataValue->copy() );
        if( NULL != dataReader )
        {
            argMap_->put( argName, dataReader );
        }
    }
}

void Aggregate::redefineArg( const Str& argName, ValueClause* valueClause )
{
    if( NULL == redefines_ )
    {
        redefines_ = new FlexArray<Assign*>( true );
    }
    DataReader* dataReader = findArgReader( argName );
    if( NULL == dataReader )
    {
        logger_.syslog( "No Redefinable Arg named " + argName, Syslog::CRITICAL );
    }
    else
    {
        Assign* assign = new Assign( getName() + ".Redefine." + argName, dataReader->getUri(), valueClause, false );
        redefines_->push( assign );
    }
}

DataReader* Aggregate::findArgReader( const Str& argName )
{
    if( NULL == argMap_ )
    {
        return NULL;
    }
    return argMap_->get( argName );
}

MissionItem* Aggregate::getChild( const Str& childName )
{
    for( unsigned int i = 0; i < behaviors_.size(); ++i )
    {
        MissionItem* child = behaviors_.get( i );
        if( NULL != child && NULL != child->getComponent() &&
                child->getComponent()->getName() == childName )
        {
            return child;
        }
    }
    return NULL;
}

/// Advances activeItem_ to the next sequential behavior.
/// Sets activeItem_ to NULL if there are no more sequential behaviors.
void Aggregate::gotoNextItem()
{
    if( /*parallel_
            ||*/ ( nonParallelChildren_ == 0
                           && ( behaviors_.size() > 0
                                || timeout_ != Timespan::INVALID_TIMESPAN ) ) )
    {
        return;
    }
    int index = NULL != activeItem_ ? ( int )( activeItem_->getTablePosition() ) : -1;
    activeItem_ = NULL;
    for( unsigned int i = ( unsigned int )( index + 1 ); i < behaviors_.size(); ++i )
    {
        if( NULL != behaviors_[ i ]
                && behaviors_[ i ]->isCompletable()
                && NULL == behaviors_[ i ]-> getWhenClause()
                && behaviors_[ i ]->getComponent()->getBehaviorState() != BEHAVIOR_TIMEOUT
                && behaviors_[ i ]->getComponent()->getBehaviorState() != BEHAVIOR_COMPLETED )
        {
            activeItem_ = behaviors_[ i ];
            activeItem_->setRepeatIndex( 0 );
            break;
        }
    }
    if( NULL == activeItem_ )
    {
        setBehaviorState( BEHAVIOR_COMPLETED );
    }
    MissionStateLogger::SetActive( getName(), activeItem_ );
}

/// Private constructor
Aggregate::Aggregate( const Str& name )
    : Behavior( name, NULL, false, false ),
      behaviors_( true ),
      ref_( NULL ),
      activeItem_( NULL ),
      resumeItem_( NULL ),
      index_( -1 ),
      parallel_( false ),
      nonParallelChildren_( 0 ),
      argMap_( NULL ),
      redefines_( NULL )
{
    setBehaviorState( BEHAVIOR_INACTIVE );
}

void Aggregate::push( MissionItem* item )
{
    behaviors_.push( item );
}
