/*
* 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 : CMission class implementation.


#include "StdAfx.h"
#include "Mission.h"
#include "CryEditDoc.h"
#include "TerrainLighting.h"
#include "GameEngine.h"
#include "MissionScript.h"
#include "IMovieSystem.h"
#include "VegetationMap.h"

#include "Objects/EntityObject.h"
#include "Objects/EntityScript.h"

#include <ITimeOfDay.h>
#include <IEntitySystem.h>
#include <I3DEngine.h>

#include "IAISystem.h"

namespace
{
    const char* kTimeOfDayFile = "TimeOfDay.xml";
    const char* kTimeOfDayRoot = "TimeOfDay";
    const char* kEnvironmentFile = "Environment.xml";
    const char* kEnvironmentRoot = "Environment";
};


//////////////////////////////////////////////////////////////////////////
CMission::CMission(CCryEditDoc* doc)
{
    m_doc = doc;
    m_objects = XmlHelpers::CreateXmlNode("Objects");
    m_layers = XmlHelpers::CreateXmlNode("ObjectLayers");
    //m_exportData = XmlNodeRef( "ExportData" );
    m_weaponsAmmo = XmlHelpers::CreateXmlNode("Ammo");
    m_timeOfDay = XmlHelpers::CreateXmlNode("TimeOfDay");
    m_environment = XmlHelpers::CreateXmlNode("Environment");
    CXmlTemplate::SetValues(m_doc->GetEnvironmentTemplate(), m_environment);

    m_time = 12; // 12 PM by default.

    m_lighting = new LightingSettings();            // default values are set in the constructor

    m_sMusicScript = "";

    m_pScript = new CMissionScript();
    m_numCGFObjects = 0;

    m_minimap.vCenter = Vec2(512, 512);
    m_minimap.vExtends = Vec2(512, 512);
    m_minimap.textureWidth = m_minimap.textureHeight = 1024;

    m_reentrancyProtector = false;
}

//////////////////////////////////////////////////////////////////////////
CMission::~CMission()
{
    delete m_lighting;
    delete m_pScript;
}

//////////////////////////////////////////////////////////////////////////
CMission* CMission::Clone()
{
    CMission* m = new CMission(m_doc);
    m->SetName(m_name);
    m->SetDescription(m_description);
    m->m_objects = m_objects->clone();
    m->m_layers = m_layers->clone();
    m->m_environment = m_environment->clone();
    m->m_time = m_time;
    m->m_sMusicScript = m_sMusicScript;
    return m;
}

//////////////////////////////////////////////////////////////////////////
void CMission::SetMusicScriptFilename(const QString& sMusicScript)
{
    m_sMusicScript = sMusicScript;
    if (!m_sMusicScript.isEmpty())
    {
        //if (!GetIEditor()->GetSystem()->LoadMusicDataFromLUA(m_sMusicScript))
        //GetIEditor()->GetSystem()->GetILog()->Log("WARNING: Cannot load music-script %s !", m_sMusicScript);
    }
}

//////////////////////////////////////////////////////////////////////////
void CMission::Serialize(CXmlArchive& ar, bool bParts)
{
    if (ar.bLoading)
    {
        // Load.
        ar.root->getAttr("Name", m_name);
        ar.root->getAttr("Description", m_description);

        //time_t time = 0;
        //ar.root->getAttr( "Time",time );
        //m_time = time;

        m_sPlayerEquipPack = QString();
        ar.root->getAttr("PlayerEquipPack", m_sPlayerEquipPack);
        QString sMusicScript;
        ar.root->getAttr("MusicScript", m_sMusicScript);
        SetMusicScriptFilename(sMusicScript);
        QString sScript;
        ar.root->getAttr("Script", sScript);
        m_pScript->SetScriptFile(sScript);

        XmlNodeRef objects = ar.root->findChild("Objects");
        if (objects)
        {
            m_objects = objects;
        }

        XmlNodeRef layers = ar.root->findChild("ObjectLayers");
        if (layers)
        {
            m_layers = layers;
        }

        SerializeTimeOfDay(ar);

        m_Animations = ar.root->findChild("MovieData");

        /*
        XmlNodeRef expData = ar.root->findChild( "ExportData" );
        if (expData)
        {
            m_exportData = expData;
        }
        */
        SerializeEnvironment(ar);

        m_lighting->Serialize(ar.root, ar.bLoading);

        m_usedWeapons.clear();

        XmlNodeRef weapons = ar.root->findChild("Weapons");
        if (weapons)
        {
            XmlNodeRef usedWeapons = weapons->findChild("Used");
            if (usedWeapons)
            {
                QString weaponName;
                for (int i = 0; i < usedWeapons->getChildCount(); i++)
                {
                    XmlNodeRef weapon = usedWeapons->getChild(i);
                    if (weapon->getAttr("Name", weaponName))
                    {
                        m_usedWeapons.push_back(weaponName);
                    }
                }
            }
            XmlNodeRef ammo = weapons->findChild("Ammo");
            if (ammo)
            {
                m_weaponsAmmo = ammo->clone();
            }
        }

        XmlNodeRef minimapNode = ar.root->findChild("MiniMap");
        if (minimapNode)
        {
            minimapNode->getAttr("CenterX", m_minimap.vCenter.x);
            minimapNode->getAttr("CenterY", m_minimap.vCenter.y);
            minimapNode->getAttr("ExtendsX", m_minimap.vExtends.x);
            minimapNode->getAttr("ExtendsY", m_minimap.vExtends.y);
            //          minimapNode->getAttr( "CameraHeight",m_minimap.cameraHeight );
            minimapNode->getAttr("TexWidth", m_minimap.textureWidth);
            minimapNode->getAttr("TexHeight", m_minimap.textureHeight);
        }
    }
    else
    {
        ar.root->setAttr("Name", m_name.toUtf8().data());
        ar.root->setAttr("Description", m_description.toUtf8().data());

        //time_t time = m_time.GetTime();
        //ar.root->setAttr( "Time",time );

        ar.root->setAttr("PlayerEquipPack", m_sPlayerEquipPack.toUtf8().data());
        ar.root->setAttr("MusicScript", m_sMusicScript.toUtf8().data());
        ar.root->setAttr("Script", m_pScript->GetFilename().toUtf8().data());

        QString timeStr;
        int nHour = floor(m_time);
        int nMins = (m_time - floor(m_time)) * 60.0f;
        timeStr = QStringLiteral("%1:%2").arg(nHour, 2, 10, QLatin1Char('0')).arg(nMins, 2, 10, QLatin1Char('0'));
        ar.root->setAttr("MissionTime", timeStr.toUtf8().data());

        // Saving.
        XmlNodeRef layers = m_layers->clone();
        layers->setTag("ObjectLayers");
        ar.root->addChild(layers);

        ///XmlNodeRef objects = m_objects->clone();
        m_objects->setTag("Objects");
        ar.root->addChild(m_objects);

        if (bParts)
        {
            SerializeTimeOfDay(ar);
            SerializeEnvironment(ar);
        }

        m_lighting->Serialize(ar.root, ar.bLoading);

        XmlNodeRef weapons = ar.root->newChild("Weapons");
        XmlNodeRef usedWeapons = weapons->newChild("Used");
        for (int i = 0; i < m_usedWeapons.size(); i++)
        {
            XmlNodeRef weapon = usedWeapons->newChild("Weapon");
            weapon->setAttr("Name", m_usedWeapons[i].toUtf8().data());
        }
        weapons->addChild(m_weaponsAmmo->clone());

        XmlNodeRef minimapNode = ar.root->newChild("MiniMap");
        minimapNode->setAttr("CenterX", m_minimap.vCenter.x);
        minimapNode->setAttr("CenterY", m_minimap.vCenter.y);
        minimapNode->setAttr("ExtendsX", m_minimap.vExtends.x);
        minimapNode->setAttr("ExtendsY", m_minimap.vExtends.y);
        //      minimapNode->setAttr( "CameraHeight",m_minimap.cameraHeight );
        minimapNode->setAttr("TexWidth", m_minimap.textureWidth);
        minimapNode->setAttr("TexHeight", m_minimap.textureHeight);
    }
}

