// Copyright (c) Wojciech Figat. All rights reserved.

#include "Level.h"
#include "ActorsCache.h"
#include "LargeWorlds.h"
#include "SceneQuery.h"
#include "SceneObjectsFactory.h"
#include "FlaxEngine.Gen.h"
#include "Scene/Scene.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Deprecated.h"
#include "Engine/Core/Cache.h"
#include "Engine/Core/Collections/CollectionPoolCache.h"
#include "Engine/Core/ObjectsRemovalService.h"
#include "Engine/Core/Config/LayersTagsSettings.h"
#include "Engine/Core/Types/LayersMask.h"
#include "Engine/Core/Types/Stopwatch.h"
#include "Engine/Debug/Exceptions/ArgumentException.h"
#include "Engine/Debug/Exceptions/ArgumentNullException.h"
#include "Engine/Debug/Exceptions/InvalidOperationException.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Threading/JobSystem.h"
#include "Engine/Platform/File.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Engine/Time.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MDomain.h"
#include "Engine/Scripting/ManagedCLR/MException.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Prefabs/Prefab.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Engine/Platform/MessageBox.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Serialization/JsonSerializer.h"
#include "Editor/Scripting/ScriptsBuilder.h"
#endif

#if USE_LARGE_WORLDS
bool LargeWorlds::Enable = true;
#else
bool LargeWorlds::Enable = false;
#endif

void LargeWorlds::UpdateOrigin(Vector3& origin, const Vector3& position)
{
    if (Enable)
    {
        constexpr Real chunkSizeInv = 1.0 / ChunkSize;
        constexpr Real chunkSizeHalf = ChunkSize * 0.5;
        origin = Vector3(Int3((position - chunkSizeHalf) * chunkSizeInv)) * ChunkSize;
    }
}

bool LayersMask::HasLayer(const StringView& layerName) const
{
    return HasLayer(Level::GetLayerIndex(layerName));
}

LayersMask LayersMask::GetMask(Span<StringView> layerNames)
{
    LayersMask mask(0);
    for (StringView& layerName : layerNames)
    {
        // Ignore blank entries
        if (layerName.Length() == 0)
            continue;
        int32 index = Level::GetLayerIndex(layerName);
        if (index != -1)
            mask.Mask |= (uint32)(1 << index);
    }
    return mask;
}

enum class SceneEventType
{
    OnSceneSaving = 0,
    OnSceneSaved = 1,
    OnSceneSaveError = 2,
    OnSceneLoading = 3,
    OnSceneLoaded = 4,
    OnSceneLoadError = 5,
    OnSceneUnloading = 6,
    OnSceneUnloaded = 7,
};

enum class SceneResult
{
    Success,
    Failed,
    Wait,
};

class SceneAction
{
public:
    virtual ~SceneAction()
    {
    }

    struct Context
    {
        // Amount of seconds that action can take to run within a budget.
        float TimeBudget = MAX_float;
    };

    virtual SceneResult Do(Context& context)
    {
        return SceneResult::Failed;
    }
};

#if USE_EDITOR

struct ScriptsReloadObject
{
    StringAnsi TypeName;
    ScriptingObject** Object;
    Array<byte> Data;
};

#endif

// Small utility for dividing the iterative work over data set that can run in equal slicer limited by time.
struct TimeSlicer
{
    int32 Index = -1;
    int32 Count = 0;
    double TimeBudget;
    double StartTime;

    void BeginSync(float timeBudget, int32 count, int32 startIndex = 0);
    bool StepSync();
    SceneResult End();
};

// Async map loading utility for state tracking and synchronization of various load stages.
class SceneLoader
{
public:
    struct Args
    {
        rapidjson_flax::Value& Data;
        StringView AssetPath;
        int32 EngineBuild;
        float TimeBudget;
    };

    enum class Stages
    {
        Begin,
        Spawn,
        SetupPrefabs,
        SyncNewPrefabs,
        Deserialize,
        SyncPrefabs,
        SetupTransforms,
        Initialize,
        BeginPlay,
        End,
        Loaded,
    } Stage = Stages::Begin;

    bool AsyncLoad;
    bool AsyncJobs;
    Guid SceneId = Guid::Empty;
    Scene* Scene = nullptr;
    float TotalTime = 0.0f;
    uint64 StartFrame;

    // Cache data
    ISerializeModifier* Modifier = nullptr;
    ActorsCache::SceneObjectsListType* SceneObjects = nullptr;
    Array<Actor*> InjectedSceneChildren;
    SceneObjectsFactory::Context Context;
    SceneObjectsFactory::PrefabSyncData* PrefabSyncData = nullptr;
    TimeSlicer StageSlicer;

    SceneLoader(bool asyncLoad = false)
        : AsyncLoad(asyncLoad)
        , AsyncJobs(JobSystem::GetThreadsCount() > 1)
        , Modifier(Cache::ISerializeModifier.GetUnscoped())
        , Context(Modifier)
    {
    }

    ~SceneLoader()
    {
        if (PrefabSyncData)
            Delete(PrefabSyncData);
        if (SceneObjects)
            ActorsCache::SceneObjectsListCache.Put(SceneObjects);
        if (Modifier)
            Cache::ISerializeModifier.Put(Modifier);
    }

    NON_COPYABLE(SceneLoader);

    FORCE_INLINE void NextStage()
    {
        Stage = (Stages)((uint8)Stage + 1);
    }

    SceneResult Tick(Args& args);
    SceneResult OnBegin(Args& args);
    SceneResult OnSpawn(Args& args);
    SceneResult OnSetupPrefabs(Args& args);
    SceneResult OnSyncNewPrefabs(Args& args);
    SceneResult OnDeserialize(Args& args);
    SceneResult OnSyncPrefabs(Args& args);
    SceneResult OnSetupTransforms(Args& args);
    SceneResult OnInitialize(Args& args);
    SceneResult OnBeginPlay(Args& args);
    SceneResult OnEnd(Args& args);
};

namespace LevelImpl
{
    Array<SceneAction*> _sceneActions;
    CriticalSection _sceneActionsLocker;
    DateTime _lastSceneLoadTime(0);
#if USE_EDITOR
    Array<ScriptsReloadObject> ScriptsReloadObjects;
#endif

    void CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId);

    void flushActions();
    SceneResult loadScene(SceneLoader& loader, JsonAsset* sceneAsset, float* timeBudget = nullptr);
    SceneResult loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene = nullptr, float* timeBudget = nullptr);
    SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene = nullptr, float* timeBudget = nullptr);
    SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene = nullptr, StringView assetPath = StringView(), float* timeBudget = nullptr);
    bool unloadScene(Scene* scene);
    bool unloadScenes();
    bool saveScene(Scene* scene);
    bool saveScene(Scene* scene, const String& path);
    bool saveScene(Scene* scene, rapidjson_flax::StringBuffer& outBuffer, bool prettyJson);
    bool saveScene(Scene* scene, rapidjson_flax::StringBuffer& outBuffer, JsonWriter& writer);
    bool spawnActor(Actor* actor, Actor* parent);
    bool deleteActor(Actor* actor);
}

using namespace LevelImpl;

class LevelService : public EngineService
{
public:
    LevelService()
        : EngineService(TEXT("Scene Manager"), 200)
    {
    }

    void Update() override;
    void LateUpdate() override;
    void FixedUpdate() override;
    void LateFixedUpdate() override;
    void Dispose() override;
};

LevelService LevelServiceInstanceService;
extern double EngineIdleTime;

