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

#include "MissionManager.h"

#include "bitModule/SBITIF.h"
#include "bitModule/IBITIF.h"
#include "data/Slate.h"
#include "data/StrValue.h"
#include "data/UniversalDataWriter.h"
#include "io/FileInStream.h"
#include "logger/MissionStateLogger.h"
#include "missionScript/MissionAPI.h"
#include "missionScript/MissionAPIRegistry.h"
#include "missionScript/MissionItem.h"
#include "missionScript/Aggregate.h"
#include "missionScript/MissionNode.h"
#include "supervisor/CommandExec.h"
#include "units/Units.h"

#include <unistd.h>

MissionManager* MissionManager::Instance_( NULL );

/// Constructor
MissionManager::MissionManager()
    : SyncMissionComponent( "MissionManager", NULL ),
      loadedMissionItem_( NULL ),
      loadedMissionFilename_(),
      okToRun_( false ),
      quitAtEnd_( false ),
      startupMissionItem_( NULL ),
      defaultMissionItem_( NULL )
{
    Instance_ = this;
    sBITRunningReader_ = newDataReader( SBITIF::SBIT_RUNNING_STATE );
    iBITRunningReader_ = newDataReader( IBITIF::IBIT_RUNNING_STATE );
    missionStartedWriter_ = newUniversalWriter( UniversalURI::MISSION_STARTED, Units::COUNT, 1 );
    setAllowableFailures( 1 );
    setRetryTimeout( 60 );
}


/// Destructor
MissionManager::~MissionManager()
{
    Instance_ = NULL;
}

MissionManager* MissionManager::GetInstance()
{
    return MissionManager::Instance_;
}

MissionItem* MissionManager::loadFromFile( const char* filename )
{
    if( NULL != loadedMissionItem_ )
    {
        uninitializeMission( loadedMissionItem_ );
    }

    MissionStateLogger::ClearStates();

    okToRun_ = false;
    loadedMissionItem_ = missionFromFile( filename );
    if( NULL != loadedMissionItem_ )
    {
        loadedMissionFilename_ = filename;
    }

    return loadedMissionItem_;
}

bool MissionManager::runLoadedMission( bool quitAtEnd )
{
    if( NULL != loadedMissionItem_ )
    {
        quitAtEnd_ = quitAtEnd;
        okToRun_ = true;
        MissionStateLogger::NewRun( loadedMissionFilename_ );
        return true;
    }
    return false;
}

/// Resumes the contained mission stack.
/// Calls initialize on the mission stack if needed.
bool MissionManager::resumeLoadedMission()
{
    if( NULL == loadedMissionItem_ && Str::EMPTY_STR != loadedMissionFilename_ )
    {
        Str filenameToResume( loadedMissionFilename_ );
        loadedMissionItem_ = loadFromFile( filenameToResume.cStr() );
    }

    if( NULL == loadedMissionItem_ || NULL == loadedMissionItem_->getComponent() )
    {
        logger_.syslog( "No mission loaded to resume.", Syslog::ERROR );
        return false;
    }

    MissionStateLogger::NewResume( loadedMissionFilename_, logger_ );
    MissionStateLogger::ShowMissionState( logger_ );

    MissionItem* missionItem = loadedMissionItem_;
    Behavior* behavior = missionItem->getComponent();

    int repeatIndex( 0 );
    Str* childName = MissionStateLogger::GetActive( behavior->getName(), repeatIndex );
    missionItem->setRepeatIndex( repeatIndex );
    while( NULL != childName && missionItem->isAggregate() )
    {
        Aggregate* aggregate = ( Aggregate* )behavior;
        missionItem = aggregate->getChild( *childName );
        if( NULL != missionItem )
        {
            aggregate->setResumeItem( missionItem );
            behavior = missionItem->getComponent();
            childName = MissionStateLogger::GetActive( behavior->getName(), repeatIndex );
            missionItem->setRepeatIndex( repeatIndex );
        }
        else
        {
            childName = NULL;
        }
    }

    okToRun_ = true;

    return true;
}