//////////////////////////////////////////////////////////////////////////
void CMission::Export(XmlNodeRef& root, XmlNodeRef& objectsNode)
{
    // Also save exported objects data.
    root->setAttr("Name", m_name.toUtf8().data());
    root->setAttr("Description", m_description.toUtf8().data());

    QString timeStr;
    int nHour = floor(m_time);
    int nMins = (m_time - floor(m_time)) * 60.0f;
    timeStr = QStringLiteral("%1:%2").arg(nHour, 2, 10, QLatin1Char('0')).arg(nMins, 2, 10, QLatin1Char('0'));
    root->setAttr("Time", timeStr.toUtf8().data());

    root->setAttr("PlayerEquipPack", m_sPlayerEquipPack.toUtf8().data());
    root->setAttr("MusicScript", m_sMusicScript.toUtf8().data());
    root->setAttr("Script", m_pScript->GetFilename().toUtf8().data());

    // Saving.
    //XmlNodeRef objects = m_exportData->clone();
    //objects->setTag( "Objects" );
    //root->addChild( objects );

    XmlNodeRef envNode = m_environment->clone();
    m_lighting->Serialize(envNode, false);
    root->addChild(envNode);

    m_timeOfDay->setAttr("Time", m_time);
    root->addChild(m_timeOfDay);

    XmlNodeRef minimapNode = root->newChild("MiniMap");
    minimapNode->setAttr("CenterX", m_minimap.vCenter.x);
    minimapNode->setAttr("CenterY", m_minimap.vCenter.y);
    minimapNode->setAttr("ExtendsX", m_minimap.vExtends.x);
    minimapNode->setAttr("ExtendsY", m_minimap.vExtends.y);
    //  minimapNode->setAttr( "CameraHeight",m_minimap.cameraHeight );
    minimapNode->setAttr("TexWidth", m_minimap.textureWidth);
    minimapNode->setAttr("TexHeight", m_minimap.textureHeight);

    XmlNodeRef weapons = root->newChild("Weapons");
    XmlNodeRef usedWeapons = weapons->newChild("Used");
    for (int i = 0; i < m_usedWeapons.size(); i++)
    {
        XmlNodeRef weapon = usedWeapons->newChild("Weapon");
        weapon->setAttr("Name", m_usedWeapons[i].toUtf8().data());
        IEntity* pEnt = GetIEditor()->GetSystem()->GetIEntitySystem()->FindEntityByName(m_usedWeapons[i].toUtf8().data());
        if (pEnt)
        {
            weapon->setAttr("id", pEnt->GetId());
        }
    }
    weapons->addChild(m_weaponsAmmo->clone());

    IObjectManager* pObjMan = GetIEditor()->GetObjectManager();

    //////////////////////////////////////////////////////////////////////////
    // Serialize objects.
    //////////////////////////////////////////////////////////////////////////
    QString path = QDir::toNativeSeparators(QFileInfo(m_doc->GetLevelPathName()).absolutePath());
    if (!path.endsWith(QDir::separator()))
        path += QDir::separator();

    objectsNode = root->newChild("Objects");
    pObjMan->Export(path, objectsNode, true); // Export shared.
    pObjMan->Export(path, objectsNode, false);  // Export not shared.

    /*
    CObjectManager objectManager;
    XmlNodeRef loadRoot = root->createNode("Root");
    loadRoot->addChild( m_objects );

    std::vector<CObjectClassDesc*> classes;
    GetIEditor()->GetObjectManager()->GetClasses( classes );
    objectManager.SetClasses( classes );
    objectManager.SetCreateGameObject(false);
    objectManager.Serialize( loadRoot,true,SERIALIZE_ALL );
    objectManager.Export( path,objects,false );
    */
}

