#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"
#include "BluetoothA2DPSink.h"
#include <SPI.h>
#include <SD.h>
#include <math.h>
#include <Preferences.h>

#define SD_CS    5
#define SD_MOSI  23
#define SD_MISO  19
#define SD_SCK   18
#define I2S_BCLK 13
#define I2S_LRC  14
#define I2S_DIN  12

#define BTN_MP3 32
#define BTN_BT  25
#define BTN_FM  26
#define BT_PAIR_BUTTON 33

#define MODE_MP3 0
#define MODE_BT  1
#define MODE_FM  2

I2SStream i2s;
VolumeStream volStream(i2s);
EncodedAudioStream decoder(&volStream, new MP3DecoderHelix());
File audioFile;
StreamCopy copier(decoder, audioFile, 4096);

BluetoothA2DPSink a2dp_sink(i2s);
Preferences prefs;

// Hoisted to file scope so we can reuse it to resurrect I2S
// after A2DP tears it down on disconnect.
I2SConfig i2sCfg;

String tracks[20];
int trackCount = 0;
int currentTrack = 0;
float volume = 0.5;
bool paused = false;
bool discoverable = false;
volatile bool connectEvent = false;
volatile bool disconnectEvent = false;
unsigned long lastBtnPress = 0;
unsigned long lastBeep = 0;
unsigned long lastModeCheck = 0;
int currentMode = MODE_MP3;
#define DEBOUNCE_MS 500
#define BEEP_INTERVAL 1500

void onConnectionStateChanged(esp_a2d_connection_state_t state, void* obj) {
  if (state == ESP_A2D_CONNECTION_STATE_CONNECTED) {
    connectEvent = true;
  } else if (state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
    disconnectEvent = true;
  }
}

void scanTracks() {
  File root = SD.open("/");
  trackCount = 0;
  while (true) {
    File entry = root.openNextFile();
    if (!entry) break;
    String name = entry.name();
    if (name.endsWith(".mp3") && !name.startsWith(".") && trackCount < 20) {
      tracks[trackCount++] = "/" + name;
    }
    entry.close();
  }
  root.close();
  Serial.print("Found tracks: ");
  Serial.println(trackCount);
}

void playTrack(int idx) {
  if (audioFile) audioFile.close();
  if (idx < 0) idx = trackCount - 1;
  if (idx >= trackCount) idx = 0;
  currentTrack = idx;
  audioFile = SD.open(tracks[idx]);
  Serial.print("Now playing: ");
  Serial.println(tracks[idx]);
}

void playBeep() {
  const int sr = 44100;
  int samples = sr * 200 / 1000;
  for (int i = 0; i < samples; i++) {
    float t = (float)i / sr;
    int16_t sample = sin(2 * PI * 880 * t) * 10000;
    int16_t stereo[2] = {sample, sample};
    i2s.write((uint8_t*)stereo, 4);
  }
}

void playChime(bool ascending) {
  const int sr = 44100;
  int notes[] = {523, 784, 1047};
  int durations[] = {180, 180, 500};

  for (int n = 0; n < 3; n++) {
    int idx = ascending ? n : (2 - n);
    int samples = sr * durations[n] / 1000;
    int fadeIn = samples / 12;
    int fadeOut = samples / 2;

    for (int i = 0; i < samples; i++) {
      float t = (float)i / sr;
      float envelope = 1.0;
      if (i < fadeIn) envelope = (float)i / fadeIn;
      else if (i > samples - fadeOut) {
        float fp = (float)(samples - i) / fadeOut;
        envelope = fp * fp;
      }
      int16_t sample = sin(2 * PI * notes[idx] * t) * 8000 * envelope;
      int16_t stereo[2] = {sample, sample};
      i2s.write((uint8_t*)stereo, 4);
    }
  }
}

void switchMode(int newMode) {
  Serial.print("Switching to mode ");
  Serial.println(newMode);
  prefs.begin("radio", false);
  prefs.putInt("mode", newMode);
  prefs.end();
  delay(200);
  ESP.restart();
}

void setupMP3() {
  Serial.println("MP3 MODE");
  decoder.begin();
  if (trackCount > 0) playTrack(0);
}

void setupBluetooth() {
  Serial.println("BLUETOOTH MODE");
  a2dp_sink.set_on_connection_state_changed(onConnectionStateChanged);
  a2dp_sink.set_discoverability(ESP_BT_NON_DISCOVERABLE);
  a2dp_sink.start("Retro Radio");
  a2dp_sink.set_volume(64);
}

void setupFM() {
  Serial.println("FM MODE (not yet wired)");
}

