/*
* 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 : Checkpoint Save/Load system for Game04


#include "CryLegacy_precompiled.h"
#include "CheckPointSystem.h"

//engine interfaces
#include "I3DEngine.h"
#include "IAISystem.h"
#include "IGame.h"
#include "IGameFramework.h"
#include "IEntitySystem.h"
#include "IEntityPoolManager.h"
#include "IActorSystem.h"
#include "IPlayerProfiles.h"
#include "IMovieSystem.h"
#include "CryPath.h"

//statics
FixedCheckpointString CCheckpointSystem::g_lastSavedCheckpoint;
StaticInstance<std::list<ICheckpointListener*>>  CCheckpointSystem::g_vCheckpointSystemListeners;

const static int CHECKPOINT_VERSION_NUMBER      = 0;
const static char* FILENAME_EXTENSION           = ".jmc";
const static char* CONSOLE_SAVEGAME_DIRECTORY   = "@user@/SaveGames";

//section flags
const static int CHECKPOINT_DATA_SIZE           = 1024000;
const static char* ACTOR_FLAGS_SECTION          = "ActorFlags";
const static char* ACTIVATED_ACTORS_SECTION     = "ActivatedActors";
const static char* META_DATA_SECTION            = "MetaData";
const static char* EXTERNAL_ENTITIES_SECTION    = "ExternalEntities";

//checkpoint data sanity check, usually triggered by changed entity Id's
static bool CHECKPOINT_RESAVE_NECESSARY         = false;

//opened XML node for writing
static XmlNodeRef CHECKPOINT_SAVE_XML_NODE      = NULL;
//opened XML node for reading
static XmlNodeRef CHECKPOINT_LOAD_XML_NODE      = NULL;

//////////////////////////////////////////////////////////////////////////
CCheckpointSystem::CCheckpointSystem()
    : m_pGameHandler(NULL)
{
}

//////////////////////////////////////////////////////////////////////////
CCheckpointSystem::~CCheckpointSystem()
{
}

//////////////////////////////////////////////////////////////////////////
CCheckpointSystem* CCheckpointSystem::GetInstance()
{
    //singleton instance
    static CCheckpointSystem* g_pSLS = NULL;
    if (g_pSLS == NULL)
    {
        g_pSLS = new CCheckpointSystem();
        g_lastSavedCheckpoint.clear();
    }
    return g_pSLS;
}

//////////////////////////////////////////////////////////////////////////
bool CCheckpointSystem::SaveXMLNode(XmlNodeRef node, const char* identifier)
{
    if (!identifier || !node)
    {
        return false;
    }
    //check whether a checkpoint is currently open
    if (!CHECKPOINT_SAVE_XML_NODE)
    {
        CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, "Tried writing checkpoint section %s while checkpoint was not open.", identifier);
        return false;
    }

    //flag section as being external and save name
    node->setAttr("external", identifier);

    //add section to opened file
    CHECKPOINT_SAVE_XML_NODE->addChild(node);
    return true;
}

//////////////////////////////////////////////////////////////////////////
XmlNodeRef CCheckpointSystem::LoadXMLNode(const char* identifier)
{
    if (!identifier)
    {
        return NULL;
    }
    //check whether a checkpoint is currently open
    if (!CHECKPOINT_LOAD_XML_NODE)
    {
        CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, "Tried reading checkpoint section %s while checkpoint was not open.", identifier);
        return NULL;
    }

    int numChildren = CHECKPOINT_LOAD_XML_NODE->getChildCount();
    for (int i = 0; i < numChildren; ++i)
    {
        XmlNodeRef child = CHECKPOINT_LOAD_XML_NODE->getChild(i);
        //return external section if name matches
        const char* key = "external";
        const char* attribName = child->getAttr(key);
        if (attribName)
        {
            //check name
            if (!azstricmp(identifier, attribName))
            {
                return child;
            }
        }
    }

    return NULL;
}

//////////////////////////////////////////////////////////////////////////
bool CCheckpointSystem::SaveExternalEntity(EntityId id)
{
    //this function allows external logic (flowgraph) to save specific entities
    if (!CHECKPOINT_SAVE_XML_NODE)
    {
        CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, "Tried writing external entity %i while savegame was not open.", (int)id);
        return false;
    }

    //find entity and access external section
    IEntity* pEntity = gEnv->pEntitySystem->GetEntity(id);
    if (pEntity)
    {
        XmlNodeRef externalEntities = CHECKPOINT_SAVE_XML_NODE->findChild(EXTERNAL_ENTITIES_SECTION);
        if (!externalEntities)
        {
            externalEntities = GetISystem()->CreateXmlNode(EXTERNAL_ENTITIES_SECTION);
            CHECKPOINT_SAVE_XML_NODE->addChild(externalEntities);
        }

        IActor* pActor = CCryAction::GetCryAction()->GetIActorSystem()->GetActor(pEntity->GetId());
        if (pActor)
        {
            CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, "The actor %s is additionally saved as external entity.", pEntity->GetName());
        }

        //create entity data
        char entityId[16];
        azsnprintf(entityId, sizeof(entityId), "%s%i", "id", id);
        XmlNodeRef nextEntity = GetISystem()->CreateXmlNode(entityId);
        if (nextEntity)
        {
            nextEntity->setAttr("id", pEntity->GetId());
            nextEntity->setAttr("name", pEntity->GetName());
            //save active / hidden
            nextEntity->setAttr("active", pEntity->IsActive());
            nextEntity->setAttr("hidden", pEntity->IsHidden());
            //save translation and rotation (complete tm matrix for simplicity)
            SerializeWorldTM(pEntity, nextEntity, true);
            //add new entity to checkpoint
            externalEntities->addChild(nextEntity);

            return true;
        }

        return false;
    }

    return false;
}

//SAVING *********************************

//////////////////////////////////////////////////////////////////////////
bool CCheckpointSystem::SaveGame(EntityId checkpointId, const char* fileName)
{
    CRY_ASSERT(fileName);
    if (!fileName || CHECKPOINT_SAVE_XML_NODE || CHECKPOINT_LOAD_XML_NODE)
    {
        return false;
    }

    //set extension
    FixedCheckpointString file(fileName);
    SetFilenameExtension(file);

    CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_COMMENT, "Saving checkpoint %s", file.c_str());

    CHECKPOINT_SAVE_XML_NODE = GetISystem()->CreateXmlNode("Checkpoint");

    //write checkpoint data
    SCheckpointData metaData;
    WriteMetaData(checkpointId, CHECKPOINT_SAVE_XML_NODE, metaData);

    //actor data
    // TODO For now, not saving actor info (AI) - If this happens later, support needs to be added for entity pools
    //WriteActorData(CHECKPOINT_SAVE_XML_NODE);

    //let game write
    if (m_pGameHandler)
    {
        m_pGameHandler->OnWriteData(CHECKPOINT_SAVE_XML_NODE);
    }

    //inform listeners
    UpdateListener(metaData, true);

    //write to file
    WriteXML(CHECKPOINT_SAVE_XML_NODE, file.c_str());

    //draw text message on screen
    //static const ColorF color (0.0f, 0.85f, 0.2f, 1.0f);
    //g_pGame->GetIGameFramework()->GetIPersistentDebug()->Add2DText("Checkpoint saved", 40.0f, color, 2.0f);

    CHECKPOINT_SAVE_XML_NODE = NULL;

    return true;
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::WriteActorData(XmlNodeRef parentNode)
{
    XmlNodeRef node = GetISystem()->CreateXmlNode(ACTOR_FLAGS_SECTION);
    //write only data for non-active or hidden actors
    XmlNodeRef activatedActors = GetISystem()->CreateXmlNode(ACTIVATED_ACTORS_SECTION);
    char buffer[100];
    IActorSystem* pActorSystem = CCryAction::GetCryAction()->GetIActorSystem();
    IActorIteratorPtr it = pActorSystem->CreateActorIterator();
    while (IActor* pActor = it->Next())
    {
        IEntity* pEntity = pActor->GetEntity();
        if (!pEntity->IsHidden() && pEntity->IsActive())
        {
            EntityId id = pEntity->GetId();
            const char* name = pEntity->GetName(); //we have to rely on names, since Id's change on level reexport
            azsnprintf(buffer, sizeof(buffer), "%s%i", "id", id);
            activatedActors->setAttr(buffer, name);
        }
    }
    node->addChild(activatedActors);

    parentNode->addChild(node);
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::WriteMetaData(EntityId checkpointId, XmlNodeRef parentNode, SCheckpointData& outMetaData)
{
    XmlNodeRef node = GetISystem()->CreateXmlNode(META_DATA_SECTION);

    const char* levelName = CCryAction::GetCryAction()->GetLevelName();
    CRY_ASSERT(levelName);
    CryFixedStringT<32> curlevelName = levelName;
    RepairLevelName(curlevelName);

    node->setAttr("Version", CHECKPOINT_VERSION_NUMBER);
    node->setAttr("LevelName", curlevelName.c_str());
    node->setAttr("CheckpointId", checkpointId);

    //write checkpoint name to be independent of entityId
    IEntity* pCheckpointEntity = gEnv->pEntitySystem->GetEntity(checkpointId);
    if (pCheckpointEntity)
    {
        node->setAttr("CheckpointName", pCheckpointEntity->GetName());
    }
    else
    {
        node->setAttr("CheckpointName", "none");
    }

    string timeString;
    GameUtils::timeToString(time(NULL), timeString);
    node->setAttr("Timestamp", timeString.c_str());

    parentNode->addChild(node);

    //write back metadata for listeners
    outMetaData.m_checkPointId = checkpointId;
    outMetaData.m_levelName = levelName;
    outMetaData.m_saveTime = timeString.c_str();
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::WriteXML(XmlNodeRef data, const char* fileName)
{
    IPlayerProfileManager* pPlayerProfMan = CCryAction::GetCryAction()->GetIPlayerProfileManager();
    ;

    string path;
    if (!pPlayerProfMan)
    {
        path = CONSOLE_SAVEGAME_DIRECTORY;
    }
    else
    {
        const char* sharedSaveGameFolder = pPlayerProfMan->GetSharedSaveGameFolder();
        path = sharedSaveGameFolder;
    }

    path = PathUtil::AddSlash(path);
    path.append(fileName);
    if (data)
    {
        //write checkpoint data to xml file with given name
        const string xmlHeader("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");

        bool bSuccess = data->saveToFile(path.c_str(), 32767 / 2, 0);
        if (bSuccess)
        {
            //remember last saved checkpoint for "quickload"
            g_lastSavedCheckpoint = fileName;
        }
        else
        {
            CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_ERROR, "Failed writing checkpoint file at %s", path.c_str());
        }
    }
}

//LOADING ********************************

//////////////////////////////////////////////////////////////////////////
bool CCheckpointSystem::LoadLastCheckpoint()
{
    if (!g_lastSavedCheckpoint.empty())
    {
        return LoadGame(g_lastSavedCheckpoint.c_str());
    }
    CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, "Can't load latest checkpoint : no recent checkpoint found!");
    return false;
}

//////////////////////////////////////////////////////////////////////////
bool CCheckpointSystem::LoadGame(const char* fileName)
{
    //make sure not not save/load recursively or multiple times at once
    if (CHECKPOINT_SAVE_XML_NODE || CHECKPOINT_LOAD_XML_NODE)
    {
        return false;
    }

    //set extension
    FixedCheckpointString file(fileName);
    SetFilenameExtension(file);

    CHECKPOINT_LOAD_XML_NODE = ReadXML(file.c_str());
    if (!CHECKPOINT_LOAD_XML_NODE)
    {
        return false;
    }

    CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_COMMENT, "Loading checkpoint %s", file.c_str());

    //check for EntityId errors
    CHECKPOINT_RESAVE_NECESSARY = false;

    //process meta data
    SCheckpointData metaData;
    if (!ReadMetaData(CHECKPOINT_LOAD_XML_NODE, metaData))
    {
        return false;
    }

    //check version number
    if (metaData.m_versionNumber != CHECKPOINT_VERSION_NUMBER)
    {
        CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_ERROR, "Checkpoint version number (%i) does not match current version (%i). Please reexport all checkpoints of this level to prevent errors!", metaData.m_versionNumber, CHECKPOINT_VERSION_NUMBER);
    }

    //check for level mismatch
    CryFixedStringT<32> curlevelName = CCryAction::GetCryAction()->GetLevelName();
    RepairLevelName(curlevelName);
    if (curlevelName.empty() || azstricmp(metaData.m_levelName.c_str(), curlevelName.c_str()))
    {
        if (!LoadCheckpointMap(metaData, curlevelName))
        {
            return false;
        }
    }
    else
    {
        //reset the dynamic parts of the engine
        ResetEngine();
    }

    //read actor data and respawn AI
    // TODO For now, not restoring actor info (AI) - If this happens later, support needs to be added for entity pools
    //RespawnAI(CHECKPOINT_LOAD_XML_NODE);

    //let game read
    if (m_pGameHandler)
    {
        m_pGameHandler->OnReadData(CHECKPOINT_LOAD_XML_NODE);
    }

    //resets some gameplay data like action filters etc.
    RestartGameplay();

    //load external entities, that are controlled by flowgraph
    LoadExternalEntities(CHECKPOINT_LOAD_XML_NODE);

    //inform listeners
    UpdateListener(metaData, false);

    //trigger flowgraph node
    OnCheckpointLoaded(metaData);

    //draw text message on screen
    //static const ColorF color (0.0f, 0.85f, 0.2f, 1.0f);
    //g_pGame->GetIGameFramework()->GetIPersistentDebug()->Add2DText("Checkpoint loaded", 40.0f, color, 2.0f);

    //checkpoint file sanity check
    if (CHECKPOINT_RESAVE_NECESSARY)
    {
        CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, "Checkpoint file contained obsolete or wrong data, trying to re-save checkpoint.");
        //resave checkpoint to fix changed entity Ids
        SaveGame(metaData.m_checkPointId, fileName);
        //make sure the script entity is aware of the activity
        OnCheckpointLoaded(metaData);
    }

    CHECKPOINT_LOAD_XML_NODE = NULL;

    //when a checkpoint was loaded, it becomes the most recent checkpoint
    g_lastSavedCheckpoint = fileName;

    //make sure the scripts are clean
    //CXP : this caused a crash after some reloads, which hints to problems in the gamerules script
    //gEnv->pScriptSystem->ForceGarbageCollection();

    return true;
}

//////////////////////////////////////////////////////////////////////////
bool CCheckpointSystem::LoadCheckpointMap(const SCheckpointData& metaData, CryFixedStringT<32>& curLevelName)
{
    CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_COMMENT, "Checkpoint loads new level :  %s", metaData.m_levelName.c_str());

    //load map
    if (gEnv->IsEditor())
    {
        return false; //can't do that
    }
    CryFixedStringT<64> mapCmd("map ");
    mapCmd += metaData.m_levelName.c_str();
    gEnv->pConsole->ExecuteString(mapCmd.c_str());

    curLevelName = CCryAction::GetCryAction()->GetLevelName();
    RepairLevelName(curLevelName);
    if (azstricmp(metaData.m_levelName.c_str(), curLevelName.c_str()))
    {
        CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_ERROR, "Failed loaded level %s from checkpoint.", metaData.m_levelName.c_str());
        return false; //still wrong level
    }

    return true;
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::LoadExternalEntities(XmlNodeRef parentNode)
{
    XmlNodeRef data = parentNode->findChild(EXTERNAL_ENTITIES_SECTION);
    if (!data)
    {
        return;
    }

    int numEntities = data->getChildCount();
    for (int i = 0; i < numEntities; ++i)
    {
        XmlNodeRef nextEntity = data->getChild(i);
        if (nextEntity)
        {
            EntityId id = 0;
            nextEntity->getAttr("id", id);
            const char* name = nextEntity->getAttr("name");

            //fix entityId if broken
            if (RepairEntityId(id, name))
            {
                IEntity* pEntity = gEnv->pEntitySystem->GetEntity(id);
                //setup entity
                bool bActive = false;
                bool bHidden = false;
                nextEntity->getAttr("active", bActive);
                nextEntity->getAttr("hidden", bHidden);
                pEntity->Activate(bActive);
                pEntity->Hide(bHidden);
                //load matrix
                SerializeWorldTM(pEntity, nextEntity, false);
            }
        }
    }
}

//////////////////////////////////////////////////////////////////////////
//clear everything out before loading a checkpoint
void CCheckpointSystem::ResetEngine()
{
    //let game do initial resetting
    if (m_pGameHandler)
    {
        m_pGameHandler->OnPreResetEngine();
    }

    //turn off physics
    gEnv->pSystem->SetThreadState(ESubsys_Physics, false);

    //reset engine similar to editor when leaving game mode
    //broken geometry and dynamics
    CCryAction::GetCryAction()->FlushBreakableObjects();
    DeleteDynamicEntities();
    CCryAction::GetCryAction()->ResetBrokenGameObjects();

    //this should just reset the velocity of all moving things, but instead vehicle doors fall off ...
    //CXP : add this back in after CXP, Anton has to fix it
    //gEnv->pPhysicalWorld->ResetDynamicEntities();

    //particle and post process effects
    gEnv->p3DEngine->ResetPostEffects();
    gEnv->p3DEngine->ResetParticlesAndDecals();

    // Audio: notify the audio system?

    //AI System
    if (gEnv->pAISystem)
    {
        gEnv->pAISystem->Reset(IAISystem::RESET_EXIT_GAME);
        gEnv->pAISystem->Reset(IAISystem::RESET_ENTER_GAME);
    }

    //reset trackview
    gEnv->pMovieSystem->Reset(true, true);

    //entity system
    SEntityEvent event;
    event.event = ENTITY_EVENT_RESET;
    event.nParam[0] = 0;
    gEnv->pEntitySystem->SendEventToAll(event);

    //make sure the scripts are clean
    gEnv->pScriptSystem->ForceGarbageCollection();

    //turn on physics again
    gEnv->pSystem->SetThreadState(ESubsys_Physics, true);

    //let game do final resetting
    if (m_pGameHandler)
    {
        m_pGameHandler->OnPostResetEngine();
    }
}

//////////////////////////////////////////////////////////////////////////
bool CCheckpointSystem::ReadMetaData(XmlNodeRef parentNode, SCheckpointData& metaData, bool bRepairId /*=true*/)
{
    XmlNodeRef data = parentNode->findChild(META_DATA_SECTION);
    if (!data)
    {
        return false;
    }

    metaData.m_versionNumber = 0;
    metaData.m_levelName.clear();
    metaData.m_saveTime.clear();
    metaData.m_checkPointId = 0;

    //read meta data
    int numAttribs = data->getNumAttributes();
    const char* key, * value;
    const char* checkpointName = NULL;
    for (int i = 0; i < numAttribs; ++i)
    {
        data->getAttributeByIndex(i, &key, &value);

        if (!azstricmp("Version", key))
        {
            metaData.m_versionNumber = atoi(value);
        }
        else if (!azstricmp("CheckpointId", key))
        {
            metaData.m_checkPointId = EntityId(atoi(value));
        }
        else if (!azstricmp("CheckpointName", key))
        {
            checkpointName = value;
        }
        else if (!azstricmp("LevelName", key))
        {
            metaData.m_levelName = value;
        }
        else if (!azstricmp("Timestamp", key))
        {
            metaData.m_saveTime = value;
        }
    }

    //EntityId's may change on level export -> fix id
    if (checkpointName && bRepairId)
    {
        if (!RepairEntityId(metaData.m_checkPointId, checkpointName))
        {
            CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_ERROR, "Failed finding checkpoint entity during loading, flowgraph might be broken.");
        }
    }

    //check all values have been read
    CRY_ASSERT(metaData.m_levelName.size() > 0);
    CRY_ASSERT(metaData.m_saveTime.size() > 0);
    //CRY_ASSERT(metaData.m_checkPointId);

    return true;
}

