
#include <FastLED.h>
#include <OneButton.h>

#define LEDsPerStrip 17
#define NumStrips 30
#define NumVertices 20
#define VertexConnections 3
#define NumLEDs  (LEDsPerStrip * NumStrips)

#define LEDPin   3
#define PatPin   5
#define ColPin   4
#define SpdPin   A14
#define HuePin   A15
#define SatPin   A13
#define BriPin   A12

CRGB leds[NumLEDs];
OneButton PatBtn = OneButton(PatPin, false, true);
OneButton ColBtn = OneButton(ColPin, false, true);

int Speed = 0;
int Hue = 0;
int Saturation = 255;
int Brightness = 0;
int Sp = 0;
int Hu = 0;
int Sa = 0;
int Br = 0;

#define NumComets 20
int CometDirection[NumComets];
int CometStart[NumComets];
int CometCounter[NumComets];
int CometLED[NumComets];
int CometColour[NumComets];

#define NumFireworks 1
int FireVertex = 0;
int FireCounter = 0;
int FireSize = 0;
int FireDelay = 0;
int FireColour = 0;
int FireDirection[VertexConnections];
int FireStart[VertexConnections];
int FireLED[VertexConnections];

int t = 0;
int Marquee = 1;
int Game = 0;
int PacManDirection[5];
int PacManStart[5];
int PacManCounter[5];
int PacManLED[5];
int PacManColour[] = {40, 0, 19, 135, 220};

int ChristmasColour[][3] = {
    {   0,  96,   0},
    { 255, 255, 0}};

int ReadAccuracy = 3;
int PatMode = 6 ;
int ColMode = 0;
int NumPatternModes = 7;
int NumColourModes = 2;

int VertexArray[][NumVertices] = {
    { 84,  85,   0},  // A
    { 16,  17, 152},  // B
    {101, 102, 339},  // C
    { 67,  68, 305},  // D
    { 50,  51, 254},  // E
    { 33,  34, 203},  // F
    {135, 136, 153},  // G
    {118, 119, 390},  // H
    {322, 323, 340},  // I
    {288, 289, 306},  // J
    {271, 272, 492},  // K
    {237, 238, 255},  // L
    {186, 187, 204},  // M
    {169, 170, 424},  // N
    {373, 374, 391},  // O
    {356, 357, 509},  // P
    {475, 476, 493},  // Q
    {220, 221, 458},  // R
    {407, 408, 425},  // S
    {441, 442, 459}   // T
  };

void setup() {
  FastLED.addLeds<WS2812B, LEDPin, GRB>(leds, NumLEDs);
  Serial.begin(9600);
  
  Speed       = analogRead(SpdPin) / 4;
  Hue         = analogRead(HuePin) / 4;
  Saturation  = analogRead(SatPin) / 4;
  Brightness  = analogRead(BriPin) / 4;

  for(int i = 0; i < NumComets; i++){
    CometStart[i] = i * NumLEDs / NumComets;
    CometCounter[i] = (i * NumLEDs / NumComets) % LEDsPerStrip;
    CometDirection[i] = 1;
    CometLED[i] = i * NumLEDs / NumComets;
    CometColour[i] = (i * 270 / NumComets) %255;
  }

  FireVertex = random(0, NumVertices);
  FireSize = random(3, LEDsPerStrip);
  FireDelay = random(0, 20);
  for(int i = 0; i < VertexConnections; i++){
    FireStart[i] = VertexArray[FireVertex][i];
    FireLED[i] = VertexArray[FireVertex][i];
    if ((FireStart[i] % LEDsPerStrip) == 0)  {
      FireDirection[i] = 1;  }
    else  {
      FireDirection[i] = -1;  }
    }

  fill_solid( leds, NumLEDs, CRGB::Black);
  FastLED.show();
  delay(500);
  for(int i = 0; i < 30; i++){
    fill_solid( leds, NumLEDs, CHSV(Hue,Saturation,(Brightness*i)/30));
    FastLED.show();
    delay(50);
  }
}



void loop() {
  
  PatBtn.attachClick(NextPatMode);
  ColBtn.attachClick(NextColMode);
  PatBtn.tick();
  ColBtn.tick();
  FastLED.show();

  if(ColMode == 0){
    ColourMode0();}
  if(ColMode == 1){
    ColourMode1();}
  if(PatMode == 0){
    PatternMode0();}
  if(PatMode == 1){
    PatternMode1();}
  if(PatMode == 2){
    PatternMode2();}
  if(PatMode == 3){
    PatternMode3();}
  if(PatMode == 4){
    PatternMode4(); Game = 0;}
  if(PatMode == 5){
    PatternMode5();}
  if(PatMode == 6){
    PatternMode6();}
}


// ==================== COLOUR MODE 0 - POTSELECT ====================//

