// https://www.instructables.com/LED-Chase-Effect-Using-an-Arduino/

int potentiometerPin = A7;  // Potentiometer connected to pin A7
int potValue = 0;  // Variable to store potentiometer reading
int minDelay = 50;  // Minimum delay (fastest speed)
int maxDelay = 400;  // Maximum delay (slowest speed)


int leds[] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19};

// https://www.arduino.cc/reference/en/language/variables/utilities/sizeof/
// sizeof(); https://forum.arduino.cc/t/how-do-you-check-the-length-of-an-array/88325/7
int total_leds = sizeof(leds) / sizeof(int); // enter the number of LEDs you want to use here

int ledDelay;
int direction = 1;
int currentLED;
unsigned long changeTime;


int effect = 0;  // Effect selector (0 = Chase, 1 = Bounce, etc.)

unsigned long lastEffectChangeTime = 0;  // To track when to switch effects
unsigned long effectDuration = 6000;    // Effect duration in milliseconds (20 seconds)


const int buttonPin = 0; // Pin for the button
const int buttonPinAuto = 2; // Pin for the button Auto Mode
bool autoMode = true;     // Track if the program is in auto mode
unsigned long buttonPressTime = 0; // Debounce timer
unsigned long buttonAutoPressTime = 0; // Debounce timer
const unsigned long debounceDelay = 800; // Debounce delay time
bool buttonPressed = false; // Track the button state

bool lastButtonState = LOW; // Track the previous state of the manual button
bool lastAutoButtonState = HIGH; // Track the previous state of the auto button


void setup() {// put your setup code here, to run once
  Serial.begin(9600);// initialize serial communication at 9600 bits per second:
#ifdef debug
  Serial.println("Debug Enabled");
#else
  Serial.println("Debug Disabled");
#endif

  for (int i = 0; i < total_leds; i++) {
    pinMode(leds[i], OUTPUT); // declare LEDs as output
  }

  pinMode(buttonPin, INPUT_PULLUP); // Use internal pull-up resistor
  pinMode(buttonPinAuto, INPUT_PULLUP); // Use internal pull-up resistor
  // changeTime = millis();
}

//this function for texting only
void loop1() {
  // Read the potentiometer value
  potValue = analogRead(potentiometerPin);

  // Map the potentiometer value to an appropriate delay range
  ledDelay = map(potValue, 0, 1023, minDelay, maxDelay);

  //if ((millis() - changeTime) > ledDelay) {
  //ledChase(false, false);  // Simple Chase
  //ledChase(true, false);   // Bounce Effect
  //ledChase(true, true);    // Knight Rider Effect
  //simpleChase();
  //bounce();
  //knightRider();
  //twoWayChase3();
  //runningLights();
  //randomBlink();
  //cometTail();
  //waveEffect();
  //stackerEffect();
  // fireworks();
  // changeTime = millis();
  //}
  if (delay_without_delaying(ledDelay)) {
    //knightRider();
    blinkAll();
    //twoWayChase3();
    //ledChase(true, true);
    //randomBlink();
    //runningLights();
  }

}

