#include <avr/pgmspace.h>
#include <EEPROM.h>
#include <Encoder.h>
#include <LiquidCrystal_I2C.h>
#include <IRremote.h>
// designed by Erik Hoffman
// Define LCD pins
LiquidCrystal_I2C lcd(0x27, 20, 4);

// Define custom character for the progress bar
byte bar[8] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111};

#define SW1 4
#define DT1 5
#define CLK1 2

#define SW2 7
#define DT2 10
#define CLK2 3

int scrollStepCount1=4;
int scrollStepCount2=4;
Encoder myEnc1(CLK1, DT1);
Encoder myEnc2(CLK2, DT2);
long oldPosition1  = 0;
long oldPosition2  = 0;
int scrollUp1=0, scrollDown1=0, click1=0; 
int scrollUp2=0, scrollDown2=0, click2=0; 

// Define button pins
const int toggleButton = 0;
const int RECV_PIN = 1;

// Define output pins
const int sounderPin = 13;
const int relayPin = 12;

int buttonDelay = 500;

IRrecv irrecv(RECV_PIN);
decode_results results;
bool IRSelect = false;
bool IRRun = false;
bool toggleRelay = 0;




// Define presets
const int totalPresets = 31; // Set number of presets according to requirement
int presets[totalPresets][5] = {};

// Store the strings in program memory
const char string1[] PROGMEM = "PP1 Practice 1 - 25";
const char string2[] PROGMEM = "PP1 Practice 2 - 15";
const char string3[] PROGMEM = "PP1 Practice 3 - 10";
const char string4[] PROGMEM = "Multi-Target 1 - 25";
const char string5[] PROGMEM = "Multi-Target 2 - 20";
const char string6[] PROGMEM = "Multi-Target 3 - 15";
const char string7[] PROGMEM = "Multi-Target 4 - 10";
const char string8[] PROGMEM = "Bianchi Plates 1-10";
const char string9[] PROGMEM = "Bianchi Plates 2-15";
const char string10[] PROGMEM = "Bianchi Plates 3-20";
const char string11[] PROGMEM = "Bianchi Plates 4-25";
const char string12[] PROGMEM = "Silhouettes1 12-25";
const char string13[] PROGMEM = "Silhouettes2 10-25";
const char string14[] PROGMEM = "Silhouettes3 8-25";
const char string15[] PROGMEM = "Silhouettes4 6-25";
const char string16[] PROGMEM = "Bow St Runner 1 - 25";
const char string17[] PROGMEM = "Bow St Runner 2 - 15";
const char string18[] PROGMEM = "Bow St Runner 3 - 10";
const char string19[] PROGMEM = "Bianchi 1   3 - 10";
const char string20[] PROGMEM = "Bianchi 1   4 - 10";
const char string21[] PROGMEM = "Bianchi 1   8 - 10";
const char string22[] PROGMEM = "Bianchi 2   4 - 15";
const char string23[] PROGMEM = "Bianchi 2   5 - 15";
const char string24[] PROGMEM = "Bianchi 2   6 - 15";
const char string25[] PROGMEM = "Bianchi 3   5 - 25";
const char string26[] PROGMEM = "Bianchi 3   6 - 25";
const char string27[] PROGMEM = "Bianchi 3   7 - 25";
const char string28[] PROGMEM = "Bianchi 4    7 - 50";
const char string29[] PROGMEM = "Bianchi 4   10 - 50";
const char string30[] PROGMEM = "Bianchi 4   15 - 50";
const char string31[] PROGMEM = "Your Choice";

// Store pointers to the strings in a PROGMEM array
const char* const description[] PROGMEM = {
  string1, string2, string3, string4, string5, string6, string7, string8, string9, string10,
  string11, string12, string13, string14, string15, string16, string17, string18, string19, string20,
  string21, string22, string23, string24, string25, string26, string27, string28, string29, string30, string31,
};

// Buffer to store the retrived description from program memory
char buffer[21];

int run[5] = {0, 0, 0, 0, 0};

int selectedPreset = 0;
bool programmingMode = false;
bool flashing = false;
int currentParameter = 0;

unsigned int previousTime = 0;
unsigned int flashingTime = 200; // 1 sec 
unsigned int initialTimeSelectButton = 0;
unsigned int finalTimeSelectButton = 0;
unsigned int timeDelaySelectButton = 1500;

void(* resetFunc) (void) = 0;

bool leftButtonFlag = false;
bool rightButtonFlag = false;
bool selectLongPress = false;

unsigned int currentTimeProgress = 0;
unsigned int previousTimeProgress = 0;
int previous_preset = 0;