/// Loads a mission from a file
MissionNode* MissionManager::LoadRootNode( const char* filename, bool insert, Logger& logger )
{
    MissionAPI* missionAPI( NULL );

    Str filenameStr( filename );
    if( !filenameStr.startsWith( "/", 1 ) && !filenameStr.startsWith( "Missions/", 9 ) && !filenameStr.startsWith( "./Missions/", 11 ) )
    {
        filenameStr = "Missions/" + filenameStr;
    }

    const char* extension = strrchr( filename + 1, '.' );
    if( NULL != extension )
    {
        missionAPI = MissionAPIRegistry::FindByExtension( extension );
        if( NULL == missionAPI )
        {
            logger.syslog( "No mission API found for extension: ", extension, Syslog::ERROR );
            return NULL;
        }
    }
    else
    {
        FastMap<const Str, MissionAPI*>* apisByExtension = MissionAPIRegistry::GetApisByExtension();
        for( int i = ( int ) apisByExtension->size() - 1; i >= 0 ; --i )
        {
            MapEntry<const Str, MissionAPI*>* entry = apisByExtension->getIndexedEntry( i );
            if( access( ( filenameStr + entry->getKey() ).cStr(), R_OK ) == 0 )
            {
                extension = entry->getKey().cStr();
                missionAPI = entry->getItem();
                filenameStr += entry->getKey();
                break;
            }
        }
    }

    if( extension == NULL )
    {
        logger.syslog( "No file found matching mission path: ", filename, Syslog::ERROR );
        return NULL;
    }

    if( !insert )
    {
        logger.syslog( "Loading Mission from file: " + filenameStr, Syslog::INFO );
    }
    else
    {
        logger.syslog( "Inserting Stack from file: " + filenameStr, Syslog::INFO );
    }

    MissionNode* rootNode = missionAPI->loadMissionFile( filenameStr.cStr(), logger, Syslog::ERROR );
    if( NULL == rootNode )
    {
        logger.syslog( "Could not load mission " + filenameStr, Syslog::CRITICAL );
        return NULL;
    }

    return rootNode;
}

/// Finds a mission node within a root node
MissionNode* MissionManager::FindTopNode( MissionNode* rootNode, Logger& logger )
{
    MissionNode* topNode = rootNode->getChild( "Mission" );
    if( NULL == topNode )
    {
        topNode = rootNode->getChild( "Aggregate" );
    }
    if( NULL == topNode )
    {
        topNode = rootNode->getChild( "DefineBehavior" );
    }
    if( NULL == topNode )
    {
        logger.syslog( "Could not load top mission Node.", Syslog::ERROR );
        return NULL;
    }

    if( !topNode->hasChildNodes() )
    {
        logger.syslog( "Top node has no child nodes: ", topNode->getName(), Syslog::ERROR );
        return NULL;
    }

    return topNode;
}

/// Logs the mission to the syslog
void MissionManager::LogMission( const char *filename, Logger& logger )
{
    FileInStream missionFile( filename );
    Str missionText( "" );
    missionFile.seek( 0 );
    while( !missionFile.eof() )
    {
        char buff[1024];
        unsigned int bytesRead = missionFile.read( buff, sizeof( buff ) - 1 ).bytesRead();
        if( bytesRead < sizeof( buff ) )
        {
            buff[bytesRead] = '\0';
        }
        missionText += buff;
    }
    if( 0 < missionText.length() )
    {
        logger.syslog( Str( "\n" ) + missionText + Str( "\n" ), Syslog::DEBUG );
    }
}

/// Loads a mission from a file
MissionItem* MissionManager::missionFromFile( const char* filename )
{

    MutexLocker stackLock( stackModMutex_ );

    MissionNode* rootNode = LoadRootNode( filename, false, logger_ );
    if( NULL == rootNode ) return NULL;

    MissionNode* missionNode = FindTopNode( rootNode, logger_ );
    if( NULL == missionNode )
    {
        delete rootNode;
        return NULL;
    }

    MissionItem* missionItem = MissionItem::Instance( NULL, missionNode, 0, logger_ );
    delete rootNode;

    // Print the mission out to Syslog::DEBUG
    LogMission( filename, logger_ );

    if( NULL != missionItem )
    {
        this->setState( Component::BLOCK_NORMAL );
    }
    return missionItem;
}