CriticalSection Level::ScenesLock;
Array<Scene*> Level::Scenes;
bool Level::TickEnabled = true;
float Level::StreamingFrameBudget = 0.3f;
Delegate<Actor*> Level::ActorSpawned;
Delegate<Actor*> Level::ActorDeleted;
Delegate<Actor*, Actor*> Level::ActorParentChanged;
Delegate<Actor*> Level::ActorOrderInParentChanged;
Delegate<Actor*> Level::ActorNameChanged;
Delegate<Actor*> Level::ActorActiveChanged;
#if USE_EDITOR
Delegate<Actor*> Level::ActorDestroyChildren;
#endif
Delegate<Scene*, const Guid&> Level::SceneSaving;
Delegate<Scene*, const Guid&> Level::SceneSaved;
Delegate<Scene*, const Guid&> Level::SceneSaveError;
Delegate<Scene*, const Guid&> Level::SceneLoading;
Delegate<Scene*, const Guid&> Level::SceneLoaded;
Delegate<Scene*, const Guid&> Level::SceneLoadError;
Delegate<Scene*, const Guid&> Level::SceneUnloading;
Delegate<Scene*, const Guid&> Level::SceneUnloaded;
#if USE_EDITOR
Action Level::ScriptsReloadStart;
Action Level::ScriptsReload;
Action Level::ScriptsReloaded;
Action Level::ScriptsReloadEnd;
#endif
String Level::Layers[32];

bool LevelImpl::spawnActor(Actor* actor, Actor* parent)
{
    if (actor == nullptr)
    {
        Log::ArgumentNullException(TEXT("Cannot spawn null actor."));
        return true;
    }

    if (actor->GetType().ManagedClass->IsAbstract())
    {
        Log::Exception(TEXT("Cannot spawn abstract actor type."));
        return true;
    }

    if (actor->Is<Scene>())
    {
        // Spawn scene
        actor->InitializeHierarchy();
        actor->OnTransformChanged();
        {
            SceneBeginData beginData;
            actor->BeginPlay(&beginData);
            beginData.OnDone();
        }
        CallSceneEvent(SceneEventType::OnSceneLoaded, (Scene*)actor, actor->GetID());
    }
    else
    {
        // Spawn actor
        if (Level::Scenes.IsEmpty())
        {
            Log::InvalidOperationException(TEXT("Cannot spawn actor. No scene loaded."));
            return true;
        }
        if (parent == nullptr)
            parent = Level::Scenes[0];

        actor->SetPhysicsScene(parent->GetPhysicsScene());
        actor->SetParent(parent, true, true);
    }

    return false;
}

bool LevelImpl::deleteActor(Actor* actor)
{
    if (actor == nullptr)
    {
        Log::ArgumentNullException(TEXT("Cannot delete null actor."));
        return true;
    }

    actor->DeleteObject();

    return false;
}

void LayersAndTagsSettings::Apply()
{
    // Note: we cannot remove tags/layers at runtime so this should deserialize them in additive mode
    // Tags/Layers are stored as index in actors so collection change would break the linkage
    for (auto& tag : Tags)
    {
        Tags::Get(tag);
    }
    for (int32 i = 0; i < ARRAY_COUNT(Level::Layers); i++)
    {
        const auto& src = Layers[i];
        auto& dst = Level::Layers[i];
        if (dst.IsEmpty() || !src.IsEmpty())
            dst = src;
    }
}

#define TICK_LEVEL(tickingStage, name) \
    PROFILE_CPU_NAMED(name); \
    PROFILE_MEM(Level); \
    ScopeLock lock(Level::ScenesLock); \
    auto& scenes = Level::Scenes; \
    if (!Time::GetGamePaused() && Level::TickEnabled) \
    { \
        for (int32 i = 0; i < scenes.Count(); i++) \
        { \
            if (scenes[i]->GetIsActive()) \
                scenes[i]->Ticking.tickingStage.Tick(); \
        } \
    }
#if USE_EDITOR
#define TICK_LEVEL_EDITOR(tickingStage) \
    else if (!Editor::IsPlayMode) \
    { \
        for (int32 i = 0; i < scenes.Count(); i++) \
        { \
            if (scenes[i]->GetIsActive()) \
                scenes[i]->Ticking.tickingStage.TickExecuteInEditor(); \
        } \
    }
#else
#define TICK_LEVEL_EDITOR(tickingStage)
#endif

void LevelService::Update()
{
    TICK_LEVEL(Update, "Level::Update")
    TICK_LEVEL_EDITOR(Update)
}

void LevelService::LateUpdate()
{
    TICK_LEVEL(LateUpdate, "Level::LateUpdate")
    TICK_LEVEL_EDITOR(LateUpdate)
    flushActions();
}

void LevelService::FixedUpdate()
{
    TICK_LEVEL(FixedUpdate, "Level::FixedUpdate")
    TICK_LEVEL_EDITOR(FixedUpdate)
}

void LevelService::LateFixedUpdate()
{
    TICK_LEVEL(LateFixedUpdate, "Level::LateFixedUpdate")
    TICK_LEVEL_EDITOR(LateFixedUpdate)
}

#undef TICK_LEVEL
#undef TICK_LEVEL_EDITOR

void LevelService::Dispose()
{
    // End scene actions
    ScopeLock lock(_sceneActionsLocker);
    _sceneActions.ClearDelete();

    // Unload scenes
    unloadScenes();

    // Ensure that all scenes and actors has been destroyed (we don't leak!)
    ASSERT(Level::Scenes.IsEmpty());
}

bool Level::IsAnyActorInGame()
{
    ScopeLock lock(ScenesLock);

    for (int32 i = 0; i < Scenes.Count(); i++)
    {
        if (Scenes[i]->Children.HasItems())
            return true;
    }

    return false;
}

bool Level::IsAnyActionPending()
{
    _sceneActionsLocker.Lock();
    const bool result = _sceneActions.HasItems();
    _sceneActionsLocker.Unlock();
    return result;
}

DateTime Level::GetLastSceneLoadTime()
{
    return _lastSceneLoadTime;
}

bool Level::SpawnActor(Actor* actor, Actor* parent)
{
    ASSERT(actor);
    ScopeLock lock(_sceneActionsLocker);
    return spawnActor(actor, parent);
}

bool Level::DeleteActor(Actor* actor)
{
    ASSERT(actor);
    ScopeLock lock(_sceneActionsLocker);
    return deleteActor(actor);
}

void Level::CallBeginPlay(Actor* obj)
{
    if (obj && !obj->IsDuringPlay())
    {
        SceneBeginData beginData;
        obj->BeginPlay(&beginData);
        beginData.OnDone();
    }
}

void Level::DrawActors(RenderContextBatch& renderContextBatch, byte category)
{
    PROFILE_CPU();

    //ScopeLock lock(ScenesLock);

    for (Scene* scene : Scenes)
    {
        if (scene->IsActiveInHierarchy())
            scene->Rendering.Draw(renderContextBatch, (SceneRendering::DrawCategory)category);
    }
}

void Level::CollectPostFxVolumes(RenderContext& renderContext)
{
    PROFILE_CPU();

    //ScopeLock lock(ScenesLock);

    for (Scene* scene : Scenes)
    {
        if (scene->IsActiveInHierarchy())
            scene->Rendering.CollectPostFxVolumes(renderContext);
    }
}

class LoadSceneAction : public SceneAction
{
public:
    Guid SceneId;
    AssetReference<JsonAsset> SceneAsset;
    SceneLoader Loader;

    LoadSceneAction(const Guid& sceneId, JsonAsset* sceneAsset, bool async)
        : Loader(async)
    {
        SceneId = sceneId;
        SceneAsset = sceneAsset;
    }

