//-----------------------------------------------------------------------------
// 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/xml/tamlXmlReader.h"

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

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

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

    // Create document.
    TiXmlDocument xmlDocument;

    // Load document from stream.
    if ( !xmlDocument.LoadFile( stream ) )
    {
        // Warn!
        Con::warnf("Taml: Could not load Taml XML file from stream.");
        return NULL;
    }

    // Parse root element.
    SimObject* pSimObject = parseElement( xmlDocument.RootElement() );

    // Reset parse.
    resetParse();

    return pSimObject;
}

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

void TamlXmlReader::resetParse( void )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlXmlReader_ResetParse);

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

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

SimObject* TamlXmlReader::parseElement( TiXmlElement* pXmlElement )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlXmlReader_ParseElement);

    SimObject* pSimObject = NULL;

    // Fetch element name.
    StringTableEntry typeName = StringTable->insert( pXmlElement->Value() );

    // Fetch reference to Id.
    const U32 tamlRefToId = getTamlRefToId( pXmlElement );

    // 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;
    }

    // No, so fetch reference Id.
    const U32 tamlRefId = getTamlRefId( pXmlElement );

#ifdef TORQUE_DEBUG
    // Format the type location.
    char typeLocationBuffer[64];
    dSprintf( typeLocationBuffer, sizeof(typeLocationBuffer), "Taml [format='xml' row=%d column=%d]", pXmlElement->Row(), pXmlElement->Column() );    

    // 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( pXmlElement, pSimObject );

    // Fetch object name.
    StringTableEntry objectName = StringTable->insert( getTamlObjectName( pXmlElement ) );

    // 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 );
    }

    // Fetch any children.
    TiXmlNode* pChildXmlNode = pXmlElement->FirstChild();

    TamlCustomNodes customProperties;

    // Do we have any element children?
    if ( pChildXmlNode != NULL )
    {
        // Fetch the Taml children.
        TamlChildren* pChildren = dynamic_cast<TamlChildren*>( pSimObject );

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

        // Iterate siblings.
        do
        {
            // Fetch element.
            TiXmlElement* pChildXmlElement = dynamic_cast<TiXmlElement*>( pChildXmlNode );

            // Move to next sibling.
            pChildXmlNode = pChildXmlNode->NextSibling();

            // Skip if this is not an element?
            if ( pChildXmlElement == NULL )
                continue;

            // Is this a standard child element?
            if ( dStrchr( pChildXmlElement->Value(), '.' ) == NULL )
            {
                // Is this a Taml child?
                if ( pChildren == NULL )
                {
                    // No, so warn.
                    Con::warnf("Taml: Child element '%s' found under parent '%s' but object cannot have children.",
                        pChildXmlElement->Value(),
                        pXmlElement->Value() );

                    // Skip.
                    continue;
                }

                // Yes, so parse child element.
                SimObject* pChildSimObject = parseElement( pChildXmlElement );

                // Skip if the child was not created.
                if ( pChildSimObject == NULL )
                    continue;

                // 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 );
                }
            }
            else
            {
                // No, so parse custom element.
                parseCustomElement( pChildXmlElement, customProperties );
            }
        }
        while( pChildXmlNode != NULL );

        // Call custom read.
        mpTaml->tamlCustomRead( pCallbacks, customProperties );
    }

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

    // Return object.
    return pSimObject;
}

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

void TamlXmlReader::parseAttributes( TiXmlElement* pXmlElement, SimObject* pSimObject )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlXmlReader_ParseAttributes);

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

    // Iterate attributes.
    for ( TiXmlAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
    {
        // Insert attribute name.
        StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );

        // Ignore if this is a Taml attribute.
        if (    attributeName == tamlRefIdName ||
                attributeName == tamlRefToIdName ||
                attributeName == tamlNamedObjectName )
            continue;

        // Set the field.
        pSimObject->setPrefixedDataField( attributeName, NULL, pAttribute->Value() );
    }
}

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