//////////////////////////////////////////////////////////////////////////
void CMission::SyncContent(bool bRetrieve, bool bIgnoreObjects, bool bSkipLoadingAI /* = false */)
{
    // The function may take a longer time when executing objMan->Serialize, which uses CWaitProgress internally
    // Adding a sync flag to prevent the function from being re-entered after the data is modified by OnEnvironmentChange
    if (m_reentrancyProtector)
    {
        return;
    }
    m_reentrancyProtector = true;

    // Save data from current Document to Mission.
    IObjectManager* objMan = GetIEditor()->GetObjectManager();
    if (bRetrieve)
    {
        // Activating this mission.
        CGameEngine* gameEngine = GetIEditor()->GetGameEngine();

        // Load mission script.
        if (m_pScript)
        {
            m_pScript->Load();
        }

        if (!bIgnoreObjects)
        {
            // Retrieve data from Mission and put to document.
            XmlNodeRef root = XmlHelpers::CreateXmlNode("Root");
            root->addChild(m_objects);
            root->addChild(m_layers);
            objMan->Serialize(root, true, SERIALIZE_ONLY_NOTSHARED);
        }

        m_doc->GetFogTemplate() = m_environment;

        CXmlTemplate::GetValues(m_doc->GetEnvironmentTemplate(), m_environment);

        UpdateUsedWeapons();
        gameEngine->SetPlayerEquipPack(m_sPlayerEquipPack.toUtf8().data());

        gameEngine->ReloadEnvironment();

        if (!bSkipLoadingAI)
        {
            gameEngine->LoadAI(gameEngine->GetLevelPath(), GetName());
        }
        objMan->SendEvent(EVENT_MISSION_CHANGE);
        m_doc->ChangeMission();

        if (gEnv->pAISystem)
        {
            gEnv->pAISystem->OnMissionLoaded();
        }

        if (GetIEditor()->Get3DEngine())
        {
            m_numCGFObjects = GetIEditor()->Get3DEngine()->GetLoadedObjectCount();
        }

        // Load time of day.
        GetIEditor()->Get3DEngine()->GetTimeOfDay()->Serialize(m_timeOfDay, true);
    }
    else
    {
        // Save time of day.
        m_timeOfDay = XmlHelpers::CreateXmlNode("TimeOfDay");
        GetIEditor()->Get3DEngine()->GetTimeOfDay()->Serialize(m_timeOfDay, false);

        if (!bIgnoreObjects)
        {
            XmlNodeRef root = XmlHelpers::CreateXmlNode("Root");
            objMan->Serialize(root, false, SERIALIZE_ONLY_NOTSHARED);
            m_objects = root->findChild("Objects");
            XmlNodeRef layers = root->findChild("ObjectLayers");
            if (layers)
            {
                m_layers = layers;
            }
        }
    }

    m_reentrancyProtector = false;
}

