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

#include "LuaNode.h"
#include "data/StrValue.h"
#include "units/UnitRegistry.h"

/// Destructor
LuaNode::~LuaNode()
{
    // Must manually delete childMap_ entries
    if( NULL != childMap_ )
    {
        for( int i = childMap_->size() - 1; i >= 0; --i )
        {
            delete childMap_->popIndexed( i );
        }
        delete childMap_;
    }

    // childList_ is set to auto-delete entries
    if( NULL != childList_ )
    {
        delete childList_;
    }
}

/// Returns true if the node contains child nodes
bool LuaNode::hasChildNodes() const
{
    bool hasChildren( false );
    if( open() )
    {
        hasChildren = LuaAPI::IsTable( isRoot() );
    }
    close();
    return hasChildren;
}

/// Returns the child node, by name, or NULL if none.
LuaNode* LuaNode::getNamedNode( const Str& nodeName )
{
    //printf("Start of %s->getNamedNode( %s ), stackHeight=%d\n", toString().cStr(), nodeName.cStr(), LuaAPI::GetStackHeight());
    LuaNode* node = NULL;
    if( NULL != childMap_ )
    {
        node = childMap_->get( nodeName );
        if( NULL != node )
        {
            return node;
        }
    }
    if( open() )
    {
        //printf("Opening table item %s, stackHeight=%d\n", nodeName.cStr(), LuaAPI::GetStackHeight());
        bool itemOpened = LuaAPI::OpenTableItemByName( nodeName.cStr(), isRoot() );
        //printf("Opened table item %s, stackHeight=%d? = %d\n", nodeName.cStr(), LuaAPI::GetStackHeight(), itemOpened);
        if( itemOpened )
        {
            node = new LuaNode( this, nodeName );
            if( NULL == childMap_ )
            {
                childMap_ = new FastMap<const Str, LuaNode*>();
            }
            childMap_->put( nodeName, node );
        }
        LuaAPI::CloseTableItem();
    }
    close();
    //printf("End of LuaNode::getNamedNode, stackHeight=%d\n", LuaAPI::GetStackHeight());
    return node;
}

/// Returns a new child node than can contain other nodes
LuaNode* LuaNode::newChildNode( const Str& nodeName )
{
    LuaNode* node = NULL;
    if( open() )
    {
        if( LuaAPI::NewTable( nodeName.cStr(), isRoot() ) )
        {
            node = getNamedNode( nodeName );
        }
    }
    close();
    return node;
}

/// Returns a new child node that is a function
LuaNode* LuaNode::newChildFunction( const Str& functionName,
                                    const Str& functionBody, Logger& logger )
{
    LuaNode* node = NULL;
    if( open() )
    {
        const char* error = LuaAPI::NewFunction( functionName.cStr(), functionBody.cStr(), isRoot() );
        if( NULL != error )
        {
            logger.syslog( "Error parsing lua function " + functionName + error, Syslog::CRITICAL );
        }
        else
        {
            node = getNamedNode( functionName );
        }
    }
    close();
    return node;
}

/// Returns the number of indexed (array, or list-like) nodes
/// Returns 0 is this is not a table.
int LuaNode::getNodeCount( const Str& nodeName ) const
{
    int count = 0;
    if( open() )
    {
        if( LuaAPI::IsTable( isRoot() ) )
        {
            count = LuaAPI::GetTableSize( isRoot() );
        }
    }
    close();
    return count;
}

LuaNode* LuaNode::getIndexedNode( unsigned int index )
{
    LuaNode* node = NULL;
    if( NULL != childList_ )
    {
        node = childList_->get( index );
        if( NULL != node )
        {
            return node;
        }
    }
    if( open() )
    {
        if( LuaAPI::OpenTableItemByIndex( index, isRoot() ) )
        {
            node = new LuaNode( this, index );
            if( NULL == childList_ )
            {
                childList_ = new FlexArray<LuaNode*>( true );
            }
            childList_->set( index, node );
        }
        LuaAPI::CloseTableItem();
    }
    close();
    return node;
}

/// Returns the parent node, or NULL if this is the root.
LuaNode* LuaNode::getParentNode() const
{
    if( NULL == parentNode_ )
    {
        return NULL;
    }
    return parentNode_;
}

/// Sets readTo to the value of the node, as a boolean
/// Returns true if read success
bool LuaNode::readBool( bool& readTo ) const
{
    bool retVal = false;
    if( open() )
    {
        retVal = true;
        LuaAPI::GetBool( readTo, isRoot() );
    }
    close();
    return retVal;
}

/// Sets readTo to the value of the node, as a double
/// Returns true if read success
bool LuaNode::readDouble( double& readTo ) const
{
    bool retVal = false;
    if( open() )
    {
        retVal = LuaAPI::GetNumber( readTo, isRoot() );
    }
    close();
    return retVal;
}

/// Sets readTo to the value of the node, unsigned char string
/// Returns true if read success
bool LuaNode::readString( Str& readTo ) const
{
    bool retVal = false;
    if( open() )
    {
        retVal = LuaAPI::GetString( readTo, isRoot() );
    }
    close();
    return retVal;
}