void TamlXmlReader::parseCustomElement( TiXmlElement* pXmlElement, TamlCustomNodes& customNodes )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlXmlReader_ParseCustomElement);

    // Is this a standard child element?
    const char* pPeriod = dStrchr( pXmlElement->Value(), '.' );

    // Sanity!
    AssertFatal( pPeriod != NULL, "Parsing extended element but no period character found." );

    // Fetch any custom XML node.
    TiXmlNode* pCustomXmlNode = pXmlElement->FirstChild();

    // Finish is no XML node exists.
    if ( pCustomXmlNode == NULL )
        return;

    // Yes, so add custom node.
    TamlCustomNode* pCustomNode = customNodes.addNode( pPeriod+1 );

    do
    {
        // Fetch element.
        TiXmlElement* pCustomXmlElement = dynamic_cast<TiXmlElement*>( pCustomXmlNode );

        // Move to next sibling.
        pCustomXmlNode = pCustomXmlNode->NextSibling();

        // Skip if this is not an element.
        if ( pCustomXmlElement == NULL )
            continue;

        // Parse custom node.
        parseCustomNode( pCustomXmlElement, pCustomNode );
    }
    while ( pCustomXmlNode != NULL );
}

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

void TamlXmlReader::parseCustomNode( TiXmlElement* pXmlElement, TamlCustomNode* pCustomNode )
{
    // Is the node a proxy object?
    if (  getTamlRefId( pXmlElement ) != 0 || getTamlRefToId( pXmlElement ) != 0 )
    {
        // Yes, so parse proxy object.
        SimObject* pProxyObject = parseElement( pXmlElement );

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

        return;
    }

    // Yes, so add child node.
    TamlCustomNode* pChildNode = pCustomNode->addNode( pXmlElement->Value() );

    // Iterate attributes.
    for ( TiXmlAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
    {
        // Insert attribute name.
        StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );

        // Skip if a Taml reference attribute.
        if ( attributeName == tamlRefIdName || attributeName == tamlRefToIdName )
            continue;

        // Add node field.
        pChildNode->addField( attributeName, pAttribute->Value() );
    }

    // Fetch any element text.
    const char* pElementText = pXmlElement->GetText();

    // Do we have any element text?
    if ( pElementText != NULL )
    {
        // Yes, so store it.
        pChildNode->setNodeText( pElementText );
    }

    // Fetch any children.
    TiXmlNode* pChildXmlNode = pXmlElement->FirstChild();

    // Do we have any element children?
    if ( pChildXmlNode != NULL )
    {
        do
        {
            // Yes, so fetch child element.
            TiXmlElement* pChildXmlElement = dynamic_cast<TiXmlElement*>( pChildXmlNode );

            // Move to next sibling.
            pChildXmlNode = pChildXmlNode->NextSibling();

            // Skip if this is not an element.
            if ( pChildXmlElement == NULL )
                continue;

            // Parse custom node.
            parseCustomNode( pChildXmlElement, pChildNode );
        }
        while( pChildXmlNode != NULL );
    }
}

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

U32 TamlXmlReader::getTamlRefId( TiXmlElement* pXmlElement )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlXmlReader_GetTamlRefId);

    // Iterate attributes.
    for ( TiXmlAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
    {
        // Insert attribute name.
        StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );

        // Skip if not the correct attribute.
        if ( attributeName != tamlRefIdName )
            continue;

        // Return it.
        return dAtoi( pAttribute->Value() );
    }

    // Not found.
    return 0;
}

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

U32 TamlXmlReader::getTamlRefToId( TiXmlElement* pXmlElement )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlXmlReader_GetTamlRefToId);

    // Iterate attributes.
    for ( TiXmlAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
    {
        // Insert attribute name.
        StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );

        // Skip if not the correct attribute.
        if ( attributeName != tamlRefToIdName )
            continue;

        // Return it.
        return dAtoi( pAttribute->Value() );
    }

    // Not found.
    return 0;
}

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

const char* TamlXmlReader::getTamlObjectName( TiXmlElement* pXmlElement )
{
    // Debug Profiling.
    PROFILE_SCOPE(TamlXmlReader_GetTamlObjectName);

    // Iterate attributes.
    for ( TiXmlAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
    {
        // Insert attribute name.
        StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );

        // Skip if not the correct attribute.
        if ( attributeName != tamlNamedObjectName )
            continue;

        // Return it.
        return pAttribute->Value();
    }

    // Not found.
    return NULL;
}