//////////////////////////////////////////////////////////////////////////
void CMission::OnEnvironmentChange()
{
    // Only execute the reload function if there is no ongoing SyncContent.
    if (m_reentrancyProtector)
    {
        return;
    }
    m_reentrancyProtector = true;
    m_environment = XmlHelpers::CreateXmlNode("Environment");
    CXmlTemplate::SetValues(m_doc->GetEnvironmentTemplate(), m_environment);
    m_reentrancyProtector = false;
}

//////////////////////////////////////////////////////////////////////////
void CMission::GetUsedWeapons(QStringList& weapons)
{
    weapons = m_usedWeapons;
}

//////////////////////////////////////////////////////////////////////////
void CMission::SetUsedWeapons(const QStringList& weapons)
{
    m_usedWeapons = weapons;
    UpdateUsedWeapons();
}

//////////////////////////////////////////////////////////////////////////
void CMission::UpdateUsedWeapons()
{
}

//////////////////////////////////////////////////////////////////////////
void CMission::ResetScript()
{
    if (m_pScript)
    {
        m_pScript->OnReset();
    }
}

//////////////////////////////////////////////////////////////////////////
void CMission::AddObjectsNode(XmlNodeRef& node)
{
    for (int i = 0; i < node->getChildCount(); i++)
    {
        m_objects->addChild(node->getChild(i)->clone());
    }
}

