#include <Adafruit_NeoPixel.h>

const byte PIN_NEO = 3;
const byte PIN_HEARTBEAT = 13;

Adafruit_NeoPixel strip = Adafruit_NeoPixel(20, PIN_NEO, NEO_GRBW + NEO_KHZ800);

#define UPDATEINTERVAL 20ul
const unsigned long UpdateMS = UPDATEINTERVAL - 1ul;

// Minska detta för att öka hastigheten (ursprungligen 100)
#define RESOLUTION 20
#define BASEPHASE (PI / 64.0)

struct pixcolor_t
{
  byte Prime;
  unsigned int NumSteps;
  unsigned int Step;
  float StepSize;
  float TubePhase;
  byte MaxPWM;
};

enum pixcolors
{
  RED,
  GREEN,
  BLUE,
  WHITE,
  PIXELSIZE
};

pixcolor_t Pixels[PIXELSIZE];

uint32_t FullWhite = strip.Color(0, 0, 0, 255);
uint32_t FullOff = strip.Color(0, 0, 0, 0);

unsigned long fullWhiteDuration = 600000; // 1 min
unsigned long rainbowDuration = 10000;    // 10 seconds
unsigned long effectStartMillis = 0;

void setup();
void loop();
void rainbowEffect();
void fullWhiteEffect();
byte StepColor(byte Color, float Phi);

void setup()
{
  pinMode(PIN_HEARTBEAT, OUTPUT);
  digitalWrite(PIN_HEARTBEAT, LOW);

  Serial.begin(57600);

  strip.begin();
  strip.show();

  // Lamp test: kör en vit punkt längs med stripens längd
  printf("Lamp test: walking white\r\n");

  strip.setPixelColor(0, FullWhite);
  strip.show();
  delay(50);

  for (int i = 1; i < strip.numPixels(); i++)
  {
    digitalWrite(PIN_HEARTBEAT, HIGH);
    strip.setPixelColor(i - 1, strip.Color(0, 0, 0, 0));
    strip.setPixelColor(i, FullWhite);
    strip.show();
    digitalWrite(PIN_HEARTBEAT, LOW);
    delay(50);
  }

  strip.setPixelColor(strip.numPixels() - 1, strip.Color(0, 0, 0, 0));
  strip.show();
  delay(50);

  // Fyll arrayen, rad för rad
  printf(" ... fill\r\n");

  for (int i = strip.numPixels() - 1; i >= 0; i--)
  { // för varje rad
    digitalWrite(PIN_HEARTBEAT, HIGH);
    for (int j = strip.numPixels() - 1; j >= 0; j--)
    {
      strip.setPixelColor(j, FullWhite);
      strip.show();
      delay(25);
    }
    digitalWrite(PIN_HEARTBEAT, LOW);
  }

  // Rensa till svart, kolumn för kolumn
  printf(" ... clear\r\n");

  // Initialisera färgparametrar
  Pixels[RED].Prime = 3;
  Pixels[GREEN].Prime = 5;
  Pixels[BLUE].Prime = 7;
  Pixels[WHITE].Prime = 11;

  Pixels[RED].MaxPWM = 255;
  Pixels[GREEN].MaxPWM = 255;
  Pixels[BLUE].MaxPWM = 255;
  Pixels[WHITE].MaxPWM = 32;

  for (byte c = 0; c < PIXELSIZE; c++)
  {
    Pixels[c].NumSteps = RESOLUTION * Pixels[c].Prime;
    Pixels[c].Step = (3 * Pixels[c].NumSteps) / 4;
    Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps;
    Pixels[c].TubePhase = Pixels[c].NumSteps * Pixels[c].StepSize;
  }

  // Byt till regnbågen efter att setup har körts
  rainbowEffect();
  effectStartMillis = millis(); // Starta räkningen för regnbågseffekten
}

byte StepColor(byte Color, float Phi)
{
  float Value = (Pixels[Color].MaxPWM / 2.0) * (1.0 + sin(Pixels[Color].Step * Pixels[Color].StepSize + Phi));
  Value = max(Value, 0);
  return byte(Value);
}

void rainbowEffect()
{
  for (int i = 0; i < strip.numPixels(); i++)
  {
    byte Value[PIXELSIZE];
    for (byte c = 0; c < PIXELSIZE; c++)
    {
      Value[c] = StepColor(c, Pixels[c].TubePhase - i * Pixels[c].TubePhase / strip.numPixels());
    }
    uint32_t UniColor = strip.Color(Value[RED], Value[GREEN], Value[BLUE], Value[WHITE]);
    strip.setPixelColor(i, UniColor);
  }
  strip.show();
}

void fullWhiteEffect()
{
  for (int i = 0; i < strip.numPixels(); i++)
  {
    strip.setPixelColor(i, FullWhite);
  }
  strip.show();
  delay(1000); // X11
}

void loop()
{
  static unsigned long MillisThen = 0;

  unsigned long MillisNow = millis();
  if ((MillisNow - MillisThen) > UpdateMS)
  {
    digitalWrite(PIN_HEARTBEAT, HIGH);

    for (byte c = 0; c < PIXELSIZE; c++)
    {
      if (++Pixels[c].Step >= Pixels[c].NumSteps)
      {
        Pixels[c].Step = 0;
      }
    }

    if (MillisNow - effectStartMillis < rainbowDuration)
    {
      // Fortsätt regnbågseffekten
      rainbowEffect();
    }
    else if (MillisNow - effectStartMillis < rainbowDuration + fullWhiteDuration)
    {
      // Starta fullwhite-effekten
      fullWhiteEffect();
    }
    else
    {
      // Återställ starttiden för att starta om cykeln
      effectStartMillis = MillisNow;
    }

    MillisThen = MillisNow;
    digitalWrite(PIN_HEARTBEAT, LOW);
  }
}