void setup() {
  // Initialize LCD
  lcd.init();
  lcd.backlight();
  lcd.createChar(0, bar);
  lcd.setCursor(0,0);
  lcd.print("  EBSC CONTROLLER");
  lcd.setCursor(0,1);
  lcd.print("    BY HOFFMAN");
  delay(2000);
  lcd.clear();
  lcd.setCursor(0,2);
  lcd.print("PR  FC ED FC ED  RPT");
  lcd.setCursor(0,0);
  const char* str = (const char*)pgm_read_word(&(description[0]));
  strcpy_P(buffer, str);
  lcd.print(buffer);

  irrecv.enableIRIn();

  // Initialize buttons
  pinMode(SW1, INPUT_PULLUP);
  pinMode(SW2, INPUT_PULLUP);
  pinMode(toggleButton, INPUT_PULLUP);

  // Initialize outputs
  pinMode(sounderPin, OUTPUT);
  pinMode(relayPin, OUTPUT);

  digitalWrite(sounderPin, HIGH);
  digitalWrite(relayPin, HIGH);

  for(int j=0; j<totalPresets; j++)
  {
    selectedPreset = EEPROM.read(6*j);
    presets[j][0] = EEPROM.read((6*j)+1);
    presets[j][1] = EEPROM.read((6*j)+2);
    presets[j][2] = EEPROM.read((6*j)+3); 
    presets[j][3] = EEPROM.read((6*j)+4);
    presets[j][4] = EEPROM.read((6*j)+5);
  }
  
}

void loop() {

  getPosition1();
  getPosition2();

  if( (leftButtonFlag) && (!flashing) )
  {
    currentParameter--;
    if(currentParameter == -1)
    {
      currentParameter = 5;
    }
    currentParameter = currentParameter % 6;
    leftButtonFlag = false;
  }
  else if( (rightButtonFlag) && (!flashing) )
  {
    currentParameter++;
    currentParameter = currentParameter % 6;
    rightButtonFlag = false;
  }

  if (irrecv.decode(&results))
  {
    switch(results.value)
    {
      case 0x2F5708F:   // Replace with IR code for Run button
        setRunParameters();
        handleRun();
        delay(buttonDelay);
      break;

      case 0x2F540BF:  // Replace with IR code for On/Off (Toggle) button
        toggleRelay = !toggleRelay;
        digitalWrite(relayPin, toggleRelay);
        delay(buttonDelay);
      break;

      case 0x2F5A05F:  // Replace with IR code for Reset button
        resetFunc();
        delay(buttonDelay);
      break;
    }
    irrecv.resume();  // Receive the next value
  }

  if ((click2 == 1) && (!flashing)) // For Select Button
  {
    if (!programmingMode) 
    {
      programmingMode = true;
      currentParameter = 0;
    } else 
    {
      programmingMode = false;
      if(selectLongPress)
      {
        saveToEEPROM();
        selectLongPress = false;
      }
    }
    //delay(buttonDelay);
    click2 = 0;
  }
  
  else if ( scrollDown1 == 1 )    // For Left Button
  {
    if (programmingMode) 
    {
      leftButtonFlag = true;
    }
    //delay(buttonDelay);
    scrollDown1 = 0;
  }

  else if ( scrollUp1 == 1 )  // For Right Button
  {
    if (programmingMode) 
    {
      rightButtonFlag = true;
    }
    //delay(buttonDelay);
    scrollUp1 = 0;
  }

  else if ( scrollUp2 == 1 )  // For Up Button
  {
    if (programmingMode) 
    {
      if(currentParameter == 0)
      {
        selectedPreset++;
        selectedPreset = selectedPreset % totalPresets;
      }
      else if(currentParameter == 5)
      {
        presets[selectedPreset][4]++;
        presets[selectedPreset][4] = presets[selectedPreset][4] % 100;
      }
      else
      {
        for(int i=1; i<=4; i++)
        {
          if(currentParameter == i)
          {
            presets[selectedPreset][i-1]++;
            presets[selectedPreset][i-1] = presets[selectedPreset][i-1] % 100;
          }
        }
      }
    }
   // delay(buttonDelay);
   scrollUp2 = 0;
  }
  
  else if ( scrollDown2 == 1 )  // For Down Button
  {
    if (programmingMode) 
    {
      if(currentParameter == 0)
      {
        selectedPreset--;
        if(selectedPreset < 0)
        {
          selectedPreset = (totalPresets-1);
        }
      }
      else if(currentParameter == 5)
      {
        presets[selectedPreset][4]--;
        if(presets[selectedPreset][4] < 0)
        {
          presets[selectedPreset][4] = 99;
        }
      }
      else
      {
        for(int i=1; i<=4; i++)
        {
          if(currentParameter == i)
          {
            presets[selectedPreset][i-1]--;
            if(presets[selectedPreset][i-1] < 0)
            { 
              presets[selectedPreset][i-1] = 99;
            }
          }
        }
      }
    }
   // delay(buttonDelay);
   scrollDown2 = 0;
  }

  if((click1 == 1) && (!flashing))
  {
    setRunParameters();
    handleRun();
   // delay(buttonDelay);
   click1 = 0;
  }

  if(digitalRead(toggleButton) == LOW)
  {
    toggleRelay = !toggleRelay;
    digitalWrite(relayPin, toggleRelay);
    delay(buttonDelay);
  }
  
  displayState();
  delay(20);

}

