#pragma once

#include "effects/Common.h"
#include "helpers/ColorRGBW.h"
#include "helpers/BellCurve.h"

#pragma pack(push, 1)
struct EffectCircularConfig
{
  float speed; // in degrees per second
  float width; // width in degrees
  ColorRGBW color;
};
#pragma pack(pop)

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

  EffectCircular(const EffectCircularConfig &cfg, TLedStrip &ledStrip)
      : config_(cfg),
        ledStrip_(ledStrip),
        currentPosition_(0),
        widthInLeds_((numLeds(ledStrip) * cfg.width / 360)),
        invWidth_(1.0f / widthInLeds_)
  {
  }

  int operator()()
  {
    int startLed = int(currentPosition_);
    float distDown = currentPosition_ - float(startLed);
    float distUp = 1.f - distDown;

    clear(ledStrip_);
    // center
    setLedRGBW(ledStrip_, startLed,
               config_.color * bellCurveApproximation(distDown, invWidth_));

    // down
    for (int i = 1; i < widthInLeds_ / 2 + 1; ++i)
    {
      setLedRGBW(ledStrip_, startLed - i,
                 config_.color * bellCurveApproximation(distDown + i, invWidth_));
    }

    // up
    for (int i = 1; i < widthInLeds_ / 2 + 1; ++i)
    {
      setLedRGBW(ledStrip_, startLed + i,
                 config_.color * bellCurveApproximation(distUp + i - 1, invWidth_));
    }

    currentPosition_ += config_.speed / 1000 / 360 * NUM_LEDS * DELAY_MS;
    if (currentPosition_ > NUM_LEDS)
      currentPosition_ -= NUM_LEDS;

    return DELAY_MS;
  }

private:
  EffectCircularConfig config_;
  TLedStrip &ledStrip_;
  float currentPosition_; // between 0 and num leds
  int widthInLeds_;
  float invWidth_;
};

// Traits
template <>
struct EffectIdToConfig<EffectId::CIRCULAR>
{
  using type = EffectCircularConfig;
};

template <>
struct EffectConfigToId<EffectCircularConfig>
{
  static constexpr auto id = EffectId::CIRCULAR;
};

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