/// @file pixel_iterator.h
/// Non-templated low level pixel data writing class

#pragma once

#include "fl/stdint.h"
#include "fl/string.h"


#include "rgbw.h"

#include "crgb.h"

namespace fl {

#ifndef FASTLED_PIXEL_ITERATOR_HAS_APA102_HD
// Takes more memory, so disable by default.
#define FASTLED_PIXEL_ITERATOR_HAS_APA102_HD 0
#endif

// Due to to the template nature of the PixelController class, the only way we can make
// it a concrete polymorphic class is to manually bind the functions and make our own
// vtable. The PixelControllerVtable is cheaper than doing fl::function<>.
template<typename PixelControllerT>
struct PixelControllerVtable {
  static void loadAndScaleRGBW(void* pixel_controller, Rgbw rgbw, uint8_t* b0_out, uint8_t* b1_out, uint8_t* b2_out, uint8_t* b3_out) {
    PixelControllerT* pc = static_cast<PixelControllerT*>(pixel_controller);
    pc->loadAndScaleRGBW(rgbw, b0_out, b1_out, b2_out, b3_out);
  }

  static void loadAndScaleRGB(void* pixel_controller, uint8_t* r_out, uint8_t* g_out, uint8_t* b_out) {
    PixelControllerT* pc = static_cast<PixelControllerT*>(pixel_controller);
    pc->loadAndScaleRGB(r_out, g_out, b_out);
  }

  #if FASTLED_PIXEL_ITERATOR_HAS_APA102_HD

  static void loadAndScale_APA102_HD(void* pixel_controller, uint8_t* b0_out, uint8_t* b1_out, uint8_t* b2_out, uint8_t* brightness_out) {
    PixelControllerT* pc = static_cast<PixelControllerT*>(pixel_controller);
    pc->loadAndScale_APA102_HD(b0_out, b1_out, b2_out, brightness_out);
  }

  #endif

  static void loadAndScale_WS2816_HD(void* pixel_controller, uint16_t *s0_out, uint16_t* s1_out, uint16_t* s2_out) {
    PixelControllerT* pc = static_cast<PixelControllerT*>(pixel_controller);
    pc->loadAndScale_WS2816_HD(s0_out, s1_out, s2_out);
  }

  static void stepDithering(void* pixel_controller) {
    PixelControllerT* pc = static_cast<PixelControllerT*>(pixel_controller);
    pc->stepDithering();
  }

  static void advanceData(void* pixel_controller) {
    PixelControllerT* pc = static_cast<PixelControllerT*>(pixel_controller);
    pc->advanceData();
  }

  static int size(void* pixel_controller) {
    PixelControllerT* pc = static_cast<PixelControllerT*>(pixel_controller);
    return pc->size();
  }
  static bool has(void* pixel_controller, int n) {
    PixelControllerT* pc = static_cast<PixelControllerT*>(pixel_controller);
    return pc->has(n);
  }

