#pragma once

/*
LUT - Look up table implementation for various types.
*/

#include "fl/stl/allocator.h"
#include "fl/stl/shared_ptr.h"         // For FASTLED_SHARED_PTR macros
// IWYU pragma: begin_keep
#include "fl/stl/unique_ptr.h"
// IWYU pragma: end_keep  // For fl::unique_ptr<T>

#include "fl/stl/int.h"
#include "fl/math/geometry.h"
#include "fl/stl/noexcept.h"
namespace fl {

// LUT holds a look up table to map data from one
// value to another. This can be quite big (1/3rd of the frame buffer)
// so fl::shared_ptr is used to allow memory sharing.

template <typename T> class LUT;

typedef LUT<u16> LUT16;
typedef LUT<vec2<u16>> LUTXY16;
typedef LUT<vec2f> LUTXYFLOAT;
typedef LUT<vec3f> LUTXYZFLOAT;

FASTLED_SHARED_PTR_NO_FWD(LUT16);
FASTLED_SHARED_PTR_NO_FWD(LUTXY16);
FASTLED_SHARED_PTR_NO_FWD(LUTXYFLOAT);
FASTLED_SHARED_PTR_NO_FWD(LUTXYZFLOAT);

// Templated lookup table.
template <typename T> class LUT {
  public:
    LUT(u32 length) FL_NOEXCEPT : length(length) {
        T *ptr = PSRamAllocator<T>::Alloc(length);
        mDataHandle.reset(ptr);
        data = ptr;
    }
    // In this version the data is passed in but not managed by this object.
    LUT(u32 length, T *data) FL_NOEXCEPT : length(length) { this->data = data; }
    ~LUT() FL_NOEXCEPT {
        PSRamAllocator<T>::Free(mDataHandle.release());
        data = mDataHandle.get();
    }

    const T &operator[](u32 index) const FL_NOEXCEPT { return data[index]; }

    const T &operator[](u16 index) const FL_NOEXCEPT { return data[index]; }

    T *getDataMutable() FL_NOEXCEPT { return data; }

    const T *getData() const FL_NOEXCEPT { return data; }

    u32 size() const FL_NOEXCEPT { return length; }

    T interp8(u8 alpha) FL_NOEXCEPT {
        if (length == 0)
            return T();
        if (alpha == 0)
            return data[0];
        if (alpha == 255)
            return data[length - 1];

        // treat alpha/255 as fraction, scale to [0..length-1]
        u32 maxIndex = length - 1;
        u32 pos = u32(alpha) * maxIndex; // numerator
        u32 idx0 = pos / 255;                 // floor(position)
        u32 idx1 = idx0 < maxIndex ? idx0 + 1 : maxIndex;
        u8 blend = pos % 255; // fractional part

        const T &a = data[idx0];
        const T &b = data[idx1];
        // a + (b-a) * blend/255
        return a + (b - a) * blend / 255;
    }

    T interp16(u16 alpha) FL_NOEXCEPT {
        if (length == 0)
            return T();
        if (alpha == 0)
            return data[0];
        if (alpha == 65535)
            return data[length - 1];

        // treat alpha/65535 as fraction, scale to [0..length-1]
        u32 maxIndex = length - 1;
        u32 pos = u32(alpha) * maxIndex; // numerator
        u32 idx0 = pos / 65535;               // floor(position)
        u32 idx1 = idx0 < maxIndex ? idx0 + 1 : maxIndex;
        u16 blend = pos % 65535; // fractional part

        const T &a = data[idx0];
        const T &b = data[idx1];
        // a + (b-a) * blend/65535
        return a + (b - a) * blend / 65535;
    }

  private:
    fl::unique_ptr<T> mDataHandle;
    T *data = nullptr;
    u32 length;
};

} // namespace fl