    SceneResult Do(Context& context) override
    {
        if (SceneAsset == nullptr)
            return SceneResult::Failed;
        if (!SceneAsset->IsLoaded())
            return SceneResult::Wait;
        return LevelImpl::loadScene(Loader, SceneAsset, &context.TimeBudget);
    }
};

class UnloadSceneAction : public SceneAction
{
public:
    Guid TargetScene;

    UnloadSceneAction(Scene* scene)
    {
        TargetScene = scene->GetID();
    }

    SceneResult Do(Context& context) override
    {
        auto scene = Level::FindScene(TargetScene);
        if (!scene)
            return SceneResult::Failed;
        return unloadScene(scene) ? SceneResult::Failed : SceneResult::Success;
    }
};

class UnloadScenesAction : public SceneAction
{
public:
    UnloadScenesAction()
    {
    }

    SceneResult Do(Context& context) override
    {
        return unloadScenes() ? SceneResult::Failed : SceneResult::Success;
    }
};

class SaveSceneAction : public SceneAction
{
public:
    Scene* TargetScene;
    bool PrettyJson;

    SaveSceneAction(Scene* scene, bool prettyJson = true)
    {
        TargetScene = scene;
        PrettyJson = prettyJson;
    }

    SceneResult Do(Context& context) override
    {
        if (saveScene(TargetScene))
        {
            LOG(Error, "Failed to save scene {0}", TargetScene ? TargetScene->GetName() : String::Empty);
            return SceneResult::Failed;
        }
        return SceneResult::Success;
    }
};

#if USE_EDITOR

class ReloadScriptsAction : public SceneAction
{
public:
    ReloadScriptsAction()
    {
    }

    SceneResult Do(Context& context) override
    {
        // Reloading scripts workflow:
        // - save scenes (to temporary files)
        // - unload scenes
        // - unload user assemblies
        // - load user assemblies
        // - load scenes (from temporary files)
        // Note: we don't want to override original scene files

        PROFILE_CPU_NAMED("Level.ReloadScripts");
        PROFILE_MEM(Level);
        LOG(Info, "Scripts reloading start");
        const auto startTime = DateTime::NowUTC();

        // Cache data
        struct SceneData
        {
            Guid ID;
            String Name;
            rapidjson_flax::StringBuffer Data;

            SceneData() = default;

            SceneData(const SceneData& other)
            {
                CRASH;
            }

            const SceneData& operator=(SceneData&) const
            {
                CRASH;
                return *this;
            }

            void Init(Scene* scene)
            {
                ID = scene->GetID();
                Name = scene->GetName();
            }
        };
        const int32 scenesCount = Level::Scenes.Count();
        Array<SceneData> scenes;
        scenes.Resize(scenesCount);
        for (int32 i = 0; i < scenesCount; i++)
            scenes[i].Init(Level::Scenes[i]);

        // Fire event
        Level::ScriptsReloadStart();

        // Save scenes (to memory)
        for (int32 i = 0; i < scenesCount; i++)
        {
            const auto scene = Level::Scenes[i];
            LOG(Info, "Caching scene {0}", scenes[i].Name);

            // Serialize to json
            if (saveScene(scene, scenes[i].Data, false))
            {
                LOG(Error, "Failed to save scene '{0}' for scripts reload.", scenes[i].Name);
                CallSceneEvent(SceneEventType::OnSceneSaveError, scene, scene->GetID());
                return SceneResult::Failed;
            }
            CallSceneEvent(SceneEventType::OnSceneSaved, scene, scene->GetID());
        }

        // Unload scenes
        unloadScenes();

        // Reload scripting
        Level::ScriptsReload();
        Scripting::Reload();
        Level::ScriptsReloaded();

        // Restore objects
        for (auto& e : ScriptsReloadObjects)
        {
            const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(e.TypeName);
            *e.Object = ScriptingObject::NewObject(typeHandle);
            if (!*e.Object)
            {
                LOG(Warning, "Failed to restore hot-reloaded object of type {0}.", String(e.TypeName));
                continue;
            }
            auto* serializable = ScriptingObject::ToInterface<ISerializable>(*e.Object);
            if (serializable && e.Data.HasItems())
            {
                JsonSerializer::LoadFromBytes(serializable, e.Data, FLAXENGINE_VERSION_BUILD);
            }
        }
        ScriptsReloadObjects.Clear();

        // Restore scenes (from memory)
        for (int32 i = 0; i < scenesCount; i++)
        {
            LOG(Info, "Restoring scene {0}", scenes[i].Name);

            // Parse json
            const auto& sceneData = scenes[i].Data;
            ISerializable::SerializeDocument document;
            {
                PROFILE_CPU_NAMED("Json.Parse");
                document.Parse(sceneData.GetString(), sceneData.GetSize());
            }
            if (document.HasParseError())
            {
                LOG(Error, "Failed to deserialize scene {0}. SceneResult: {1}", scenes[i].Name, GetParseError_En(document.GetParseError()));
                return SceneResult::Failed;
            }

            // Load scene
            SceneLoader loader;
            if (LevelImpl::loadScene(loader, document) != SceneResult::Success)
            {
                LOG(Error, "Failed to deserialize scene {0}", scenes[i].Name);
                CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, scenes[i].ID);
                return SceneResult::Failed;
            }
        }
        scenes.Resize(0);

        // Fire event
        LOG(Info, "Scripts reloading end. Total time: {0}ms", static_cast<int32>((DateTime::NowUTC() - startTime).GetTotalMilliseconds()));
        Level::ScriptsReloadEnd();

        return SceneResult::Success;
    }
};

void Level::ScriptsReloadRegisterObject(ScriptingObject*& obj)
{
    if (!obj)
        return;
    auto& e = ScriptsReloadObjects.AddOne();
    e.Object = &obj;
    e.TypeName = obj->GetType().Fullname;
    if (auto* serializable = ScriptingObject::ToInterface<ISerializable>(obj))
        e.Data = JsonSerializer::SaveToBytes(serializable);
    ScriptingObject* o = obj;
    obj = nullptr;
    o->DeleteObjectNow();
}

#endif

class SpawnActorAction : public SceneAction
{
public:
    ScriptingObjectReference<Actor> TargetActor;
    ScriptingObjectReference<Actor> ParentActor;

    SpawnActorAction(Actor* actor, Actor* parent)
        : TargetActor(actor)
        , ParentActor(parent)
    {
    }

    SceneResult Do(Context& context) override
    {
        return spawnActor(TargetActor, ParentActor) ? SceneResult::Failed : SceneResult::Success;
    }
};

class DeleteActorAction : public SceneAction
{
public:
    ScriptingObjectReference<Actor> TargetActor;

    DeleteActorAction(Actor* actor)
        : TargetActor(actor)
    {
    }

    SceneResult Do(Context& context) override
    {
        return deleteActor(TargetActor) ? SceneResult::Failed : SceneResult::Success;
    }
};

void LevelImpl::CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId)
{
    PROFILE_CPU_NAMED("Level::CallSceneEvent");

    // Call event
    const auto scriptsDomain = Scripting::GetScriptsDomain();
    if (scriptsDomain != nullptr)
        scriptsDomain->Dispatch();
    switch (eventType)
    {
    case SceneEventType::OnSceneSaving:
        Level::SceneSaving(scene, sceneId);
        break;
    case SceneEventType::OnSceneSaved:
        Level::SceneSaved(scene, sceneId);
        break;
    case SceneEventType::OnSceneSaveError:
        Level::SceneSaveError(scene, sceneId);
        break;
    case SceneEventType::OnSceneLoading:
        Level::SceneLoading(scene, sceneId);
        break;
    case SceneEventType::OnSceneLoaded:
        Level::SceneLoaded(scene, sceneId);
        break;
    case SceneEventType::OnSceneLoadError:
        Level::SceneLoadError(scene, sceneId);
        break;
    case SceneEventType::OnSceneUnloading:
        Level::SceneUnloading(scene, sceneId);
        break;
    case SceneEventType::OnSceneUnloaded:
        Level::SceneUnloaded(scene, sceneId);
        break;
    }
}

