#pragma once

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

#pragma pack(push, 1)
struct EffectAlexaSwipeConfig
{
  float primaryColorWidth; // in degrees
  float transitionWidth;
  float swipeSpeed; // in degrees per second
  float bellCurveWidthInLeds;
  float startPosition;
  bool forward;
  ColorRGBW primaryColor;
  ColorRGBW secondaryColor;
};
#pragma pack(pop)

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

  using ConfigType = EffectAlexaSwipeConfig;

  EffectAlexaSwipe(const EffectAlexaSwipeConfig &cfg, TLedStrip &ledStrip)
      : ledStrip_(ledStrip),
        currentPosition_(0),
        transitionWidth_(cfg.transitionWidth / 360.0f * NUM_LEDS),
        invTransitionWidth_(1.0f / transitionWidth_),
        primaryColorWidth_(cfg.primaryColorWidth / 360.0f * NUM_LEDS + 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),
        primaryColor_(rgb2hsv(cfg.primaryColor)),
        secondaryColor_(rgb2hsv(cfg.secondaryColor)),
        finished_(false)
  {
    if (cfg.forward)
    {
      currentPosition_ = 0;
      direction_ = 1;
    }
    else
    {
      currentPosition_ = float(NUM_LEDS) / 2.0f + bellCurveWidth_ / 2;
      direction_ = -1;
    }
  }

  bool finished() const { return finished_; }

  int operator()()
  {
    clear(ledStrip_);

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

    setLedRGBW(ledStrip_, startPosition_, getColor(currentPosition_));
    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;
        const ColorRGBW color = getColor(x);
        setLedRGBW(ledStrip_, led1, color);
        setLedRGBW(ledStrip_, led2, color);
      }
    }
    currentPosition_ += direction_ * speed_;
    const auto maxPosition = float(NUM_LEDS) / 2.0f + bellCurveWidth_ / 2;
    const auto minPosition = 0.0f;
    currentPosition_ = std::min(currentPosition_, maxPosition);
    currentPosition_ = std::max(currentPosition_, minPosition);
    if (currentPosition_ <= minPosition || currentPosition_ >= maxPosition)
      finished_ = true;

    return DELAY_MS;
  }

private:
  void getParams(float x, float &interpFac, float &brightness)
  {
    brightness = stepFunction(x, bellCurveWidth_, invBellCurveWidth_);
    if (x < primaryColorWidth_)
      interpFac = 0.0f;
    else if (x > primaryColorWidth_ + transitionWidth_)
      interpFac = 1.0f;
    else
      interpFac = (x - primaryColorWidth_) * invTransitionWidth_;
  }

  // x is positive distance from running front
  ColorRGBW getColor(float x)
  {
    float interpFac;
    float brightness;
    getParams(x, interpFac, brightness);
    ColorHSV result{
        interpFac * secondaryColor_.h + (1.0f - interpFac) * primaryColor_.h,
        interpFac * secondaryColor_.s + (1.0f - interpFac) * primaryColor_.s,
        interpFac * secondaryColor_.v + (1.0f - interpFac) * primaryColor_.v};
    result.v *= brightness;
    return hsv2rgb(result);
  }

  TLedStrip &ledStrip_;

  // in number of leds
  float currentPosition_;
  float transitionWidth_;
  float invTransitionWidth_;
  float primaryColorWidth_;
  float bellCurveWidth_;
  float invBellCurveWidth_;
  float speed_;

  int startPosition_;

  const ColorHSV primaryColor_;
  const ColorHSV secondaryColor_;
  float direction_;

  bool finished_;
};

// Traits
template <>
struct EffectIdToConfig<EffectId::ALEXA_SWIPE>
{
  using type = EffectAlexaSwipeConfig;
};

template <>
struct EffectConfigToId<EffectAlexaSwipeConfig>
{
  static constexpr auto id = EffectId::ALEXA_SWIPE;
};

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