/// If the node contains a dataValue, materialize such a dataValue
/// Uses the Unit + value paradigm
DataValue* LuaNode::asNewDataValue()
{
    // URIs are valid StrValues
    LuaNode* nameNode = getNamedNode( "uri" );
    if( NULL != nameNode )
    {
        Str uriName;
        if( nameNode->readString( uriName ) )
        {
            return new StrValue( uriName );
        }
    }
    LuaNode* unitNode = getNamedNode( "unit" );
    if( NULL == unitNode )
    {
        printf( "Could not find unit part of DataValue for %s\n", toString().cStr() );
        return NULL;
    }
    Str unitName;
    if( !unitNode->readString( unitName ) )
    {
        printf( "Could not read unit part of DataValue\n" );
        return NULL;
    }
    const Unit* unit = UnitRegistry::FindUnit( unitName );
    if( NULL == unit )
    {
        printf( "Could not find unit named %s\n", unitName.cStr() );
        return NULL;
    }
    LuaNode* valuesNode = getNamedNode( "Value" );
    if( NULL == valuesNode )
    {
        printf( "Could not find Value part of DataValue\n" );
        return NULL;
    }
    int length = valuesNode->getNodeCount( Str::EMPTY_STR );
    double* values = new double[ length <= 0 ? 1 : length ];
    for( int i = 0; i < ( length <= 0 ? 1 : length ); ++i )
    {
        double value = __builtin_nan( "" );
        if( length <= 0 )
        {
            valuesNode->readDouble( value );
        }
        else
        {
            ScriptNode* aValueNode = valuesNode->getIndexedNode( i );
            if( NULL != aValueNode )
            {
                aValueNode->readDouble( value );
            }
        }
        values[ i ] = value;
    }

    //DataValue* dataValue = ( *unit )( values, ( length <= 0 ? 1 : length ) );
    DataValue* dataValue = ( *unit )( values[0] );
    delete[] values;

    return dataValue;
};

/// Attempt to execute the function contained in this node.
bool LuaNode::execute( FlexArray<DataReader*>& inputs, FlexArray<SettingReader*>& settings,
                       FlexArray<DataWriter*>& outputs, FlexArray<const Unit*>& outUnits, Logger& logger )
{
    bool retVal = false;
    if( open() )
    {
        bool boolValue = false;
        retVal = LuaAPI::Exec( &inputs, &settings, boolValue, &outputs, &outUnits, logger );
        LuaAPI::PushNil(); // Exec takes "this" off the stack.
    }
    close();
    return retVal;
}

/// Attempt to execute the function contained in this node, returning
/// only a single boolean value.
bool LuaNode::execute( FlexArray<DataReader*>& inputs, FlexArray<SettingReader*>& settings,
                       bool& boolOutput, Logger& logger )
{
    bool retVal = false;
    if( open() )
    {
        boolOutput = true;
        retVal = LuaAPI::Exec( &inputs, &settings, boolOutput, NULL, NULL, logger );
        LuaAPI::PushNil(); // Exec takes "this" off the stack.
    }
    close();
    return retVal;
}

/// Attempt to execute the function contained in this node, with no inputs and no return value
bool LuaNode::execute( Logger& logger )
{
    bool retVal = false;
    if( open() )
    {
        bool boolOutput = false;
        retVal = LuaAPI::Exec( NULL, NULL, boolOutput, NULL, NULL, logger );
        LuaAPI::PushNil(); // Exec takes "this" off the stack.
    }
    close();
    return retVal;
}

/// Returns a string representation of this node, with its ancestors,
/// separated by "." characters.
Str LuaNode::toString() const
{
    Str ancestorStr( parentNode_ == NULL ? "" : parentNode_->toString() + "." );
    if( index_ >= 0 )
    {
        return ancestorStr + index_;
    }
    else
    {
        return ancestorStr + name_;
    }
}

/// Private root Node constructor
LuaNode::LuaNode()
    : parentNode_( NULL ),
      index_( -1 ),
      name_( "Root" ),
      childMap_( NULL ),
      childList_( NULL )
{}

/// Private "index" constructor, available to LuaAPI too
LuaNode::LuaNode( LuaNode* parentNode, int index )
    : parentNode_( parentNode ),
      index_( index ),
      name_(),
      childMap_( NULL ),
      childList_( NULL )
{}

/// Private "name" constructor, available to LuaAPI too
LuaNode::LuaNode( LuaNode* parentNode, const Str& name )
    : parentNode_( parentNode ),
      index_( -1 ),
      name_( name ),
      childMap_( NULL ),
      childList_( NULL )
{}

/// Open this node
bool LuaNode::open() const
{
    //printf("Start of %s->open(), stackHeight=%d\n", toString().cStr(), LuaAPI::GetStackHeight());
    bool opened = false;
    if( isRoot() )
    {
        opened = true;
    }
    else if( parentNode_->open() )
    {
        if( 0 <= index_ )
        {
            opened = LuaAPI::OpenTableItemByIndex( index_, parentNode_->isRoot() );
        }
        else
        {
            opened = LuaAPI::OpenTableItemByName( name_.cStr(), parentNode_->isRoot() );
        }
    }
    //printf("End of %s->open(), stackHeight=%d, opened=%d\n", toString().cStr(), LuaAPI::GetStackHeight(), opened);
    return opened;
}

/// Close this node
void LuaNode::close() const
{
    if( NULL != parentNode_ )
    {
        LuaAPI::CloseTableItem();
        parentNode_->close();
    }
}