int32 Level::GetNonEmptyLayerNamesCount()
{
    int32 result = 31;
    while (result >= 0 && Layers[result].IsEmpty())
        result--;
    return result + 1;
}

int32 Level::GetLayerIndex(const StringView& layer)
{
    int32 result = -1;
    for (int32 i = 0; i < 32; i++)
    {
        if (Layers[i] == layer)
        {
            result = i;
            break;
        }
    }
    return result;
}

StringView Level::GetLayerName(const int32 layerIndex)
{
    for (int32 i = 0; i < 32; i++)
    {
        if (i == layerIndex)
        {
            return Layers[i];
        }
    }
    return TEXT("");
}

void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b)
{
    PROFILE_CPU();

    ASSERT(a);

    // Call event
    const auto scriptsDomain = Scripting::GetScriptsDomain();
    if (scriptsDomain != nullptr)
        scriptsDomain->Dispatch();
    switch (eventType)
    {
    case ActorEventType::OnActorSpawned:
        ActorSpawned(a);
        break;
    case ActorEventType::OnActorDeleted:
        ActorDeleted(a);
        break;
    case ActorEventType::OnActorParentChanged:
        ActorParentChanged(a, b);
        break;
    case ActorEventType::OnActorOrderInParentChanged:
        ActorOrderInParentChanged(a);
        break;
    case ActorEventType::OnActorNameChanged:
        ActorNameChanged(a);
        break;
    case ActorEventType::OnActorActiveChanged:
        ActorActiveChanged(a);
        break;
#if USE_EDITOR
    case ActorEventType::OnActorDestroyChildren:
        ActorDestroyChildren(a);
        break;
#endif
    }
}

void LevelImpl::flushActions()
{
    // Calculate time budget for the streaming (relative to the game frame rate to scale across different devices)
    SceneAction::Context context;
    float targetFps = 60;
    if (Time::UpdateFPS > ZeroTolerance)
        targetFps = Time::UpdateFPS;
    else if (Engine::GetFramesPerSecond() > 0)
        targetFps = (float)Engine::GetFramesPerSecond();
    context.TimeBudget = Level::StreamingFrameBudget / targetFps;
    if (EngineIdleTime > 0.001)
        context.TimeBudget += (float)(EngineIdleTime * 0.5); // Increase time budget if engine has some idle time for spare
#if USE_EDITOR
    // Throttle up in Editor
    context.TimeBudget *= Editor::IsPlayMode ? 1.2f : 2.0f;
#endif
#if BUILD_DEBUG
    // Throttle up in Debug
    context.TimeBudget *= 1.2f;
#endif
    if (context.TimeBudget <= 0.0f)
        context.TimeBudget = MAX_float; // Unlimited if 0
    context.TimeBudget = Math::Max(context.TimeBudget, 0.001f); // Minimum 1ms

    // Runs actions in order
    ScopeLock lock(_sceneActionsLocker);
    for (int32 i = 0; i < _sceneActions.Count() && context.TimeBudget >= 0.0; i++)
    {
        auto action = _sceneActions[0];
        Stopwatch time;
        auto result = action->Do(context);
        time.Stop();
        context.TimeBudget -= time.GetTotalSeconds();
        if (result != SceneResult::Wait)
        {
            _sceneActions.RemoveAtKeepOrder(i--);
            Delete(action);
        }
    }
}

bool LevelImpl::unloadScene(Scene* scene)
{
    if (scene == nullptr)
    {
        Log::ArgumentNullException();
        return true;
    }
    const auto sceneId = scene->GetID();

    PROFILE_CPU_NAMED("Level.UnloadScene");
    PROFILE_MEM(Level);

    // Fire event
    CallSceneEvent(SceneEventType::OnSceneUnloading, scene, sceneId);

    // Call end play
    if (scene->IsDuringPlay())
        scene->EndPlay();

    // Remove from scenes list
    Level::Scenes.Remove(scene);

    // Fire event
    CallSceneEvent(SceneEventType::OnSceneUnloaded, scene, sceneId);

    // Simple enqueue scene root object to be deleted
    scene->DeleteObject();

    return false;
}

bool LevelImpl::unloadScenes()
{
    PROFILE_MEM(Level);
    auto scenes = Level::Scenes;
    for (int32 i = scenes.Count() - 1; i >= 0; i--)
    {
        if (unloadScene(scenes[i]))
            return true;
    }
    return false;
}

SceneResult LevelImpl::loadScene(SceneLoader& loader, JsonAsset* sceneAsset, float* timeBudget)
{
    // Keep reference to the asset (prevent unloading during action)
    AssetReference<JsonAsset> ref = sceneAsset;
    if (sceneAsset == nullptr || sceneAsset->WaitForLoaded())
    {
        LOG(Error, "Cannot load scene asset.");
        return SceneResult::Failed;
    }

    return loadScene(loader, *sceneAsset->Data, sceneAsset->DataEngineBuild, nullptr, sceneAsset->GetPath(), timeBudget);
}

SceneResult LevelImpl::loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene, float* timeBudget)
{
    if (sceneData.IsInvalid())
    {
        LOG(Error, "Missing scene data.");
        return SceneResult::Failed;
    }
    PROFILE_MEM(Level);

    // Parse scene JSON file
    rapidjson_flax::Document document;
    {
        PROFILE_CPU_NAMED("Json.Parse");
        document.Parse(sceneData.Get<char>(), sceneData.Length());
    }
    if (document.HasParseError())
    {
        Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
        return SceneResult::Failed;
    }

    ScopeLock lock(Level::ScenesLock);
    return loadScene(loader, document, outScene, timeBudget);
}

SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene, float* timeBudget)
{
    auto data = document.FindMember("Data");
    if (data == document.MemberEnd())
    {
        LOG(Error, "Missing Data member.");
        return SceneResult::Failed;
    }
    const int32 saveEngineBuild = JsonTools::GetInt(document, "EngineBuild", 0);
    return loadScene(loader, data->value, saveEngineBuild, outScene, nullptr, timeBudget);
}

SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, StringView assetPath, float* timeBudget)
{
    PROFILE_CPU_NAMED("Level.LoadScene");
    PROFILE_MEM(Level);
#if USE_EDITOR
    ContentDeprecated::Clear();
#endif
    SceneResult result = SceneResult::Success;
    float timeLeft = timeBudget ? *timeBudget : MAX_float;
    SceneLoader::Args args = { data, assetPath, engineBuild, timeLeft };
    while (timeLeft > 0.0f && loader.Stage != SceneLoader::Stages::Loaded)
    {
        Stopwatch time;
        result = loader.Tick(args);
        time.Stop();
        const float delta = time.GetTotalSeconds();
        loader.TotalTime += delta;
        timeLeft -= delta;
        if (timeLeft < 0.0f && result == SceneResult::Success)
        {
            result = SceneResult::Wait;
            break;
        }
    }
    if (outScene)
        *outScene = loader.Scene;
    return result;
}

void TimeSlicer::BeginSync(float timeBudget, int32 count, int32 startIndex)
{
    if (Index == -1)
    {
        // Starting
        Index = startIndex;
        Count = count;
    }
    TimeBudget = (double)timeBudget;
    StartTime = Platform::GetTimeSeconds();
}

