/** \file
 *
 *  Contains the DefineBehavior and DefinedBehavior class implementations.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */
#include "DefineBehavior.h"

#include "data/Slate.h"
#include "missionScript/MissionNode.h"
#include "missionScript/Method.h"
#include "missionScript/ScriptAPI.h"
#include "missionScript/ScriptNode.h"
#include "missionScript/ScriptAPIRegistry.h"
#include "missionScript/ValueClause.h"
#include "units/UnitRegistry.h"

DefineBehavior* DefineBehavior::Instance( MissionNode* node, const Str& itemName, Logger& logger )
{
    const char* language = node->getAttribute( "Language" );
    if( NULL == language )
    {
        logger.syslog( Str( "No language specified for DefineBehavior named " ) + itemName, Syslog::CRITICAL );
        return NULL;
    }
    ScriptAPI* scriptAPI = ScriptAPIRegistry::FindByName( language );
    if( NULL == scriptAPI )
    {
        logger.syslog( Str( "No api defined for the language " ) + language + " in the script for " + itemName, Syslog::CRITICAL );
        return NULL;
    }

    DefineBehavior* define = new DefineBehavior( itemName, scriptAPI );

    MissionNode* childNode = node->getFirstChild();
    while( NULL != childNode )
    {
        MethodInfo* methodInfo = NULL;
        //FlexArray<char*> inputNames( false );
        const char* childName = childNode->getName();
        if( strncmp( childName, "DefineSetting", 8 ) == 0 )
        {
            MissionNode* uriNode = childNode->getFirstChild();
            if( NULL == uriNode )
            {
                logger.syslog( Str( "Unable to find URI node for " ) + itemName + " DefineBehavior setting", Syslog::CRITICAL );
            }
            else
            {
                const char* settingName = childNode->getAttribute( "Name" );
                if( NULL == settingName )
                {
                    logger.syslog( Str( "No name for DefineSetting in " ) + itemName + " DefineBehavior", Syslog::CRITICAL );
                    break;
                }

                Str uriPart;
                if( strncmp( uriNode->getName(), "CustomUri", 9 ) == 0 )
                {
                    uriPart = uriNode->getAttribute( "Uri" );
                }
                else
                {
                    uriPart = ValueClause::StripPastLastDot( uriNode->getName() );
                }

                MissionNode* unitNode = uriNode->getNextSibling();
                if( NULL != unitNode && 0 == strncmp( unitNode->getName(), "Description", 12 ) )
                {
                    unitNode = unitNode->getNextSibling();
                }
                if( NULL == unitNode )
                {
                    logger.syslog( Str( "No Unit for " ) + uriPart + " DefineSetting of " + itemName + " DefineBehavior", Syslog::CRITICAL );
                    break;
                }
                DataValue* defaultValue = ValueClause::ReadDataValue( unitNode, logger );

                if( NULL == defaultValue )
                {
                    logger.syslog( Str( "No default value for " ) + uriPart + " DefineSetting of " + itemName + " DefineBehavior", Syslog::CRITICAL );
                    break;
                }
                else
                {
                    define->settings_.push( new SettingInfo( settingName, uriPart, defaultValue ) );
                }
            }
        }
        else if( strncmp( childName, "Construct", 9 ) == 0 )
        {
            methodInfo = define->constructMethod_ = new MethodInfo();
        }
        else if( strncmp( childName, "Initialize", 11 ) == 0 )
        {
            methodInfo = define->initializeMethod_ = new MethodInfo();
        }
        else if( strncmp( childName, "Run", 4 ) == 0 )
        {
            methodInfo = define->runMethod_ = new MethodInfo();
        }
        else if( strncmp( childName, "IsSatisfied", 10 ) == 0 )
        {
            methodInfo = define->isSatisfiedMethod_ = new MethodInfo();
        }
        else if( strncmp( childName, "Uninitialize", 13 ) == 0 )
        {
            methodInfo = define->uninitializeMethod_ = new MethodInfo();
        }

        if( NULL != methodInfo )
        {
            MissionNode* methodItemNode = childNode->getFirstChild();
            while( NULL != methodItemNode )
            {
                const char* methodItemName = methodItemNode->getName();
                if( strncmp( methodItemName, "Input", 5 ) == 0 )
                {
                    const char* inputName = methodItemNode->getAttribute( "Name" );
                    if( NULL == inputName )
                    {
                        logger.syslog( "No name for Input argument for " + itemName, Syslog::CRITICAL );
                        break;
                    }

                    MissionNode* uriNode = methodItemNode->getFirstChild();
                    if( NULL == uriNode )
                    {
                        logger.syslog( Str( "No URI for " ) + itemName + " Input argument for " + childName + " method of " + itemName, Syslog::CRITICAL );
                        break;
                    }
                    Str uriStr;
                    if( strncmp( uriNode->getName(), "CustomUri", 10 ) == 0 )
                    {
                        uriStr = uriNode->getAttribute( "Uri" );
                    }
                    else
                    {
                        uriStr = ValueClause::StripPastColon( uriNode->getName() );
                    }

                    MissionNode* unitNode = uriNode->getNextSibling();
                    if( NULL != unitNode && 0 == strncmp( unitNode->getName(), "Description", 12 ) )
                    {
                        unitNode = unitNode->getNextSibling();
                    }
                    if( NULL == unitNode )
                    {
                        logger.syslog( Str( "No Unit for " ) + itemName + " Input argument for " + childName + " method of " + itemName, Syslog::CRITICAL );
                        break;
                    }
                    DataValue* defaultValue = ValueClause::ReadDataValue( unitNode, logger );

                    if( NULL == defaultValue )
                    {
                        logger.syslog( Str( "No default value for " ) + itemName + " Input argument for " + childName + " method of " + itemName, Syslog::CRITICAL );
                        break;
                    }
                    //DataReader* input = Slate::NewReader( ElementURI( "", uriStr ), behavior, defaultValue );
                    //method->addInput( input, &defaultValue->getUnit() );
                    //inputNames.push( ( char* )inputName );
                    methodInfo->addInput( new InputInfo( inputName, uriStr, defaultValue ) );
                }
                else if( strncmp( methodItemName, "Output", 7 ) == 0 )
                {
                    MissionNode* uriNode = methodItemNode->getFirstChild();
                    if( NULL == uriNode )
                    {
                        logger.syslog( Str( "No URI for " ) + itemName + " Output argument for " + childName + " method of " + itemName, Syslog::CRITICAL );
                        break;
                    }
                    Str uriStr;
                    if( strncmp( uriNode->getName(), "CustomUri", 9 ) == 0 )
                    {
                        uriStr = uriNode->getAttribute( "Uri" );
                    }
                    else
                    {
                        uriStr = ValueClause::StripPastColon( uriNode->getName() );
                    }

                    ElementURI* elementURI = Slate::FindElementURI( uriStr );
                    if( NULL == elementURI )
                    {
                        logger.syslog( "Could not find variable " + uriStr, Syslog::CRITICAL );
                    }
                    else
                    {
                        MissionNode* unitNode = uriNode->getNextSibling();
                        if( NULL != unitNode && 0 == strncmp( unitNode->getName(), "Description", 12 ) )
                        {
                            unitNode = unitNode->getNextSibling();
                        }
                        const Unit* unit = NULL == unitNode ? NULL : ValueClause::ReadUnit( unitNode, logger );
                        if( NULL == unit )
                        {
                            logger.syslog( Str( "No unit for " ) + itemName + " Output argument for " + childName + " method of " + itemName, Syslog::CRITICAL );
                            break;
                        }
                        //DataWriter* output = Slate::NewWriter( *elementURI, behavior );
                        methodInfo->addOutput( new OutputInfo( elementURI, unit, false ) );
                    }
                }
                else if( strncmp( methodItemName, "OutputArg", 10 ) == 0 )
                {
                    MissionNode* uriNode = methodItemNode->getFirstChild();
                    if( NULL == uriNode )
                    {
                        logger.syslog( Str( "No URI for " ) + itemName + " Output argument for " + childName + " method of " + itemName, Syslog::CRITICAL );
                        break;
                    }
                    Str uriStr;
                    if( strncmp( uriNode->getName(), "CustomUri", 9 ) == 0 )
                    {
                        uriStr = uriNode->getAttribute( "Uri" );
                    }
                    else
                    {
                        uriStr = ValueClause::StripPastLastDot( uriNode->getName() );
                    }

                    MissionNode* unitNode = uriNode->getNextSibling();
                    if( NULL != unitNode && 0 == strncmp( unitNode->getName(), "Description", 12 ) )
                    {
                        unitNode = unitNode->getNextSibling();
                    }
                    const Unit* unit = NULL == unitNode ? NULL : ValueClause::ReadUnit( unitNode, logger );
                    if( NULL == unit )
                    {
                        logger.syslog( Str( "No unit for " ) + itemName + " Output argument for " + childName + " method of " + itemName, Syslog::CRITICAL );
                        break;
                    }

                    OutputURI* outputUri = new OutputURI( uriStr.cStr(), *unit );

                    methodInfo->addOutput( new OutputInfo( outputUri, unit, true ) );

                }
                else if( strncmp( methodItemName, "Script", 6 ) == 0 )
                {

                    const char* code = methodItemNode->getChildTextValue();
                    //ScriptNode* scriptNode = scriptAPI->makeScriptFunction(
                    //							 NULL, itemName.cStr(), methodName, inputNames,
                    //							 code, logger );
                    //method->setScriptNode( scriptNode );
                    methodInfo->setScript( childName, code );
                }
                methodItemNode = methodItemNode->getNextSibling();
            }
        }
        childNode = childNode->getNextSibling();
    }
    return define;
}

