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

#include "CSGBuilder.h"
#include "CSGMesh.h"
#include "CSGData.h"
#include "Engine/Level/Level.h"
#include "Engine/Level/SceneQuery.h"
#include "Engine/Level/Actor.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Graphics/Models/ModelData.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Physics/CollisionData.h"
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/ContentImporters/CreateCollisionData.h"
#include "Engine/Content/Assets/RawDataAsset.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#endif

#if COMPILE_WITH_CSG_BUILDER

using namespace CSG;

// Enable/disable locking scene during building CSG brushes nodes
#define CSG_USE_SCENE_LOCKS 0

struct BuildData;

namespace CSGBuilderImpl
{
    Array<Scene*> ScenesToRebuild;

    void onSceneUnloading(Scene* scene, const Guid& sceneId);
    bool buildInner(Scene* scene, BuildData& data);
    void build(Scene* scene);
    bool generateRawDataAsset(Scene* scene, RawData& meshData, Guid& assetId, const String& assetPath);
}

using namespace CSGBuilderImpl;

class CSGBuilderService : public EngineService
{
public:
    CSGBuilderService()
        : EngineService(TEXT("CSG Builder"), 90)
    {
    }

    bool Init() override;
    void Update() override;
};

CSGBuilderService CSGBuilderServiceInstance;

Delegate<Brush*> Builder::OnBrushModified;

void CSGBuilderImpl::onSceneUnloading(Scene* scene, const Guid& sceneId)
{
    // Ensure to remove scene (prevent crashes)
    ScenesToRebuild.Remove(scene);
}

bool CSGBuilderService::Init()
{
    Level::SceneUnloading.Bind(onSceneUnloading);

    return false;
}

void CSGBuilderService::Update()
{
    // Check if build is pending
    if (ScenesToRebuild.HasItems() && Engine::IsReady())
    {
        auto now = DateTime::NowUTC();

        for (int32 i = 0; ScenesToRebuild.HasItems() && i < ScenesToRebuild.Count(); i++)
        {
            auto scene = ScenesToRebuild[i];
            if (now - scene->CSGData.BuildTime >= 0)
            {
                scene->CSGData.BuildTime.Ticks = 0;
                ScenesToRebuild.RemoveAt(i--);
                build(scene);
            }
        }
    }
}

bool Builder::IsActive()
{
    return ScenesToRebuild.HasItems();
}

void Builder::Build(Scene* scene, float timeoutMs)
{
    if (scene == nullptr)
        return;

#if USE_EDITOR
    // Disable building during play mode
    if (Editor::IsPlayMode)
        return;
#endif

    // Register building
    if (!ScenesToRebuild.Contains(scene))
    {
        ScenesToRebuild.Add(scene);
    }
    scene->CSGData.BuildTime = DateTime::NowUTC() + TimeSpan::FromMilliseconds(timeoutMs);
}

namespace CSG
{
    typedef Dictionary<Actor*, Mesh*> MeshesLookup;

    bool walkTree(Actor* actor, MeshesArray& meshes, MeshesLookup& cache)
    {
        // Check if actor is a brush
        auto brush = dynamic_cast<Brush*>(actor);
        if (brush)
        {
            // Check if can build it
            if (brush->CanUseCSG())
            {
                // Skip subtract/common meshes from the beginning (they have no effect)
                if (meshes.Count() > 0 || brush->GetBrushMode() == Mode::Additive)
                {
                    // Create new mesh and build for given brush
                    auto mesh = New<CSG::Mesh>();
                    mesh->Build(brush);

                    // Save results
                    meshes.Add(mesh);
                    cache.Add(actor, mesh);
                }
                else
                {
                    // Info
                    LOG(Info, "Skipping CSG brush '{0}'", actor->ToString());
                }
            }
        }

        return true;
    }

    Mesh* Combine(Actor* actor, MeshesLookup& cache, Mesh* combineParent)
    {
        ASSERT(actor);
        Mesh* result = nullptr;
        Mesh* myBrush = nullptr;
        cache.TryGet(actor, myBrush);

        // Get first child mesh with valid data (has additive brush)
        int32 childIndex = 0;
        while (childIndex < actor->Children.Count())
        {
            auto child = Combine(actor->Children[childIndex], cache, combineParent);

            childIndex++;
            if (child)
            {
                // If brush was based on additive brush or current actor is a brush we can stop searching
                if (child->HasMode(Mode::Additive) || myBrush)
                {
                    // End searching
                    result = child;
                    break;
                }

                if (combineParent)
                {
                    // Combine
                    combineParent->PerformOperation(child);
                }
            }
        }

        // Check if has any child with CSG brush
        if (result)
        {
            // Check if has own brush
            if (myBrush)
            {
                // Combine with first child
                myBrush->PerformOperation(result);

                // Set this actor brush as a result
                result = myBrush;
            }

            // Merge with the other children
            while (childIndex < actor->Children.Count())
            {
                auto child = Combine(actor->Children[childIndex], cache, result);
                if (child)
                {
                    // Combine
                    result->PerformOperation(child);
                }

                childIndex++;
            }
        }
        else
        {
            // Use this actor brush (may be empty)
            result = myBrush;
        }

        return result;
    }