bool TimeSlicer::StepSync()
{
    Index++;
    double time = Platform::GetTimeSeconds();
    double dt = time - StartTime;
    return dt >= TimeBudget;
}

SceneResult TimeSlicer::End()
{
    if (Index >= Count)
    {
        // Finished
        *this = TimeSlicer();
        return SceneResult::Success;
    }

    return SceneResult::Wait;
}

SceneResult SceneLoader::Tick(Args& args)
{
    switch (Stage)
    {
    case Stages::Begin:
        return OnBegin(args);
    case Stages::Spawn:
        return OnSpawn(args);
    case Stages::SetupPrefabs:
        return OnSetupPrefabs(args);
    case Stages::SyncNewPrefabs:
        return OnSyncNewPrefabs(args);
    case Stages::Deserialize:
        return OnDeserialize(args);
    case Stages::SyncPrefabs:
        return OnSyncPrefabs(args);
    case Stages::Initialize:
        return OnInitialize(args);
    case Stages::SetupTransforms:
        return OnSetupTransforms(args);
    case Stages::BeginPlay:
        return OnBeginPlay(args);
    case Stages::End:
        return OnEnd(args);
    default:
        return SceneResult::Failed;
    }
}

SceneResult SceneLoader::OnBegin(Args& args)
{
    PROFILE_CPU_NAMED("Begin");
    LOG(Info, "Loading scene...");
    _lastSceneLoadTime = DateTime::Now();
    StartFrame = Engine::UpdateCount;

    // Validate arguments
    if (!args.Data.IsArray())
    {
        LOG(Error, "Invalid Data member.");
        CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, Guid::Empty);
        return SceneResult::Failed;
    }

    // Peek scene node value (it's the first actor serialized)
    SceneId = JsonTools::GetGuid(args.Data[0], "ID");
    if (!SceneId.IsValid())
    {
        LOG(Error, "Invalid scene id.");
        CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
        return SceneResult::Failed;
    }

    // Peek meta
    if (args.EngineBuild < 6000)
    {
        LOG(Error, "Invalid serialized engine build.");
        CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
        return SceneResult::Failed;
    }
    Modifier->EngineBuild = args.EngineBuild;

    // Scripting backend should be loaded for the current project before loading scene
    if (!Scripting::HasGameModulesLoaded())
    {
        LOG(Error, "Cannot load scene without game modules loaded.");
#if USE_EDITOR
        if (!CommandLine::Options.Headless.IsTrue())
        {
            if (ScriptsBuilder::LastCompilationFailed())
                MessageBox::Show(TEXT("Script compilation failed.\n\nCannot load scene without game script modules. Please fix any compilation issues.\n\nSee Output Log or logs for more info."), TEXT("Failed to compile scripts"), MessageBoxButtons::OK, MessageBoxIcon::Error);
            else
                MessageBox::Show(TEXT("Failed to load scripts.\n\nCannot load scene without game script modules.\n\nSee logs for more info."), TEXT("Missing game modules"), MessageBoxButtons::OK, MessageBoxIcon::Error);
        }
#endif
        CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
        return SceneResult::Failed;
    }

    // Skip is that scene is already loaded
    if (Level::FindScene(SceneId) != nullptr)
    {
        LOG(Info, "Scene {0} is already loaded.", SceneId);
        CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
        return SceneResult::Failed;
    }

    // Create scene actor
    Scene = New<::Scene>(ScriptingObjectSpawnParams(SceneId, Scene::TypeInitializer));
    Scene->RegisterObject();
    Scene->Deserialize(args.Data[0], Modifier);

    // Fire event
    CallSceneEvent(SceneEventType::OnSceneLoading, Scene, SceneId);

    NextStage();
    return SceneResult::Success;
}

SceneResult SceneLoader::OnSpawn(Args& args)
{
    PROFILE_CPU_NAMED("Spawn");

    // Get any injected children of the scene.
    InjectedSceneChildren = Scene->Children;

    // Allocate scene objects list
    SceneObjects = ActorsCache::SceneObjectsListCache.GetUnscoped();
    const int32 dataCount = (int32)args.Data.Size();
    SceneObjects->Resize(dataCount);
    SceneObjects->At(0) = Scene;
    AsyncJobs &= dataCount > 10;

    // Spawn all scene objects
    Context.Async = AsyncJobs;
    SceneObject** objects = SceneObjects->Get();
    if (Context.Async)
    {
        Level::ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize)
        JobSystem::Execute([&](int32 i)
        {
            PROFILE_MEM(Level);
            i++; // Start from 1. at index [0] was scene
            auto& stream = args.Data[i];
            auto obj = SceneObjectsFactory::Spawn(Context, stream);
            objects[i] = obj;
            if (obj)
            {
                if (!obj->IsRegistered())
                    obj->RegisterObject();
#if USE_EDITOR
                // Auto-create C# objects for all actors in Editor during scene load when running in async (so main thread already has all of them)
                if (!obj->GetManagedInstance())
                    obj->CreateManaged();
#endif
            }
            else
                SceneObjectsFactory::HandleObjectDeserializationError(stream);
        }, dataCount - 1);
        Level::ScenesLock.Lock();
    }
    else
    {
        for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene
        {
            auto& stream = args.Data[i];
            auto obj = SceneObjectsFactory::Spawn(Context, stream);
            objects[i] = obj;
            if (obj)
                obj->RegisterObject();
            else
                SceneObjectsFactory::HandleObjectDeserializationError(stream);
        }
    }

    NextStage();
    return SceneResult::Success;
}

SceneResult SceneLoader::OnSetupPrefabs(Args& args)
{
    // Capture prefab instances in a scene to restore any missing objects (eg. newly added objects to prefab that are missing in scene file)
    PrefabSyncData = New<SceneObjectsFactory::PrefabSyncData>(*SceneObjects, args.Data, Modifier);
    SceneObjectsFactory::SetupPrefabInstances(Context, *PrefabSyncData);

    NextStage();
    return SceneResult::Success;
}

SceneResult SceneLoader::OnSyncNewPrefabs(Args& args)
{
    // Sync the new prefab instances by spawning missing objects that were added to prefab but were not saved in a scene
    // TODO: resave and force sync scenes during game cooking so this step could be skipped in game
    SceneObjectsFactory::SynchronizeNewPrefabInstances(Context, *PrefabSyncData);

    NextStage();
    return SceneResult::Success;
}

SceneResult SceneLoader::OnDeserialize(Args& args)
{
    PROFILE_CPU_NAMED("Deserialize");
    const int32 dataCount = (int32)args.Data.Size();
    SceneObject** objects = SceneObjects->Get();
    bool wasAsync = Context.Async;
    Context.Async = false; // TODO: before doing full async for scene objects fix:
    // TODO: - fix Actor's Scripts and Children order when loading objects data out of order via async jobs
    // TODO: - add _loadNoAsync flag to SceneObject or Actor to handle non-async loading for those types (eg. UIControl/UICanvas)

    // Load all scene objects
    if (Context.Async)
    {
        Level::ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize)
#if USE_EDITOR
        volatile int64 deprecated = 0;
#endif
        JobSystem::Execute([&](int32 i)
        {
            i++; // Start from 1. at index [0] was scene
            auto obj = objects[i];
            if (obj)
            {
                auto& idMapping = Scripting::ObjectsLookupIdMapping.Get();
                idMapping = &Context.GetModifier()->IdsMapping;
                SceneObjectsFactory::Deserialize(Context, obj, args.Data[i]);
#if USE_EDITOR
                if (ContentDeprecated::Clear())
                    Platform::InterlockedIncrement(&deprecated);
#endif
                idMapping = nullptr;
            }
        }, dataCount - 1);
#if USE_EDITOR
        if (deprecated != 0)
            ContentDeprecated::Mark();
#endif
        Level::ScenesLock.Lock();
    }
    else
    {
        Scripting::ObjectsLookupIdMapping.Set(&Modifier->IdsMapping);
        StageSlicer.BeginSync(args.TimeBudget, dataCount, 1); // start from 1. at index [0] was scene
        while (StageSlicer.Index < StageSlicer.Count)
        {
            auto& objData = args.Data[StageSlicer.Index];
            auto obj = objects[StageSlicer.Index];
            if (obj)
                SceneObjectsFactory::Deserialize(Context, obj, objData);
            if (StageSlicer.StepSync())
                break;
        }
        Scripting::ObjectsLookupIdMapping.Set(nullptr);
    }
    Context.Async = wasAsync;

    auto result = StageSlicer.End();
    if (result != SceneResult::Wait)
        NextStage();
    return result;
}