void ColourMode0()  {

  Sp = analogRead(SpdPin) / 4;
  Hu = analogRead(HuePin) / 4;
  Sa = analogRead(SatPin) / 4;
  Br = analogRead(BriPin) / 4;
  if( Sp % ReadAccuracy == 0){ Speed = Sp;}
  if( Hu % ReadAccuracy == 0){ Hue = Hu;}
  if( Sa % ReadAccuracy == 0){ Saturation = Sa;}
  if( Br % ReadAccuracy == 0){ Brightness = Br;}
}


// ==================== COLOUR MODE 1 - RAINBOW ====================//

void ColourMode1()  {

  Sp = analogRead(SpdPin) / 4;
  Br = analogRead(BriPin) / 4;
  if( Sp % ReadAccuracy == 0){ Speed = Sp;}
  if( Br % ReadAccuracy == 0){ Brightness = Br;}
  Saturation = 255;
  Hue = (Hue + 1) %254;
}


// ==================== PATTERN MODE 0 - STATIC ====================//

void PatternMode0() {
  
  fill_solid( leds, NumLEDs, CHSV(Hue,Saturation,Brightness));
}


// ==================== PATTERN MODE 1 - SPARKLE ====================//

void PatternMode1() {

  int n = random(0, NumLEDs);
  int x = (Hue - random(-10, 10)) % 255;
  int y = Saturation - random(0, 70);
  int z = Brightness - random(0, 80);
  if  (y < 0) { y = 0;}
  if  (z < 0) { z = 0;}
  
  leds[n] = CHSV(x, y, z);
  fadeToBlackBy(leds, NumLEDs,15); 
}


// ==================== PATTERN MODE 2 - COMETS ====================//

void PatternMode2() {

  for(int x = 0; x < NumComets; x++){
    if(ColMode == 0)  {
      leds[CometLED[x]] = CHSV(Hue,Saturation,Brightness);
    }
    if(ColMode == 1)  {
      leds[CometLED[x]] = leds[CometLED[x]] + CHSV(CometColour[x], Saturation, Brightness);
    }
    
    if(CometCounter[x] < (LEDsPerStrip - 1))  {
      CometLED[x] = CometLED[x] + CometDirection[x];
    }
    else {
      int RandPick = random(1,VertexConnections);
      for(int j = 0; j < VertexConnections; j++){
        for(int k = 0; k < 20; k++){
          if(CometLED[x] == VertexArray[k][j]){
          CometStart[x] = VertexArray[k][((j + RandPick ) % VertexConnections)];
          }
        }
      }
      CometLED[x] = CometStart[x];
      if((CometLED[x] % LEDsPerStrip) == 0){
        CometDirection[x] = 1;
      }
      else {
        CometDirection[x] = -1;
      }
    }
    
    CometCounter[x] = (CometCounter[x] + 1) % LEDsPerStrip;
  }
  
  fadeToBlackBy(leds, NumLEDs,50);
  FastLED.show();
}


// ==================== PATTERN MODE 3 - FIREWORKS ====================//

void PatternMode3() {

  if  (FireCounter < FireSize) {
   for(int x = 0; x < VertexConnections; x++){
      if(ColMode == 0)  {
        leds[FireLED[x]] = CHSV(Hue,Saturation,Brightness);
      }
      if(ColMode == 1)  {
        leds[FireLED[x]] = CHSV(FireColour,Saturation,Brightness);
      }
      FireLED[x] = FireLED[x] + FireDirection[x];
    }
    FireCounter = FireCounter + 1;
    fadeToBlackBy(leds, NumLEDs,70);
    FastLED.show();
  }
  
  else  {
    FireCounter = FireCounter + 1;
    fadeToBlackBy(leds, NumLEDs,70);
    FastLED.show();
    delay(50);
  }
  
  if (FireCounter > (FireSize + FireDelay)){
    FireVertex = random(0, NumVertices);
    FireSize = random(3, LEDsPerStrip);
    FireDelay = random(0, 20);
    FireColour = random8();
    for(int x = 0; x < VertexConnections; x++){
      FireStart[x] = VertexArray[FireVertex][x];
      FireLED[x] = VertexArray[FireVertex][x];
      if ((FireStart[x] % LEDsPerStrip) == 0)  {
        FireDirection[x] = 1;  }
      else  {
        FireDirection[x] = -1;  }
      }
    FireCounter = 0;
    delay(50);
  }
}


// ==================== PATTERN MODE 4 - MARQUEE ====================//

void PatternMode4() {

  if((t % 30) == 0)  {
    for(int i = 0; i < NumLEDs; i++){
      if((i % 2) == Marquee)  {
        leds[i] = CHSV(Hue,Saturation,Brightness);
      }
      else  {
        leds[i] = CRGB::Black;
      }
    }
    Marquee = (Marquee + 1) % 2;
    FastLED.show();
  }
  t = t + 1;
}


// ==================== PATTERN MODE 5 - PACMAN ====================//