DefineBehavior::~DefineBehavior()
{
    if( NULL != constructMethod_ )
    {
        delete constructMethod_;
    }
    if( NULL != initializeMethod_ )
    {
        delete initializeMethod_;
    }
    if( NULL != runMethod_ )
    {
        delete runMethod_;
    }
    if( NULL != uninitializeMethod_ )
    {
        delete uninitializeMethod_;
    }
    if( NULL != isSatisfiedMethod_ )
    {
        delete isSatisfiedMethod_;
    }

}

DefineBehavior::DefineBehavior( const Str& name, ScriptAPI* scriptAPI )
    : name_( name ),
      scriptAPI_( scriptAPI ),
      settings_( true ),
      constructMethod_( NULL ),
      initializeMethod_( NULL ),
      runMethod_( NULL ),
      uninitializeMethod_( NULL ),
      isSatisfiedMethod_( NULL )
{}


//////////////////////////////////////////////////////////////////////////////
/// DefinedBehavior
///       ^
//////////////////////////////////////////////////////////////////////////////

DefinedBehavior::~DefinedBehavior()
{
    if( NULL != constructMethod_ )
    {
        delete constructMethod_;
    }
    if( NULL != initializeMethod_ )
    {
        delete initializeMethod_;
    }
    if( NULL != runMethod_ )
    {
        delete runMethod_;
    }
    if( NULL != uninitializeMethod_ )
    {
        delete uninitializeMethod_;
    }
    if( NULL != isSatisfiedMethod_ )
    {
        delete isSatisfiedMethod_;
    }

}

