
#pragma once

// Takes multiple CRGB arrays[] of different sizes and generates one CRGB
// array of size fl::max(WIDTH) * NUM_OF_STRIPS that contains them all.

// This allows the flexible LED array usage in FastLED for block renderers.

// Needed by controllers that require a compact, rectangular buffer of pixel
// data. Namely, ObjectFLED and the I2S controllers. This class handles using
// multiple independent strips of LEDs, each with their own buffer of pixel
// data. The strips are not necessarily contiguous in memory. One or more
// DrawItems containing the pin number and number are queued up. When the
// queue-ing is done, the buffers are compacted into the rectangular buffer.
// Data access is achieved through a span<u8> representing the pixel data
// for that pin.

#include "fl/stl/int.h"
#include "fl/stl/allocator.h"
#include "fl/stl/flat_map.h"
#include "fl/stl/unique_ptr.h"
#include "fl/stl/vector.h"
#include "fl/stl/strstream.h"
#include "fl/stl/noexcept.h"

namespace fl {

struct DrawItem {
    DrawItem() FL_NOEXCEPT = default;
    DrawItem(u8 pin, u16 numLeds, bool is_rgbw);
    
    // Rule of 5 for POD data
    DrawItem(const DrawItem &other) FL_NOEXCEPT = default;
    DrawItem &operator=(const DrawItem &other) = default;
    DrawItem(DrawItem &&other) FL_NOEXCEPT = default;
    DrawItem &operator=(DrawItem &&other) FL_NOEXCEPT = default;
    
    u8 mPin = 0;
    u32 mNumBytes = 0;
    bool mIsRgbw = false;
    bool operator!=(const DrawItem &other) const {
        return mPin != other.mPin || mNumBytes != other.mNumBytes ||
               mIsRgbw != other.mIsRgbw;
    }
};

class RectangularDrawBuffer {
  public:

    RectangularDrawBuffer() FL_NOEXCEPT
        : mAllLedsBufferUint8Size(0)
        , mDrawListChangedThisFrame(false)
        , mQueueState(IDLE)
    {
        mPinToLedSegment.reserve(50);
    }
    ~RectangularDrawBuffer() FL_NOEXCEPT = default;

    fl::span<u8> getLedsBufferBytesForPin(u8 pin,
                                               bool clear_first = true);

    // Safe to call multiple times before calling queue() once. Returns true on
    // the first call, false after.
    bool onQueuingStart();
    void queue(const DrawItem &item);

    // Compiles the RectangularBuffer if necessary.
    // Safe to call multiple times before calling onQueueingStart() again.
    // Returns true on the first call, false after.
    bool onQueuingDone();

    // Valid after onQueueDone:
    u32 getMaxBytesInStrip() const;
    u32 getTotalBytes() const;
    void getBlockInfo(u32 *num_strips, u32 *bytes_per_strip,
                      u32 *total_bytes) const;

// protected:
    typedef fl::vector<DrawItem> DrawList;
    // We manually manage the memory for the buffer of all LEDs so that it can
    // go into psram on ESP32S3, which is managed by fl::PSRamAllocator.
    // Use PSRamDeleter to properly free memory allocated with PSRamAllocator.
    unique_ptr<u8[], PSRamDeleter<u8>> mAllLedsBufferUint8;
    u32 mAllLedsBufferUint8Size = 0;
    fl::flat_map<u8, fl::span<u8>> mPinToLedSegment;
    DrawList mDrawList;
    DrawList mPrevDrawList;
    bool mDrawListChangedThisFrame = false;

    enum QueueState { IDLE, QUEUEING, QUEUE_DONE };
    QueueState mQueueState = IDLE;
};

} // namespace fl
