#include "VikTronome.h"
#include <LiquidCrystal.h>

#define LED 13
#define ENCODER_CLK 2
#define ENCODER_DT  3
#define ENCODER_BTN 4
#define SELECT_BTN 6

#define MAX_BEATS_STEPS 9

#define NOTE_DURATION_MIN 1
#define NOTE_DURATION_MAX 2

#define SOUND_MIN 1
#define SOUND_MAX 9

#define MENU_TEMPO 0
#define MENU_BEATS 1
#define MENU_NOTE_DURATION 2
#define MENU_SOUND 3

VikTronome metronome;
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
int lastClk = HIGH;
bool isOn = false;

byte step = 0;
byte totalSteps = 4;

byte sound = 8;

byte noteDuration = NOTE_QUARTERS;

byte menuSelect = 0;
byte customCharArrow[] = {
  B01000,
  B01100,
  B11110,
  B11111,
  B11111,
  B11110,
  B01100,
  B01000
};

byte customCharSpeaker[] = {
  B00001,
  B00011,
  B01111,
  B01111,
  B01111,
  B00011,
  B00001,
  B00000
};

byte customCharNoteEights[] = {
  B00001,
  B00011,
  B00101,
  B01001,
  B01001,
  B01011,
  B11011,
  B11000
};

byte customCharNoteQuarter[] = {
  B00011,
  B00010,
  B00010,
  B00010,
  B00010,
  B00110,
  B01110,
  B00100
};

void handleTickCallback() {
  if(!isOn) return;
  step++;
  if(step > totalSteps - 1) step = 0;
  digitalWrite(LED, digitalRead(LED) ^ 1);
  refreshScreen(true);
}

void setup() {
  pinMode(LED, OUTPUT);
  pinMode(ENCODER_CLK, INPUT);
  pinMode(ENCODER_DT, INPUT);
  pinMode(ENCODER_BTN, INPUT_PULLUP);
  pinMode(SELECT_BTN, INPUT_PULLUP);

  metronome.setup();

  //  Serial.begin(9600);  
  //  metronome.setTempo(40);

  // setup LCD
  lcd.begin(16, 2);
  lcd.createChar(1, customCharArrow);
  lcd.createChar(2, customCharSpeaker);
  lcd.createChar(3, customCharNoteQuarter);
  lcd.createChar(4, customCharNoteEights);
  lcd.setCursor(0, 0);
  lcd.print("VikTronome");
  lcd.setCursor(0, 1);
  lcd.print("v0.2");
  delay(2000);
  refreshScreen(false);
}

void refreshScreen(bool playTone) {
  lcd.clear();

  // draw tempo
  lcd.setCursor(1, 0);
  lcd.print((String)"BPM: " + metronome.tempo);
  
  // handle tone
  if(playTone) {
    soundBeats();
  }

  // draw beats
  drawBeats();

  // draw notes
  lcd.setCursor(13, 0);
  lcd.write(4);
  lcd.write(":");
  switch(noteDuration) {
    case NOTE_QUARTERS:
      lcd.write(3);
    break;
    case NOTE_EIGHTS:
      lcd.write(4);
    break;
  }

  // draw sound
  lcd.setCursor(13, 1);
  lcd.write(2);
  lcd.write(":");
  lcd.print(sound);

  // draw menu
  drawMenu();
}

void drawBeats() {
  lcd.setCursor(1, 1);
  lcd.print(totalSteps + (String)":");

  for(byte i = 0; i < totalSteps; i++) {
    if(step == i && isOn) {
      if(step == 0) {
        lcd.write(255);
      } else {
        lcd.write(219);
      }
    } else {
      lcd.write(".");  
    }
  }
}

void soundBeats() {
  lcd.setCursor(1, 1);
  lcd.print(totalSteps + (String)":");

  for(byte i = 0; i < totalSteps; i++) {
    if(step == i && isOn) {
      if(step == 0) {
        tone(5, 400 * sound + 200, 20);
      } else {
        tone(5, 300 * sound + 200, 20);
      }
    } else {
      lcd.write(".");  
    }
  }
}

void drawMenu() {
  switch(menuSelect) {
    case MENU_TEMPO:
      lcd.setCursor(0, 0);
      lcd.write(1);
    break;
    case MENU_BEATS:
      lcd.setCursor(0, 1);
      lcd.write(1);
    break;
    case MENU_NOTE_DURATION:
      lcd.setCursor(12, 0);
      lcd.write(1);
    break;
    case MENU_SOUND:
      lcd.setCursor(12, 1);
      lcd.write(1);
    break;
  }
}

void loop() {
  int newClk = digitalRead(ENCODER_CLK);
  if (newClk != lastClk) {
    // There was a change on the CLK pin
    lastClk = newClk;
    int dtValue = digitalRead(ENCODER_DT);
    if (newClk == LOW && dtValue == HIGH) {
      handleRotaryUp();
      refreshScreen(false);
    } else if (newClk == LOW && dtValue == LOW) {
      handleRotaryDown();
      refreshScreen(false);
    }
    delay(5);
  }
  // handle button
  if (digitalRead(SELECT_BTN) == LOW) {
    isOn = !isOn;
    step = 0;
    metronome.reset();
    refreshScreen(true);
    delay(300);
  }
  // handle menu
  if(digitalRead(ENCODER_BTN) == LOW) {
    menuSelect++;
    if(menuSelect > 3) menuSelect = 0;
    refreshScreen(false);
    delay(300);
  }
}

void handleRotaryUp() {
  switch(menuSelect) {
    case MENU_TEMPO:
      metronome.addOneBPM();
    break;
    case MENU_BEATS:
      addTotalSteps();
    break;
    case MENU_NOTE_DURATION:
      addNoteDuration();
    break;
    case MENU_SOUND:
      addSoundStep();
    break;
  }
}

void handleRotaryDown() {
  switch(menuSelect) {
    case MENU_TEMPO:
      metronome.subtractOneBPM();
    break;
    case MENU_BEATS:
      subtractTotalSteps();
    break;
    case MENU_NOTE_DURATION:
      subtractNoteDuration();
    break;
    case MENU_SOUND:
      subtractSoundStep();
    break;
  }
}

void addNoteDuration() {
  if(noteDuration < NOTE_DURATION_MAX) {
    noteDuration++;
  } else {
    noteDuration = NOTE_DURATION_MIN;
  }
  metronome.setNoteDuration(noteDuration);
  metronome.reset();
  step = 0;
  refreshScreen(true);
}

void subtractNoteDuration() {
  if(noteDuration > NOTE_DURATION_MIN) {
    noteDuration--;
  } else {
    noteDuration = NOTE_DURATION_MAX;
  }
  step = 0;
  metronome.setNoteDuration(noteDuration);
  metronome.reset();
  refreshScreen(true);
}

void addSoundStep() {
  if(sound < SOUND_MAX) {
    sound++;
  }  
}

void subtractSoundStep() {
  if(sound > SOUND_MIN) {
    sound--;
  }  
}

void addTotalSteps() {
  if(totalSteps / NOTE_QUARTERS < MAX_BEATS_STEPS) {
    totalSteps++;
  }
  totalSteps = totalSteps / NOTE_QUARTERS;
}

void subtractTotalSteps() {
  if(totalSteps > 1) {
    totalSteps--;
  }
  totalSteps = totalSteps / NOTE_QUARTERS;
}

ISR(TIMER1_COMPA_vect) {
  handleTickCallback();
}