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

#include "data/BlobValue.h"
#include "data/DataWriter.h"
#include "data/Slate.h"
#include "missionScript/MissionItem.h"
#include "missionScript/MissionNode.h"
#include "missionScript/SettingReader.h"
#include "missionScript/ValueClause.h"

Assign* Assign::Instance( MissionNode* node, const Str& itemName, MissionItem* missionItem, Logger& logger )
{
    MissionNode* uriNode = node->getFirstChild();
    if( NULL != uriNode && strncmp( uriNode->getName(), "Sequence", 9 ) == 0 )
    {
        uriNode = uriNode->getNextSibling( true );
    }
    if( NULL != uriNode && strncmp( uriNode->getName(), "Parallel", 9 ) == 0 )
    {
        uriNode = uriNode->getNextSibling( true );
    }
    if( NULL != uriNode && strncmp( uriNode->getName(), "Progression", 12 ) == 0 )
    {
        uriNode = uriNode->getNextSibling( true );
    }
    if( NULL != uriNode && strncmp( uriNode->getName(), "When", 5 ) == 0 )
    {
        uriNode = uriNode->getNextSibling( true );
    }
    if( NULL != uriNode && strncmp( uriNode->getName(), "While", 6 ) == 0 )
    {
        uriNode = uriNode->getNextSibling( true );
    }
    if( NULL != uriNode && strncmp( uriNode->getName(), "Break", 6 ) == 0 )
    {
        uriNode = uriNode->getNextSibling( true );
    }

    if( uriNode )
    {
        Str uriPart;
        bool isArg( false );
        if( strncmp( uriNode->getName(), "CustomUri", 9 ) == 0 )
        {
            uriPart = uriNode->getAttribute( "Uri" );
        }
        else if( strncmp( uriNode->getName(), "Arg", 9 ) == 0 )
        {
            uriPart = uriNode->getAttribute( "Name" );
            isArg = true;
        }
        else
        {
            uriPart = ValueClause::StripPastColon( uriNode->getName() );
        }
        ValueClause* scratchPadValueClause = NULL;
        unsigned short code = Slate::GetElementURICode( uriPart.cStr() );
        if( !isArg && code == ElementURI::NO_CODE && uriPart.length() > 1
                && ( uriPart.cStr()[ 0 ] == '_' ||  uriPart.cStr()[ 0 ] == '#' )
                && uriPart.cStr()[ 1 ] == '.' )
        {
            // This is a new scratchpad variable
            MissionNode* valueNode = uriNode->getNextSibling( true );
            if( NULL != valueNode )
            {
                scratchPadValueClause = ValueClause::Instance( valueNode, false, missionItem, logger );
            }
            if( NULL != scratchPadValueClause )
            {
                BinaryDataType binaryType = uriPart.cStr()[ 0 ] == '_' ? NO_TYPE : MULTIVALUE;
                BlobType blobType = uriPart.cStr()[ 0 ] == '_' ? NOT_BLOB : BLOB_FLOAT64LE;
                ElementURI uri( "", uriPart, ElementURI::BASE_ELEMENT, scratchPadValueClause->getDataValue()->getBaseUnit(), binaryType, blobType );
                DataElement* dataElement = new SimpleDataElement( uri, scratchPadValueClause->getDataValue()->copy(), 0 );
                Slate::MapDataElement( &uri, dataElement );
                ElementURI* regURI = Slate::FindElementURI( uri );
                code = regURI == NULL ? ElementURI::NO_CODE : regURI->getCode();
            }
        }
        if( isArg || code != ElementURI::NO_CODE )
        {
            Assign* assign( NULL );
            if( !isArg )
            {
                ElementURI uri( "", uriPart );
                assign = new Assign( itemName, uri );
            }
            else
            {
                DataReader* dataReader = missionItem->findArgReader( uriPart );
                if( NULL == dataReader )
                {
                    logger.syslog( "No mission argument named " + uriPart, Syslog::CRITICAL );
                    return NULL;
                }
                assign = new Assign( itemName, dataReader->getUri() );
            }
            missionItem->setComponent( assign ); // temporary assigment for configuring DataReaders
            MissionNode* valueNode = uriNode->getNextSibling( true );
            if( NULL != valueNode )
            {
                if( NULL != scratchPadValueClause )
                {
                    assign->valueClause_ = scratchPadValueClause;
                }
                else
                {
                    assign->valueClause_ = ValueClause::Instance( valueNode, false, missionItem, logger );
                }
            }
            return assign;
        }
        else
        {
            logger.syslog( "Slate does not contain " + uriPart, Syslog::CRITICAL );
        }
    }
    else
    {
        logger.syslog( "URI missing", Syslog::CRITICAL );
    }
    return NULL;
}

Assign::~Assign()
{
    if( NULL != writer_ )
    {
        delete writer_;
    }
    if( NULL != valueClause_ )
    {
        delete valueClause_;
    }
}

void Assign::initialize()
{
    DEBUG_LOG( "Initialize" )
}

void Assign::run()
{
    //DEBUG_LOG( "Run" )
    if( NULL != writer_ )
    {
        if( NULL != valueClause_ && ( ignoreTouch_ || firstRun_ || valueClause_->wasTouchedSinceLastRun( this ) ) )
        {
            firstRun_ = false;
            if( valueClause_->eval() )
            {
                writer_->write( *( valueClause_->getDataValue() ) );
                DEBUG_LOG( "Wrote " + valueClause_->getDataValue()->toString() + " to " + writer_->getUri() + " which now =" + writer_->getElement().getDataValueCopy()->toString() );
            }
            else
            {
                logger_.syslog( "Could not evaluate expression" + valueClause_->toString(), Syslog::CRITICAL );
            }
        }
        else
        {
            writer_->getElement().setTimestamp( Timestamp::Now() );
        }
    }
}

bool Assign::runIfUnsatisfied()
{
    DEBUG_LOG( "RunIfUnsatisfied" )
    run();
    return true;
}

void Assign::uninitialize()
{
    DEBUG_LOG( "Uninitialize" )
}

Assign::Assign( const Str& name, const ElementURI& uri, ValueClause* valueClause, bool ignoreTouch )
    : Behavior( name,  NULL, true, true ),
      writer_( Slate::NewWriter( uri, this ) ),
      valueClause_( valueClause ),
      ignoreTouch_( ignoreTouch ),
      firstRun_( true )
{
    //debugLevel_=Syslog::INFO;
}