//////////////////////////////////////////////////////////////////////////
void CMission::SetLayersNode(XmlNodeRef& node)
{
    m_layers = node->clone();
}

//////////////////////////////////////////////////////////////////////////
void CMission::SetMinimap(const SMinimapInfo& minimap)
{
    m_minimap = minimap;
}


//////////////////////////////////////////////////////////////////////////
void CMission::SaveParts()
{
    // Save Time of Day
    {
        CTempFileHelper helper((GetIEditor()->GetLevelDataFolder() + kTimeOfDayFile).toUtf8().data());

        m_timeOfDay->saveToFile(helper.GetTempFilePath().toUtf8().data());

        if (!helper.UpdateFile(false))
        {
            return;
        }
    }


    // Save Environment
    {
        CTempFileHelper helper((GetIEditor()->GetLevelDataFolder() + kEnvironmentFile).toUtf8().data());

        XmlNodeRef root = m_environment->clone();
        root->setTag(kEnvironmentRoot);
        root->saveToFile(helper.GetTempFilePath().toUtf8().data());

        if (!helper.UpdateFile(false))
        {
            return;
        }
    }
}


//////////////////////////////////////////////////////////////////////////
void CMission::LoadParts()
{
    // Load Time of Day
    {
        QString filename = GetIEditor()->GetLevelDataFolder() + kTimeOfDayFile;
        XmlNodeRef root = XmlHelpers::LoadXmlFromFile(filename.toUtf8().data());
        if (root && !_stricmp(root->getTag(), kTimeOfDayRoot))
        {
            m_timeOfDay = root;
            m_timeOfDay->getAttr("Time", m_time);
        }
    }

    // Load Environment
    {
        QString filename = GetIEditor()->GetLevelDataFolder() + kEnvironmentFile;
        XmlNodeRef root = XmlHelpers::LoadXmlFromFile(filename.toUtf8().data());
        if (root && !_stricmp(root->getTag(), kEnvironmentRoot))
        {
            m_environment = root;
        }
    }
}

//////////////////////////////////////////////////////////////////////////
void CMission::SerializeTimeOfDay(CXmlArchive& ar)
{
    if (ar.bLoading)
    {
        XmlNodeRef todNode = ar.root->findChild("TimeOfDay");
        if (todNode)
        {
            m_timeOfDay = todNode;
            todNode->getAttr("Time", m_time);
        }
        else
        {
            m_timeOfDay = XmlHelpers::CreateXmlNode("TimeOfDay");
        }
    }
    else
    {
        m_timeOfDay->setAttr("Time", m_time);
        ar.root->addChild(m_timeOfDay);
    }
}

//////////////////////////////////////////////////////////////////////////
void CMission::SerializeEnvironment(CXmlArchive& ar)
{
    if (ar.bLoading)
    {
        XmlNodeRef env = ar.root->findChild("Environment");
        if (env)
        {
            m_environment = env;
        }
    }
    else
    {
        XmlNodeRef env = m_environment->clone();
        env->setTag("Environment");
        ar.root->addChild(env);
    }
}

//////////////////////////////////////////////////////////////////////////
const SMinimapInfo& CMission::GetMinimap() const
{
    return m_minimap;
}