/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.

#include <StdAfx.h>
#include <IXml.h>
#include "xml.h"
#include "XmlUtils.h"
#include "ReadWriteXMLSink.h"

#include "../SimpleStringPool.h"
#include "SerializeXMLReader.h"
#include "SerializeXMLWriter.h"

#include "XMLBinaryWriter.h"
#include "XMLBinaryReader.h"

#include "XMLPatcher.h"
#include <md5.h>

//////////////////////////////////////////////////////////////////////////
CXmlNode_PoolAlloc* g_pCXmlNode_PoolAlloc = 0;
#ifdef CRY_COLLECT_XML_NODE_STATS
SXmlNodeStats* g_pCXmlNode_Stats = 0;
#endif

extern bool g_bEnableBinaryXmlLoading;

//////////////////////////////////////////////////////////////////////////
CXmlUtils::CXmlUtils(ISystem* pSystem)
{
    m_pSystem = pSystem;
    m_pSystem->GetISystemEventDispatcher()->RegisterListener(this);

    // create IReadWriteXMLSink object
    m_pReadWriteXMLSink = new CReadWriteXMLSink();
    g_pCXmlNode_PoolAlloc = new CXmlNode_PoolAlloc;
#ifdef CRY_COLLECT_XML_NODE_STATS
    g_pCXmlNode_Stats = new SXmlNodeStats();
#endif
    m_pStatsXmlNodePool = 0;
#ifndef _RELEASE
    m_statsThreadOwner = CryGetCurrentThreadId();
#endif
    m_pXMLPatcher = NULL;
}

//////////////////////////////////////////////////////////////////////////
CXmlUtils::~CXmlUtils()
{
    m_pSystem->GetISystemEventDispatcher()->RemoveListener(this);
    delete g_pCXmlNode_PoolAlloc;
#ifdef CRY_COLLECT_XML_NODE_STATS
    delete g_pCXmlNode_Stats;
#endif
    SAFE_DELETE(m_pStatsXmlNodePool);
    SAFE_DELETE(m_pXMLPatcher);
}

//////////////////////////////////////////////////////////////////////////
IXmlParser* CXmlUtils::CreateXmlParser()
{
    const bool bReuseStrings = false; //TODO: do we ever want to reuse strings here?
    return new XmlParser(bReuseStrings);
}

//////////////////////////////////////////////////////////////////////////
XmlNodeRef CXmlUtils::LoadXmlFromFile(const char* sFilename, bool bReuseStrings, bool bEnablePatching)
{
    XmlParser parser(bReuseStrings);
    XmlNodeRef node = parser.ParseFile(sFilename, true);

    // XmlParser is supposed to log warnings and errors (if any),
    // so we don't need to call parser.getErrorString(),
    // CryLog() etc here.

    if (node && bEnablePatching && m_pXMLPatcher)
    {
        node = m_pXMLPatcher->ApplyXMLDataPatch(node, sFilename);
    }

    return node;
}

//////////////////////////////////////////////////////////////////////////
XmlNodeRef CXmlUtils::LoadXmlFromBuffer(const char* buffer, size_t size, bool bReuseStrings, bool bSuppressWarnings)
{
    XmlParser parser(bReuseStrings);
    XmlNodeRef node = parser.ParseBuffer(buffer, size, true, bSuppressWarnings);
    return node;
}


void GetMD5(const char* pSrcBuffer, int nSrcSize, char signatureMD5[16])
{
    MD5Context md5c;
    MD5Init(&md5c);
    MD5Update(&md5c, (unsigned char*)pSrcBuffer, nSrcSize);
    MD5Final((unsigned char*)signatureMD5, &md5c);
}

//////////////////////////////////////////////////////////////////////////
const char* CXmlUtils::HashXml(XmlNodeRef node)
{
    static char signature[16 * 2 + 1];
    static char temp[16];
    static const char* hex = "0123456789abcdef";
    XmlString str = node->getXML();
    GetMD5(str.data(), str.length(), temp);
    for (int i = 0; i < 16; i++)
    {
        signature[2 * i + 0] = hex[((uint8)temp[i]) >> 4];
        signature[2 * i + 1] = hex[((uint8)temp[i]) & 0xf];
    }
    signature[16 * 2] = 0;
    return signature;
}