void loop() {
  // Read the potentiometer value
  potValue = analogRead(potentiometerPin);

  // Map the potentiometer value to an appropriate delay range
  ledDelay = map(potValue, 0, 1023, minDelay, maxDelay);

  // Read the current button states
  bool currentButtonState = digitalRead(buttonPin);         // Manual button
  bool currentAutoButtonState = digitalRead(buttonPinAuto); // Auto button

  // Check for button press to switch to auto mode
  if (currentAutoButtonState == LOW && lastAutoButtonState == HIGH && millis() - buttonAutoPressTime > debounceDelay) {
    autoMode = true; // Switch to auto mode
    Serial.println("Switched to Auto Mode");
    buttonAutoPressTime = millis(); // Update last button press time
  }
  //lastAutoButtonState = currentAutoButtonState; // Update the last state for the auto button

  // Check for button press to switch to manual mode and change effect
  if (currentButtonState == HIGH && lastButtonState == LOW && millis() - buttonPressTime > debounceDelay) {
    // Change effect immediately when button is pressed
    autoMode = false; // Switch to manual mode
    effect++; // Increment effect

    if (effect > 10) {  // Assuming 10 effects (0 to 10)
      effect = 0;
    }

    Serial.print("Manual Mode  ");
    Serial.print("Effect changed to: "); // Print the current effect
    Serial.println(effect);

    buttonPressTime = millis(); // Update last button press time
  }

  // Update the last button state for the next loop
  lastButtonState = currentButtonState;

  // Check if it's time to change the effect automatically in auto mode
  if (autoMode) {
    if ((millis() - lastEffectChangeTime) > effectDuration) {
      effect++;
      if (effect > 10) {  // Assuming 10 effects (0 to 10)
        effect = 0;
      }

      Serial.print("Auto Mode  ");
      Serial.print("Effect changed to: "); // Print the current effect
      Serial.println(effect);

      lastEffectChangeTime = millis();  // Reset effect timer
    }
  }

  // Call the effect based on the current effect number
  if ((millis() - changeTime) > ledDelay) {
    changeTime = millis();

    switch (effect) {
      case 0:
        ledChase(false, false);  // Simple Chase
        break;
      case 1:
        ledChase(true, false);   // Bounce Effect
        break;
      case 2:
        ledChase(true, true);    // Knight Rider Effect
        break;
      case 3:
        twoWayChase3();          // Middle to sides and back
        break;
      case 4:
        blinkAll();              // All LEDs Blink
        break;
      case 5:
        runningLights();         // Alternating Running Lights
        break;
      case 6:
        randomBlink();           // Random LED Blinking
        break;
      case 7:
        cometTail();             // Comet Tail Effect
        break;
      case 8:
        waveEffect();            // Wave Effect
        break;
      case 9:
        stackerEffect();         // Stacking LEDs
        break;
      case 10:
        fireworks();             // Fireworks Effect
        break;
    }
  }
}

void loopOld() {
  // Read the potentiometer value
  potValue = analogRead(potentiometerPin);

  // Map the potentiometer value to an appropriate delay range
  ledDelay = map(potValue, 0, 1023, minDelay, maxDelay);

  // Read the current button states
  bool currentButtonState = digitalRead(buttonPin); // Manual button
  bool currentAutoButtonState = digitalRead(buttonPinAuto); // Auto button

  // Check for button press to switch to manual mode
  if (digitalRead(buttonPinAuto) == LOW) { // Button pressed
    if (millis() - buttonAutoPressTime > debounceDelay) { // Debounce check
      autoMode = true; // Switch to manual mode
      //effect = 0;
      buttonAutoPressTime = millis(); // Update last button press time
    }
  }

  // Check for button press to switch to manual mode
  if (digitalRead(buttonPin) == LOW) { // Button pressed
    if (millis() - buttonPressTime > debounceDelay) { // Debounce check

      autoMode = false; // Switch to manual mode

      effect++; // Change effect immediately
      if (effect > 10) {  // Assuming 10 effects (0 to 10)
        effect = 0;
      }
      Serial.print("Manual Mode  ");
      Serial.print("Effect changed to: "); // Print the current effect
      Serial.println(effect);

      buttonPressTime = millis(); // Update last button press time
    }
  }
  lastButtonState = currentButtonState; // Update the last state for the manual button
  
  // Check if it's time to change the effect automatically in auto mode
  if (autoMode) {
    if ((millis() - lastEffectChangeTime) > effectDuration) {
      effect++;
      if (effect > 10) {  // Assuming 10 effects (0 to 10)
        effect = 0;
      }
      Serial.print("Auto Mode  ");
      Serial.print("Effect changed to: "); // Print the current effect
      Serial.println(effect);

      lastEffectChangeTime = millis();  // Reset effect timer
    }
  }

  // Call the effect based on the current effect number
  if ((millis() - changeTime) > ledDelay) {
    changeTime = millis();

    switch (effect) {
      case 0:
        ledChase(false, false);  // Simple Chase
        break;
      case 1:
        ledChase(true, false);   // Bounce Effect
        break;
      case 2:
        ledChase(true, true);    // Knight Rider Effect
        break;
      case 3:
        twoWayChase3();           // Middle to sides and back
        break;
      case 4:
        blinkAll();              // All LEDs Blink
        break;
      case 5:
        runningLights();         // Alternating Running Lights
        break;
      case 6:
        randomBlink();           // Random LED Blinking
        break;
      case 7:
        cometTail();             // Comet Tail Effect
        break;
      case 8:
        waveEffect();            // Wave Effect
        break;
      case 9:
        stackerEffect();         // Stacking LEDs
        break;
      case 10:
        fireworks();             // Fireworks Effect
        break;
    }
  }
}





