
#pragma once

#include "effects/Common.h"
#include "helpers/ColorRGBW.h"
#include "helpers/ColorHSV.h"
#include "helpers/ColorConversions.h"

#pragma pack(push, 1)
struct EffectRandomTwoColorInterpolationConfig
{
  int32_t cycleDurationMs;
  bool startWithExisting;
  int32_t numSegments;

  bool hue1Random;
  bool hue2Random;

  ColorHSV color1;
  ColorHSV color2;
};
#pragma pack(pop)

template <typename TLedStrip>
class EffectRandomTwoColorInterpolation
{
public:
  static constexpr auto NUM_LEDS = numLeds<TLedStrip>();
  static constexpr int DELAY_MS = 10;

  EffectRandomTwoColorInterpolation(const EffectRandomTwoColorInterpolationConfig &cfg, TLedStrip &ledStrip)
      : ledStrip_(ledStrip),
        config_(cfg),
        progress_(0.0f)
  {
    currentColors_ = arr1;
    nextColors_ = arr2;

    if (config_.startWithExisting)
      for (int i = 0; i < NUM_LEDS; ++i)
        currentColors_[i] = rgb2hsv(getLedRGBW(ledStrip_, i));
    else
      randomizeColors(currentColors_);

    randomizeColors(nextColors_);
  }

  void begin()
  {
    if (config_.startWithExisting)
      for (int i = 0; i < NUM_LEDS; ++i)
        currentColors_[i] = rgb2hsv(getLedRGBW(ledStrip_, i));
  }

  int operator()()
  {
    for (int i = 0; i < NUM_LEDS; ++i)
      setLedRGBW(ledStrip_, i, hsv2rgb(interpolate(currentColors_[i], nextColors_[i], progress_)));

    progress_ += (float(DELAY_MS) / config_.cycleDurationMs);
    if (progress_ > 1)
    {
      progress_ = 0;
      std::swap(currentColors_, nextColors_);
      randomizeColors(nextColors_);
    }
    return DELAY_MS;
  }

private:
  float randomFloat()
  {
    return float(esp_random()) / float(UINT32_MAX);
  }

  ColorHSV randomColor()
  {
    ColorHSV color1 = config_.color1;
    ColorHSV color2 = config_.color2;

    if (config_.hue1Random)
      color1.h = randomFloat() * 360;
    if (config_.hue2Random)
      color2.h = randomFloat() * 360;

    float f = randomFloat();
    return interpolate(color1, color2, f);
  }

  void randomizeColors(ColorHSV *arr)
  {
    int segmentLength = NUM_LEDS / config_.numSegments;
    int lastSegmentLength = NUM_LEDS - (segmentLength * (config_.numSegments - 1));

    ColorHSV firstColor = randomColor();

    ColorHSV currentColor = firstColor;
    ColorHSV nextColor = randomColor();

    int position = random(0, NUM_LEDS);
    const auto incrPosition = [&position]()
    {
      position = (position == NUM_LEDS - 1) ? 0 : position + 1;
    };

    for (int segmentIdx = 0; segmentIdx < config_.numSegments - 1; ++segmentIdx)
    {
      for (int i = 0; i < segmentLength; ++i)
      {
        float f = float(i) / float(segmentLength);
        arr[position] = interpolate(currentColor, nextColor, f);
        incrPosition();
      }
      currentColor = nextColor;
      nextColor = randomColor();
    }
    // last segment
    for (int i = 0; i < lastSegmentLength; ++i)
    {
      float f = float(i) / float(lastSegmentLength);
      arr[position] = interpolate(currentColor, firstColor, f);
      incrPosition();
    }
  }

  TLedStrip &ledStrip_;
  EffectRandomTwoColorInterpolationConfig config_;

  ColorHSV arr1[NUM_LEDS];
  ColorHSV arr2[NUM_LEDS];

  ColorHSV *currentColors_;
  ColorHSV *nextColors_;

  float progress_;
};

// Traits
template <>
struct EffectIdToConfig<EffectId::RANDOM_TWO_COLOR_INTERPOLATION>
{
  using type = EffectRandomTwoColorInterpolationConfig;
};

template <>
struct EffectConfigToId<EffectRandomTwoColorInterpolationConfig>
{
  static constexpr auto id = EffectId::RANDOM_TWO_COLOR_INTERPOLATION;
};

template <typename TLedStrip>
struct EffectIdToClass<EffectId::RANDOM_TWO_COLOR_INTERPOLATION, TLedStrip>
{
  using type = EffectRandomTwoColorInterpolation<TLedStrip>;
};