#include <FastLED.h>
#include <LiquidCrystal.h>
#include <EEPROM.h>

#define NUM_LEDS 29
#define RIGHT_LED_PIN 11
#define LEFT_LED_PIN 12
#define RIGHT_INDICATOR_PIN A0
#define LEFT_INDICATOR_PIN A1
#define BRAKE_PIN A2
#define RUNNING_LIGHTS_PIN A3
#define THRESHOLD_VOLTAGE 800
#define RS_PIN 10
#define ENABLE_PIN 9
#define SCREEN7_PIN 7
#define SCREEN6_PIN 6
#define SCREEN5_PIN 5
#define SCREEN4_PIN 4
#define BTN_UP 3
#define BTN_DOWN 2
#define ROTARY_ENCODER_SW  0
#define ROTARY_ENCODER_CLK 8
#define ROTARY_ENCODER_DT  1

LiquidCrystal lcd(RS_PIN, ENABLE_PIN, SCREEN4_PIN, SCREEN5_PIN, SCREEN6_PIN, SCREEN7_PIN);
CRGB leftLeds[NUM_LEDS];
CRGB rightLeds[NUM_LEDS];
bool rightIndicatorOn = false;
bool pastRightIndicatorOn = false;
bool leftIndicatorOn = false;
bool pastLeftIndicatorOn = false;
uint8_t endIndexRight = 0;
uint8_t endIndexLeft = 0;
static unsigned long lastUpdate = 0;
static unsigned long blinkerLeftLastUpdate = 0;
static unsigned long blinkerRightLastUpdate = 0;
static unsigned long blinkersOffTimerLeft = -1;
static unsigned long blinkersOffTimerRight = -1;
uint8_t indicatorDelay = 10; // user modifiable : blinker's speed
uint8_t hazardDelay = 10; // user modifiable : hazard lights speed
int blinkersDelay = 700; // user modifiable : delay turning off the blinker
static unsigned long blinkersOffDelay = 100; // user modifiable : fix weird glitch when car does [blinker on, blinker off, quickly blinker on, quickly blinker off]
int startupAnimationVal = 1; //user modifiable : change the startup animation
int startupAnimationSpeed = 10; // user modifiable
int animationTileSize = 3; // user modifiable /!\ a large size or a size inferior to 1 can breake (not tested)
String valNames[] = {"indicatorDelay", "hazardDelay", "blinkersDelay", "blinkersOffDelay", "startupAnimation", "animationSpeed", "tileSize"};
int screenVarPtr = 0;
int upBtnPressed = 0;
int downBtnPressed = 0;
int lastSW;
int lastCLK;
static unsigned long lastSave = 0;
uint8_t saveDelay = 1000;

void setup() {
  // put your setup code here, to run once:
  lcd.begin(16,2);
  lcd.print(valNames[screenVarPtr]);
  displayScreenVar();
  FastLED.addLeds<WS2812, LEFT_LED_PIN, GRB>(leftLeds, NUM_LEDS);
  FastLED.addLeds<WS2812, RIGHT_LED_PIN, GRB>(rightLeds, NUM_LEDS);
  FastLED.setBrightness(255);

  pinMode(RIGHT_INDICATOR_PIN, INPUT);
  pinMode(LEFT_INDICATOR_PIN, INPUT);
  pinMode(BRAKE_PIN, INPUT);
  pinMode(RUNNING_LIGHTS_PIN, INPUT);
  pinMode(BTN_UP, INPUT_PULLUP);
  pinMode(BTN_DOWN, INPUT_PULLUP);

  pinMode(ROTARY_ENCODER_SW, INPUT_PULLUP);
  pinMode(ROTARY_ENCODER_DT, INPUT);
  pinMode(ROTARY_ENCODER_CLK, INPUT);
  lastSW  = digitalRead(ROTARY_ENCODER_SW);
  lastCLK = digitalRead(ROTARY_ENCODER_CLK);

  //Serial.begin(9600);

  uint8_t test = 0;

  EEPROM.get(0, test);

  //Serial.println(test);
  
  if(test != 197){
    uint8_t myNum = 197;
    EEPROM.put(0, myNum);
    EEPROM.put(1, indicatorDelay);
    EEPROM.put(2, hazardDelay);
    EEPROM.put(3, blinkersDelay);
    EEPROM.put(5, startupAnimationVal);
    EEPROM.put(7, startupAnimationSpeed);
    EEPROM.put(9, animationTileSize);
    EEPROM.put(11, blinkersOffDelay);
  } else {
    EEPROM.get(1, indicatorDelay);
    EEPROM.get(2, hazardDelay);
    EEPROM.get(3, blinkersDelay);
    EEPROM.get(5, startupAnimationVal);
    EEPROM.get(7, startupAnimationSpeed);
    EEPROM.get(9, animationTileSize);
    EEPROM.get(11, blinkersOffDelay);
  }

  startupAnimation(startupAnimationVal); // user modifiable : comment to disable startup animation, change number to change animation (if multiple animation exist)

}