void loop3() {
  // Read the potentiometer value
  potValue = analogRead(potentiometerPin);

  // Map the potentiometer value to an appropriate delay range
  ledDelay = map(potValue, 0, 1023, minDelay, maxDelay);

  // Check if it's time to change the effect
  if ((millis() - lastEffectChangeTime) > effectDuration) {
    effect++;
    if (effect > 10) {  // Assuming 5 effects (0 to 4)
      effect = 0;
    }
    lastEffectChangeTime = millis();  // Reset effect timer
  }

  if ((millis() - changeTime) > ledDelay) {
    changeTime = millis();

    switch (effect) {
      case 0:
        ledChase(false, false);  // Simple Chase
        break;
      case 1:
        ledChase(true, false);   // Bounce Effect
        break;
      case 2:
        ledChase(true, true);    // Knight Rider Effect
        break;
      case 3:
        twoWayChase();           // Middle to sides and back
        break;
      case 4:
        blinkAll();              // All LEDs Blink
        break;
      case 5:
        runningLights();         // Alternating Running Lights
        break;
      case 6:
        randomBlink();           // Random LED Blinking
        break;
      case 7:
        cometTail();             // Comet Tail Effect
        break;
      case 8:
        waveEffect();            // Wave Effect
        break;
      case 9:
        stackerEffect();         // Stacking LEDs
        break;
      case 10:
        fireworks();             // Fireworks Effect
        break;
    }
  }
}



void loop2() {
  // Read the potentiometer value
  potValue = analogRead(potentiometerPin);

  // Map the potentiometer value to an appropriate delay range
  ledDelay = map(potValue, 0, 1023, minDelay, maxDelay);

  // Check if it's time to change the effect
  if ((millis() - lastEffectChangeTime) > effectDuration) {
    effect++;
    if (effect > 4) {  // Assuming 5 effects (0 to 4)
      effect = 0;
    }
    lastEffectChangeTime = millis();  // Reset effect timer
  }

  // Execute the current effect
  if ((millis() - changeTime) > ledDelay) {
    changeTime = millis();

    switch (effect) {
      case 0:
        simpleChase();
        break;
      case 1:
        bounce();
        break;
      case 2:
        twoWayChase();
        break;
      case 3:
        blinkAll();
        break;
      case 4:
        knightRider();
        break;
    }
  }
}

void ledChase(bool bounce, bool knightRider) {

  static int direction = 1;  // 1 = forward, -1 = backward
  static int currentLED = 0; // Current LED index

  allLedsOff();  // Turn off all LEDs
  digitalWrite(leds[currentLED], HIGH);  // Light up the current LED

  // Update the current LED based on the direction
  currentLED += direction;

  // Handle bouncing logic
  if (bounce || knightRider) {
    if (currentLED == total_leds || currentLED < 0) {
      direction = -direction;  // Reverse direction
      currentLED += direction; // Adjust the position back into bounds
    }
  } else {
    // For simple chase, reset to the first LED when reaching the end
    if (currentLED == total_leds) {
      currentLED = 0;
    }
  }

  // If it's the Knight Rider effect, allow the LEDs to move inward (similar to bounce)
  if (knightRider && (currentLED == total_leds || currentLED < 0)) {
    direction = -direction;
  }
}


void simpleChase() {
  allLedsOff();
  currentLED += direction;
  if (currentLED == -1) {
    direction = 1;
  }
  else if (currentLED == total_leds) {
    direction = -1;
  }
  digitalWrite(leds[currentLED], HIGH);
}