void loopMP3() {
  if (!paused) copier.copy();
  if (audioFile && !audioFile.available() && !paused) {
    playTrack(currentTrack + 1);
  }

  if (Serial.available()) {
    char cmd = Serial.read();
    if (cmd == 'n') playTrack(currentTrack + 1);
    else if (cmd == 'p') playTrack(currentTrack - 1);
    else if (cmd == 's') paused = !paused;
    else if (cmd == '+') { volume = min(1.0f, volume + 0.1f); volStream.setVolume(volume); }
    else if (cmd == '-') { volume = max(0.0f, volume - 0.1f); volStream.setVolume(volume); }
  }
}

void loopBluetooth() {
  unsigned long now = millis();
  bool nowConnected = a2dp_sink.is_connected();

  if (connectEvent) {
    connectEvent = false;
    Serial.println("BT CONNECTED");
    discoverable = false;
    a2dp_sink.set_discoverability(ESP_BT_NON_DISCOVERABLE);
    delay(200);
    playChime(true);
  }

  if (disconnectEvent) {
    disconnectEvent = false;
    Serial.println("BT DISCONNECTED");

    // A2DP has torn the I2S driver down as part of its disconnect
    // cleanup. Give that a moment to finish, then bring I2S back up
    // ourselves at our chime's expected rate (44.1 kHz). Without this
    // step the chime's writes go to a dead driver and never reach the
    // amp.
    //
    // We do NOT call i2s.end() here — A2DP already did. Calling begin()
    // reinstalls the driver. On the next phone reconnect the library
    // will call begin() again with its own config; that's fine.
    delay(300);
    i2s.begin(i2sCfg);
    playChime(false);
  }

  if (!digitalRead(BT_PAIR_BUTTON) && now - lastBtnPress > DEBOUNCE_MS) {
    lastBtnPress = now;
    discoverable = !discoverable;
    if (discoverable) {
      a2dp_sink.set_discoverability(ESP_BT_GENERAL_DISCOVERABLE);
      Serial.println("Pairing mode ON");
    } else {
      a2dp_sink.set_discoverability(ESP_BT_NON_DISCOVERABLE);
      Serial.println("Pairing mode OFF");
    }
  }

  if (discoverable && !nowConnected && now - lastBeep > BEEP_INTERVAL) {
    lastBeep = now;
    playBeep();
  }
}

void loopFM() {
  // Placeholder
}

void setup() {
  Serial.begin(115200);
  delay(2000);
  AudioLogger::instance().begin(Serial, AudioLogger::Warning);

  pinMode(BTN_MP3, INPUT_PULLUP);
  pinMode(BTN_BT, INPUT_PULLUP);
  pinMode(BTN_FM, INPUT_PULLUP);
  pinMode(BT_PAIR_BUTTON, INPUT_PULLUP);

  prefs.begin("radio", true);
  currentMode = prefs.getInt("mode", MODE_MP3);
  prefs.end();

  SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
  if (!SD.begin(SD_CS)) {
    Serial.println("SD failed");
  } else {
    scanTracks();
  }

  // Populate the file-scope config and bring up I2S.
  i2sCfg = i2s.defaultConfig(TX_MODE);
  i2sCfg.pin_bck  = I2S_BCLK;
  i2sCfg.pin_ws   = I2S_LRC;
  i2sCfg.pin_data = I2S_DIN;
  i2sCfg.buffer_size = 1024;
  i2sCfg.buffer_count = 8;
  i2sCfg.sample_rate = 44100;
  i2sCfg.channels = 2;
  i2sCfg.bits_per_sample = 16;
  i2s.begin(i2sCfg);

  auto vcfg = volStream.defaultConfig();
  vcfg.copyFrom(i2sCfg);
  volStream.begin(vcfg);
  volStream.setVolume(volume);

  if (currentMode == MODE_MP3) setupMP3();
  else if (currentMode == MODE_BT) setupBluetooth();
  else if (currentMode == MODE_FM) setupFM();
}

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

  if (now - lastModeCheck > 200) {
    lastModeCheck = now;
    if (!digitalRead(BTN_MP3) && currentMode != MODE_MP3) switchMode(MODE_MP3);
    if (!digitalRead(BTN_BT)  && currentMode != MODE_BT)  switchMode(MODE_BT);
    if (!digitalRead(BTN_FM)  && currentMode != MODE_FM)  switchMode(MODE_FM);
  }

  if (currentMode == MODE_MP3) loopMP3();
  else if (currentMode == MODE_BT) loopBluetooth();
  else if (currentMode == MODE_FM) loopFM();
}