SceneResult SceneLoader::OnSyncPrefabs(Args& args)
{
    // Add injected children of scene (via OnSceneLoading) into sceneObjects to be initialized
    for (auto child : InjectedSceneChildren)
    {
        Array<SceneObject*> injectedSceneObjects;
        injectedSceneObjects.Add(child);
        SceneQuery::GetAllSceneObjects(child, injectedSceneObjects);
        for (auto o : injectedSceneObjects)
        {
            if (!o->IsRegistered())
                o->RegisterObject();
            SceneObjects->Add(o);
        }
    }

    // Synchronize prefab instances (prefab may have objects removed or reordered so deserialized instances need to synchronize with it)
    // TODO: resave and force sync scenes during game cooking so this step could be skipped in game
    SceneObjectsFactory::SynchronizePrefabInstances(Context, *PrefabSyncData);

    NextStage();
    return SceneResult::Success;
}

SceneResult SceneLoader::OnSetupTransforms(Args& args)
{
    // Cache actor transformations
    PROFILE_CPU_NAMED("SetupTransforms");
    Scene->OnTransformChanged();

    NextStage();
    return SceneResult::Success;
}

SceneResult SceneLoader::OnInitialize(Args& args)
{
    // Initialize scene objects
    PROFILE_CPU_NAMED("Initialize");
    ASSERT_LOW_LAYER(IsInMainThread());
    SceneObject** objects = SceneObjects->Get();
    for (int32 i = 0; i < SceneObjects->Count(); i++)
    {
        SceneObject* obj = objects[i];
        if (obj)
        {
            obj->Initialize();

            // Delete objects without parent
            if (i != 0 && obj->GetParent() == nullptr)
            {
                LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString());
                obj->DeleteObject();
            }
        }
    }
    PrefabSyncData->InitNewObjects();

    NextStage();
    return SceneResult::Success;
}

SceneResult SceneLoader::OnBeginPlay(Args& args)
{
    PROFILE_CPU_NAMED("BeginPlay");
    ASSERT_LOW_LAYER(IsInMainThread());

    // Link scene
    ScopeLock lock(Level::ScenesLock);
    Level::Scenes.Add(Scene);

    // TODO: prototype time-slicing with load-balancing for Begin Play:
    // TODO: - collect all actors to enable
    // TODO: - invoke in order OnBeginPlay -> Child Actors Begin -> Child Scripts Begin -> OnEnable for each actor
    // TODO: - consider not drawing level until it's fully loaded (other engine systems should respect this too?)
    // TODO: - consider refactoring Joints creation maybe? to get rid of SceneBeginData

    // Start the game for scene objects
    SceneBeginData beginData;
    Scene->BeginPlay(&beginData);
    beginData.OnDone();

    NextStage();
    return SceneResult::Success;
}

SceneResult SceneLoader::OnEnd(Args& args)
{
    PROFILE_CPU_NAMED("End");
    Stopwatch time;

    // Fire event
    CallSceneEvent(SceneEventType::OnSceneLoaded, Scene, SceneId);

    time.Stop();
    LOG(Info, "Scene loaded in {}ms ({} frames)", (int32)((TotalTime + time.GetTotalSeconds()) * 1000.0), Engine::UpdateCount - StartFrame);

#if USE_EDITOR
    // Resave assets that use deprecated data format
    for (auto& e : Context.DeprecatedPrefabs)
    {
        AssetReference<Prefab> prefab = e.Item;
        LOG(Info, "Resaving asset '{}' that uses deprecated data format", prefab->GetPath());
        if (prefab->Resave())
        {
            LOG(Error, "Failed to resave asset '{}'", prefab->GetPath());
        }
    }
    if (ContentDeprecated::Clear() && args.AssetPath != StringView())
    {
        LOG(Info, "Resaving asset '{}' that uses deprecated data format", args.AssetPath);
        if (saveScene(Scene, args.AssetPath))
        {
            LOG(Error, "Failed to resave asset '{}'", args.AssetPath);
        }
    }
#endif

    NextStage();
    return SceneResult::Success;
}

bool LevelImpl::saveScene(Scene* scene)
{
#if USE_EDITOR
    const auto path = scene->GetPath();
    if (path.IsEmpty())
    {
        LOG(Error, "Missing scene path.");
        return true;
    }

    return saveScene(scene, path);
#else
    LOG(Error, "Cannot save data to the cooked content.");
    return false;
#endif
}

bool LevelImpl::saveScene(Scene* scene, const String& path)
{
    PROFILE_CPU_NAMED("Level.SaveScene");
    PROFILE_MEM(Level);
    ASSERT(scene && EnumHasNoneFlags(scene->Flags, ObjectFlags::WasMarkedToDelete));
    auto sceneId = scene->GetID();

    LOG(Info, "Saving scene {0} to \'{1}\'", scene->GetName(), path);
    Stopwatch stopwatch;

    // Serialize to json
    rapidjson_flax::StringBuffer buffer;
    if (saveScene(scene, buffer, true) && buffer.GetSize() > 0)
    {
        CallSceneEvent(SceneEventType::OnSceneSaveError, scene, sceneId);
        return true;
    }

    // Save json to file
    if (File::WriteAllBytes(path, (byte*)buffer.GetString(), (int32)buffer.GetSize()))
    {
        LOG(Error, "Cannot save scene file");
        CallSceneEvent(SceneEventType::OnSceneSaveError, scene, sceneId);
        return true;
    }

    stopwatch.Stop();
    LOG(Info, "Scene saved! Time {0}ms", stopwatch.GetMilliseconds());

#if USE_EDITOR
    // Reload asset at the target location if is loaded
    Asset* asset = Content::GetAsset(sceneId);
    if (!asset)
        asset = Content::GetAsset(path);
    if (asset)
        asset->Reload();
#endif

    // Fire event
    CallSceneEvent(SceneEventType::OnSceneSaved, scene, sceneId);

    return false;
}

bool LevelImpl::saveScene(Scene* scene, rapidjson_flax::StringBuffer& outBuffer, bool prettyJson)
{
    PROFILE_CPU_NAMED("Level.SaveScene");
    PROFILE_MEM(Level);
    if (prettyJson)
    {
        PrettyJsonWriter writerObj(outBuffer);
        return saveScene(scene, outBuffer, writerObj);
    }
    else
    {
        CompactJsonWriter writerObj(outBuffer);
        return saveScene(scene, outBuffer, writerObj);
    }
}