void updatePosition1(){
  if(click1) {
    click1=0;
  }
  if(scrollUp1) {
    scrollUp1=0;
  }
  if(scrollDown1) {
    scrollDown1=0;
  }
}
void updatePosition2(){
  if(click2) {
    click2=0;
  }
  if(scrollUp2) {
    scrollUp2=0;
  }
  if(scrollDown2) {
    scrollDown2=0;
  }
}

void getPosition1(){
  long newPosition1 = myEnc1.read();
  
  if ((newPosition1 - oldPosition1>=scrollStepCount1)) { 
    oldPosition1 = newPosition1;
    scrollDown1=1;
  }
  if ((newPosition1 - oldPosition1<=-scrollStepCount1)) { 
    oldPosition1 = newPosition1;
    scrollUp1=1; 
  }

  if(digitalRead(SW1)==0) {
    click1=1; 
    while(digitalRead(SW1)==0) delay(15); 
  }
}
void getPosition2(){
  long newPosition2 = myEnc2.read(); 
  
  if ((newPosition2 - oldPosition2>=scrollStepCount2)) { 
    oldPosition2 = newPosition2;
    scrollDown2=1; 
  }
  if ((newPosition2 - oldPosition2<=-scrollStepCount2)) { 
    oldPosition2 = newPosition2;
    scrollUp2=1; 
  }

  if(digitalRead(SW2)==0) { 
    click2=1; 
    initialTimeSelectButton = millis();
    while(digitalRead(SW2)==0)
    {
     delay(15);
     finalTimeSelectButton = millis();
     if(finalTimeSelectButton-initialTimeSelectButton > timeDelaySelectButton)
     {
      selectLongPress = true;
     }
    }
  }

}

void displayState() 
{
  if (programmingMode) 
  {
    unsigned int currentTime = millis();
    if (currentParameter == 0) 
    {
      if((flashing) && ((currentTime-previousTime) > flashingTime))
      {
        if(selectedPreset != previous_preset)
        {   
          lcd.setCursor(0, 0);
          lcd.print("                    ");
          lcd.setCursor(0, 0);
          const char* str = (const char*)pgm_read_word(&(description[selectedPreset]));
          strcpy_P(buffer, str);
          lcd.print(buffer);
        }
        lcd.setCursor(0, 1);
        lcd.print(selectedPreset+1);
        getPosition1();
        getPosition2();
        previous_preset = selectedPreset;
        previousTime = currentTime;
        flashing = false;
      }
      else if((!flashing) && ((currentTime-previousTime) > flashingTime))
      {
        lcd.setCursor(0, 1);
        lcd.print("  ");
        getPosition1();
        getPosition2();
        previousTime = currentTime;
        flashing = true;
      }
      for(int i=1; i<=5; i++)
      {
        lcd.setCursor((3*i)+1, 1);
        if(i == 5)
        {
          lcd.setCursor((3*i)+2, 1);
        }
        lcd.print(presets[selectedPreset][i-1]);
        getPosition1();
        getPosition2();        
        lcd.print(" ");
        getPosition1();
        getPosition2();        
      }

    }
    else
    {
      for(int i = 1; i<=5; i++)
      {
        if (currentParameter == i) 
        {
          if((flashing) && ((currentTime-previousTime) > flashingTime))
          {
            lcd.setCursor((3*i)+1, 1);
            if(i == 5)
            {
              lcd.setCursor((3*i)+2, 1);
            }
            lcd.print(presets[selectedPreset][i-1]);
            getPosition1();
            getPosition2();
            previousTime = currentTime;
            flashing = false;
          }
          else if((!flashing) && ((currentTime-previousTime) > flashingTime))
          {
            lcd.setCursor((3*i)+1, 1);
            if(i == 5)
            {
              lcd.setCursor((3*i)+2, 1);              
            }
            lcd.print("  ");
            getPosition1();
            getPosition2();            
            previousTime = currentTime;
            flashing = true;
          }
        }
        else
        {
          lcd.setCursor((3*i)+1, 1);
          if(i == 5)
          {
            lcd.setCursor((3*i)+2, 1);
          }
          lcd.print(presets[selectedPreset][i-1]);
          getPosition1();
          getPosition2();
        }
      }
    }
  }
  else 
  {
    lcd.setCursor(0, 1);
    lcd.print(selectedPreset+1);
    getPosition1();
    getPosition2();
    lcd.setCursor(4, 1);
    lcd.print(presets[selectedPreset][0]);
    getPosition1();
    getPosition2();
    lcd.setCursor(7, 1);
    lcd.print(presets[selectedPreset][1]);
    getPosition1();
    getPosition2();
    lcd.setCursor(10, 1);
    lcd.print(presets[selectedPreset][2]);
    getPosition1();
    getPosition2();
    lcd.setCursor(13, 1);
    lcd.print(presets[selectedPreset][3]);
    getPosition1();
    getPosition2();
    lcd.setCursor(17, 1);
    lcd.print(presets[selectedPreset][4]);
    getPosition1();
    getPosition2();
  }
}