    Mesh* Combine(Scene* scene, MeshesLookup& cache)
    {
#if CSG_USE_SCENE_LOCKS
		auto Level = Level::Instance();
		Level->Lock();
#endif

        Mesh* result = Combine(scene, cache, nullptr);

#if CSG_USE_SCENE_LOCKS
		Level->Unlock();
#endif

        return result;
    }
}

struct BuildData
{
    MeshesArray meshes;
    MeshesLookup cache;
    Guid outputModelAssetId = Guid::Empty;
    Guid outputRawDataAssetId = Guid::Empty;
    Guid outputCollisionDataAssetId = Guid::Empty;

    BuildData(int32 meshesCapacity = 32)
        : meshes(meshesCapacity * 32)
        , cache(meshesCapacity * 4)
    {
    }
};

bool CSGBuilderImpl::buildInner(Scene* scene, BuildData& data)
{
    // Setup CSG meshes list and build them
    {
        Function<bool(Actor*, MeshesArray&, MeshesLookup&)> treeWalkFunction(walkTree);
        SceneQuery::TreeExecute<Array<CSG::Mesh*>&, MeshesLookup&>(treeWalkFunction, data.meshes, data.cache);
    }
    if (data.meshes.IsEmpty())
        return false;

    // Process all meshes (performs actual CSG opterations on geometry in tree structure)
    CSG::Mesh* combinedMesh = Combine(scene, data.cache);
    if (combinedMesh == nullptr)
        return false;

    // TODO: split too big meshes (too many verts, to far parts, etc.)

    // Triangulate meshes
    {
        // TODO: setup valid loop for splited meshes

        // Convert CSG meshes into raw triangles data
        RawData meshData;
        Array<MeshVertex> vertexBuffer;
        combinedMesh->Triangulate(meshData, vertexBuffer);
        meshData.RemoveEmptySlots();
        if (meshData.Slots.HasItems())
        {
            const auto sceneDataFolderPath = scene->GetDataFolderPath();

            // Convert CSG mesh data to common storage type
            ModelData modelData;
            meshData.ToModelData(modelData);

            // Convert CSG mesh to the local transformation of the scene
            if (!scene->GetTransform().IsIdentity())
            {
                Matrix t;
                scene->GetWorldToLocalMatrix(t);
                modelData.TransformBuffer(t);
            }

            // Import model data to the asset
            {
                Guid modelDataAssetId = scene->CSGData.Model.GetID();
                if (!modelDataAssetId.IsValid())
                    modelDataAssetId = Guid::New();
                const String modelDataAssetPath = sceneDataFolderPath / TEXT("CSG_Mesh") + ASSET_FILES_EXTENSION_WITH_DOT;
                if (AssetsImportingManager::Create(AssetsImportingManager::CreateModelTag, modelDataAssetPath, modelDataAssetId, &modelData))
                {
                    LOG(Warning, "Failed to import CSG mesh data");
                    return true;
                }
                data.outputModelAssetId = modelDataAssetId;
            }

            // Generate asset with CSG mesh metadata (for collisions and brush queries)
            {
                Guid rawDataAssetId = scene->CSGData.Data.GetID();
                if (!rawDataAssetId.IsValid())
                    rawDataAssetId = Guid::New();
                const String rawDataAssetPath = sceneDataFolderPath / TEXT("CSG_Data") + ASSET_FILES_EXTENSION_WITH_DOT;
                if (generateRawDataAsset(scene, meshData, rawDataAssetId, rawDataAssetPath))
                {
                    LOG(Warning, "Failed to create raw CSG data");
                    return true;
                }
                data.outputRawDataAssetId = rawDataAssetId;
            }

            // Generate CSG mesh collision asset
            {
                // Convert CSG mesh to scene local space (fix issues when scene has transformation applied)
                if (!scene->GetTransform().IsIdentity())
                {
                    Matrix m1, m2;
                    scene->GetTransform().GetWorld(m1);
                    Matrix::Invert(m1, m2);

                    for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++)
                    {
                        auto lod = &modelData.LODs[lodIndex];
                        for (int32 meshIndex = 0; meshIndex < lod->Meshes.Count(); meshIndex++)
                        {
                            Array<Float3>& v = lod->Meshes[meshIndex]->Positions;
                            for (int32 i = 0; i < v.Count(); i++)
                                Float3::Transform(v[i], m2, v[i]);
                        }
                    }
                }

#if COMPILE_WITH_PHYSICS_COOKING
                CollisionCooking::Argument arg;
                arg.Type = CollisionDataType::TriangleMesh;
                arg.OverrideModelData = &modelData;
                Guid collisionDataAssetId = scene->CSGData.CollisionData.GetID();
                if (!collisionDataAssetId.IsValid())
                    collisionDataAssetId = Guid::New();
                const String collisionDataAssetPath = sceneDataFolderPath / TEXT("CSG_Collision") + ASSET_FILES_EXTENSION_WITH_DOT;
                if (AssetsImportingManager::Create(AssetsImportingManager::CreateCollisionDataTag, collisionDataAssetPath, collisionDataAssetId, &arg))
                {
                    LOG(Warning, "Failed to cook CSG mesh collision data");
                    return true;
                }
                data.outputCollisionDataAssetId = collisionDataAssetId;
#else
                data.outputCollisionDataAssetId = Guid::Empty;
#endif
            }
        }
    }

    return false;
}

