/////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////
///////////// MORNINGSTAR MIDI CONTROLER/////////////////
//////////////////VERSION 2.4////////////////////////////
//////////// développé par antoine laloux ///////////////
/////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////

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

MIDI_CREATE_DEFAULT_INSTANCE();
LiquidCrystal lcd(12, 11, 8, 7, 6, 5);

#define BTN_INC 2
#define BTN_DEC 3
#define LONG_PRESS_TIME 1000
#define VERY_LONG_PRESS_TIME 3000
#define DEBOUNCE_DELAY 50

const int TOTAL_BANKS = 4;
const int PRESETS_PER_BANK = 128;

#define EEPROM_BANK_ADDR   0
#define EEPROM_PRESET_ADDR 1
#define EEPROM_MODE_ADDR   2

int bank = 0;
int preset = 0;
bool directTransmissionMode = false;

// Bloc plein
byte fullBlock[8] = {
  B11111,B11111,B11111,B11111,
  B11111,B11111,B11111,B11111
};

struct ButtonState {
  bool pressed;
  bool longHandled;
  unsigned long pressTime;
  unsigned long lastDebounceTime;
  bool lastReading;
  bool stableState;
};

ButtonState incButton = {false, false, 0, 0, HIGH, false};
ButtonState decButton = {false, false, 0, 0, HIGH, false};

bool simultaneousPressHandled = false;
bool veryLongPressHandled = false;

void setup() {
  pinMode(BTN_INC, INPUT_PULLUP);
  pinMode(BTN_DEC, INPUT_PULLUP);

  MIDI.begin(1);

  lcd.begin(16, 2);
  lcd.createChar(0, fullBlock);

  showStartupMessage();
  loadFromEEPROM();
  updateDisplay();
}

void showStartupMessage() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("MorningStar Midi");
  lcd.setCursor(0, 1);
  lcd.print("V2.4");
  delay(2000);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("developed by");
  lcd.setCursor(0, 1);
  lcd.print("Antoine Laloux");
  delay(2000);

  lcd.clear();
}

void loop() {
  unsigned long now = millis();

  bool incReading = digitalRead(BTN_INC) == LOW;
  bool decReading = digitalRead(BTN_DEC) == LOW;

  if (incReading != incButton.lastReading) incButton.lastDebounceTime = now;
  if (decReading != decButton.lastReading) decButton.lastDebounceTime = now;

  incButton.lastReading = incReading;
  decButton.lastReading = decReading;

  if ((now - incButton.lastDebounceTime) > DEBOUNCE_DELAY)
    incButton.stableState = incReading;
  if ((now - decButton.lastDebounceTime) > DEBOUNCE_DELAY)
    decButton.stableState = decReading;

  // --- Appui simultané ---
  if (incButton.stableState && decButton.stableState) {
    if (!simultaneousPressHandled) {
      incButton.pressTime = now;
      decButton.pressTime = now;
      simultaneousPressHandled = true;
      veryLongPressHandled = false;
    }

    unsigned long elapsed = now - incButton.pressTime;

    // Animation seulement après 1s
    if (elapsed >= LONG_PRESS_TIME && !veryLongPressHandled) {
      showLongPressAnimation(elapsed);
    }

    if (!veryLongPressHandled && elapsed >= VERY_LONG_PRESS_TIME) {
      toggleTransmissionMode();
      veryLongPressHandled = true;
    }
    return;
  }

  // Relâchement simultané
  if (!incButton.stableState && !decButton.stableState && simultaneousPressHandled) {
    if (!veryLongPressHandled && !directTransmissionMode) {
      sendMidi();
    }
    simultaneousPressHandled = false;
    veryLongPressHandled = false;
    incButton.pressed = false;
    decButton.pressed = false;
    return;
  }

  if (!simultaneousPressHandled) {
    handleButton(incButton, true, now);
    handleButton(decButton, false, now);
  }
}

