
#pragma once

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

#pragma pack(push, 1)
struct EffectReverseSwipeConfig
{
  float swipeSpeed; // in degrees per second
  float bellCurveWidthInLeds;
  float startPosition;
};
#pragma pack(pop)

template <typename TLedStrip>
class EffectReverseSwipe
{

public:
  static constexpr auto NUM_LEDS = numLeds<TLedStrip>();
  static constexpr int DELAY_MS = 10;

  using ConfigType = EffectReverseSwipeConfig;

  EffectReverseSwipe(const EffectReverseSwipeConfig &cfg, TLedStrip &ledStrip)
      : ledStrip_(ledStrip),
        currentPosition_(float(NUM_LEDS) / 2 + cfg.bellCurveWidthInLeds),
        bellCurveWidth_(cfg.bellCurveWidthInLeds),
        invBellCurveWidth_(1.0f / cfg.bellCurveWidthInLeds),
        speed_(cfg.swipeSpeed / 360 / 1000 * NUM_LEDS * DELAY_MS),
        startPosition_(cfg.startPosition / 360.0f * NUM_LEDS),
        finished_(false)
  {
    for (int i = 0; i < NUM_LEDS; ++i)
      state_[i] = getLedRGBW(ledStrip_, i);
  }

  bool finished() const { return finished_; }

  int operator()()
  {
    if (finished_)
      return 60000;

    const auto width = std::min(int(currentPosition_ + 1), int(NUM_LEDS / 2) + 1);

    {
      float brightness = stepFunction(currentPosition_, bellCurveWidth_, invBellCurveWidth_);
      ColorRGBW &prevC = state_[ledStrip_.normalizeIdx(startPosition_)];
      setLedRGBW(ledStrip_, startPosition_, prevC * brightness);
    }

    for (int i = 1; i < width; ++i)
    {
      const float x = currentPosition_ - float(i);
      if (x > 0.0f)
      {
        const int led1 = startPosition_ + i;
        const int led2 = startPosition_ - i;
        float brightness = stepFunction(x, bellCurveWidth_, invBellCurveWidth_);
        ColorRGBW &prevC1 = state_[ledStrip_.normalizeIdx(led1)];
        ColorRGBW &prevC2 = state_[ledStrip_.normalizeIdx(led2)];

        setLedRGBW(ledStrip_, led1, prevC1 * brightness);
        setLedRGBW(ledStrip_, led2, prevC2 * brightness);
      }
    }

    currentPosition_ -= speed_;
    if (currentPosition_ < 0)
    {
      finished_ = true;
      clear(ledStrip_);
    }

    return DELAY_MS;
  }

private:
  TLedStrip &ledStrip_;
  float currentPosition_;

  float bellCurveWidth_;
  float invBellCurveWidth_;
  float speed_;

  int startPosition_;

  bool finished_;

  ColorRGBW state_[NUM_LEDS];
};

// Traits
template <>
struct EffectIdToConfig<EffectId::REVERSE_SWIPE>
{
  using type = EffectReverseSwipeConfig;
};

template <>
struct EffectConfigToId<EffectReverseSwipeConfig>
{
  static constexpr auto id = EffectId::REVERSE_SWIPE;
};

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