#include "erased-constraints.hpp"
#include "compat/assert.hpp"
#include <cmath>
#include <limits>
#include <Magnum/Magnum.h>
#include <Magnum/Math/Vector2.h>
#include <Magnum/Math/Vector3.h>
#include <Magnum/Math/Vector4.h>

namespace floormat::entities::erased_constraints {

static_assert(sizeof(size_t) == sizeof(uintptr_t));
static_assert(sizeof(size_t) == sizeof(ptrdiff_t));

template<typename T> std::pair<T, T> range::convert() const
{
    static_assert(sizeof(T) <= sizeof(min));

    if (type == type_none)
    {
        if constexpr (Math::IsVector<T>())
        {
            using U = typename T::Type;
            constexpr auto Size = T::Size;
            T a, b;
            for (auto i = 0uz; i < Size; i++)
                a[i] = std::numeric_limits<U>::min(), b[i] = std::numeric_limits<U>::max();
            return {a, b};
        }
        else
            return { std::numeric_limits<T>::min(), std::numeric_limits<T>::max() };
    }
    else
    {
        if constexpr (std::is_integral_v<T>)
        {
            if (std::is_signed_v<T>)
            {
                fm_assert(type == type_int);
                return { T(min.i), T(max.i) };
            }
            else
            {
                if (type == type_int)
                {
                    fm_assert(min.i >= 0 && max.i >= 0);
                    return { T(min.i), T(max.i) };
                }
                else
                {
                    fm_assert(type == type_uint);
                    return { T(min.u), T(max.u) };
                }
            }
        }
        else if constexpr(Math::IsVector<T>())
        {
            using U = typename T::Type;
            constexpr auto Size = T::Size;
            static_assert(Size >= 2 && Size <= 4);
            T a, b;
            if constexpr(std::is_integral_v<U>)
            {
                if constexpr(std::is_signed_v<U>)
                {
                    fm_assert(type == type_int4);
                    for (auto i = 0uz; i < Size; i++)
                        a[i] = U(min.i4[i]), b[i] = U(max.i4[i]);
                }
                else
                {
                    if (type == type_int4)
                    {
                        for (auto i = 0uz; i < Size; i++)
                        {
                            fm_assert(min.i4[i] >= 0 && max.i4[i] >= 0);
                            a[i] = U(min.i4[i]), b[i] = U(max.i4[i]);
                        }
                    }
                    else
                    {
                        fm_assert(type == type_uint4);
                        for (auto i = 0uz; i < Size; i++)
                            a[i] = U(min.u4[i]), b[i] = U(max.u4[i]);
                    }
                }
            }
            else
            {
                static_assert(std::is_floating_point_v<U>);
                fm_assert(type == type_float4);
                for (auto i = 0uz; i < Size; i++)
                    a[i] = U(min.f4[i]), b[i] = U(max.f4[i]);
            }
            return { a, b };
        }
        else
        {
            static_assert(std::is_floating_point_v<T>);
            fm_assert(type == type_float);
            return { T(min.f), T(max.f) };
        }
    }
}

template<typename T> using pair2 = std::pair<T, T>;

template pair2<uint8_t> range::convert() const;
template pair2<uint16_t> range::convert() const;
template pair2<uint32_t> range::convert() const;
template pair2<uint64_t> range::convert() const;
template pair2<int8_t> range::convert() const;
template pair2<int16_t> range::convert() const;
template pair2<int32_t> range::convert() const;
template pair2<int64_t> range::convert() const;
template pair2<float> range::convert() const;
template pair2<Vector2ub> range::convert() const;
template pair2<Vector2us> range::convert() const;
template pair2<Vector2ui> range::convert() const;
template pair2<Math::Vector2<uint64_t>> range::convert() const;
template pair2<Vector2b> range::convert() const;
template pair2<Vector2s> range::convert() const;
template pair2<Vector2i> range::convert() const;
template pair2<Math::Vector2<int64_t>> range::convert() const;
template pair2<Vector2> range::convert() const;
template pair2<Vector3ub> range::convert() const;
template pair2<Vector3us> range::convert() const;
template pair2<Vector3ui> range::convert() const;
template pair2<Math::Vector3<uint64_t>> range::convert() const;
template pair2<Vector3b> range::convert() const;
template pair2<Vector3s> range::convert() const;
template pair2<Vector3i> range::convert() const;
template pair2<Math::Vector3<int64_t>> range::convert() const;
template pair2<Vector3> range::convert() const;
template pair2<Vector4ub> range::convert() const;
template pair2<Vector4us> range::convert() const;
template pair2<Vector4ui> range::convert() const;
template pair2<Math::Vector4<int64_t>> range::convert() const;
template pair2<Vector4b> range::convert() const;
template pair2<Vector4s> range::convert() const;
template pair2<Vector4i> range::convert() const;
template pair2<Math::Vector4<uint64_t>> range::convert() const;
template pair2<Vector4> range::convert() const;

bool operator==(const range& a, const range& b)
{
    if (a.type != b.type)
        return false;

    constexpr float eps = 1e-6f;

    switch (a.type)
    {
    default: return false;
    case range::type_none:  return true;
    case range::type_float: return std::fabs(a.min.f - b.min.f) < eps && std::fabs(a.max.f - b.max.f) < eps;
    case range::type_uint:  return a.min.u == b.min.u && a.max.u == b.max.u;
    case range::type_int:   return a.min.i == b.min.i && a.max.i == b.max.i;
    }
}

} // namespace floormat::entities::erased_constraints