bool LevelImpl::saveScene(Scene* scene, rapidjson_flax::StringBuffer& outBuffer, JsonWriter& writer)
{
    ASSERT(scene);
    const auto sceneId = scene->GetID();

    // Fire event
    CallSceneEvent(SceneEventType::OnSceneSaving, scene, sceneId);

    // Get all objects in the scene
    Array<SceneObject*> allObjects;
    SceneQuery::GetAllSerializableSceneObjects(scene, allObjects);

    // Serialize to json
    writer.StartObject();
    {
        PROFILE_CPU_NAMED("Serialize");

        // Json resource header
        writer.JKEY("ID");
        writer.Guid(sceneId);
        writer.JKEY("TypeName");
        writer.String("FlaxEngine.SceneAsset");
        writer.JKEY("EngineBuild");
        writer.Int(FLAXENGINE_VERSION_BUILD);

        // Json resource data
        writer.JKEY("Data");
        writer.StartArray();
        SceneObject** objects = allObjects.Get();
        for (int32 i = 0; i < allObjects.Count(); i++)
            writer.SceneObject(objects[i]);
        writer.EndArray();
    }
    writer.EndObject();

    return false;
}

bool Level::SaveScene(Scene* scene, bool prettyJson)
{
    ScopeLock lock(_sceneActionsLocker);
    SceneAction::Context context;
    return SaveSceneAction(scene, prettyJson).Do(context) != SceneResult::Success;
}

bool Level::SaveSceneToBytes(Scene* scene, rapidjson_flax::StringBuffer& outData, bool prettyJson)
{
    ASSERT(scene);
    ScopeLock lock(_sceneActionsLocker);
    Stopwatch stopwatch;
    LOG(Info, "Saving scene {0} to bytes", scene->GetName());

    // Serialize to json
    if (saveScene(scene, outData, prettyJson))
    {
        CallSceneEvent(SceneEventType::OnSceneSaveError, scene, scene->GetID());
        return true;
    }

    stopwatch.Stop();
    LOG(Info, "Scene saved! Time {0}ms", stopwatch.GetMilliseconds());

    // Fire event
    CallSceneEvent(SceneEventType::OnSceneSaved, scene, scene->GetID());

    return false;
}

Array<byte> Level::SaveSceneToBytes(Scene* scene, bool prettyJson)
{
    Array<byte> data;
    rapidjson_flax::StringBuffer sceneData;
    if (!SaveSceneToBytes(scene, sceneData, prettyJson))
    {
        data.Set((const byte*)sceneData.GetString(), (int32)sceneData.GetSize() * sizeof(rapidjson_flax::StringBuffer::Ch));
    }
    return data;
}

void Level::SaveSceneAsync(Scene* scene)
{
    ScopeLock lock(_sceneActionsLocker);
    _sceneActions.Enqueue(New<SaveSceneAction>(scene));
}

bool Level::SaveAllScenes()
{
    ScopeLock lock(_sceneActionsLocker);
    SceneAction::Context context;
    for (int32 i = 0; i < Scenes.Count(); i++)
    {
        if (SaveSceneAction(Scenes[i]).Do(context) != SceneResult::Success)
            return true;
    }
    return false;
}

void Level::SaveAllScenesAsync()
{
    ScopeLock lock(_sceneActionsLocker);
    for (int32 i = 0; i < Scenes.Count(); i++)
        _sceneActions.Enqueue(New<SaveSceneAction>(Scenes[i]));
}

bool Level::LoadScene(const Guid& id)
{
    // Check ID
    if (!id.IsValid())
    {
        Log::ArgumentException();
        return true;
    }

    // Skip is that scene is already loaded
    if (FindScene(id) != nullptr)
    {
        LOG(Info, "Scene {0} is already loaded.", id);
        return false;
    }

    // Now to deserialize scene in a proper way we need to load scripting
    if (!Scripting::IsEveryAssemblyLoaded())
    {
#if USE_EDITOR
        LOG(Error, "Scripts must be compiled without any errors in order to load a scene. Please fix it.");
#else
        LOG(Warning, "Scripts must be compiled without any errors in order to load a scene.");
#endif
        return true;
    }

    // Preload scene asset
    const auto sceneAsset = Content::LoadAsync<JsonAsset>(id);
    if (sceneAsset == nullptr)
    {
        LOG(Error, "Cannot load scene asset.");
        return true;
    }

    // Load scene
    ScopeLock lock(ScenesLock);
    SceneLoader loader;
    if (loadScene(loader, sceneAsset) != SceneResult::Success)
    {
        LOG(Error, "Failed to deserialize scene {0}", id);
        CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, id);
        return true;
    }
    return false;
}

Scene* Level::LoadSceneFromBytes(const BytesContainer& data)
{
    Scene* scene = nullptr;
    SceneLoader loader;
    if (loadScene(loader, data, &scene) != SceneResult::Success)
    {
        LOG(Error, "Failed to deserialize scene from bytes");
        CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, Guid::Empty);
    }
    return scene;
}

bool Level::LoadSceneAsync(const Guid& id)
{
    if (!id.IsValid())
    {
        Log::ArgumentException();
        return true;
    }

    // Preload scene asset
    const auto sceneAsset = Content::LoadAsync<JsonAsset>(id);
    if (sceneAsset == nullptr)
    {
        LOG(Error, "Cannot load scene asset.");
        return true;
    }

    ScopeLock lock(_sceneActionsLocker);
    _sceneActions.Enqueue(New<LoadSceneAction>(id, sceneAsset, true));

    return false;
}

bool Level::UnloadScene(Scene* scene)
{
    return unloadScene(scene);
}

void Level::UnloadSceneAsync(Scene* scene)
{
    CHECK(scene);
    ScopeLock lock(_sceneActionsLocker);
    _sceneActions.Enqueue(New<UnloadSceneAction>(scene));
}

bool Level::UnloadAllScenes()
{
    ScopeLock lock(_sceneActionsLocker);
    return unloadScenes();
}

void Level::UnloadAllScenesAsync()
{
    ScopeLock lock(_sceneActionsLocker);
    _sceneActions.Enqueue(New<UnloadScenesAction>());
}

#if USE_EDITOR

void Level::ReloadScriptsAsync()
{
    ScopeLock lock(_sceneActionsLocker);
    _sceneActions.Enqueue(New<ReloadScriptsAction>());
}

#endif

Actor* Level::FindActor(const Guid& id)
{
    return Scripting::TryFindObject<Actor>(id);
}

Actor* Level::FindActor(const StringView& name)
{
    Actor* result = nullptr;
    ScopeLock lock(ScenesLock);
    for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
        result = Scenes[i]->FindActor(name);
    return result;
}

Actor* Level::FindActor(const MClass* type, bool activeOnly)
{
    CHECK_RETURN(type, nullptr);
    Actor* result = nullptr;
    ScopeLock lock(ScenesLock);
    for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
        result = Scenes[i]->FindActor(type, activeOnly);
    return result;
}

Actor* Level::FindActor(const MClass* type, const StringView& name)
{
    CHECK_RETURN(type, nullptr);
    Actor* result = nullptr;
    ScopeLock lock(ScenesLock);
    for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
        result = Scenes[i]->FindActor(type, name);
    return result;
}

Actor* FindActorRecursive(Actor* node, const Tag& tag, bool activeOnly)
{
    if (activeOnly && !node->GetIsActive())
        return nullptr;
    if (node->HasTag(tag))
        return node;
    Actor* result = nullptr;
    for (Actor* child : node->Children)
    {
        result = FindActorRecursive(child, tag, activeOnly);
        if (result)
            break;
    }
    return result;
}