void bounce() {
  allLedsOff();
  digitalWrite(leds[currentLED], HIGH);
  currentLED += direction;
  if (currentLED == total_leds || currentLED < 0) {
    direction = -direction;  // Reverse direction when hitting the ends
  }
}

void twoWayChase1() {
  allLedsOff();
  for (int i = 0; i < total_leds / 2; i++) {
    digitalWrite(leds[i], HIGH);
    digitalWrite(leds[total_leds - 1 - i], HIGH);
  }
}

void twoWayChase2() {
  allLedsOff();  // Turn all LEDs off

  static int leftSide = total_leds / 2 - 1;  // Start from the middle-left LED
  static int rightSide = total_leds / 2;     // Start from the middle-right LED

  // Turn on the LEDs at leftSide and rightSide
  digitalWrite(leds[leftSide], HIGH);
  digitalWrite(leds[rightSide], HIGH);

  // Move the chase outward
  leftSide--;
  rightSide++;

  // Reset when the chase reaches the edges
  if (leftSide < 0 && rightSide >= total_leds) {
    leftSide = total_leds / 2 - 1;
    rightSide = total_leds / 2;
  }
}

void twoWayChase3() {
  allLedsOff();  // Turn all LEDs off

  static int leftSide = total_leds / 2 - 1;  // Start from the middle-left LED
  static int rightSide = total_leds / 2;     // Start from the middle-right LED
  static int direction = 1;  // 1 = outward, -1 = inward

  // Turn on the LEDs at leftSide and rightSide
  digitalWrite(leds[leftSide], HIGH);
  digitalWrite(leds[rightSide], HIGH);

  // Move based on direction
  leftSide -= direction;
  rightSide += direction;

  // Check if we reached the edges (outward movement)
  if (leftSide < 0 && rightSide >= total_leds) {
    direction = -1;  // Reverse direction, go inward
    leftSide = 0;    // Start from the edges for inward movement
    rightSide = total_leds - 1;
  }

  // Check if we reached the middle (inward movement)
  if (leftSide >= total_leds / 2 - 1 && rightSide <= total_leds / 2) {
    direction = 1;  // Reverse direction, go outward again
    leftSide = total_leds / 2 - 1;  // Reset to the middle for outward movement
    rightSide = total_leds / 2;
  }
}


void twoWayChase() {
  allLedsOff();  // Turn all LEDs off

  static int leftSide = 0;  // Start from the first LED on the left
  static int rightSide = total_leds - 1;  // Start from the last LED on the right

  // Turn on the LEDs at leftSide and rightSide
  digitalWrite(leds[leftSide], HIGH);
  digitalWrite(leds[rightSide], HIGH);

  // Move the chase inward
  leftSide++;
  rightSide--;

  // Reset when they cross over in the middle
  if (leftSide > rightSide) {
    leftSide = 0;
    rightSide = total_leds - 1;
  }
}

void blinkAll() {
  static bool state = false;  // Keeps track of whether the LEDs are on or off
  static unsigned long lastBlinkTime = 0;  // Store the last time the LEDs toggled
  int blinkInterval = ledDelay + 100; // Set a fixed blink interval (500ms = 0.5 seconds)

  // Check if it's time to toggle the LEDs
  if (millis() - lastBlinkTime >= blinkInterval) {
    if (state) {
      allLedsOff();  // Turn all LEDs off
    } else {
      allLedsOn();
      //for (int i = 0; i < total_leds; i++) {
      //  digitalWrite(leds[i], HIGH);  // Turn all LEDs on
      //}
    }

    state = !state;  // Toggle the state for the next call
    lastBlinkTime = millis();  // Update the last blink time
  }
}


void blinkAll2() {
  static bool state = false;  // Keeps track of whether the LEDs are on or off

  if (state) {
    // If state is true, turn all LEDs off
    allLedsOff();
  } else {
    // If state is false, turn all LEDs on
    allLedsOn();

  }

  state = !state;  // Toggle the state for the next call
}


