//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "persistence/taml/binary/tamlBinaryReader.h"

#ifndef _ZIPSUBSTREAM_H_
#include "io/zip/zipSubStream.h"
#endif

// Debug Profiling.
#include "debug/profiler.h"

//-----------------------------------------------------------------------------

SimObject* TamlBinaryReader::read( FileStream& stream )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlBinaryReader_Read);

    // Read Taml signature.
    StringTableEntry tamlSignature = stream.readSTString();

    // Is the signature correct?
    if ( tamlSignature != StringTable->insert( TAML_SIGNATURE ) )
    {
        // Warn.
        Con::warnf("Taml: Cannot read binary file as signature is incorrect '%s'.", tamlSignature );
        return NULL;
    }

    // Read version Id.
    U32 versionId;
    stream.read( &versionId );

    // Read compressed flag.
    bool compressed;
    stream.read( &compressed );

    SimObject* pSimObject = NULL;

    // Is the stream compressed?
    if ( compressed )
    {
        // Yes, so attach zip stream.
        ZipSubRStream zipStream;
        zipStream.attachStream( &stream );

        // Parse element.
        pSimObject = parseElement( zipStream, versionId );

        // Detach zip stream.
        zipStream.detachStream();
    }
    else
    {
        // No, so parse element.
        pSimObject = parseElement( stream, versionId );
    }

    return pSimObject;
}

//-----------------------------------------------------------------------------

void TamlBinaryReader::resetParse( void )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlBinaryReader_ResetParse);

    // Clear object reference map.
    mObjectReferenceMap.clear();
}

//-----------------------------------------------------------------------------

SimObject* TamlBinaryReader::parseElement( Stream& stream, const U32 versionId )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlBinaryReader_ParseElement);

    SimObject* pSimObject = NULL;

#ifdef TORQUE_DEBUG
    // Format the type location.
    char typeLocationBuffer[64];
    dSprintf( typeLocationBuffer, sizeof(typeLocationBuffer), "Taml [format='binary' offset=%u]", stream.getPosition() );
#endif

    // Fetch element name.    
    StringTableEntry typeName = stream.readSTString();

    // Fetch object name.
    StringTableEntry objectName = stream.readSTString();

    // Read references.
    U32 tamlRefId;
    U32 tamlRefToId;
    stream.read( &tamlRefId );
    stream.read( &tamlRefToId );

    // Do we have a reference to Id?
    if ( tamlRefToId != 0 )
    {
        // Yes, so fetch reference.
        typeObjectReferenceHash::iterator referenceItr = mObjectReferenceMap.find( tamlRefToId );

        // Did we find the reference?
        if ( referenceItr == mObjectReferenceMap.end() )
        {
            // No, so warn.
            Con::warnf( "Taml: Could not find a reference Id of '%d'", tamlRefToId );
            return NULL;
        }

        // Return object.
        return referenceItr->value;
    }

#ifdef TORQUE_DEBUG
    // Create type.
    pSimObject = Taml::createType( typeName, mpTaml, typeLocationBuffer );
#else
    // Create type.
    pSimObject = Taml::createType( typeName, mpTaml );