//////////////////////////////////////////////////////////////////////////
IReadWriteXMLSink* CXmlUtils::GetIReadWriteXMLSink()
{
    return m_pReadWriteXMLSink;
}

//////////////////////////////////////////////////////////////////////////
class CXmlSerializer
    : public IXmlSerializer
{
public:
    CXmlSerializer()
        : m_nRefCount(0)
        , m_pReaderImpl(NULL)
        , m_pReaderSer(NULL)
        , m_pWriterSer(NULL)
        , m_pWriterImpl(NULL)
    {
    }
    ~CXmlSerializer()
    {
        ClearAll();
    }
    void ClearAll()
    {
        SAFE_DELETE(m_pReaderSer);
        SAFE_DELETE(m_pReaderImpl);
        SAFE_DELETE(m_pWriterSer);
        SAFE_DELETE(m_pWriterImpl);
    }

    //////////////////////////////////////////////////////////////////////////
    virtual void AddRef() { ++m_nRefCount; }
    virtual void Release()
    {
        if (--m_nRefCount <= 0)
        {
            delete this;
        }
    }

    virtual ISerialize* GetWriter(XmlNodeRef& node)
    {
        ClearAll();
        m_pWriterImpl = new CSerializeXMLWriterImpl(node);
        m_pWriterSer = new CSimpleSerializeWithDefaults<CSerializeXMLWriterImpl>(*m_pWriterImpl);
        return m_pWriterSer;
    }
    virtual ISerialize* GetReader(XmlNodeRef& node)
    {
        ClearAll();
        m_pReaderImpl = new CSerializeXMLReaderImpl(node);
        m_pReaderSer = new CSimpleSerializeWithDefaults<CSerializeXMLReaderImpl>(*m_pReaderImpl);
        return m_pReaderSer;
    }

    virtual void GetMemoryUsage(ICrySizer* pSizer) const
    {
        pSizer->Add(*this);
        pSizer->AddObject(m_pReaderImpl);
        pSizer->AddObject(m_pWriterImpl);
    }
    //////////////////////////////////////////////////////////////////////////
private:
    int m_nRefCount;
    CSerializeXMLReaderImpl* m_pReaderImpl;
    CSimpleSerializeWithDefaults<CSerializeXMLReaderImpl>* m_pReaderSer;

    CSerializeXMLWriterImpl* m_pWriterImpl;
    CSimpleSerializeWithDefaults<CSerializeXMLWriterImpl>* m_pWriterSer;
};

//////////////////////////////////////////////////////////////////////////
IXmlSerializer* CXmlUtils::CreateXmlSerializer()
{
    return new CXmlSerializer;
}

//////////////////////////////////////////////////////////////////////////
void CXmlUtils::GetMemoryUsage(ICrySizer* pSizer)
{
    {
        SIZER_COMPONENT_NAME(pSizer, "Nodes");
        g_pCXmlNode_PoolAlloc->GetMemoryUsage(pSizer);
    }

#ifdef CRY_COLLECT_XML_NODE_STATS
    // yes, slow
    std::vector<const CXmlNode*> rootNodes;
    {
        TXmlNodeSet::const_iterator iter = g_pCXmlNode_Stats->nodeSet.begin();
        TXmlNodeSet::const_iterator iterEnd = g_pCXmlNode_Stats->nodeSet.end();
        while (iter != iterEnd)
        {
            const CXmlNode* pNode = *iter;
            if (pNode->getParent() == 0)
            {
                rootNodes.push_back(pNode);
            }
            ++iter;
        }
    }

    // use the following to log to console
#if 0
    CryLogAlways("NumXMLRootNodes=%d NumXMLNodes=%d TotalAllocs=%d TotalFrees=%d",
        rootNodes.size(), g_pCXmlNode_Stats->nodeSet.size(),
        g_pCXmlNode_Stats->nAllocs, g_pCXmlNode_Stats->nFrees);
#endif


    // use the following to debug the nodes in the system
#if 0
    {
        std::vector<const CXmlNode*>::const_iterator iter = rootNodes.begin();
        std::vector<const CXmlNode*>::const_iterator iterEnd = rootNodes.end();
        while (iter != iterEnd)
        {
            const CXmlNode* pNode = *iter;
            CryLogAlways("Node 0x%p Tag='%s'", pNode, pNode->getTag());
            ++iter;
        }
    }
#endif

    // only for debugging, add it as pseudo numbers to the CrySizer.
    // shift it by 10, so we get the actual number
    {
        SIZER_COMPONENT_NAME(pSizer, "#NumTotalNodes");
        pSizer->Add("#NumTotalNodes", g_pCXmlNode_Stats->nodeSet.size() << 10);
    }

    {
        SIZER_COMPONENT_NAME(pSizer, "#NumRootNodes");
        pSizer->Add("#NumRootNodes", rootNodes.size() << 10);
    }
#endif
}