void showLongPressAnimation(unsigned long elapsed) {
  lcd.setCursor(0, 0);
  lcd.print("MODE SWITCH     ");

  int totalBlocks = 16;
  int progress = map(elapsed, LONG_PRESS_TIME, VERY_LONG_PRESS_TIME, 0, totalBlocks);
  progress = constrain(progress, 0, totalBlocks);

  lcd.setCursor(0, 1);
  for (int i = 0; i < totalBlocks; i++) {
    if (i < progress) lcd.write(byte(0));
    else lcd.print(" ");
  }
}

void handleButton(ButtonState &btn, bool isIncrement, unsigned long now) {
  if (btn.stableState && !btn.pressed) {
    btn.pressed = true;
    btn.pressTime = now;
    btn.longHandled = false;
  }

  if (btn.stableState && btn.pressed && !btn.longHandled) {
    if (now - btn.pressTime >= LONG_PRESS_TIME) {
      changeBank(isIncrement);
      btn.longHandled = true;
    }
  }

  if (!btn.stableState && btn.pressed) {
    if (!btn.longHandled) {
      changePreset(isIncrement);
    }
    btn.pressed = false;
  }
}

void changeBank(bool increment) {
  if (increment) bank = (bank + 1) % TOTAL_BANKS;
  else bank = (bank > 0) ? bank - 1 : TOTAL_BANKS - 1;

  saveToEEPROM();
  if (directTransmissionMode) sendMidiDirect();
  else updateDisplay();
}

void changePreset(bool increment) {
  if (increment) preset = (preset + 1) % PRESETS_PER_BANK;
  else preset = (preset > 0) ? preset - 1 : PRESETS_PER_BANK - 1;

  saveToEEPROM();
  if (directTransmissionMode) sendMidiDirect();
  else updateDisplay();
}

void toggleTransmissionMode() {
  directTransmissionMode = !directTransmissionMode;
  saveToEEPROM();

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(directTransmissionMode ? "MODE: TD ACTIF" : "MODE: NORMAL");
  delay(1500);
  updateDisplay();
}

void sendMidi() {
  int absolutePreset = (bank * PRESETS_PER_BANK) + preset;
  int bankMSB = absolutePreset / 128;
  int programNumber = absolutePreset % 128;

  MIDI.sendControlChange(0, bankMSB, 1);
  delay(20);
  MIDI.sendControlChange(32, 0, 1);
  delay(20);
  MIDI.sendProgramChange(programNumber, 1);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("MIDI SENT!");
  delay(600);
  updateDisplay();
}

void sendMidiDirect() {
  int absolutePreset = (bank * PRESETS_PER_BANK) + preset;
  int bankMSB = absolutePreset / 128;
  int programNumber = absolutePreset % 128;

  MIDI.sendControlChange(0, bankMSB, 1);
  delay(20);
  MIDI.sendControlChange(32, 0, 1);
  delay(20);
  MIDI.sendProgramChange(programNumber, 1);

  updateDisplay();
}

void updateDisplay() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("BANK: ");
  lcd.print(bank + 1);

  if (directTransmissionMode) {
    lcd.setCursor(14, 0);
    lcd.print("TD");
  }

  lcd.setCursor(0, 1);
  lcd.print("PRESET: ");
  if (preset < 10) lcd.print("00");
  else if (preset < 100) lcd.print("0");
  lcd.print(preset);
}

void saveToEEPROM() {
  EEPROM.update(EEPROM_BANK_ADDR, bank);
  EEPROM.update(EEPROM_PRESET_ADDR, preset);
  EEPROM.update(EEPROM_MODE_ADDR, directTransmissionMode ? 1 : 0);
}

void loadFromEEPROM() {
  bank = EEPROM.read(EEPROM_BANK_ADDR);
  preset = EEPROM.read(EEPROM_PRESET_ADDR);
  directTransmissionMode = EEPROM.read(EEPROM_MODE_ADDR) == 1;

  if (bank >= TOTAL_BANKS || bank < 0) bank = 0;
  if (preset >= PRESETS_PER_BANK || preset < 0) preset = 0;
}