//////////////////////////////////////////////////////////////////////////
//this respawns the active AI at their spawn locations
void CCheckpointSystem::RespawnAI(XmlNodeRef data)
{
    if (!data)
    {
        return;
    }

    XmlNodeRef actorData = data->findChild(ACTOR_FLAGS_SECTION);
    if (!actorData)
    {
        CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_ERROR, "Failed reading actor data from checkpoint, actors won't be respawned");
        return;
    }

    IActorSystem* pActorSystem = CCryAction::GetCryAction()->GetIActorSystem();

    //first run through all actors and hide/deactivate them
    IActorIteratorPtr it = pActorSystem->CreateActorIterator();
    while (IActor* pActor = it->Next())
    {
        IEntity* pEntity = pActor->GetEntity();
        //deactivate all actors
        pEntity->Hide(true);
        pEntity->Activate(false);
    }

    //load actorflags for active actors
    XmlNodeRef activatedActors = actorData->findChild(ACTIVATED_ACTORS_SECTION);
    if (activatedActors)
    {
        int actorFlags = activatedActors->getNumAttributes();
        const char* key;
        const char* value;
        for (int i = 0; i < actorFlags; ++i)
        {
            activatedActors->getAttributeByIndex(i, &key, &value);
            //format is "idXXX"
            CRY_ASSERT(strlen(key) > 2);
            EntityId id = (EntityId)(atoi(&key[2]));
            bool foundEntity = RepairEntityId(id, value);
            if (foundEntity)
            {
                IActor* pActor = pActorSystem->GetActor(id);
                if (pActor)
                {
                    pActor->GetEntity()->Hide(false);
                    pActor->GetEntity()->Activate(true);
                }
                else
                {
                    CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_ERROR, "Failed finding actor %i from checkpoint.", (int)id);
                }
            }
            else
            {
                CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_ERROR, "Failed finding actor %s from checkpoint, actor is not setup correctly.", value);
            }
        }
    }
    else
    {
        CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_ERROR, "Deactivated actors section was missing in checkpoint.");
    }

    it = pActorSystem->CreateActorIterator();
    //iterate all actors and respawn if active
    while (IActor* pActor = it->Next())
    {
        IEntity* pEntity = pActor->GetEntity();
        if (pEntity->GetId() == LOCAL_PLAYER_ENTITY_ID) //don't respawn player
        {
            continue;
        }

        //we don't respawn deactivated actors
        if (!pEntity->IsHidden() && pEntity->IsActive())
        {
            pActor->SetHealth(0);
            pActor->Respawn();
        }
        else //but we still reset their position
        {
            pActor->ResetToSpawnLocation();
        }
    }
}