//////////////////////////////////////////////////////////////////////////
void CXmlUtils::OnSystemEvent(ESystemEvent event, UINT_PTR wparam, UINT_PTR lparam)
{
    switch (event)
    {
    case ESYSTEM_EVENT_LEVEL_POST_UNLOAD:
    case ESYSTEM_EVENT_LEVEL_LOAD_END:
        g_pCXmlNode_PoolAlloc->FreeMemoryIfEmpty();
        STLALLOCATOR_CLEANUP;
        break;
    }
}

//////////////////////////////////////////////////////////////////////////
class CXmlBinaryDataWriterFile
    : public XMLBinary::IDataWriter
{
public:
    CXmlBinaryDataWriterFile(const char* file)
    {
        m_fileHandle = gEnv->pCryPak->FOpen(file, "wb");
    }
    ~CXmlBinaryDataWriterFile()
    {
        if (m_fileHandle != AZ::IO::InvalidHandle)
        {
            gEnv->pCryPak->FClose(m_fileHandle);
        }
    };
    virtual bool IsOk()
    {
        return m_fileHandle != AZ::IO::InvalidHandle;
    }
    ;
    virtual void Write(const void* pData, size_t size)
    {
        if (m_fileHandle != AZ::IO::InvalidHandle)
        {
            gEnv->pCryPak->FWrite(pData, size, 1, m_fileHandle);
        }
    }
private:
    AZ::IO::HandleType m_fileHandle;
};

//////////////////////////////////////////////////////////////////////////
bool CXmlUtils::SaveBinaryXmlFile(const char* filename, XmlNodeRef root)
{
    CXmlBinaryDataWriterFile fileSink(filename);
    if (!fileSink.IsOk())
    {
        return false;
    }
    XMLBinary::CXMLBinaryWriter writer;
    string error;
    return writer.WriteNode(&fileSink, root, false, 0, error);
}

//////////////////////////////////////////////////////////////////////////
XmlNodeRef CXmlUtils::LoadBinaryXmlFile(const char* filename, bool bEnablePatching)
{
    XMLBinary::XMLBinaryReader reader;
    XMLBinary::XMLBinaryReader::EResult result;
    XmlNodeRef root = reader.LoadFromFile(filename, result);

    if (result == XMLBinary::XMLBinaryReader::eResult_Success && bEnablePatching == true && m_pXMLPatcher != NULL)
    {
        root = m_pXMLPatcher->ApplyXMLDataPatch(root, filename);
    }

    return root;
}

//////////////////////////////////////////////////////////////////////////
bool CXmlUtils::EnableBinaryXmlLoading(bool bEnable)
{
    bool bPrev = g_bEnableBinaryXmlLoading;
    g_bEnableBinaryXmlLoading = bEnable;
    return bPrev;
}

//////////////////////////////////////////////////////////////////////////
class CXmlTableReader
    : public IXmlTableReader
{
public:
    CXmlTableReader();
    virtual ~CXmlTableReader();

    virtual void Release();

    virtual bool Begin(XmlNodeRef rootNode);
    virtual int  GetEstimatedRowCount();
    virtual bool ReadRow(int& rowIndex);
    virtual bool ReadCell(int& columnIndex, const char*& pContent, size_t& contentSize);
    float GetCurrentRowHeight() override;

private:
    bool m_bExcel;

    XmlNodeRef m_tableNode;

    XmlNodeRef m_rowNode;

    float m_currentRowHeight;
    int m_rowNodeIndex;
    int m_row;

    int m_columnNodeIndex;  // used if m_bExcel == true
    int m_column;

    size_t m_rowTextSize;   // used if m_bExcel == false
    size_t m_rowTextPos;    // used if m_bExcel == false
};