Actor* FindActorRecursiveByType(Actor* node, const MClass* type, const Tag& tag, bool activeOnly)
{
    CHECK_RETURN(type, nullptr);
    if (activeOnly && !node->GetIsActive())
        return nullptr;
    if (node->HasTag(tag) && (node->GetClass()->IsSubClassOf(type) || node->GetClass()->HasInterface(type)))
        return node;
    Actor* result = nullptr;
    for (Actor* child : node->Children)
    {
        result = FindActorRecursiveByType(child, type, tag, activeOnly);
        if (result)
            break;
    }
    return result;
}

void FindActorsRecursive(Actor* node, const Tag& tag, const bool activeOnly, Array<Actor*>& result)
{
    if (activeOnly && !node->GetIsActive())
        return;
    if (node->HasTag(tag))
        result.Add(node);
    for (Actor* child : node->Children)
        FindActorsRecursive(child, tag, activeOnly, result);
}

void FindActorsRecursiveByParentTags(Actor* node, const Array<Tag>& tags, const bool activeOnly, Array<Actor*>& result)
{
    if (activeOnly && !node->GetIsActive())
        return;
    for (Tag tag : tags)
    {
        if (node->HasTag(tag))
        {
            result.Add(node);
            break;
        }
    }
    for (Actor* child : node->Children)
        FindActorsRecursiveByParentTags(child, tags, activeOnly, result);
}

Actor* Level::FindActor(const Tag& tag, bool activeOnly, Actor* root)
{
    PROFILE_CPU();
    if (root)
        return FindActorRecursive(root, tag, activeOnly);
    Actor* result = nullptr;
    for (Scene* scene : Scenes)
    {
        result = FindActorRecursive(scene, tag, activeOnly);
        if (result)
            break;
    }
    return result;
}

Actor* Level::FindActor(const MClass* type, const Tag& tag, bool activeOnly, Actor* root)
{
    CHECK_RETURN(type, nullptr);
    if (root)
        return FindActorRecursiveByType(root, type, tag, activeOnly);
    Actor* result = nullptr;
    ScopeLock lock(ScenesLock);
    for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
        result = Scenes[i]->FindActor(type, tag, activeOnly);
    return result;
}

void FindActorRecursive(Actor* node, const Tag& tag, Array<Actor*>& result)
{
    if (node->HasTag(tag))
        result.Add(node);
    for (Actor* child : node->Children)
        FindActorRecursive(child, tag, result);
}

Array<Actor*> Level::FindActors(const Tag& tag, const bool activeOnly, Actor* root)
{
    PROFILE_CPU();
    Array<Actor*> result;
    if (root)
    {
        FindActorsRecursive(root, tag, activeOnly, result);
    }
    else
    {
        ScopeLock lock(ScenesLock);
        for (Scene* scene : Scenes)
            FindActorsRecursive(scene, tag, activeOnly, result);
    }
    return result;
}

Array<Actor*> Level::FindActorsByParentTag(const Tag& parentTag, const bool activeOnly, Actor* root)
{
    PROFILE_CPU();
    Array<Actor*> result;
    const Array<Tag> subTags = Tags::GetSubTags(parentTag);

    if (subTags.Count() == 0)
    {
        return result;
    }
    if (subTags.Count() == 1)
    {
        result = FindActors(subTags[0], activeOnly, root);
        return result;
    }

    if (root)
    {
        FindActorsRecursiveByParentTags(root, subTags, activeOnly, result);
    }
    else
    {
        ScopeLock lock(ScenesLock);
        for (Scene* scene : Scenes)
            FindActorsRecursiveByParentTags(scene, subTags, activeOnly, result);
    }

    return result;
}

Script* Level::FindScript(const MClass* type)
{
    CHECK_RETURN(type, nullptr);
    Script* result = nullptr;
    ScopeLock lock(ScenesLock);
    for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
        result = Scenes[i]->FindScript(type);
    return result;
}

namespace
{
    void GetActors(const MClass* type, bool isInterface, Actor* actor, bool activeOnly, Array<Actor*>& result)
    {
        if (activeOnly && !actor->GetIsActive())
            return;
        if ((!isInterface && actor->GetClass()->IsSubClassOf(type)) ||
            (isInterface && actor->GetClass()->HasInterface(type)))
            result.Add(actor);
        for (auto child : actor->Children)
            GetActors(type, isInterface, child, activeOnly, result);
    }

    void GetScripts(const MClass* type, bool isInterface, Actor* actor, Array<Script*>& result)
    {
        for (auto script : actor->Scripts)
        {
            if ((!isInterface && script->GetClass()->IsSubClassOf(type)) ||
                (isInterface && script->GetClass()->HasInterface(type)))
                result.Add(script);
        }
        for (auto child : actor->Children)
            GetScripts(type, isInterface, child, result);
    }
}

Array<Actor*> Level::GetActors(const MClass* type, bool activeOnly)
{
    Array<Actor*> result;
    CHECK_RETURN(type, result);
    ScopeLock lock(ScenesLock);
    for (int32 i = 0; i < Scenes.Count(); i++)
        ::GetActors(type, type->IsInterface(), Scenes[i], activeOnly, result);
    return result;
}

Array<Script*> Level::GetScripts(const MClass* type, Actor* root)
{
    Array<Script*> result;
    CHECK_RETURN(type, result);
    ScopeLock lock(ScenesLock);
    const bool isInterface = type->IsInterface();
    if (root)
        ::GetScripts(type, isInterface, root, result);
    else
        for (int32 i = 0; i < Scenes.Count(); i++)
            ::GetScripts(type, isInterface, Scenes[i], result);
    return result;
}

Scene* Level::FindScene(const Guid& id)
{
    ScopeLock lock(ScenesLock);
    for (int32 i = 0; i < Scenes.Count(); i++)
        if (Scenes[i]->GetID() == id)
            return Scenes[i];
    return nullptr;
}

void Level::GetScenes(Array<Scene*>& scenes)
{
    ScopeLock lock(ScenesLock);

    scenes = Scenes;
}

void Level::GetScenes(Array<Actor*>& scenes)
{
    ScopeLock lock(ScenesLock);

    scenes.Clear();
    scenes.EnsureCapacity(Scenes.Count());

    for (int32 i = 0; i < Scenes.Count(); i++)
        scenes.Add(Scenes[i]);
}

void Level::GetScenes(Array<Guid>& scenes)
{
    ScopeLock lock(ScenesLock);

    scenes.Clear();
    scenes.EnsureCapacity(Scenes.Count());

    for (int32 i = 0; i < Scenes.Count(); i++)
        scenes.Add(Scenes[i]->GetID());
}

void FillTree(Actor* node, Array<Actor*>& result)
{
    result.Add(node->Children);
    for (int i = 0; i < node->Children.Count(); i++)
    {
        FillTree(node->Children[i], result);
    }
}

void Level::ConstructSolidActorsTreeList(const Array<Actor*>& input, Array<Actor*>& output)
{
    for (int32 i = 0; i < input.Count(); i++)
    {
        auto target = input[i];

        // Check if has been already added
        if (output.Contains(target))
            continue;

        // Add whole child tree to the results
        output.Add(target);
        FillTree(target, output);
    }
}

void Level::ConstructParentActorsTreeList(const Array<Actor*>& input, Array<Actor*>& output)
{
    // Build solid part of the tree
    Array<Actor*> fullTree;
    ConstructSolidActorsTreeList(input, fullTree);

    for (int32 i = 0; i < input.Count(); i++)
    {
        Actor* target = input[i];

        // If there is no target node parent in the solid tree list,
        // then it means it's a local root node and can be added to the results.
        if (!fullTree.Contains(target->GetParent()))
            output.Add(target);
    }
}