#endif

    // Finish if we couldn't create the type.
    if ( pSimObject == NULL )
        return NULL;

    // Find Taml callbacks.
    TamlCallbacks* pCallbacks = dynamic_cast<TamlCallbacks*>( pSimObject );

    // Are there any Taml callbacks?
    if ( pCallbacks != NULL )
    {
        // Yes, so call it.
        mpTaml->tamlPreRead( pCallbacks );
    }

    // Parse attributes.
    parseAttributes( stream, pSimObject, versionId );

    // Does the object require a name?
    if ( objectName == StringTable->EmptyString )
    {
        // No, so just register anonymously.
        pSimObject->registerObject();
    }
    else
    {
        // Yes, so register a named object.
        pSimObject->registerObject( objectName );

        // Was the name assigned?
        if ( pSimObject->getName() != objectName )
        {
            // No, so warn that the name was rejected.
#ifdef TORQUE_DEBUG
            Con::warnf( "Taml::parseElement() - Registered an instance of type '%s' but a request to name it '%s' was rejected.  This is typically because an object of that name already exists.  '%s'", typeName, objectName, typeLocationBuffer );
#else
            Con::warnf( "Taml::parseElement() - Registered an instance of type '%s' but a request to name it '%s' was rejected.  This is typically because an object of that name already exists.", typeName, objectName );
#endif
        }
    }

    // Do we have a reference Id?
    if ( tamlRefId != 0 )
    {
        // Yes, so insert reference.
        mObjectReferenceMap.insert( tamlRefId, pSimObject );
    }

    // Parse custom elements.
    TamlCustomNodes customProperties;

    // Parse children.
    parseChildren( stream, pCallbacks, pSimObject, versionId );

    // Parse custom elements.
    parseCustomElements( stream, pCallbacks, customProperties, versionId );

    // Are there any Taml callbacks?
    if ( pCallbacks != NULL )
    {
        // Yes, so call it.
        mpTaml->tamlPostRead( pCallbacks, customProperties );
    }

    // Return object.
    return pSimObject;
}

//-----------------------------------------------------------------------------

void TamlBinaryReader::parseAttributes( Stream& stream, SimObject* pSimObject, const U32 versionId )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlBinaryReader_ParseAttributes);

    // Sanity!
    AssertFatal( pSimObject != NULL, "Taml: Cannot parse attributes on a NULL object." );

    // Fetch attribute count.
    U32 attributeCount;
    stream.read( &attributeCount );

    // Finish if no attributes.
    if ( attributeCount == 0 )
        return;

    char valueBuffer[4096];

    // Iterate attributes.
    for ( U32 index = 0; index < attributeCount; ++index )
    {
        // Fetch attribute.
        StringTableEntry attributeName = stream.readSTString();
        stream.readLongString( 4096, valueBuffer );

        // We can assume this is a field for now.
        pSimObject->setPrefixedDataField( attributeName, NULL, valueBuffer );
    }
}

//-----------------------------------------------------------------------------

void TamlBinaryReader::parseChildren( Stream& stream, TamlCallbacks* pCallbacks, SimObject* pSimObject, const U32 versionId )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlBinaryReader_ParseChildren);

    // Sanity!
    AssertFatal( pSimObject != NULL, "Taml: Cannot parse children on a NULL object." );

    // Fetch children count.
    U32 childrenCount;
    stream.read( &childrenCount );

    // Finish if no children.
    if ( childrenCount == 0 )
        return;

    // Fetch the Taml children.
    TamlChildren* pChildren = dynamic_cast<TamlChildren*>( pSimObject );

    // Is this a sim set?
    if ( pChildren == NULL )
    {
        // No, so warn.
        Con::warnf("Taml: Child element found under parent but object cannot have children." );
        return;
    }

    // Fetch any container child class specifier.
    AbstractClassRep* pContainerChildClass = pSimObject->getClassRep()->getContainerChildClass( true );

    // Iterate children.
    for ( U32 index = 0; index < childrenCount; ++ index )
    {
        // Parse child element.
        SimObject* pChildSimObject = parseElement( stream, versionId );

        // Finish if child failed.
        if ( pChildSimObject == NULL )
            return;

        // Do we have a container child class?
        if ( pContainerChildClass != NULL )
        {
            // Yes, so is the child object the correctly derived type?
            if ( !pChildSimObject->getClassRep()->isClass( pContainerChildClass ) )
            {
                // No, so warn.
                Con::warnf("Taml: Child element '%s' found under parent '%s' but object is restricted to children of type '%s'.",
                    pChildSimObject->getClassName(),
                    pSimObject->getClassName(),
                    pContainerChildClass->getClassName() );

                // NOTE: We can't delete the object as it may be referenced elsewhere!
                pChildSimObject = NULL;

                // Skip.
                continue;
            }
        }

        // Add child.
        pChildren->addTamlChild( pChildSimObject );

        // Find Taml callbacks for child.
        TamlCallbacks* pChildCallbacks = dynamic_cast<TamlCallbacks*>( pChildSimObject );

        // Do we have callbacks on the child?
        if ( pChildCallbacks != NULL )
        {
            // Yes, so perform callback.
            mpTaml->tamlAddParent( pChildCallbacks, pSimObject );
        }
    }
}