void PatternMode5() {

  if  (Game == 0) {
    SetPacMan();
  }

  // PACMAN -----------------------------------------
  leds[PacManLED[0]] = CRGB::Black;
  PacManLED[0] = PacManLED[0] + PacManDirection[0];
  leds[PacManLED[0]] = CHSV(PacManColour[0],255,Brightness);
  PacManCounter[0] = PacManCounter[0] + 1;

  if (PacManCounter[0] == (LEDsPerStrip - 1)) {
    int RandPick = random(0,VertexConnections);
    for(int j = 0; j < VertexConnections; j++){
      for(int k = 0; k < 20; k++){
        if(PacManLED[0] == VertexArray[k][j]){
        PacManStart[0] = VertexArray[k][((j + RandPick ) % VertexConnections)];
        }
      }
    }
    PacManLED[0] = PacManStart[0];
    if((PacManLED[0] % LEDsPerStrip) == 0){
      PacManDirection[0] = 1;
    }
    else {
      PacManDirection[0] = -1;
    }
    PacManCounter[0] = 0;
  }

  if  ((Game % 2) == 0) {
    // GHOSTS -------------------------------------------
    for(int i = 1; i < 5; i++){
      leds[PacManLED[i]] = CRGB::Black;
      PacManLED[i] = PacManLED[i] + PacManDirection[i];
      leds[PacManLED[i]] = CHSV(PacManColour[i],255,Brightness);
      PacManCounter[i] = PacManCounter[i] + 1;
      
    for(int i = 1; i < 5; i++){
        if  (PacManLED[i] == PacManLED[0])  {
          DiePacMan();
        }
      }
      
      if (PacManCounter[i] == (LEDsPerStrip - 1)) {
        int RandPick = random(1,VertexConnections);
        for(int j = 0; j < VertexConnections; j++){
          for(int k = 0; k < 20; k++){
            if(PacManLED[i] == VertexArray[k][j]){
            PacManStart[i] = VertexArray[k][((j + RandPick ) % VertexConnections)];
            }
          }
        }
        PacManLED[i] = PacManStart[i];
        if((PacManLED[i] % LEDsPerStrip) == 0){
          PacManDirection[i] = 1;
        }
        else {
          PacManDirection[i] = -1;
        }
        PacManCounter[i] = 0;
      }
    }
  }

  //STAGE
  for(int i = 0; i < NumVertices; i++){
    for(int j = 0; j < VertexConnections; j++){
      leds[VertexArray[i][j]] = CHSV(165,255,Brightness/2);
    }
  }
  
  Game = Game + 1;
  FastLED.show();
  Serial.println(PacManCounter[0]);
  delay(70);
}


// ==================== PATTERN MODE 6 - CHRISTMAS ====================//

void PatternMode6() {

  int n = random(0, NumLEDs);
  int x = random(0, 3);
  
  leds[n] = CHSV(ChristmasColour[0][x], ChristmasColour[1][x], Brightness);
  fadeToBlackBy(leds, NumLEDs,15); 
}


// ==================== MODE CHANGER ====================//

void NextPatMode()  {
  
  PatMode = (PatMode + 1) %NumPatternModes;
}

void NextColMode()  {
  
  ColMode = (ColMode + 1) %NumColourModes;
}


// ==================== MISCELLANEOUS ====================//

void SetPacMan()   {

  ResetPlayers();
  
  FastLED.clear();
  for(int i = 0; i < NumLEDs; i++){
    if((i % 2) == 0)  {
      leds[i] = CHSV(10,80,(Brightness/10));
    }
    else  {
      leds[i] = CRGB::Black;
    }
  }
  for(int i = 0; i < NumVertices; i++){
    for(int j = 0; j < VertexConnections; j++){
      leds[VertexArray[i][j]] = CHSV(165,255,Brightness/2);
    }
  }
}


void DiePacMan()   {

  int Flash = 1;
  for(int i = 0; i < 5; i++){
    leds[PacManLED[i]] = CRGB::Black;
    FastLED.show();
  }
  ResetPlayers();
  
  for(int j = 0; j < 6; j++){
    for(int i = 0; i < NumVertices; i++){
      for(int j = 0; j < VertexConnections; j++){
        leds[VertexArray[i][j]] = CHSV(0,255,Flash * (Brightness/2));
      }
    }
    FastLED.show();
    delay(300);
    Flash = (Flash + 1) % 2;
  }
}

void ResetPlayers() {

  for(int i = 1; i < 5; i++){
    int RandPick = random(0,3);
    PacManStart[i] = VertexArray[19][RandPick];
    PacManCounter[i] = 0;
    PacManLED[i] = PacManStart[i];
  }
  int RandPick = random(0,3);
  PacManStart[0] = VertexArray[0][RandPick];
  PacManCounter[0] = 0;
  PacManLED[0] = PacManStart[0];
  for(int i = 0; i < 5; i++){
    if ((PacManStart[i] % LEDsPerStrip) == 0)  {
      PacManDirection[i] = 1;  }
    else  {
      PacManDirection[i] = -1;  }
  }
}