//////////////////////////////////////////////////////////////////////////
CXmlTableReader::CXmlTableReader()
{
}

//////////////////////////////////////////////////////////////////////////
CXmlTableReader::~CXmlTableReader()
{
}

//////////////////////////////////////////////////////////////////////////
void CXmlTableReader::Release()
{
    delete this;
}

//////////////////////////////////////////////////////////////////////////
bool CXmlTableReader::Begin(XmlNodeRef rootNode)
{
    m_tableNode = 0;

    if (!rootNode)
    {
        return false;
    }

    XmlNodeRef worksheetNode = rootNode->findChild("Worksheet");
    if (worksheetNode)
    {
        m_bExcel = true;
        m_tableNode = worksheetNode->findChild("Table");
    }
    else
    {
        m_bExcel = false;
        m_tableNode = rootNode->findChild("Table");
    }

    m_rowNode = 0;
    m_rowNodeIndex = -1;
    m_row = -1;

    return (m_tableNode != 0);
}

//////////////////////////////////////////////////////////////////////////
int CXmlTableReader::GetEstimatedRowCount()
{
    if (!m_tableNode)
    {
        return -1;
    }
    return m_tableNode->getChildCount();
}

//////////////////////////////////////////////////////////////////////////
bool CXmlTableReader::ReadRow(int& rowIndex)
{
    m_currentRowHeight = 0.0f;
    if (!m_tableNode)
    {
        return false;
    }

    m_columnNodeIndex = -1;
    m_column = -1;

    const int rowNodeCount = m_tableNode->getChildCount();

    if (m_bExcel)
    {
        for (;; )
        {
            if (++m_rowNodeIndex >= rowNodeCount)
            {
                m_rowNodeIndex = rowNodeCount;
                return false;
            }

            m_rowNode = m_tableNode->getChild(m_rowNodeIndex);
            if (!m_rowNode)
            {
                m_rowNodeIndex = rowNodeCount;
                return false;
            }

            if (!m_rowNode->isTag("Row"))
            {
                m_rowNode = 0;
                continue;
            }

            ++m_row;

            int index = 0;
            if (m_rowNode->getAttr("ss:Index", index))
            {
                --index;  // one-based -> zero-based
                if (index < m_row)
                {
                    m_rowNodeIndex = rowNodeCount;
                    m_rowNode = 0;
                    return false;
                }
                m_row = index;
            }
            float height;
            if (m_rowNode->getAttr("ss:Height", height))
            {
                m_currentRowHeight = height;
            }

            rowIndex = m_row;
            return true;
        }
    }

    {
        m_rowTextSize = 0;
        m_rowTextPos = 0;

        if (++m_rowNodeIndex >= rowNodeCount)
        {
            m_rowNodeIndex = rowNodeCount;
            return false;
        }

        m_rowNode = m_tableNode->getChild(m_rowNodeIndex);
        if (!m_rowNode)
        {
            m_rowNodeIndex = rowNodeCount;
            return false;
        }

        const char* const pContent = m_rowNode->getContent();
        if (pContent)
        {
            m_rowTextSize = strlen(pContent);
        }

        m_row = m_rowNodeIndex;
        rowIndex = m_rowNodeIndex;
        return true;
    }
}