void loop() {
  // put your main code here, to run repeatedly:
  
  updateScreen();
  
  for(int i=0; i<NUM_LEDS; ++i){
    rightLeds[i].nscale8(255); // Full brightness
    leftLeds[i].nscale8(255); // Full brightness
  }
  

  int rightIndicatorVoltage = analogRead(RIGHT_INDICATOR_PIN);
  int leftIndicatorVoltage = analogRead(LEFT_INDICATOR_PIN);
  int brakeVoltage = analogRead(BRAKE_PIN);
  int runningLightsVoltage = analogRead(RUNNING_LIGHTS_PIN);

  /*Serial.print("voltage right : ");
  Serial.println(rightIndicatorVoltage);
  Serial.print("voltage left : ");
  Serial.println(leftIndicatorVoltage);*/

  if(brakeVoltage > THRESHOLD_VOLTAGE){ // if brake is on
    if(!rightIndicatorOn && millis() - blinkerRightLastUpdate > blinkersDelay){

      for(int i=0; i<NUM_LEDS; ++i){
        rightLeds[i] = CRGB::Red;
      }
    }

    if(!leftIndicatorOn && millis() - blinkerLeftLastUpdate > blinkersDelay){
      for(int i=0; i<NUM_LEDS; ++i){
        leftLeds[i] = CRGB::Red;
      }
    }
  } else if(runningLightsVoltage > THRESHOLD_VOLTAGE){

    

    if(!rightIndicatorOn && millis() - blinkerRightLastUpdate > blinkersDelay){

      for(int i=0; i<NUM_LEDS; ++i){
        rightLeds[i] = CRGB::Red;
        rightLeds[i].nscale8(128); // Half brightness
      }
    }

    if(!leftIndicatorOn && millis() - blinkerLeftLastUpdate > blinkersDelay){
      for(int i=0; i<NUM_LEDS; ++i){
        leftLeds[i] = CRGB::Red;
        leftLeds[i].nscale8(128); // Half brightness
      }
    }
    
    
    
    
  } else {

    //Serial.println(millis() - blinkerRightLastUpdate > blinkersDelay);
    //Serial.println(millis() - blinkerLeftLastUpdate > blinkersDelay);

    if(!rightIndicatorOn && millis() - blinkerRightLastUpdate > blinkersDelay){

      for(int i=0; i<NUM_LEDS; ++i){
        rightLeds[i] = CRGB::Black;
      }
    }

    if(!leftIndicatorOn && millis() - blinkerLeftLastUpdate > blinkersDelay){
      for(int i=0; i<NUM_LEDS; ++i){
        leftLeds[i] = CRGB::Black;
      }
    }
    
  }

  if(rightIndicatorVoltage <= THRESHOLD_VOLTAGE){
    if(blinkersOffTimerRight == -1){
      blinkersOffTimerRight = millis();
    }
    if(millis() - blinkersOffTimerRight > blinkersOffDelay){
      if(leftIndicatorOn && rightIndicatorOn){
        endIndexLeft = 0;
      }
      rightIndicatorOn = false;
      endIndexRight = 0;
    }
    
  }

  if(leftIndicatorVoltage <= THRESHOLD_VOLTAGE){
    if(blinkersOffTimerLeft == -1){
      blinkersOffTimerLeft = millis();
    }
    if(millis() - blinkersOffTimerLeft > blinkersOffDelay){
      if(leftIndicatorOn && rightIndicatorOn){
        endIndexRight = 0;
      }
      leftIndicatorOn = false;
      endIndexLeft = 0;
    }
    
  }

  if(rightIndicatorVoltage > THRESHOLD_VOLTAGE && leftIndicatorVoltage > THRESHOLD_VOLTAGE){ // if hazard is on
    //turnAllOff();
    if(!rightIndicatorOn || !leftIndicatorOn){
      rightIndicatorOn = true;
      leftIndicatorOn = true;
      resetIndicator();
    }
    hazards();
    blinkerLeftLastUpdate = millis();
    blinkerRightLastUpdate = millis();
    blinkersOffTimerLeft = -1;
    blinkersOffTimerRight = -1;
    FastLED.show();
    return;
  } else if(rightIndicatorVoltage > THRESHOLD_VOLTAGE){ // if right blinker
    fill_solid(rightLeds, NUM_LEDS, CRGB::Black); // Turn off right LEDs
    if(!rightIndicatorOn){
      rightIndicatorOn = true;
      resetIndicator();
    }
    blinker(rightLeds, &endIndexRight);
    blinkerRightLastUpdate = millis();
    blinkersOffTimerRight = -1;
    FastLED.show();
    return;
  } else if(leftIndicatorVoltage > THRESHOLD_VOLTAGE){ // if left blinker
    fill_solid(leftLeds, NUM_LEDS, CRGB::Black); // Turn off left LEDs
    if(!leftIndicatorOn){
      leftIndicatorOn = true;
      resetIndicator();
    }
    blinker(leftLeds, &endIndexLeft);
    blinkerLeftLastUpdate = millis();
    blinkersOffTimerLeft = -1;
    FastLED.show();
    return;
  }
FastLED.show(); // Call FastLED.show() here
  
}