/// Runs the contained mission stack.
/// Calls initialize on the mission stack if needed.
void MissionManager::run()
{
    // Don't run a mission if SBIT/IBIT is busy running
    bool sbitRunning( false ), ibitRunning( false );
    if( ( sBITRunningReader_->read( sbitRunning ) && sbitRunning ) || ( iBITRunningReader_->read( ibitRunning ) && ibitRunning ) )
    {
        return;
    }

    // Avoid hangups...
    if( !stackModMutex_.tryLock() )
    {
        return;
    }

    MissionItem* activeMissionItem = startupMissionItem_ != NULL ? startupMissionItem_ :
                                     ( loadedMissionItem_ != NULL && okToRun_ ? loadedMissionItem_ : defaultMissionItem_ );

    if( NULL == activeMissionItem )
    {
        logger_.syslog( "No startup, active, or default mission!", Syslog::ERROR );
        stackModMutex_.unlock();
        return;
    }

    Aggregate* missionStack = activeMissionItem->getAggregate();

    if( NULL == missionStack )
    {
        logger_.syslog( "No stack within mission!", Syslog::ERROR );
        stackModMutex_.unlock();
        uninitializeMission( activeMissionItem );
        return;
    }

    bool isStartupMissionItem = activeMissionItem == startupMissionItem_;
    bool isDefaultMissionItem = activeMissionItem == defaultMissionItem_;

    if( !isDefaultMissionItem
            && NULL != defaultMissionItem_
            && defaultMissionItem_->isInitialized() )
    {
        defaultMissionItem_->uninitialize();
    }

    if( !activeMissionItem->isInitialized() )
    {
        logger_.syslog( "Started mission " + activeMissionItem->getComponent()->getName(), Syslog::IMPORTANT );
        missionStartedWriter_->write( Units::COUNT, 1 );
        activeMissionItem->initialize();
    }

    bool satisfied = activeMissionItem->runIfUnsatisfied();

    stackModMutex_.unlock();

    if( this->isCompleted() && !isStartupMissionItem && !isDefaultMissionItem )
    {
        logger_.syslog( "MissionManager is completed.", Syslog::INFO );
        uninitializeMission( activeMissionItem );
    }
    else if( missionStack->isCompleted() || satisfied )
    {
        if( isDefaultMissionItem )
        {
            defaultMissionItem_->uninitialize();
        }
        else
        {
            logger_.syslog( missionStack->getName() + " is completed.", Syslog::INFO );
            uninitializeMission( activeMissionItem );
        }
    }

}

/// Initialize the mission manager.
void MissionManager::initialize()
{
    StrValue startupScript;
    Slate::ReadOnce( "Config/workSite", "startupScript", startupScript, logger_ );
    if( NULL == startupMissionItem_ )
    {
        startupMissionItem_ = missionFromFile( startupScript.asString().cStr() );
        if( NULL == startupMissionItem_ )
        {
            logger_.syslog(
                "Could not load startup mission.", Syslog::ERROR );
        }
    }

    StrValue defaultScript;
    Slate::ReadOnce( "Config/workSite", "defaultScript", defaultScript, logger_ );
    if( NULL == defaultMissionItem_ )
    {
        defaultMissionItem_ = missionFromFile( defaultScript.asString().cStr() );
        if( NULL == defaultMissionItem_ )
        {
            logger_.syslog(
                "Could not load default mission.", Syslog::ERROR );
        }
    }
}

/// Display the currently running stack to Syslog
void MissionManager::logStack()
{
    MutexLocker stackLock( stackModMutex_ );

    MissionItem* activeMissionItem = startupMissionItem_ != NULL ? startupMissionItem_ :
                                     ( loadedMissionItem_ != NULL && okToRun_ ? loadedMissionItem_ : defaultMissionItem_ );

    if( NULL == activeMissionItem )
    {
        logger_.syslog( "No startup, active, or default mission!", Syslog::IMPORTANT );
    }
    else
    {
        int priority( 0 );
        activeMissionItem->getAggregate()->logStack( priority );
        if( priority == 0 )
        {
            logger_.syslog( "Mission loaded, but not running.", Syslog::IMPORTANT );
        }
    }

}

/// Uninitialize all mission stacks.
void MissionManager::uninitialize()
{
    if( NULL != defaultMissionItem_ )
    {
        uninitializeMission( defaultMissionItem_ );
        defaultMissionItem_ = NULL;
    }
    if( NULL != loadedMissionItem_ )
    {
        uninitializeMission( loadedMissionItem_ );
    }
    if( NULL != startupMissionItem_ )
    {
        uninitializeMission( startupMissionItem_ );
    }
}

/// Uninitialize a running mission stack.
void MissionManager::uninitializeMission( MissionItem* missionItem )
{
    logger_.syslog( Str( "Uninitializing Mission " ) + ( NULL != missionItem ? missionItem->getComponent()->getName().cStr() : "NULL" ), Syslog::INFO );
    bool wasLoadedMissionItem( missionItem == loadedMissionItem_ );
    if( wasLoadedMissionItem )
    {
#ifdef __FASTSIM
        Handler::SetAllowFastSim( false );
#endif
        loadedMissionItem_ = NULL;
    }
    if( missionItem == startupMissionItem_ )
    {
        startupMissionItem_ = NULL;
    }
    if( NULL != missionItem )
    {
        MutexLocker stackLock( stackModMutex_ );

        Aggregate* missionStack = missionItem->getAggregate();

        if( NULL != missionStack )
        {
            if( missionStack->getBehaviorState() != Behavior::BEHAVIOR_INACTIVE )
            {
                //missionStack->uninitialize();
                missionItem->uninitialize();
            }
        }
        delete missionItem;

        if( wasLoadedMissionItem && quitAtEnd_ )
        {
            CommandExec::DoCommand( "quit", false );
        }
    }

}