  // function for getHdScale
  #if FASTLED_HD_COLOR_MIXING
  static void getHdScale(void* pixel_controller, uint8_t* c0, uint8_t* c1, uint8_t* c2, uint8_t* brightness) {
    PixelControllerT* pc = static_cast<PixelControllerT*>(pixel_controller);
    pc->getHdScale(c0, c1, c2, brightness);
  }
  #endif
};

typedef void (*loadAndScaleRGBWFunction)(void* pixel_controller, Rgbw rgbw, uint8_t* b0_out, uint8_t* b1_out, uint8_t* b2_out, uint8_t* b3_out);
typedef void (*loadAndScaleRGBFunction)(void* pixel_controller, uint8_t* r_out, uint8_t* g_out, uint8_t* b_out);
#if FASTLED_PIXEL_ITERATOR_HAS_APA102_HD
typedef void (*loadAndScale_APA102_HDFunction)(void* pixel_controller, uint8_t* b0_out, uint8_t* b1_out, uint8_t* b2_out, uint8_t* brightness_out);
#endif
typedef void (*loadAndScale_WS2816_HDFunction)(void* pixel_controller, uint16_t* b0_out, uint16_t* b1_out, uint16_t* b2_out);
typedef void (*stepDitheringFunction)(void* pixel_controller);
typedef void (*advanceDataFunction)(void* pixel_controller);
typedef int (*sizeFunction)(void* pixel_controller);
typedef bool (*hasFunction)(void* pixel_controller, int n);
typedef uint8_t (*globalBrightness)(void* pixel_controller);
typedef void (*getHdScaleFunction)(void* pixel_controller, uint8_t* c0, uint8_t* c1, uint8_t* c2, uint8_t* brightness);


// PixelIterator is turns a PixelController<> into a concrete object that can be used to iterate
// over pixels and transform them into driver data. See PixelController<>::as_iterator() for how
// to create a PixelIterator.
// Note: This is designed for micro-controllers with a lot of memory. DO NOT use this in the core library
// as a PixelIterator consumes a *lot* more instruction data than an instance of PixelController<RGB_ORDER>.
// This iterator is designed for code in src/platforms/**.
class PixelIterator {
  public:
    template<typename PixelControllerT>
    PixelIterator(PixelControllerT* pc, Rgbw rgbw)
         : mPixelController(pc), mRgbw(rgbw) {
      // Manually build up a vtable.
      // Wait... what? Stupid nerds trying to show off how smart they are...
      // Why not just use a virtual function?!
      //
      // Before you think this then you should know that the alternative straight
      // forward way is to have a virtual interface class that PixelController inherits from.
      // ...and that was already tried. And if you try to do this yourself
      // this then let me tell you what is going to happen...
      //
      // EVERY SINGLE PLATFORM THAT HAS A COMPILED BINARY SIZE CHECK WILL IMMEDIATELY
      // FAIL AS THE BINARY BLOWS UP BY 10-30%!!! It doesn't matter if only one PixelController
      // with a vtable is used, gcc seems not to de-virtualize the calls. And we really care
      // about binary size since FastLED needs to run on those tiny little microcontrollers like
      // the Attiny85 (and family) which are in the sub $1 range used for commercial products.
      //
      // So to satisfy these tight memory requirements we make the dynamic dispatch used in PixelIterator
      // an optional zero-cost abstraction which doesn't affect the binary size for platforms that
      // don't use it. So that's why we are using this manual construction of the vtable that is built
      // up using template magic. If your platform has lots of memory then you'll gladly trade
      // a sliver of memory for the convenience of having a concrete implementation of
      // PixelController that you can use without having to make all your driver code a template.
      //
      // Btw, this pattern in C++ is called the "type-erasure pattern". It allows non virtual
      // polymorphism by leveraging the C++ template system to ensure type safety.
      typedef PixelControllerVtable<PixelControllerT> Vtable;
      mLoadAndScaleRGBW = &Vtable::loadAndScaleRGBW;
      mLoadAndScaleRGB = &Vtable::loadAndScaleRGB;
      #if FASTLED_PIXEL_ITERATOR_HAS_APA102_HD
      mLoadAndScale_APA102_HD = &Vtable::loadAndScale_APA102_HD;
      #endif
      mLoadAndScale_WS2816_HD = &Vtable::loadAndScale_WS2816_HD;
      mStepDithering = &Vtable::stepDithering;
      mAdvanceData = &Vtable::advanceData;
      mSize = &Vtable::size;
      mHas = &Vtable::has;
      #if FASTLED_HD_COLOR_MIXING
      mGetHdScale = &Vtable::getHdScale;
      #endif
    }

    bool has(int n) { return mHas(mPixelController, n); }
    void loadAndScaleRGBW(uint8_t *b0_out, uint8_t *b1_out, uint8_t *b2_out, uint8_t *w_out) {
      mLoadAndScaleRGBW(mPixelController, mRgbw, b0_out, b1_out, b2_out, w_out);
    }
    void loadAndScaleRGB(uint8_t *r_out, uint8_t *g_out, uint8_t *b_out) {
      mLoadAndScaleRGB(mPixelController, r_out, g_out, b_out);
    }
    #if FASTLED_PIXEL_ITERATOR_HAS_APA102_HD
    void loadAndScale_APA102_HD(uint8_t *b0_out, uint8_t *b1_out, uint8_t *b2_out, uint8_t *brightness_out) {
      mLoadAndScale_APA102_HD(mPixelController, b0_out, b1_out, b2_out, brightness_out);
    }
    #endif
    void loadAndScale_WS2816_HD(uint16_t *s0_out, uint16_t *s1_out, uint16_t *s2_out) {
      mLoadAndScale_WS2816_HD(mPixelController, s0_out, s1_out, s2_out);
    }
    void stepDithering() { mStepDithering(mPixelController); }
    void advanceData() { mAdvanceData(mPixelController); }
    int size() { return mSize(mPixelController); }

    void set_rgbw(Rgbw rgbw) { mRgbw = rgbw; }
    Rgbw get_rgbw() const { return mRgbw; }

    #if FASTLED_HD_COLOR_MIXING
    void getHdScale(uint8_t* c0, uint8_t* c1, uint8_t* c2, uint8_t* brightness) {
      mGetHdScale(mPixelController, c0, c1, c2, brightness);
    }
    #endif

  private:
    // vtable emulation
    void* mPixelController = nullptr;
    Rgbw mRgbw;
    loadAndScaleRGBWFunction mLoadAndScaleRGBW = nullptr;
    loadAndScaleRGBFunction mLoadAndScaleRGB = nullptr;
    #if FASTLED_PIXEL_ITERATOR_HAS_APA102_HD
    loadAndScale_APA102_HDFunction mLoadAndScale_APA102_HD = nullptr;
    #endif
    loadAndScale_WS2816_HDFunction mLoadAndScale_WS2816_HD = nullptr;
    stepDitheringFunction mStepDithering = nullptr;
    advanceDataFunction mAdvanceData = nullptr;
    sizeFunction mSize = nullptr;
    hasFunction mHas = nullptr;
    #if FASTLED_HD_COLOR_MIXING
    getHdScaleFunction mGetHdScale = nullptr;
    #endif
};

}  // namespace


using PixelIterator = fl::PixelIterator;