void updateScreen(){
  if(!digitalRead(BTN_UP) && !upBtnPressed){
    upBtnPressed = 1;
    --screenVarPtr;
    if(screenVarPtr < 0){
      screenVarPtr = 6;
    }
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print(valNames[screenVarPtr]);
    displayScreenVar();
  }
  if(digitalRead(BTN_UP) && upBtnPressed){
    upBtnPressed = 0;
  }
  if(!digitalRead(BTN_DOWN) && !downBtnPressed){
    downBtnPressed = 1;
    ++screenVarPtr;
    if(screenVarPtr > 6){
      screenVarPtr = 0;
    }
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print(valNames[screenVarPtr]);
    displayScreenVar();
  }
  if(digitalRead(BTN_DOWN) && downBtnPressed){
    downBtnPressed = 0;
  }

  if(!upBtnPressed && !downBtnPressed && digitalRead(BTN_UP) && digitalRead(BTN_DOWN)){
    if(digitalRead(ROTARY_ENCODER_SW) != lastSW){
      lastSW = digitalRead(ROTARY_ENCODER_SW);
      delay(10);
    }
    if(digitalRead(ROTARY_ENCODER_CLK) != lastCLK){
      lastCLK = digitalRead(ROTARY_ENCODER_CLK);
      if(digitalRead(ROTARY_ENCODER_CLK) == LOW){
        if(digitalRead(ROTARY_ENCODER_CLK) != digitalRead(ROTARY_ENCODER_DT)){
          changeScreenVar(1);
        } else {
          changeScreenVar(-1);
        }
        displayScreenVar();
      }
    }

  }

  if(millis() - lastSave > saveDelay){
    saveToMemory();
    lastSave = millis();
  }
  

}

void saveToMemory(){

  EEPROM.put(1, indicatorDelay);
  EEPROM.put(2, hazardDelay);
  EEPROM.put(3, blinkersDelay);
  EEPROM.put(5, startupAnimationVal);
  EEPROM.put(7, startupAnimationSpeed);
  EEPROM.put(9, animationTileSize);
  EEPROM.put(11, blinkersOffDelay);

}


void changeScreenVar(int inc){
  switch(screenVarPtr){
    case 0:
        indicatorDelay += inc;
        break;
    case 1:
        hazardDelay += inc;
        break;
    case 2:
        blinkersDelay += inc;
        break;
    case 3:
        blinkersOffDelay += inc;
        break;
    case 4:
        startupAnimationVal += inc;
        break;
    case 5:
        startupAnimationSpeed += inc;
        break;
    case 6:
        animationTileSize += inc;
        break;
    default:
        break;
  }
}