void setRunParameters()
{
  for(int i=0; i<5; i++)
  {
    run[i] = presets[selectedPreset][i];
  }
}

void saveToEEPROM()
{
  EEPROM.write((6*selectedPreset), selectedPreset);
  EEPROM.write(((6*selectedPreset)+1), presets[selectedPreset][0]);
  EEPROM.write(((6*selectedPreset)+2), presets[selectedPreset][1]);
  EEPROM.write(((6*selectedPreset)+3), presets[selectedPreset][2]);
  EEPROM.write(((6*selectedPreset)+4), presets[selectedPreset][3]);
  EEPROM.write(((6*selectedPreset)+5), presets[selectedPreset][4]);
}

void handleRun()
{
  unsigned long totalTime = 0;
  unsigned long timePerUnit = 0;
  int barCount = 0;
  for(int i=0; i<4; i++)
  {
    totalTime = (totalTime) + (presets[selectedPreset][i]);
  }
  totalTime = (totalTime)*(presets[selectedPreset][4]);
  totalTime = (totalTime)-(presets[selectedPreset][3]);
  totalTime = 1000*totalTime;
  timePerUnit = (totalTime)/20;

  digitalWrite(sounderPin, LOW);
  delay(200);
  digitalWrite(sounderPin, HIGH);
  delay(5000);
  toggleRelay = false;
  digitalWrite(relayPin, toggleRelay);
  previousTimeProgress = millis();  
  for (int i=presets[selectedPreset][4]; i>0; i--)
  {
    for(int j=0; j<4; j++)
    {
      if( (i==1) && (j==3) )
      {
        while(barCount <= 19)
        {
          lcd.setCursor(barCount, 3);
          lcd.write(0);        
          barCount++;
        }
        digitalWrite(sounderPin, LOW);
        delay(1000);
        digitalWrite(sounderPin, HIGH);
        lcd.setCursor(0,3);
        lcd.print("                    ");
      }
      for(int k=run[j]; k>=0; k--)
      {
        lcd.setCursor((3*(j+1)+1), 1);
        lcd.print(k);
        getPosition1();
        getPosition2();        
        lcd.print(" ");
        getPosition1();
        getPosition2();
        if(k!=0)
        {
          digitalWrite(relayPin, toggleRelay);
          unsigned int currentTime = millis();
          previousTime = millis();
          while(currentTime - previousTime < 1000)
          {
            currentTimeProgress = millis();
            if(currentTimeProgress - previousTimeProgress >= timePerUnit)
            {
              if(barCount <= 19)
              {            
                lcd.setCursor(barCount, 3);
                lcd.write(0);
                barCount++;
              }
              previousTimeProgress = currentTimeProgress;              
            }
            if(digitalRead(toggleButton) == LOW)
            {
              k = 0;
              j = 3;
              i = 1;
              digitalWrite(relayPin, HIGH);
              delay(buttonDelay);
            }
            currentTime = millis();
            delay(20);
          }
        }
      }
      toggleRelay = !toggleRelay;
    }
    run[4]--;
    if(run[4]>0)
    {
      for(int l=0; l<5; l++)
      {
        lcd.setCursor((3*(l+1)+1), 1);
        if(l == 4)
        {
          lcd.setCursor((3*(l+1)+2), 1);
        }
        lcd.print(run[l]);
        lcd.print(" ");
        getPosition1();
        getPosition2();
      }
    }
    else
    {
      lcd.setCursor(17, 1);
      lcd.print(run[4]);
      lcd.print("  ");
      getPosition1();
      getPosition2();
      digitalWrite(relayPin, HIGH);
    }
  }
  totalTime = 0;
}
