// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.

#include "CollisionData.h"
#include "Engine/Core/Log.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Physics/Utilities.h"
#include "Engine/Physics/CollisionCooking.h"
#include "Engine/Threading/Threading.h"
#include <ThirdParty/PhysX/extensions/PxDefaultStreams.h>
#include <ThirdParty/PhysX/geometry/PxTriangleMesh.h>
#include <ThirdParty/PhysX/geometry/PxConvexMesh.h>
#include <ThirdParty/PhysX/PxPhysics.h>

REGISTER_BINARY_ASSET(CollisionData, "FlaxEngine.CollisionData", true);

CollisionData::CollisionData(const SpawnParams& params, const AssetInfo* info)
    : BinaryAsset(params, info)
    , _convexMesh(nullptr)
    , _triangleMesh(nullptr)
{
}

#if COMPILE_WITH_PHYSICS_COOKING

bool CollisionData::CookCollision(CollisionDataType type, ModelBase* modelObj, int32 modelLodIndex, uint32 materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
{
    // Validate state
    if (!IsVirtual())
    {
        LOG(Warning, "Only virtual assets can be modified at runtime.");
        return true;
    }

    // Prepare
    CollisionCooking::Argument arg;
    arg.Type = type;
    arg.Model = modelObj;
    arg.ModelLodIndex = modelLodIndex;
    arg.MaterialSlotsMask = materialSlotsMask;
    arg.ConvexFlags = convexFlags;
    arg.ConvexVertexLimit = convexVertexLimit;

    // Cook collision
    SerializedOptions options;
    BytesContainer outputData;
    if (CollisionCooking::CookCollision(arg, options, outputData))
    {
        return true;
    }

    // Clear state
    unload(true);

    // Load data
    if (load(&options, outputData.Get(), outputData.Length()) != LoadResult::Ok)
    {
        return true;
    }

    // Mark as loaded (eg. Mesh Colliders using this asset will update shape for physics simulation)
    onLoaded();
    return false;
}

bool CollisionData::CookCollision(CollisionDataType type, const Span<Vector3>& vertices, const Span<uint32>& triangles, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
{
    CHECK_RETURN(vertices.Length() != 0, true);
    CHECK_RETURN(triangles.Length() != 0 && triangles.Length() % 3 == 0, true);
    ModelData modelData;
    modelData.LODs.Resize(1);
    auto meshData = New<MeshData>();
    modelData.LODs[0].Meshes.Add(meshData);
    meshData->Positions.Set(vertices.Get(), vertices.Length());
    meshData->Indices.Set(triangles.Get(), triangles.Length());
    return CookCollision(type, &modelData, convexFlags, convexVertexLimit);
}

bool CollisionData::CookCollision(CollisionDataType type, ModelData* modelData, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
{
    // Validate state
    if (!IsVirtual())
    {
        LOG(Warning, "Only virtual assets can be modified at runtime.");
        return true;
    }

    // Prepare
    CollisionCooking::Argument arg;
    arg.Type = type;
    arg.OverrideModelData = modelData;
    arg.ConvexFlags = convexFlags;
    arg.ConvexVertexLimit = convexVertexLimit;

    // Cook collision
    SerializedOptions options;
    BytesContainer outputData;
    if (CollisionCooking::CookCollision(arg, options, outputData))
    {
        return true;
    }

    // Clear state
    unload(true);

    // Load data
    if (load(&options, outputData.Get(), outputData.Length()) != LoadResult::Ok)
    {
        return true;
    }

    // Mark as loaded (eg. Mesh Colliders using this asset will update shape for physics simulation)
    onLoaded();
    return false;
}

#endif

void CollisionData::ExtractGeometry(Array<Vector3>& vertexBuffer, Array<int32>& indexBuffer) const
{
    vertexBuffer.Clear();
    indexBuffer.Clear();

    ScopeLock lock(Locker);

    uint32 numVertices = 0;
    uint32 numIndices = 0;

    // Convex Mesh
    if (_convexMesh)
    {
        numVertices = _convexMesh->getNbVertices();

        const uint32 numPolygons = _convexMesh->getNbPolygons();
        for (uint32 i = 0; i < numPolygons; i++)
        {
            PxHullPolygon face;
            const bool status = _convexMesh->getPolygonData(i, face);
            ASSERT(status);

            numIndices += (face.mNbVerts - 2) * 3;
        }
    }
        // Triangle Mesh
    else if (_triangleMesh)
    {
        numVertices = _triangleMesh->getNbVertices();
        numIndices = _triangleMesh->getNbTriangles() * 3;
    }
        // No collision data
    else
    {
        return;
    }

    // Prepare vertex and index buffers
    vertexBuffer.Resize(numVertices);
    indexBuffer.Resize(numIndices);
    auto outVertices = vertexBuffer.Get();
    auto outIndices = indexBuffer.Get();

    if (_convexMesh)
    {
        const PxVec3* convexVertices = _convexMesh->getVertices();
        const byte* convexIndices = _convexMesh->getIndexBuffer();

        for (uint32 i = 0; i < numVertices; i++)
            *outVertices++ = P2C(convexVertices[i]);

        uint32 numPolygons = _convexMesh->getNbPolygons();
        for (uint32 i = 0; i < numPolygons; i++)
        {
            PxHullPolygon face;
            bool status = _convexMesh->getPolygonData(i, face);
            ASSERT(status);

            const PxU8* faceIndices = convexIndices + face.mIndexBase;
            for (uint32 j = 2; j < face.mNbVerts; j++)
            {
                *outIndices++ = faceIndices[0];
                *outIndices++ = faceIndices[j];
                *outIndices++ = faceIndices[j - 1];
            }
        }
    }
    else
    {
        const PxVec3* vertices = _triangleMesh->getVertices();
        for (uint32 i = 0; i < numVertices; i++)
            *outVertices++ = P2C(vertices[i]);

        if (_triangleMesh->getTriangleMeshFlags() & PxTriangleMeshFlag::e16_BIT_INDICES)
        {
            const uint16* indices = (const uint16*)_triangleMesh->getTriangles();

            uint32 numTriangles = numIndices / 3;
            for (uint32 i = 0; i < numTriangles; i++)
            {
                outIndices[i * 3 + 0] = (uint32)indices[i * 3 + 0];
                outIndices[i * 3 + 1] = (uint32)indices[i * 3 + 1];
                outIndices[i * 3 + 2] = (uint32)indices[i * 3 + 2];
            }
        }
        else
        {
            const uint32* indices = (const uint32*)_triangleMesh->getTriangles();

            uint32 numTriangles = numIndices / 3;
            for (uint32 i = 0; i < numTriangles; i++)
            {
                outIndices[i * 3 + 0] = indices[i * 3 + 0];
                outIndices[i * 3 + 1] = indices[i * 3 + 1];
                outIndices[i * 3 + 2] = indices[i * 3 + 2];
            }
        }
    }
}

#if USE_EDITOR

const Array<Vector3>& CollisionData::GetDebugLines()
{
    if (_hasMissingDebugLines && IsLoaded())
    {
        ScopeLock lock(Locker);
        _hasMissingDebugLines = false;

        // Get triangles
        ExtractGeometry(_debugVertexBuffer, _debugIndexBuffer);

        // Get lines
        _debugLines.Resize(_debugIndexBuffer.Count() * 2);
        int32 lineIndex = 0;
        for (int32 i = 0; i < _debugIndexBuffer.Count(); i += 3)
        {
            const auto a = _debugVertexBuffer[_debugIndexBuffer[i + 0]];
            const auto b = _debugVertexBuffer[_debugIndexBuffer[i + 1]];
            const auto c = _debugVertexBuffer[_debugIndexBuffer[i + 2]];

            _debugLines[lineIndex++] = a;
            _debugLines[lineIndex++] = b;

            _debugLines[lineIndex++] = b;
            _debugLines[lineIndex++] = c;

            _debugLines[lineIndex++] = c;
            _debugLines[lineIndex++] = a;
        }
    }

    return _debugLines;
}

void CollisionData::GetDebugTriangles(Array<Vector3>*& vertexBuffer, Array<int32>*& indexBuffer)
{
    GetDebugLines();
    vertexBuffer = &_debugVertexBuffer;
    indexBuffer = &_debugIndexBuffer;
}

#endif

Asset::LoadResult CollisionData::load()
{
    const auto chunk0 = GetChunk(0);
    if (chunk0 == nullptr || chunk0->IsMissing() || chunk0->Size() < sizeof(SerializedOptions))
        return LoadResult::MissingDataChunk;

    const auto options = (SerializedOptions*)chunk0->Get();
    const auto dataPtr = chunk0->Get() + sizeof(SerializedOptions);
    const int32 dataSize = chunk0->Size() - sizeof(SerializedOptions);

    return load(options, dataPtr, dataSize);
}

CollisionData::LoadResult CollisionData::load(const SerializedOptions* options, byte* dataPtr, int32 dataSize)
{
    // Load options
    _options.Type = options->Type;
    _options.Model = options->Model;
    _options.ModelLodIndex = options->ModelLodIndex;
    _options.ConvexFlags = options->ConvexFlags;
    _options.ConvexVertexLimit = options->ConvexVertexLimit < 4 ? 255 : options->ConvexVertexLimit;

    // Load data (rest of the chunk is a cooked collision data)
    if (_options.Type == CollisionDataType::None && dataSize > 0)
    {
        LOG(Warning, "Missing collision data.");
        return LoadResult::InvalidData;
    }
    if (_options.Type != CollisionDataType::None)
    {
        if (dataSize <= 0)
            return LoadResult::InvalidData;

        // Create PhysX object
        PxDefaultMemoryInputData input(dataPtr, dataSize);
        if (_options.Type == CollisionDataType::ConvexMesh)
        {
            _convexMesh = Physics::GetPhysics()->createConvexMesh(input);
            _options.Box = P2C(_convexMesh->getLocalBounds());
        }
        else if (_options.Type == CollisionDataType::TriangleMesh)
        {
            _triangleMesh = Physics::GetPhysics()->createTriangleMesh(input);
            _options.Box = P2C(_triangleMesh->getLocalBounds());
        }
        else
        {
            LOG(Warning, "Invalid collision data type.");
            return LoadResult::InvalidData;
        }
    }

    return LoadResult::Ok;
}

void CollisionData::unload(bool isReloading)
{
    if (_convexMesh)
    {
        Physics::RemoveObject(_convexMesh);
        _convexMesh = nullptr;
    }
    if (_triangleMesh)
    {
        Physics::RemoveObject(_triangleMesh);
        _triangleMesh = nullptr;
    }
    _options = CollisionDataOptions();
#if USE_EDITOR
    _hasMissingDebugLines = true;
    _debugLines.Resize(0);
    _debugVertexBuffer.Resize(0);
    _debugIndexBuffer.Resize(0);
#endif
}

AssetChunksFlag CollisionData::getChunksToPreload() const
{
    return GET_CHUNK_FLAG(0);
}