void displayScreenVar(){
  switch(screenVarPtr){
    case 0:
        lcd.setCursor(0,1);
        lcd.print("                ");
        lcd.setCursor(0,1);
        lcd.print(indicatorDelay);
        break;
    case 1:
        lcd.setCursor(0,1);
        lcd.print("                ");
        lcd.setCursor(0,1);
        lcd.print(hazardDelay);
        break;
    case 2:
        lcd.setCursor(0,1);
        lcd.print("                ");
        lcd.setCursor(0,1);
        lcd.print(blinkersDelay);
        break;
    case 3:
        lcd.setCursor(0,1);
        lcd.print("                ");
        lcd.setCursor(0,1);
        lcd.print(blinkersOffDelay);
        break;
    case 4:
        lcd.setCursor(0,1);
        lcd.print("                ");
        lcd.setCursor(0,1);
        lcd.print(startupAnimationVal);
        break;
    case 5:
        lcd.setCursor(0,1);
        lcd.print("                ");
        lcd.setCursor(0,1);
        lcd.print(startupAnimationSpeed);
        break;
    case 6:
        lcd.setCursor(0,1);
        lcd.print("                ");
        lcd.setCursor(0,1);
        lcd.print(animationTileSize);
        break;
    default:
        lcd.setCursor(0,1);
        lcd.print("                ");
        lcd.setCursor(0,1);
        lcd.print("error");
        break;
  }
}

void blinker(CRGB* leds, uint8_t* endIndex){
    indicatorAnimation(leds, *endIndex);

    if (millis() - lastUpdate > indicatorDelay && *endIndex < NUM_LEDS) {
      lastUpdate = millis();
      *endIndex += 1;
      
    }
}

void turnAllOff(){
  for(int i=0; i<NUM_LEDS; ++i){
    rightLeds[i] = CRGB::Black;
    leftLeds[i] = CRGB::Black;
  }
}

void resetIndicator() {
  endIndexRight = 0; // Reset endIndexRight to NUM_LEDS
  endIndexLeft = 0; // Reset endIndexLeft to NUM_LEDS
}

void hazards() {
  indicatorAnimation(rightLeds, endIndexRight);
  indicatorAnimation(leftLeds, endIndexLeft);

  if (millis() - lastUpdate > hazardDelay && endIndexRight < NUM_LEDS) {
    lastUpdate = millis();

    endIndexRight += 1;
    endIndexLeft += 1;
  }

}

void indicatorAnimation(CRGB* leds, uint8_t endIndex) {

  fill_solid(leds, NUM_LEDS, CRGB::Black); // Turn off right LEDs
  
  for (int i = 0; i < endIndex; ++i) {
    leds[i] = CHSV(78, 255, 255); // More greenish color
  }

  return;
}

void startupAnimation(int animation){
  switch(animation){// add animation functions in here
    case 3:
          animation3();
          break;
    case 2:
          animation2();
          break;
    default:
          animation1();
          break;
  }
}

void animation1(){
  int rightIndex = 0;
  for(int k=0; k<NUM_LEDS; k+=animationTileSize){
    for(int i=NUM_LEDS-animationTileSize; i>=k; --i){
      fill_solid(rightLeds+k, NUM_LEDS-k, CRGB::Black);
      fill_solid(leftLeds+k, NUM_LEDS-k, CRGB::Black);
      for(int j=i; j<i+animationTileSize; ++j){
        rightLeds[j] = CRGB::Red;
        leftLeds[j] = CRGB::Red;
      }
      FastLED.show();
      delay(startupAnimationSpeed);
    }
  }
  fill_solid(rightLeds+NUM_LEDS-animationTileSize, animationTileSize, CRGB::Red);
  fill_solid(leftLeds+NUM_LEDS-animationTileSize, animationTileSize, CRGB::Red);
  FastLED.show();
  delay(750);
  fill_solid(rightLeds+NUM_LEDS-NUM_LEDS % animationTileSize, NUM_LEDS % animationTileSize, CRGB::Black);
  fill_solid(leftLeds+NUM_LEDS-NUM_LEDS % animationTileSize, NUM_LEDS % animationTileSize, CRGB::Black);
  FastLED.show();
  for(int i = NUM_LEDS-(NUM_LEDS % animationTileSize) - animationTileSize; i>=0; i-=animationTileSize){
    for(int j=i+1; j<NUM_LEDS; ++j){
      fill_solid(rightLeds+i, NUM_LEDS-i, CRGB::Black);
      fill_solid(leftLeds+i, NUM_LEDS-i, CRGB::Black);
      for(int k=j; k<NUM_LEDS && k-j<animationTileSize; ++k){
        rightLeds[k] = CRGB::Red;
        leftLeds[k] = CRGB::Red;
      }
      FastLED.show();
      delay(startupAnimationSpeed);
    }
  }
  fill_solid(rightLeds+NUM_LEDS-animationTileSize, animationTileSize, CRGB::Black);
  fill_solid(leftLeds+NUM_LEDS-animationTileSize, animationTileSize, CRGB::Black);
  FastLED.show();

  for(int i=0; i<256; ++i){
    fill_solid(leftLeds, NUM_LEDS, CRGB(i, 0, 0));
    fill_solid(rightLeds, NUM_LEDS, CRGB(i, 0, 0));
    FastLED.show();
    delay(3);
  }
  
}