//-----------------------------------------------------------------------------

void TamlBinaryReader::parseCustomElements( Stream& stream, TamlCallbacks* pCallbacks, TamlCustomNodes& customNodes, const U32 versionId )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlBinaryReader_ParseCustomElement);

    // Read custom node count.
    U32 customNodeCount;
    stream.read( &customNodeCount );

    // Finish if no custom nodes.
    if ( customNodeCount == 0 )
        return;

    // Iterate custom nodes.
    for ( U32 nodeIndex = 0; nodeIndex < customNodeCount; ++nodeIndex )
    {
        //Read custom node name.
        StringTableEntry nodeName = stream.readSTString();

        // Add custom node.
        TamlCustomNode* pCustomNode = customNodes.addNode( nodeName );

        // Parse the custom node.
        parseCustomNode( stream, pCustomNode, versionId );
    }

    // Do we have callbacks?
    if ( pCallbacks == NULL )
    {
        // No, so warn.
        Con::warnf( "Taml: Encountered custom data but object does not support custom data." );
        return;
    }

    // Custom read callback.
    mpTaml->tamlCustomRead( pCallbacks, customNodes );
}

//-----------------------------------------------------------------------------

void TamlBinaryReader::parseCustomNode( Stream& stream, TamlCustomNode* pCustomNode, const U32 versionId )
{
    // Fetch if a proxy object.
    bool isProxyObject;
    stream.read( &isProxyObject );

    // Is this a proxy object?
    if ( isProxyObject )
    {
        // Yes, so parse proxy object.
        SimObject* pProxyObject = parseElement( stream, versionId );

        // Add child node.
        pCustomNode->addNode( pProxyObject );

        return;
    }

    // No, so read custom node name.
    StringTableEntry nodeName = stream.readSTString();

    // Add child node.
    TamlCustomNode* pChildNode = pCustomNode->addNode( nodeName );

    // Read child node text.
    char childNodeTextBuffer[MAX_TAML_NODE_FIELDVALUE_LENGTH];
    stream.readLongString( MAX_TAML_NODE_FIELDVALUE_LENGTH, childNodeTextBuffer );
    pChildNode->setNodeText( childNodeTextBuffer );

    // Read child node count.
    U32 childNodeCount;
    stream.read( &childNodeCount );

    // Do we have any children nodes?
    if ( childNodeCount > 0 )
    {
        // Yes, so parse children nodes.
        for( U32 childIndex = 0; childIndex < childNodeCount; ++childIndex )
        {
            // Parse child node.
            parseCustomNode( stream, pChildNode, versionId );
        }
    }

    // Read child field count.
    U32 childFieldCount;
    stream.read( &childFieldCount );

    // Do we have any child fields?
    if ( childFieldCount > 0 )
    {
        // Yes, so parse child fields.
        for( U32 childFieldIndex = 0; childFieldIndex < childFieldCount; ++childFieldIndex )
        {
            // Read field name.
            StringTableEntry fieldName = stream.readSTString();

            // Read field value.
            char valueBuffer[MAX_TAML_NODE_FIELDVALUE_LENGTH];
            stream.readLongString( MAX_TAML_NODE_FIELDVALUE_LENGTH, valueBuffer );

            // Add field.
            pChildNode->addField( fieldName, valueBuffer );
        }
    }
}