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

#pragma once

#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Math/Transform.h"
#include "Engine/Core/Math/Ray.h"
#include "Engine/Core/Math/CollisionsHelper.h"
#include "Engine/Core/Math/Half.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Graphics/PixelFormat.h"

/// <summary>
/// Helper container used for detailed triangle mesh intersections tests.
/// </summary>
class FLAXENGINE_API CollisionProxy
{
public:
    struct CollisionTriangle
    {
        Float3 V0, V1, V2;
    };

    /// <summary>
    /// The triangles.
    /// </summary>
    Array<CollisionTriangle> Triangles;

public:
    FORCE_INLINE bool HasData() const
    {
        return Triangles.HasItems();
    }

    template<typename IndexType>
    void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices, uint32 positionsStride = sizeof(Float3), PixelFormat positionsFormat = PixelFormat::R32G32B32_Float)
    {
        Triangles.Clear();
        Triangles.EnsureCapacity(triangles, false);
        const IndexType* it = indices;
#define LOOP_BEGIN() \
    for (uint32 i = 0; i < triangles; i++) \
    { \
        const IndexType i0 = *(it++); \
        const IndexType i1 = *(it++); \
        const IndexType i2 = *(it++); \
        if (i0 < vertices && i1 < vertices && i2 < vertices) \
        {
#define LOOP_END() } }
        if (positionsFormat == PixelFormat::R32G32B32_Float)
        {
            LOOP_BEGIN()
#define GET_POS(idx) *(const Float3*)((const byte*)positions + positionsStride * idx)
                Triangles.Add({ GET_POS(i0), GET_POS(i1), GET_POS(i2) });
#undef GET_POS
            LOOP_END()
        }
        else if (positionsFormat == PixelFormat::R16G16B16A16_Float)
        {
            LOOP_BEGIN()
#define GET_POS(idx) ((const Half4*)((const byte*)positions + positionsStride * idx))->ToFloat3()
                Triangles.Add({ GET_POS(i0), GET_POS(i1), GET_POS(i2) });
#undef GET_POS
            LOOP_END()
        }
#undef LOOP_BEGIN
#undef LOOP_END
    }

    void Clear()
    {
        Triangles.Clear();
    }

    bool Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal) const
    {
        distance = MAX_Real;
        for (int32 i = 0; i < Triangles.Count(); i++)
        {
            CollisionTriangle triangle = Triangles[i];
            Float3::Transform(triangle.V0, world, triangle.V0);
            Float3::Transform(triangle.V1, world, triangle.V1);
            Float3::Transform(triangle.V2, world, triangle.V2);

            // TODO: use 32-bit precision for intersection
            Real d;
            if (CollisionsHelper::RayIntersectsTriangle(ray, triangle.V0, triangle.V1, triangle.V2, d) && d < distance)
            {
                normal = Vector3::Normalize((triangle.V1 - triangle.V0) ^ (triangle.V2 - triangle.V0));
                distance = d;
            }
        }
        return distance < MAX_Real;
    }

    bool Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal) const
    {
        distance = MAX_Real;
        for (int32 i = 0; i < Triangles.Count(); i++)
        {
            CollisionTriangle triangle = Triangles[i];
            Vector3 v0, v1, v2;
            transform.LocalToWorld(triangle.V0, v0);
            transform.LocalToWorld(triangle.V1, v1);
            transform.LocalToWorld(triangle.V2, v2);

            // TODO: use 32-bit precision for intersection
            Real d;
            if (CollisionsHelper::RayIntersectsTriangle(ray, v0, v1, v2, d) && d < distance)
            {
                normal = Vector3::Normalize((v1 - v0) ^ (v2 - v0));
                distance = d;
            }
        }
        return distance < MAX_Real;
    }
};