//////////////////////////////////////////////////////////////////////////
bool CXmlTableReader::ReadCell(int& columnIndex, const char*& pContent, size_t& contentSize)
{
    pContent = 0;
    contentSize = 0;

    if (!m_tableNode)
    {
        return false;
    }

    if (!m_rowNode)
    {
        return false;
    }

    if (m_bExcel)
    {
        const int columnNodeCount = m_rowNode->getChildCount();

        for (;; )
        {
            if (++m_columnNodeIndex >= columnNodeCount)
            {
                m_columnNodeIndex = columnNodeCount;
                return false;
            }

            XmlNodeRef columnNode = m_rowNode->getChild(m_columnNodeIndex);
            if (!columnNode)
            {
                m_columnNodeIndex = columnNodeCount;
                return false;
            }

            if (!columnNode->isTag("Cell"))
            {
                continue;
            }

            ++m_column;

            int index = 0;
            if (columnNode->getAttr("ss:Index", index))
            {
                --index;  // one-based -> zero-based
                if (index < m_column)
                {
                    m_columnNodeIndex = columnNodeCount;
                    return false;
                }
                m_column = index;
            }
            columnIndex = m_column;

            XmlNodeRef dataNode = columnNode->findChild("Data");
            if (dataNode)
            {
                pContent = dataNode->getContent();
                if (pContent)
                {
                    contentSize = strlen(pContent);
                }
            }
            return true;
        }
    }

    {
        if (m_rowTextPos >= m_rowTextSize)
        {
            return false;
        }

        const char* const pRowContent = m_rowNode->getContent();
        if (!pRowContent)
        {
            m_rowTextPos = m_rowTextSize;
            return false;
        }
        pContent = &pRowContent[m_rowTextPos];

        columnIndex = ++m_column;

        for (;; )
        {
            char c = pRowContent[m_rowTextPos++];

            if ((c == '\n') || (c == '\0'))
            {
                return true;
            }

            if (c == '\r')
            {
                // ignore all '\r' chars
                for (;; )
                {
                    c = pRowContent[m_rowTextPos++];
                    if ((c == '\n') || (c == '\0'))
                    {
                        return true;
                    }
                    if (c != '\r')
                    {
                        // broken data. '\r' expected to be followed by '\n' or '\0'.
                        contentSize = 0;
                        m_rowTextPos = m_rowTextSize;
                        return false;
                    }
                }
            }

            ++contentSize;
        }
    }
}

float CXmlTableReader::GetCurrentRowHeight()
{
    return m_currentRowHeight;
}
//////////////////////////////////////////////////////////////////////////
IXmlTableReader* CXmlUtils::CreateXmlTableReader()
{
    return new CXmlTableReader;
}

//////////////////////////////////////////////////////////////////////////
// Init xml stats nodes pool
void CXmlUtils::InitStatsXmlNodePool(uint32 nPoolSize)
{
    CHECK_STATS_THREAD_OWNERSHIP();
    if (0 == m_pStatsXmlNodePool)
    {
        // create special xml node pools for game statistics

        const bool bReuseStrings = true;    // TODO parameterise?
        m_pStatsXmlNodePool = new CXmlNodePool(nPoolSize, bReuseStrings);
        assert(m_pStatsXmlNodePool);
    }
    else
    {
        CryLog("[CXmlNodePool]: Xml stats nodes pool already initialized");
    }
}

//////////////////////////////////////////////////////////////////////////
// Creates new xml node for statistics.
XmlNodeRef CXmlUtils::CreateStatsXmlNode(const char* sNodeName)
{
    CHECK_STATS_THREAD_OWNERSHIP();
    if (0 == m_pStatsXmlNodePool)
    {
        CryLog("[CXmlNodePool]: Xml stats nodes pool isn't initialized. Perform default initialization.");
        InitStatsXmlNodePool();
    }
    return m_pStatsXmlNodePool->GetXmlNode(sNodeName);
}

void CXmlUtils::SetStatsOwnerThread(threadID threadId)
{
#ifndef _RELEASE
    m_statsThreadOwner = threadId;
#endif
}

void CXmlUtils::FlushStatsXmlNodePool()
{
    CHECK_STATS_THREAD_OWNERSHIP();
    if (m_pStatsXmlNodePool)
    {
        if (m_pStatsXmlNodePool->empty())
        {
            SAFE_DELETE(m_pStatsXmlNodePool);
        }
    }
}

void CXmlUtils::SetXMLPatcher(XmlNodeRef* pPatcher)
{
    SAFE_DELETE(m_pXMLPatcher);

    if (pPatcher != NULL)
    {
        m_pXMLPatcher = new CXMLPatcher(*pPatcher);
    }
}