void CSGBuilderImpl::build(Scene* scene)
{
    // Start
    auto startTime = DateTime::Now();
    LOG(Info, "Start building CSG...");

    // Build
    BuildData data;
    bool failed = buildInner(scene, data);

    // Link new (or empty) CSG mesh
    scene->CSGData.Data = Content::LoadAsync<RawDataAsset>(data.outputRawDataAssetId);
    scene->CSGData.Model = Content::LoadAsync<Model>(data.outputModelAssetId);
    scene->CSGData.CollisionData = Content::LoadAsync<CollisionData>(data.outputCollisionDataAssetId);
    // TODO: also set CSGData.InstanceBuffer - lightmap scales for the entries so csg mesh gets better quality in lightmaps
    scene->CSGData.PostCSGBuild();

    // End
    data.meshes.ClearDelete();
    auto endTime = DateTime::Now();
    LOG(Info, "CSG build in {0} ms! {1} brush(es)", (endTime - startTime).GetTotalMilliseconds(), data.meshes.Count());
}

bool CSGBuilderImpl::generateRawDataAsset(Scene* scene, RawData& meshData, Guid& assetId, const String& assetPath)
{
    // Prepare data
    MemoryWriteStream stream(4096);
    {
        // Header (with version number)
        stream.WriteInt32(1);
        const int32 brushesCount = meshData.Brushes.Count();
        stream.WriteInt32(brushesCount);

        // Surfaces data locations
        int32 surfacesDataOffset = sizeof(int32) * 2 + (sizeof(Guid) + sizeof(int32)) * brushesCount;
        for (auto brush = meshData.Brushes.Begin(); brush.IsNotEnd(); ++brush)
        {
            auto& surfaces = brush->Value.Surfaces;

            stream.Write(brush->Key);
            stream.WriteInt32(surfacesDataOffset);

            // Calculate offset in data storage to the next brush data
            int32 brushDataSize = 0;
            for (int32 i = 0; i < surfaces.Count(); i++)
            {
                brushDataSize += sizeof(int32) + sizeof(RawData::SurfaceTriangle) * surfaces[i].Triangles.Count();
            }
            surfacesDataOffset += brushDataSize;
        }

        // Surfaces data
        for (auto brush = meshData.Brushes.Begin(); brush.IsNotEnd(); ++brush)
        {
            auto& surfaces = brush->Value.Surfaces;

            for (int32 i = 0; i < surfaces.Count(); i++)
            {
                auto& triangles = surfaces[i].Triangles;

                stream.WriteInt32(triangles.Count());
                stream.WriteBytes(triangles.Get(), triangles.Count() * sizeof(RawData::SurfaceTriangle));
            }
        }
    }

    // Serialize
    BytesContainer bytesContainer;
    bytesContainer.Link(ToSpan(stream));
    return AssetsImportingManager::Create(AssetsImportingManager::CreateRawDataTag, assetPath, assetId, (void*)&bytesContainer);
}

#endif