void DefinedBehavior::initialize()
{
    logger_.syslog( "Initialize", Syslog::INFO );
    if( NULL != constructMethod_ && !constructMethodRun_ )
    {
        constructMethod_->executeVoid( logger_ );
        if( NULL != runMethod_ )
        {
            FlexArray<OutputURI*>& outputArgs = runMethod_->getOutputArgs();
            for( unsigned int i = 0; i < outputArgs.size(); ++i )
            {
                OutputURI* outputURI = outputArgs.get( i );
                if( NULL != outputURI )
                {
                    DataWriter* argWriter( findArgWriter( *outputURI ) );
                    runMethod_->getOutputs().set( i, argWriter );
                }
            }
        }

        constructMethodRun_ = true;
    }
    if( NULL != initializeMethod_ )
    {
        initializeMethod_->execute( logger_ );
    }
}

void DefinedBehavior::run()
{
    //logger_.syslog("Run",Syslog::INFO);
    if( NULL != runMethod_ )
    {
        runMethod_->execute( logger_ );
    }
}

void DefinedBehavior::uninitialize()
{
    logger_.syslog( "Uninitialize", Syslog::INFO );
    if( NULL != uninitializeMethod_ )
    {
        uninitializeMethod_->execute( logger_ );
    }
}

bool DefinedBehavior::isSatisfied()
{
    //logger_.syslog("IsSatisfied",Syslog::INFO);
    if( NULL != isSatisfiedMethod_ )
    {
        return isSatisfiedMethod_->executeBool( logger_ );
    }
    return false;
}

DefinedBehavior::DefinedBehavior( const Str& prefix, DefineBehavior* define )
    : Behavior( prefix + define->name_,  NULL, true, NULL != define->isSatisfiedMethod_ ),
      define_( define ),
      settings_( false ),
      constructMethod_( NULL == define->constructMethod_ ? NULL : new Method( this, define->constructMethod_, define->scriptAPI_ ) ),
      initializeMethod_( NULL == define->initializeMethod_ ? NULL : new Method( this, define->initializeMethod_, define->scriptAPI_ ) ),
      runMethod_( NULL == define->runMethod_ ? NULL : new Method( this, define->runMethod_, define->scriptAPI_ ) ),
      uninitializeMethod_( NULL == define->uninitializeMethod_ ? NULL : new Method( this, define->uninitializeMethod_, define->scriptAPI_ ) ),
      isSatisfiedMethod_( NULL == define->isSatisfiedMethod_ ? NULL : new Method( this, define->isSatisfiedMethod_, define->scriptAPI_ ) ),
      constructMethodRun_( false )
{
    for( unsigned int i = 0; i < define->settings_.size(); ++i )
    {
        SettingInfo* settingInfo = define->settings_.get( i );
        //settings_.push(addValueClause( settingInfo->getUriPart(), new ValueClause(settingInfo->getDataValue(), getLogger()) ));
        settings_.push( newSettingReader( settingInfo->getUriPart(), settingInfo->getDataValue(), false ) );
    }
}