void animation2(){
  fill_solid(rightLeds, NUM_LEDS, CRGB::Black);
  fill_solid(leftLeds, NUM_LEDS, CRGB::Black);
  for(int i=0; i<animationTileSize; ++i){
    rightLeds[i] = CRGB::Red;
    leftLeds[i] = CRGB::Red;
    FastLED.show();
    delay(startupAnimationSpeed);
  }
  for(int i=-1; i<NUM_LEDS; ++i){
    fill_solid(rightLeds, NUM_LEDS, CRGB::Black);
    fill_solid(leftLeds, NUM_LEDS, CRGB::Black);
    for(int j=i+1; j<animationTileSize+i+1 && j<NUM_LEDS; ++j){
      rightLeds[j] = CRGB::Red;
      leftLeds[j] = CRGB::Red;
    }
    FastLED.show();
    delay(startupAnimationSpeed);
  }
  delay(startupAnimationSpeed*3);
  for(int i=0; i<animationTileSize; ++i){
    rightLeds[i] = CRGB::Red;
    leftLeds[i] = CRGB::Red;
    FastLED.show();
    delay(startupAnimationSpeed);
  }
  for(int i=-1; i<NUM_LEDS; ++i){
    fill_solid(rightLeds, NUM_LEDS, CRGB::Black);
    fill_solid(leftLeds, NUM_LEDS, CRGB::Black);
    for(int j=i+1; j<animationTileSize+i+1 && j<NUM_LEDS; ++j){
      rightLeds[j] = CRGB::Red;
      leftLeds[j] = CRGB::Red;
    }
    FastLED.show();
    delay(startupAnimationSpeed);
  }
  delay(startupAnimationSpeed*80);
  fill_solid(rightLeds, NUM_LEDS, CRGB::Black);
  fill_solid(leftLeds, NUM_LEDS, CRGB::Black);
  for(int i=0; i<NUM_LEDS; ++i){
    rightLeds[i] = CRGB::Red;
    leftLeds[i] = CRGB::Red;
    FastLED.show();
    delay(startupAnimationSpeed*6);
  }
  delay(1500);
}

void animation3(){
  for(int i=0; i<NUM_LEDS; ++i){
    int led = random(0, NUM_LEDS);
    while(rightLeds[led] != CRGB::Black){
      led = random(0, NUM_LEDS);
    }
    double j = 2;
    while(j<255){
      rightLeds[led] = CRGB(j, 0, 0);
      leftLeds[led] = CRGB(j, 0, 0);
      j*=1.3;
      FastLED.show();
      delay(4);
    }
  }
  delay(750);
  for(int i=255; i>=0; i-=6){
    fill_solid(rightLeds, NUM_LEDS, CRGB(i, 0, 0));
    fill_solid(leftLeds, NUM_LEDS, CRGB(i, 0, 0));
    FastLED.show();
  }
  delay(2);
  for(int i=0; i<256; i+=6){
    fill_solid(rightLeds, NUM_LEDS, CRGB(i, 0, 0));
    fill_solid(leftLeds, NUM_LEDS, CRGB(i, 0, 0));
    FastLED.show();
  }
  for(int i=255; i>=0; i-=6){
    fill_solid(rightLeds, NUM_LEDS, CRGB(i, 0, 0));
    fill_solid(leftLeds, NUM_LEDS, CRGB(i, 0, 0));
    FastLED.show();
  }
  delay(2);
  for(int i=0; i<256; i+=6){
    fill_solid(rightLeds, NUM_LEDS, CRGB(i, 0, 0));
    fill_solid(leftLeds, NUM_LEDS, CRGB(i, 0, 0));
    FastLED.show();
  }
  delay(1500);
}