/*
* 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.

// Description : This is the source file for the module Realtime remote
//               update  The purpose of this module is to allow data update to happen
//               remotely so that you can  for example  edit the terrain and see the changes
//               in the console


#include "CryLegacy_precompiled.h"
#include "RealtimeRemoteUpdate.h"
#include "ISystem.h"
#include "I3DEngine.h"
#include <IEntitySystem.h>
#include "IGame.h"
#include "IViewSystem.h"
#include "IEntitySystem.h"
#include "IGameFramework.h"
#include "IGameRulesSystem.h"
#include "ITimeOfDay.h"
#include <Terrain/Bus/LegacyTerrainBus.h>

// Should CERTAINLY be moved to CryCommon.
template <typename TObjectType, bool bArray = false>
class TScopedPointer
{
public:
    TScopedPointer(TObjectType* pPointer)
        : m_pPointer(pPointer){}

    ~TScopedPointer()
    {
        if (bArray)
        {
            SAFE_DELETE_ARRAY(m_pPointer);
        }
        else
        {
            SAFE_DELETE(m_pPointer);
        }
    }

protected:

    TObjectType*    m_pPointer;
};

//////////////////////////////////////////////////////////////////////////
CRealtimeRemoteUpdateListener&  CRealtimeRemoteUpdateListener::GetRealtimeRemoteUpdateListener()
{
    static CRealtimeRemoteUpdateListener oRealtimeUpdateListener;
    return oRealtimeUpdateListener;
}
//////////////////////////////////////////////////////////////////////////
bool CRealtimeRemoteUpdateListener::Enable(bool boEnable)
{
    if (!gEnv)
    {
        return false;
    }

    if (!gEnv->pSystem)
    {
        return false;
    }

    INotificationNetwork*   piNotificationNetwork = gEnv->pSystem->GetINotificationNetwork();
    if (!piNotificationNetwork)
    {
        return false;
    }

    if (boEnable)
    {
        m_boIsEnabled = piNotificationNetwork->ListenerBind("RealtimeUpdate", this);
    }
    else
    {
        piNotificationNetwork->ListenerRemove(this);
        m_boIsEnabled = false;
    }

    return m_boIsEnabled;
}
//////////////////////////////////////////////////////////////////////////
bool    CRealtimeRemoteUpdateListener::IsEnabled()
{
    if (!gEnv)
    {
        return false;
    }

    if (!gEnv->pSystem)
    {
        return false;
    }

    INotificationNetwork*   piNotificationNetwork = gEnv->pSystem->GetINotificationNetwork();
    if (!piNotificationNetwork)
    {
        return false;
    }

    // We should instead query the notification network here.
    return m_boIsEnabled;
}

//////////////////////////////////////////////////////////////////////////
void CRealtimeRemoteUpdateListener::AddGameHandler(IRealtimeUpdateGameHandler* handler)
{
    GameHandlerList::iterator item = m_gameHandlers.begin();
    GameHandlerList::iterator end = m_gameHandlers.end();

    for (; item != end; ++item)
    {
        if (handler == (*item))
        {
            return; //already present
        }
    }

    m_gameHandlers.push_back(handler);
}
//////////////////////////////////////////////////////////////////////////
void CRealtimeRemoteUpdateListener::RemoveGameHandler(IRealtimeUpdateGameHandler* handler)
{
    GameHandlerList::iterator item = m_gameHandlers.begin();
    GameHandlerList::iterator end = m_gameHandlers.end();
    for (; item != end; ++item)
    {
        if (handler == (*item))
        {
            break;
        }
    }

    m_gameHandlers.erase(item);
}


//////////////////////////////////////////////////////////////////////////
void CRealtimeRemoteUpdateListener::OnNotificationNetworkReceive(const void* pBuffer, size_t length)
{
    TDBuffer& rBuffer = *(new TDBuffer);

    rBuffer.resize(length, 0);
    memcpy(&rBuffer.front(), pBuffer, length);

    m_ProcessingQueue.push(&rBuffer);
}
//////////////////////////////////////////////////////////////////////////
CRealtimeRemoteUpdateListener::CRealtimeRemoteUpdateListener()
    : m_boIsEnabled(false)
    , m_lastKeepAliveMessageTime((const int64)0)
{
}
//////////////////////////////////////////////////////////////////////////
CRealtimeRemoteUpdateListener::~CRealtimeRemoteUpdateListener()
{
}
//////////////////////////////////////////////////////////////////////////
void    CRealtimeRemoteUpdateListener::LoadArchetypes(XmlNodeRef& root)
{
    IEntitySystem* pEntitySystem  = gEnv->pEntitySystem;

    // Remove Entities with ID`s from the list.
    for (int i = 0; i < root->getChildCount(); i++)
    {
        XmlNodeRef entityNode = root->getChild(i);
        if (entityNode->isTag("EntityPrototype"))
        {
            pEntitySystem->LoadEntityArchetype(entityNode);
        }
    }
}

//////////////////////////////////////////////////////////////////////////
void CRealtimeRemoteUpdateListener::LoadTimeOfDay(XmlNodeRef& root)
{
    gEnv->p3DEngine->GetTimeOfDay()->Serialize(root, true);
    gEnv->p3DEngine->GetTimeOfDay()->Update(true, true);
}

//////////////////////////////////////////////////////////////////////////
void CRealtimeRemoteUpdateListener::LoadMaterials(XmlNodeRef& root)
{
    // Remove Entities with ID`s from the list.
    for (int i = 0; i < root->getChildCount(); i++)
    {
        XmlNodeRef mtlNode = root->getChild(i);
        if (mtlNode->isTag("Material"))
        {
            const char* mtlName = mtlNode->getAttr("name");
            gEnv->p3DEngine->GetMaterialManager()->LoadMaterialFromXml(mtlName, mtlNode);
        }
    }
}
//////////////////////////////////////////////////////////////////////////
void  CRealtimeRemoteUpdateListener::LoadConsoleVariables(XmlNodeRef& root)
{
    IConsole*   piConsole(NULL);
    char*           szKey(NULL);
    char*           szValue(NULL);
    ICVar*      piCVar(NULL);

    piConsole = gEnv->pConsole;
    if (!piConsole)
    {
        return;
    }

    // Remove Entities with ID`s from the list.
    for (int i = 0; i < root->getNumAttributes(); ++i)
    {
        root->getAttributeByIndex(i, (const char**)&szKey, (const char**)&szValue);
        piCVar = piConsole->GetCVar(szKey);
        if (!piCVar)
        {
            continue;
        }
        piCVar->Set(szValue);
    }
}
//////////////////////////////////////////////////////////////////////////
void  CRealtimeRemoteUpdateListener::LoadParticles(XmlNodeRef& root)
{
    XmlNodeRef      oParticlesLibrary;
    XmlNodeRef      oLibrary;
    XmlString           strLibraryName;

    int                     nCurrentChild(0);
    int                     nNumberOfChildren(0);

    oParticlesLibrary = root->findChild("ParticlesLibrary");
    if (!oParticlesLibrary)
    {
        return;
    }

    nNumberOfChildren = oParticlesLibrary->getChildCount();
    for (nCurrentChild = 0; nCurrentChild < nNumberOfChildren; ++nCurrentChild)
    {
        oLibrary = oParticlesLibrary->getChild(nCurrentChild);
        if (oLibrary->isTag("Library"))
        {
            continue;
        }
        if (!oLibrary->getAttr("name", strLibraryName))
        {
            continue;
        }
        gEnv->pParticleManager->LoadLibrary((const char*)strLibraryName, oLibrary, true);
    }
}
//////////////////////////////////////////////////////////////////////////
void  CRealtimeRemoteUpdateListener::LoadTerrainLayer(XmlNodeRef& root, unsigned char*  uchData)
{
    int texId(0);
    int posx(0), posy(0);
    int w(0), h(0);
    int                 nSourceFormat(0);
    ETEX_Format eTFSrc(eTF_B8G8R8);

    if (!root->getAttr("Posx", posx))
    {
        return;
    }

    if (!root->getAttr("Posy", posy))
    {
        return;
    }

    if (!root->getAttr("w", w))
    {
        return;
    }

    if (!root->getAttr("h", h))
    {
        return;
    }

    if (!root->getAttr("ETEX_Format", nSourceFormat))
    {
        return;
    }

    eTFSrc = (ETEX_Format)nSourceFormat;

    if (gEnv->pRenderer && gEnv->p3DEngine)
    {
        texId = gEnv->pRenderer->DownLoadToVideoMemory(uchData, w, h, eTFSrc, eTFSrc, 0, false, FILTER_NONE, 0, NULL, FT_USAGE_ALLOWREADSRGB);
        // Swapped x & y for historical reasons.
        LegacyTerrain::LegacyTerrainDataRequestBus::Broadcast(&LegacyTerrain::LegacyTerrainDataRequests::SetTerrainSectorTexture
            , posy, posx, texId, w, h, true);
    }
}

//////////////////////////////////////////////////////////////////////////
void CRealtimeRemoteUpdateListener::LoadEntities(XmlNodeRef& root)
{
    IEntitySystem* pEntitySystem = gEnv->pEntitySystem;

    bool bTransformOnly(false);
    bool bDeleteOnly = false;
    bool bRemoveAllOld = true;

    gEnv->pSystem->SetThreadState(ESubsys_Physics, false);

    if (root->haveAttr("PartialUpdate"))
    {
        bRemoveAllOld = false;
    }
    if (root->haveAttr("Delete"))
    {
        bDeleteOnly = true;
    }

    //////////////////////////////////////////////////////////////////////////
    // Delete all entities except the unremovable ones and the local player.
    if (bRemoveAllOld)
    {
        IEntityItPtr pIt = pEntitySystem->GetEntityIterator();

        if (!gEnv->pGame)
        {
            return;
        }

        IGameFramework* piGameFramework(gEnv->pGame->GetIGameFramework());
        IEntity* piRulesEntity(NULL);
        if (piGameFramework)
        {
            IGameRulesSystem* piGameRulesSystem(piGameFramework->GetIGameRulesSystem());
            if (piGameRulesSystem)
            {
                piRulesEntity = piGameRulesSystem->GetCurrentGameRulesEntity();
            }
        }

        pIt->MoveFirst();
        while (!pIt->IsEnd())
        {
            IEntity* pEntity = pIt->Next();
            IEntityClass* pEntityClass = pEntity->GetClass();
            uint32 nEntityFlags = pEntity->GetFlags();

            // Local player must not be deleted.
            if (nEntityFlags & ENTITY_FLAG_LOCAL_PLAYER)
            {
                continue;
            }

            // Rules should not be deleted as well.
            if (piRulesEntity)
            {
                if (pEntity->GetId() == piRulesEntity->GetId())
                {
                    continue;
                }
            }

            pEntity->ClearFlags(ENTITY_FLAG_UNREMOVABLE);
            pEntitySystem->RemoveEntity(pEntity->GetId());
        }
        // Force deletion of removed entities.
        pEntitySystem->DeletePendingEntities();
        //////////////////////////////////////////////////////////////////////////
    }
    else
    {
        // Remove Entities with ID`s from the list.
        for (int i = 0; i < root->getChildCount(); i++)
        {
            XmlNodeRef objectNode = root->getChild(i);
            if (objectNode->isTag("Entity"))
            {
                // reserve the id
                EntityId id;
                if (objectNode->getAttr("EntityId", id))
                {
                    IEntity* pEntity = pEntitySystem->GetEntity(id);

                    if (!pEntity)
                    {
                        pEntitySystem->RemoveEntity(id, true);
                        continue;
                    }

                    if (!objectNode->getAttr("TransformOnly", bTransformOnly))
                    {
                        pEntity->ClearFlags(ENTITY_FLAG_UNREMOVABLE);
                        pEntitySystem->RemoveEntity(id, true);
                        continue;
                    }

                    if (!bTransformOnly)
                    {
                        pEntity->ClearFlags(ENTITY_FLAG_UNREMOVABLE);
                        pEntitySystem->RemoveEntity(id, true);
                        continue;
                    }

                    Vec3 oPos(0.0f, 0.0f, 0.0f);
                    Vec3 oScale(1.0f, 1.0f, 1.0f);
                    Quat oRotate(1.0f, 0.0f, 0.0f, 0.0f);

                    bool bHasPos = objectNode->getAttr("Pos", oPos);
                    bool bHasRot = objectNode->getAttr("Rotate", oRotate);
                    bool bHasScl = objectNode->getAttr("Scale", oScale);

                    if (!bHasPos)
                    {
                        oPos = pEntity->GetPos();
                    }

                    if (!bHasRot)
                    {
                        oRotate = pEntity->GetRotation();
                    }

                    if (!bHasScl)
                    {
                        oScale = pEntity->GetScale();
                    }

                    pEntity->SetPosRotScale(oPos, oRotate, oScale);
                }
            }
        }
        // Force deletion of removed entities.
        pEntitySystem->DeletePendingEntities();
    }

    if (!bDeleteOnly)
    {
        pEntitySystem->LoadEntities(root, false);
    }

    // you can't pass temporaries to non-const references, so objects on the stack must be created
    SEntityEvent LevelLoaded(ENTITY_EVENT_LEVEL_LOADED);
    SEntityEvent StartGame(ENTITY_EVENT_START_GAME);

    pEntitySystem->SendEventToAll(LevelLoaded);
    pEntitySystem->SendEventToAll(StartGame);
    gEnv->pSystem->SetThreadState(ESubsys_Physics, true);
}
//////////////////////////////////////////////////////////////////////////
bool CRealtimeRemoteUpdateListener::IsSyncingWithEditor()
{
    CTimeValue  oTimeValue(gEnv->pTimer->GetAsyncTime());
    oTimeValue -= m_lastKeepAliveMessageTime;

    return (fabs((oTimeValue).GetSeconds()) <= 30.0f);
}
//////////////////////////////////////////////////////////////////////////
void CRealtimeRemoteUpdateListener::Update()
{
    while (!m_ProcessingQueue.empty())
    {
        TDBuffer* pCurrentBuffer(m_ProcessingQueue.pop());

        if (!pCurrentBuffer)
        {
            continue;
        }

        TScopedPointer<TDBuffer> oScopedPointer(pCurrentBuffer);

        char* const szBuffer = (char*)&(pCurrentBuffer->front());
        const size_t nStringSize = strlen(szBuffer) + 1;
        unsigned char* const chBinaryBuffer = (unsigned char*)(szBuffer + nStringSize);
        const size_t nBinaryBufferSize = pCurrentBuffer->size() - nStringSize;

        XmlNodeRef oXmlNode = gEnv->pSystem->LoadXmlFromBuffer(szBuffer, nStringSize - 1);

        // Currently, if we have no XML node this is not a well formed message and
        // thus we stop processing.
        if (!oXmlNode)
        {
            continue;
        }

        if (strcmp(oXmlNode->getTag(), "SyncMessage") != 0)
        {
            continue;
        }

        string oSyncType = oXmlNode->getAttr("Type");
        if (oSyncType.empty())
        {
            continue;
        }

        size_t nBinaryDataSize = 0;
        if (!oXmlNode->getAttr("BinaryDataSize", nBinaryDataSize))
        {
            continue;
        }

        bool requiresFurtherProcessing = false;

        for (GameHandlerList::iterator item = m_gameHandlers.begin(), end = m_gameHandlers.end(); item != end; ++item)
        {
            if ((*item)->UpdateGameData(oXmlNode, chBinaryBuffer))
            {
                requiresFurtherProcessing = true;
            }
        }

        if (!requiresFurtherProcessing)
        {
            continue;
        }

        static std::vector<struct IStatObj*>* pStatObjTable = NULL;
        static std::vector<_smart_ptr<IMaterial>>* pMatTable = NULL;

        if (oSyncType.compare("EngineTerrainData") == 0)
        {
            gEnv->p3DEngine->LockCGFResources();

            if (nBinaryDataSize > 0)
            {
                size_t nUncompressedBinarySize(nBinaryDataSize);
                unsigned char* szData = new unsigned char[nBinaryDataSize];

                gEnv->pSystem->DecompressDataBlock(chBinaryBuffer, nBinaryBufferSize, szData, nUncompressedBinarySize);

                SHotUpdateInfo* pExportInfo = (SHotUpdateInfo*)szData;

                // As messages of oSyncType "EngineTerrainData" always come before
                // "EngineIndoorData" and are always paired together, and have
                // inter-dependencies amongst themselves, the locking is done here
                // and the unlocking is done when we receive a "EngineIndoorData".
                // Currently if we, for any reason, don't receive the second message,
                // we should expect horrible things to happen.
                gEnv->p3DEngine->LockCGFResources();

                pStatObjTable = NULL;
                pMatTable = NULL;

                gEnv->p3DEngine->SetOctreeCompiledData((uint8*)szData + sizeof(SHotUpdateInfo), nBinaryDataSize - sizeof(SHotUpdateInfo), &pStatObjTable, &pMatTable, true, pExportInfo);
                SAFE_DELETE_ARRAY(szData);
            }
        }
        else if (oSyncType.compare("EngineIndoorData") == 0)
        {
            if (nBinaryDataSize > 0)
            {
                if (IVisAreaManager* piIVisAreaManager = gEnv->p3DEngine->GetIVisAreaManager())
                {
                    size_t nUncompressedBinarySize(nBinaryDataSize);
                    unsigned char* szData = new unsigned char[nBinaryDataSize];

                    gEnv->pSystem->DecompressDataBlock(chBinaryBuffer, nBinaryBufferSize, szData, nUncompressedBinarySize);

                    SHotUpdateInfo* pExportInfo = (SHotUpdateInfo*)szData;

                    if (piIVisAreaManager)
                    {
                        piIVisAreaManager->SetCompiledData((uint8*)szData + sizeof(SHotUpdateInfo), nBinaryDataSize - sizeof(SHotUpdateInfo), &pStatObjTable, &pMatTable, true, pExportInfo);
                    }

                    SAFE_DELETE_ARRAY(szData);
                }
            }

            gEnv->p3DEngine->UnlockCGFResources();

            pStatObjTable = NULL;
            pMatTable = NULL;
        }
        else if (oSyncType.compare("Vegetation") == 0)
        {
            XmlNodeRef oCurrentNode = oXmlNode->findChild("Vegetation");
        }
        else if (oSyncType.compare("DetailLayers") == 0)
        {
            XmlNodeRef oChildRootNode = oXmlNode->findChild("SurfaceTypes");
            if (oChildRootNode)
            {
                LegacyTerrain::LegacyTerrainDataRequestBus::Broadcast(&LegacyTerrain::LegacyTerrainDataRequests::LoadTerrainSurfacesFromXML, oChildRootNode);
            }
        }
        else if (oSyncType.compare("Environment") == 0)
        {
            XmlNodeRef oChildRootNode = oXmlNode->findChild("Environment");
            if (oChildRootNode)
            {
                gEnv->p3DEngine->LoadEnvironmentSettingsFromXML(oChildRootNode);
            }
        }

        else if (oSyncType.compare("TimeOfDay") == 0)
        {
            XmlNodeRef oChildRootNode = oXmlNode->findChild("TimeOfDay");
            if (oChildRootNode)
            {
                LoadTimeOfDay(oChildRootNode);
            }
        }
        else if (oSyncType.compare("Materials") == 0)
        {
            XmlNodeRef oChildRootNode = oXmlNode->findChild("Materials");
            if (oChildRootNode)
            {
                LoadMaterials(oChildRootNode);
            }
        }
        else if (oSyncType.compare("EntityArchetype") == 0)
        {
            XmlNodeRef oChildRootNode = oXmlNode->findChild("EntityPrototypes");
            if (oChildRootNode)
            {
                LoadArchetypes(oChildRootNode);
            }
        }
        else if (oSyncType.compare("ConsoleVariables") == 0)
        {
            LoadConsoleVariables(oXmlNode);
        }
        else if (oSyncType.compare("Particles") == 0)
        {
            LoadParticles(oXmlNode);
        }
        else if (oSyncType.compare("LayerTexture") == 0)
        {
            if (nBinaryDataSize > 0)
            {
                size_t nUncompressedBinarySize(nBinaryDataSize);
                unsigned char*  szData = new unsigned char[nBinaryDataSize];

                if (!szData)
                {
                    continue;
                }

                if (!gEnv->pSystem->DecompressDataBlock(chBinaryBuffer, nBinaryBufferSize, szData, nUncompressedBinarySize))
                {
                    SAFE_DELETE_ARRAY(szData);
                    continue;
                }

                LoadTerrainLayer(oXmlNode, szData);

                SAFE_DELETE_ARRAY(szData);
            }
        }
        else if (oSyncType.compare("Particle.Library") == 0)
        {
            XmlNodeRef oChildRootNode = oXmlNode->findChild("ParticleLibrary");
            if (oChildRootNode)
            {
                const char* szEffectName(NULL);

                oXmlNode->removeChild(oChildRootNode);

                if (!oChildRootNode->getAttr("Effect", &szEffectName))
                {
                    continue;
                }

                XmlNodeRef oEffectNode = oChildRootNode->findChild("Effect");
                if (!oEffectNode)
                {
                    continue;
                }

                gEnv->pParticleManager->LoadEffect(szEffectName, oEffectNode, true);
            }
        }
        else if (oSyncType.compare("ChangeLevel") == 0)
        {
            if (oXmlNode->haveAttr("LevelName"))
            {
                const char*         szLevelName(NULL);
                string      strMapCommand("map ");

                oXmlNode->getAttr("LevelName", &szLevelName);
                strMapCommand += szLevelName;

                gEnv->pConsole->ExecuteString(strMapCommand);
            }
        }
        else if (oSyncType.compare("Entities") == 0)
        {
            XmlNodeRef root = oXmlNode->findChild("Entities");

            if (!root)
            {
                continue;
            }

            LoadEntities(root);
        }
        else if (oSyncType.compare("GeometryList") == 0)
        {
            XmlNodeRef root = oXmlNode->findChild("Geometries");
            size_t nNumAttributes(root->getNumAttributes());
            size_t nCurrentAttribute(0);

            const char* szAttributeValue(NULL);
            const char* szAttributeName(NULL);

            for (nCurrentAttribute = 0; nCurrentAttribute < nNumAttributes; ++nCurrentAttribute)
            {
                root->getAttributeByIndex(nCurrentAttribute, &szAttributeName, &szAttributeValue);
                gEnv->p3DEngine->LoadStatObjUnsafeManualRef(szAttributeName);
            }
        }
        else if (oSyncType.compare("KeepAlive") == 0)
        {
            // Here we reset the time counter for the keep alive message.
            m_lastKeepAliveMessageTime = gEnv->pTimer->GetAsyncTime();
        }
    }
}
//////////////////////////////////////////////////////////////////////////