void blinkAll1() {
  static bool on = false;
  static bool off = false;

  if (on) {
    allLedsOn();
  }
  on = !on;
  if (off) {
    allLedsOff();
  }
  off = !off;
}

void knightRider() {
  allLedsOff();
  digitalWrite(leds[currentLED], HIGH);
  currentLED += direction;
  if (currentLED == total_leds || currentLED == -1) {
    direction = -direction;
  }
}

void runningLights() {
  static bool alternate = false;


  allLedsOff();  // Turn all LEDs off
  for (int i = 0; i < total_leds; i += 2) {
    if (alternate) {
      digitalWrite(leds[i], HIGH);  // Light up every other LED
    } else {
      digitalWrite(leds[i + 1], HIGH);  // Invert the pattern
    }
  }
  alternate = !alternate;  // Toggle the pattern

}

void randomBlink() {
  allLedsOff();  // Turn off all LEDs

  //int numLedsToLight = random(1, total_leds);  // Choose how many LEDs to light up
  int numLedsToLight = random(1, 3);  // Choose how many LEDs to light up
  for (int i = 0; i < numLedsToLight; i++) {
    int randomLed = random(0, total_leds);  // Pick a random LED
    digitalWrite(leds[randomLed], HIGH);  // Turn it on
  }
}

void cometTail() {
  static int position = 0;
  int tailLength = 4;  // Number of LEDs in the tail
  float fadeAmount = 0.3;  // Brightness decrease per tail LED

  allLedsOff();  // Turn off all LEDs

  // Light up the head of the comet
  digitalWrite(leds[position], HIGH);

  // Create the tail by lighting previous LEDs with decreasing brightness
  for (int i = 1; i < tailLength; i++) {
    int tailPos = position - i;
    if (tailPos >= 0) {
      analogWrite(leds[tailPos], 255 - (i * fadeAmount * 255));
    }
  }

  // Move the position forward
  position++;
  if (position >= total_leds) {
    position = 0;
  }
}

////////////////////////////////////Stacker (LEDs Filling Up)
void stackerEffect() {
  static int position = 0;
  //allLedsOff();
  if (position < total_leds) {
    digitalWrite(leds[position], HIGH);  // Light up the current LED
    position++;
  } else {
    allLedsOff();  // Reset and turn all LEDs off after it's filled
    position = 0;
  }
}



//////////////////////////////////// Wave (Sinusoidal Motion)
void waveEffect() {
  float speed = 0.2;  // Adjust this to change wave speed
  float amplitude = 127;  // Wave amplitude
  int center = 128;  // Center value for the wave

  for (int i = 0; i < total_leds; i++) {
    int brightness = int(sin(i * speed + millis() / 100.0) * amplitude + center);
    analogWrite(leds[i], brightness);  // Write brightness to each LED
  }
}

///////////////////////////////////Fireworks (Exploding LEDs)
void fireworks() {
  allLedsOff();  // Turn off all LEDs

  // Choose a random "explosion" center
  int centerLed = random(0, total_leds);

  // Light the center LED
  digitalWrite(leds[centerLed], HIGH);

  // Light up adjacent LEDs
  if (centerLed - 1 >= 0) digitalWrite(leds[centerLed - 1], HIGH);
  if (centerLed + 1 < total_leds) digitalWrite(leds[centerLed + 1], HIGH);

  delay(100);  // Short delay for explosion effect
  allLedsOff();  // Clear the LEDs for next explosion
}


//////////////////////////////////////////////////////////////////////////////////////////////

void allLedsOff() {
  for (int x = 0; x < total_leds; x++) {
    digitalWrite(leds[x], LOW);
  }
}

void allLedsOn() {
  for (int x = 0; x < total_leds; x++) {
    digitalWrite(leds[x], HIGH);
  }
}

boolean delay_without_delaying(unsigned long time) {
  // return false if we're still "delaying", true if time ms has passed.
  // this should look a lot like "blink without delay"
  static unsigned long previousmillis = 0;
  unsigned long currentmillis = millis();
  if (currentmillis - previousmillis >= time) {
    previousmillis = currentmillis;
    return true;
  }
  return false;
}