/*
  --------------- by JediRick -----------------
  boolean        (8 bit)   -  [true/false]
  byte           (8 bit)   -  [0-255] unsigned number
  char           (8 bit)   -  [-128 to 127] signed number
  unsigned char  (8 bit)   -  [-128 to 127] signed number
  word           (16 bit)  -  [0-65535] unsigned number
  unsigned int   (16 bit)  -  [0-65535] unsigned number
  int            (16 bit)  -  [-32768 to 32767] signed number
  unsigned long  (32 bit)  -  [0-4,294,967,295] unsigned number usually for millis
  long           (32 bit)  -  [-2,147,483,648 to 2,147,483,647] signed number
  float          (32 bit)  -  [-3.4028235E38 to 3.4028235E38] signed number
  uint8_t        (8 bit)   -  [0-255] unsigned number
  int8_t         (8 bit)   -  [-127 - 127] signed number
  uint16_t       (16 bit)  -  [0-65,535] unsigned number
  int16_t        (16 bit)  -  [-32,768 - 32,767] signed number
  uint32_t       (32 bit)  -  [0-4,294,967,295] unsigned number
  int32_t        (32 bit)  -  [-2,147,483,648 - 2,147,483,647] signed number
  uint64_t       (64 bit)  -  [0-18,446,744,073,709,551,615] unsigned number
  int64_t        (64 bit)  -  [−9,223,372,036,854,775,808 - 9,223,372,036,854,775,807] signed number
  --------------------------------------------
*/
uint8_t redPins[6] = {2, 5, 8, 11, 14, 17};
uint8_t greenPins[6] = {3, 6, 9, 12, 15, 18};
uint8_t bluePins[6] = {4, 7, 10, 13, 16, 19};
uint8_t blackPins[6] = {20, 21, 22, 26, 27, 28};
// Animation state
uint8_t currentAnimation = 0; // 0: Sparkle, 1: Color Wipe, 2: Theater Chase, 3: Rainbow Chase, 4: Cylon, 5: White Twinkle
unsigned long lastAnimationSwitchMs = 0;
const unsigned long animationDurationMs = 7000; // switch every 7 seconds
bool randomCycleMode = true; // true: button 0 enables random timer cycling; false: button 1 locks a fixed animation
// Button state (GPIO 0 and 1)
const uint8_t buttonPins[2] = {0, 1};
const unsigned long debounceMs = 25;
bool buttonStable[2] = {true, true}; // using INPUT_PULLUP, HIGH=not pressed
bool buttonLastRead[2] = {true, true};
unsigned long buttonLastChangeMs[2] = {0, 0};
bool pollButtonPressed(uint8_t idx) {
  bool reading = digitalRead(buttonPins[idx]);
  if (reading != buttonLastRead[idx]) {
    buttonLastChangeMs[idx] = millis();
    buttonLastRead[idx] = reading;
  }
  if ((millis() - buttonLastChangeMs[idx]) > debounceMs) {
    if (reading != buttonStable[idx]) {
      buttonStable[idx] = reading;
      // Active-low press detection
      if (buttonStable[idx] == LOW) return true;
    }
  }
  return false;
}
// Utility helpers
void setAllColorsHigh() {
  for (uint8_t i = 0; i <= 5; i++) {
    digitalWrite(redPins[i], HIGH);
    digitalWrite(greenPins[i], HIGH);
    digitalWrite(bluePins[i], HIGH);
  }
}
void setAllBlackLow() {
  for (uint8_t i = 0; i <= 5; i++) {
    digitalWrite(blackPins[i], LOW);
  }
}
void activateLayer(uint8_t layerIdx) {
  for (uint8_t i = 0; i <= 5; i++) {
    digitalWrite(blackPins[i], (i == layerIdx) ? HIGH : LOW);
  }
}
void setColorAt(uint8_t colorIdx, uint8_t ledIdx, uint8_t level) {
  // colorIdx: 0=R, 1=G, 2=B | level: LOW=on (sink), HIGH=off
  if (colorIdx == 0) digitalWrite(redPins[ledIdx], level);
  else if (colorIdx == 1) digitalWrite(greenPins[ledIdx], level);
  else digitalWrite(bluePins[ledIdx], level);
}
void setRGBAt(uint8_t ledIdx, bool rOn, bool gOn, bool bOn) {
  digitalWrite(redPins[ledIdx], rOn ? LOW : HIGH);
  digitalWrite(greenPins[ledIdx], gOn ? LOW : HIGH);
  digitalWrite(bluePins[ledIdx], bOn ? LOW : HIGH);
}
void setup() {
  delay(1000);
  Serial1.begin(115200);
  while (!Serial1) {
    delay(100); // wait for serial port to connect. Needed for native USB
  }
  Serial1.println("Hello, Serial1 Monitor!");
  for (uint8_t i = 0; i <= 5; i++) {    
    pinMode(redPins[i], OUTPUT);
    pinMode(greenPins[i], OUTPUT);
    pinMode(bluePins[i], OUTPUT);
    pinMode(blackPins[i], OUTPUT);
  }
  // Buttons with internal pull-ups (active-low)
  pinMode(buttonPins[0], INPUT_PULLUP);
  pinMode(buttonPins[1], INPUT_PULLUP);
  setAllColorsHigh();
  setAllBlackLow();
  Serial1.println("Startup: random mode enabled; timer-based switching active.");
}
void loop() {  
  // Switch animations on a timer only in random cycle mode
  unsigned long nowMs = millis();
  if (randomCycleMode && (nowMs - lastAnimationSwitchMs >= animationDurationMs)) {
    currentAnimation = random(6);
    lastAnimationSwitchMs = nowMs;
  }
  // Button handling (manual override)
  if (pollButtonPressed(0)) { // Enable random timed animations
    randomCycleMode = true;
    lastAnimationSwitchMs = millis();
    Serial1.print("Button 0: random mode enabled; switching every ");
    Serial1.print(animationDurationMs);
    Serial1.println(" ms.");
  }
  if (pollButtonPressed(1)) { // Set/advance permanent fixed animation
    if (randomCycleMode) {
      randomCycleMode = false;
      currentAnimation = 0; // first press: set animation 0 permanently
      Serial1.println("Button 1: fixed mode set. Animation = 0");
    } else {
      currentAnimation = (currentAnimation + 1) % 6; // subsequent presses: next animation
      Serial1.print("Button 1: fixed mode next. Animation = ");
      Serial1.println(currentAnimation);
    }
    lastAnimationSwitchMs = millis();
  }
  // Run current animation frame
  if (currentAnimation == 0) {
    // Sparkle
    for (uint8_t n = 0; n < 8; n++) {
      uint8_t layer = random(6);
      uint8_t color = random(3);
      uint8_t idx = random(6);
      activateLayer(layer);
      setColorAt(color, idx, LOW);
      delay(30);
      setColorAt(color, idx, HIGH);
    }
  } else if (currentAnimation == 1) {
    // Color wipe across indices for each color
    for (uint8_t color = 0; color < 3; color++) {
      for (uint8_t i = 0; i <= 5; i++) {
        activateLayer(i);
        setColorAt(color, i, LOW);
        delay(70);
        setColorAt(color, i, HIGH);
      }
    }
  } else {
    // Theater chase style: light every 3rd index per phase
    uint8_t color = random(3);
    for (uint8_t phase = 0; phase < 3; phase++) {
      for (uint8_t i = 0; i <= 5; i++) {
        activateLayer(i);
        for (uint8_t j = 0; j <= 5; j++) {
          bool on = ((j + phase) % 3) == 0;
          setColorAt(color, j, on ? LOW : HIGH);
        }
        delay(60);
        for (uint8_t j = 0; j <= 5; j++) setColorAt(color, j, HIGH);
      }
    }
  }
  // Additional animations
  if (currentAnimation == 3) {
    // Rainbow Chase: rotate a rainbow palette across indices for each layer
    static uint8_t offset = 0;
    const uint8_t steps = 12; // multiple frames per loop
    for (uint8_t s = 0; s < steps; s++) {
      for (uint8_t layer = 0; layer <= 5; layer++) {
        activateLayer(layer);
        for (uint8_t j = 0; j <= 5; j++) {
          uint8_t k = (j + offset) % 6;
          // Palette: R, G, B, Y, C, M
          if (k == 0) setRGBAt(j, true, false, false);
          else if (k == 1) setRGBAt(j, false, true, false);
          else if (k == 2) setRGBAt(j, false, false, true);
          else if (k == 3) setRGBAt(j, true, true, false);
          else if (k == 4) setRGBAt(j, false, true, true);
          else setRGBAt(j, true, false, true);
        }
        delay(40);
        for (uint8_t j = 0; j <= 5; j++) setRGBAt(j, false, false, false);
      }
      offset = (offset + 1) % 6;
    }
  } else if (currentAnimation == 4) {
    // Cylon scanner on a single layer with a random color
    uint8_t layer = random(6);
    uint8_t color = random(3);
    activateLayer(layer);
    for (int8_t j = 0; j <= 5; j++) {
      setColorAt(color, j, LOW);
      delay(70);
      setColorAt(color, j, HIGH);
    }
    for (int8_t j = 4; j >= 1; j--) {
      setColorAt(color, j, LOW);
      delay(70);
      setColorAt(color, j, HIGH);
    }
  } else if (currentAnimation == 5) {
    // White twinkle bursts across random layers and indices
    for (uint8_t n = 0; n < 10; n++) {
      uint8_t layer = random(6);
      uint8_t idx = random(6);
      activateLayer(layer);
      setRGBAt(idx, true, true, true);
      delay(50);
      setRGBAt(idx, false, false, false);
      delay(20);
    }
  }
}
void selectBlack() {
  uint8_t pinRandoBlack = random(6);
  digitalWrite(blackPins[pinRandoBlack], HIGH);
  pinRandoBlack = random(6);
  digitalWrite(blackPins[pinRandoBlack], LOW);
}
void lightLED() {
  uint8_t pinRando = random(20);
  digitalWrite(pinRando, LOW);
  pinRando = random(20);
  digitalWrite(pinRando, HIGH);    
}
void generateLights() {
  uint8_t countRando = random(1,5);
  for (uint8_t w = 0; w <= countRando; w++) {
    selectBlack();
    lightLED();
  }
}