//////////////////////////////////////////////////////////////////////////
XmlNodeRef CCheckpointSystem::ReadXML(const char* fileName)
{
    IPlayerProfileManager* pPlayerProfMan = CCryAction::GetCryAction()->GetIPlayerProfileManager();
    ;

    string path;
    if (!pPlayerProfMan)
    {
        //on consoles there is no profile manager
        path = CONSOLE_SAVEGAME_DIRECTORY;
    }
    else
    {
        const char* sharedSaveGameFolder = pPlayerProfMan->GetSharedSaveGameFolder();
        path = sharedSaveGameFolder;
    }

    path = PathUtil::AddSlash(path);
    path.append(fileName);

    //read XML data from given checkpoint file
    _smart_ptr<IXmlParser> xmlParser;
    xmlParser.reset(GetISystem()->GetXmlUtils()->CreateXmlParser());
    XmlNodeRef data = xmlParser->ParseFile(path.c_str(), true);

    if (!data)
    {
        CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_ERROR, "Failed reading checkpoint at %s", path.c_str());
    }

    return data;
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::RestartGameplay()
{
    //if paused - start game
    gEnv->pGame->GetIGameFramework()->PauseGame(false, true);

    //let game restart
    if (m_pGameHandler)
    {
        m_pGameHandler->OnRestartGameplay();
    }
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::OnCheckpointLoaded(SCheckpointData metaData)
{
    IEntity* pCheckpoint = gEnv->pEntitySystem->GetEntity(metaData.m_checkPointId);
    if (pCheckpoint)
    {
        //Trigger OnLoad
        IScriptTable* pScript = pCheckpoint->GetScriptTable();
        if (pScript)
        {
            HSCRIPTFUNCTION hScriptFunc(NULL);
            pScript->GetValue("Event_OnLoadCheckpoint", hScriptFunc);

            if (hScriptFunc) //this will trigger the flowgraph output
            {
                IScriptSystem* pIScriptSystem = gEnv->pScriptSystem;
                Script::Call(pIScriptSystem, hScriptFunc, pScript);
                pIScriptSystem->ReleaseFunc(hScriptFunc);
            }
        }
    }
}

//SHARED ********************************

//////////////////////////////////////////////////////////////////////////
bool CCheckpointSystem::GetMetaData(const char* fileName, SCheckpointData& metaData, bool bRepairId /*=true*/)
{
    XmlNodeRef data = ReadXML(fileName);
    if (!data)
    {
        return false;
    }

    //process meta data
    return ReadMetaData(data, metaData, bRepairId);
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::SetFilenameExtension(FixedCheckpointString& fileName)
{
    int fileNameLen = fileName.length();

    //check for empty string
    if (fileNameLen == 0)
    {
        fileName = "placeholder";
        fileName.append(FILENAME_EXTENSION);
        return;
    }

    //look up extension
    int pos = fileName.find(FILENAME_EXTENSION);
    if (pos != FixedCheckpointString::npos && pos == (fileNameLen - strlen(FILENAME_EXTENSION)))
    {
        return;
    }

    //look up and remove wrong extension
    pos = fileName.rfind('.');
    if (pos != FixedCheckpointString::npos)
    {
        fileName = fileName.substr(0, pos);
    }

    //add extension
    fileName.append(FILENAME_EXTENSION);
}

//////////////////////////////////////////////////////////////////////////
bool CCheckpointSystem::RepairEntityId(EntityId& id, const char* pEntityName)
{
    //EntityId's may change on level export -> fix id
    if (pEntityName)
    {
        //test the original entity id
        IEntity* pOriginalEntity = gEnv->pEntitySystem->GetEntity(id);
        if (pOriginalEntity && !azstricmp(pOriginalEntity->GetName(), pEntityName))
        {
            return true; //seems correct
        }
        IEntity* pNewEntity = gEnv->pEntitySystem->FindEntityByName(pEntityName);
        if (!pNewEntity)
        {
            CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, "Entity %s in loaded checkpoint could not be found. This means the checkpoint file is not compatible with this level version.", pEntityName);
        }
        else if (!azstricmp(pNewEntity->GetName(), pEntityName))
        {
            //if the level was re-exported, the entity id might differ
            CHECKPOINT_RESAVE_NECESSARY = true;
            //this is a weakness of our entity system/editor and might be fixed in future
            id = pNewEntity->GetId();
            return true;
        }
        return false;
    }

    return false;
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::RepairLevelName(CryFixedStringT<32>& levelName)
{
    if (levelName.empty())
    {
        return;
    }
    int posSlash = levelName.rfind('/');
    if (posSlash != levelName.npos)  //This is a work-around for the editor/fullgame incompatible level name convention
    {
        levelName = levelName.substr(posSlash + 1, levelName.size() - posSlash);
    }
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::SetGameHandler(ICheckpointGameHandler* pHandler)
{
    CRY_ASSERT(pHandler);
    m_pGameHandler = pHandler;
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::ClearGameHandler()
{
    m_pGameHandler = NULL;
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::RegisterListener(ICheckpointListener* pListener)
{
    CRY_ASSERT(pListener);

    //check for existing listener
    if (std::find(g_vCheckpointSystemListeners.begin(), g_vCheckpointSystemListeners.end(), pListener) == g_vCheckpointSystemListeners.end())
    {
        //add listener
        g_vCheckpointSystemListeners.push_back(pListener);
    }
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::RemoveListener(ICheckpointListener* pListener)
{
    CRY_ASSERT(pListener);

    //look up listener
    std::list<ICheckpointListener*>::iterator it = std::find(g_vCheckpointSystemListeners.begin(), g_vCheckpointSystemListeners.end(), pListener);
    if (it != g_vCheckpointSystemListeners.end())
    {
        //remove listener
        g_vCheckpointSystemListeners.erase(it);
    }
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::UpdateListener(SCheckpointData data, bool wasSaving)
{
    //external listeners may also read/write checkpoint data now
    std::list<ICheckpointListener*>::iterator it = GetInstance()->g_vCheckpointSystemListeners.begin();
    std::list<ICheckpointListener*>::iterator end = GetInstance()->g_vCheckpointSystemListeners.end();
    for (; it != end; ++it)
    {
        //send update - the global checkpoint flownode also is set here, but is not triggered until the flowgraph updates
        if (wasSaving)
        {
            (*it)->OnSave(&data, GetInstance());
        }
        else
        {
            (*it)->OnLoad(&data, GetInstance());
        }
    }
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::SerializeWorldTM(IEntity* pEntity, XmlNodeRef data, bool writing)
{
    if (!pEntity || !data)
    {
        return;
    }

    if (writing)
    {
        //write all TM columns to checkpoint node
        const Matrix34& tm = pEntity->GetWorldTM();
        data->setAttr("TMcol0", tm.GetColumn0());
        data->setAttr("TMcol1", tm.GetColumn1());
        data->setAttr("TMcol2", tm.GetColumn2());
        data->setAttr("TMcol3", tm.GetColumn3());
    }
    else
    {
        //read and set TM columns from node
        Matrix34 tm;
        Vec3 temp = Vec3(0, 0, 0);
        bool foundData = data->getAttr("TMcol0", temp);
        tm.SetColumn(0, temp);
        foundData &= data->getAttr("TMcol1", temp);
        tm.SetColumn(1, temp);
        foundData &= data->getAttr("TMcol2", temp);
        tm.SetColumn(2, temp);
        foundData &= data->getAttr("TMcol3", temp);
        tm.SetColumn(3, temp);
        CRY_ASSERT(foundData);
        //set matrix to entity
        pEntity->SetWorldTM(tm);
    }
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::DeleteDynamicEntities()
{
    IEntitySystem* pEntitySystem = gEnv->pEntitySystem;
    IEntityItPtr pIt = pEntitySystem->GetEntityIterator();
    //////////////////////////////////////////////////////////////////////////
    pIt->MoveFirst();
    while (!pIt->IsEnd())
    {
        IEntity* pEntity = pIt->Next();
        uint32 nEntityFlags = pEntity->GetFlags();

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

        if (nEntityFlags & ENTITY_FLAG_SPAWNED)
        {
            pEntitySystem->RemoveEntity(pEntity->GetId());
        }
    }
    // Force deletion of removed entities.
    pEntitySystem->DeletePendingEntities();
    //////////////////////////////////////////////////////////////////////////

    // Reset entity pools
    pEntitySystem->GetIEntityPoolManager()->ResetPools(false);
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::GetCurrentLevelCheckpoints(std::vector<string>& saveNames)
{
    //get current level
    const char* levelName = CCryAction::GetCryAction()->GetLevelName();
    CryFixedStringT<32> curlevelName = levelName;
    if (levelName)
    {
        CCheckpointSystem::GetInstance()->RepairLevelName(curlevelName);
    }

    //parse savegames fitting current level
    ParseSavedFiles(saveNames, curlevelName.c_str());
}

//////////////////////////////////////////////////////////////////////////
void CCheckpointSystem::ParseSavedFiles(std::vector<string>& saveNames, const char* pLevelName)
{
    //create search index
    string search = string(CONSOLE_SAVEGAME_DIRECTORY) + "/*" + string(FILENAME_EXTENSION);

    //scan savegame directory
    _finddata_t fd;
    intptr_t handle = gEnv->pCryPak->FindFirst(search.c_str(), &fd);
    if (handle != -1)
    {
        do
        {
            //if levelname is provided, check metaData
            if (pLevelName)
            {
                //try to get meta data for save file
                SCheckpointData metaData;
                if (!CCheckpointSystem::GetInstance()->GetMetaData(fd.name, metaData, false))
                {
                    CRY_ASSERT(0); //shouldn't happen
                    continue;
                }

                if (azstricmp(metaData.m_levelName.c_str(), pLevelName))
                {
                    continue;
                }
            }

            //add filename to list
            saveNames.push_back(fd.name);
        }
        while (gEnv->pCryPak->FindNext(handle, &fd) >= 0);

        gEnv->pCryPak->FindClose(handle);
    }
}

//~CCheckpointSystem