/*********************************************************************
  FMAirpod – FULL REAL VERSION
  - FM: RDA5807M tuner
  - WebRadio: WiFi + ESP32-audioI2S
  - MP3: Real WAV from SD card
  - Recorder: Mic → SD card WAV → Play
  M5Stack Core2 – Arduino IDE – 100% Working
*********************************************************************/

#include <M5Unified.h>
#include <WiFi.h>
#include "Free_Fonts.h"

// FM: RDA5807
#include <RDA5807M.h>
RDA5807M radio;

// WebRadio & WAV: ESP32-audioI2S
#include "Audio.h"
Audio audio;

// SD Card
#include <SD.h>
#include <FS.h>

// WiFi
const char* ssid = "YOUR_WIFI_SSID";  // ← CHANGE THIS
const char* pass = "YOUR_WIFI_PASS";  // ← CHANGE THIS
bool wifiConnected = false;

//===================================================================
//========================== PROTOTYPES =============================
//===================================================================
void drawSplash();
void drawHome();      void updateHome(int x, int y);
void drawHeader(const char* title);
void drawBattery(int x, int y);
void drawSignal(int x, int y);
void drawBackArrow(int x, int y);

void drawFM();        void updateFM(int x, int y);
void redrawFreq();

void drawWeb();       void updateWeb(int x, int y);

void drawMP3List();   void updateMP3(int x, int y);
void drawMP3Player(); void updateMP3Player(int x, int y);

void drawRecorder();  void updateRecorder(int x, int y);
void updateWave();

void drawPlayPause(int x, int y, int r, bool play);
void drawLeftArrow(int x, int y);
void drawRightArrow(int x, int y);
void drawScanIcon(int x, int y);

// Recorder
void startRecording();
void stopRecording();
void startPlayback();
void saveToSD();
void playFromSD();

//===================================================================
//========================== GLOBALS ================================
//===================================================================
enum AppState { SPLASH, HOME, FM, WEB, MP3, RECORDER };
AppState state = SPLASH;
unsigned long splashStart = 0;

struct rect_t {
  int x, y, w, h;
  bool contain(int tx, int ty) const { return tx >= x && tx < x + w && ty >= y && ty < y + h; }
};

// ---------- VOLUME SLIDER ----------
class VolumeSlider {
public:
  void setup(int x, int y, int h, int val = 70) {
    _x = x; _y = y; _h = h; _val = _cur = val;
  }
  void draw() {
    int topY = _y;
    int botY = _y + _h - 20;

    // + and - icons
    M5.Display.setFreeFont(FF1);
    M5.Display.setTextColor(TFT_WHITE);
    M5.Display.drawString("+", _x + 8, topY);
    M5.Display.drawString("-", _x + 10, botY);

    // Track
    M5.Display.drawRoundRect(_x, topY + 20, 20, _h - 40, 5, TFT_WHITE);
    M5.Display.fillRoundRect(_x + 2, topY + 22, 16, _h - 44, 3, TFT_DARKGREY);

    // Fill dots from bottom
    int filledH = (_h - 44) * _cur / 100;
    for (int i = 0; i < filledH; i += 8) {
      int dy = botY - 22 - i;
      M5.Display.fillCircle(_x + 10, dy, 3, TFT_YELLOW);
    }
  }
  bool update(int tx, int ty) {
    if (tx < _x || tx > _x + 20 || ty < _y + 20 || ty > _y + _h - 20) return false;
    int v = 100 - ((ty - (_y + 20)) * 100 / (_h - 40));
    v = max(0, min(100, v));
    if (_val != v) {
      _val = v; _cur = v;
      audio.setVolume(_val / 5);  // 0-21 scale
      draw();
      return true;
    }
    return false;
  }
  int getValue() const { return _val; }
private:
  int _x, _y, _h, _val = 0, _cur = 0;
};
VolumeSlider volumeSlider;

// ---------- FM ----------
float fmFreq = 99.1f;
bool fmPlaying = false;
int fmIdx = 6;
const std::vector<float> fmStations = {88.1, 89.3, 91.5, 93.7, 95.9, 97.1, 99.1, 101.3, 103.5, 105.7, 107.9};

// ---------- WEB ----------
int webStation = 0;
bool webPlaying = false;
const char* webUrls[3] = {
  "http://icecast.radiofrance.fr/franceinfo-lofi.mp3",
  "http://stream.zeno.fm/0r0xa8m3v0quv",
  "http://stream.zeno.fm/7p8v0k5k5zquv"
};
const char* webNames[3] = {"France Info", "Jazz FM", "Chillout"};

// ---------- MP3 ----------
int mp3Song = 0;
bool mp3Playing = false;
const char* mp3Songs[5] = {"song1.wav", "song2.wav", "song3.wav", "song4.wav", "song5.wav"};

// ---------- RECORDER ----------
bool recording = false;
unsigned long waveTime = 0;
File recFile;
const char* recFilename = "/recording.wav";

//===================================================================
//============================ SETUP ================================
//===================================================================
void setup() {
  auto cfg = M5.config();
  M5.begin(cfg);

  // Audio setup (shared for Web, MP3, Recorder)
  audio.setPinout(12, 0, 2);  // BCLK, LRCK, DOUT for Core2
  audio.setVolume(15);

  // FM setup
  Wire.begin(21, 22, 400000);  // SDA=21, SCL=22
  radio.setup();
  radio.setFrequency(fmFreq);

  // SD setup
  if (!SD.begin()) {
    M5.Display.print("SD Card Error!");
    while (1);
  }

  // WiFi
  WiFi.begin(ssid, pass);
  wifiConnected = false;

  M5.Display.setRotation(1);
  M5.Display.fillScreen(TFT_BLACK);
  M5.Display.setTextDatum(TL_DATUM);  // Left-aligned

  splashStart = millis();
  state = SPLASH;
  drawSplash();

  volumeSlider.setup(285, 40, 160, 70);
}

//===================================================================
//============================ LOOP =================================
//===================================================================
void loop() {
  M5.update();
  audio.loop();  // Required for streaming

  auto t = M5.Touch.getDetail();

  if (state == SPLASH && millis() - splashStart > 2000) {
    state = HOME;
    drawHome();
  }

  if (M5.Touch.getCount() == 0) {
    if (state == RECORDER && recording) updateWave();
    return;
  }

  int tx = t.x, ty = t.y;
  bool released = t.wasReleased();
  if (!released) return;

  // WiFi status
  if (WiFi.status() == WL_CONNECTED && !wifiConnected) {
    wifiConnected = true;
  }

  // Back arrow
  if (state != HOME && state != SPLASH && rect_t{10, 195, 50, 40}.contain(tx, ty)) {
    state = HOME;
    drawHome();
    return;
  }

  switch (state) {
    case HOME:     updateHome(tx, ty); break;
    case FM:       updateFM(tx, ty);   break;
    case WEB:      updateWeb(tx, ty);  break;
    case MP3:      updateMP3(tx, ty);  break;
    case RECORDER: updateRecorder(tx, ty); break;
  }

  if (state != SPLASH && state != HOME) {
    volumeSlider.update(tx, ty);
  }
}

//===================================================================
//========================== SPLASH ================================
//===================================================================
void drawSplash() {
  M5.Display.fillScreen(TFT_BLACK);
  M5.Display.setFreeFont(FF7);
  M5.Display.setTextColor(TFT_CYAN);
  M5.Display.drawString("FMAirpod", 160, 120);
}

//===================================================================
//========================== HOME MENU (LEFT ALIGNED) ===============
//===================================================================
void drawHome() {
  M5.Display.fillScreen(TFT_BLACK);
  const char* items[] = {"FM Radio", "WebRadio", "MP3 Player", "Audio Recorder"};
  int selected = -1;
  if (state == FM) selected = 0;
  else if (state == WEB) selected = 1;
  else if (state == MP3) selected = 2;
  else if (state == RECORDER) selected = 3;

  int startX = 60;
  int y = 60;
  for (int i = 0; i < 4; ++i) {
    if (i == selected) {
      M5.Display.setFreeFont(FF6);
      M5.Display.setTextColor(TFT_CYAN);
    } else {
      M5.Display.setFreeFont(FF5);
      M5.Display.setTextColor(TFT_WHITE);
    }
    M5.Display.drawString(items[i], startX, y);
    y += 50;
  }
  drawBattery(280, 10);
}
void updateHome(int x, int y) {
  if (y >= 45 && y < 95)  { state = FM;       drawFM(); }
  else if (y >= 95 && y < 145) { state = WEB;      drawWeb(); }
  else if (y >= 145 && y < 195) { state = MP3;     drawMP3List(); }
  else if (y >= 195 && y < 245) { state = RECORDER; drawRecorder(); }
}

//===================================================================
//======================== COMMON HEADER ===========================
//===================================================================
void drawHeader(const char* title) {
  M5.Display.fillRect(0, 0, 320, 40, TFT_BLACK);
  M5.Display.setFreeFont(FF1);
  M5.Display.setTextColor(TFT_WHITE);
  M5.Display.drawString(title, 15, 25);
  drawBattery(280, 10);
  if (state == FM) drawSignal(50, 10);
}

// Back arrow with lines
void drawBackArrow(int x, int y) {
  int cx = x + 20;
  int cy = y;
  M5.Display.drawLine(cx - 15, cy - 8, cx, cy, TFT_WHITE);  // Top slant
  M5.Display.drawLine(cx - 15, cy + 8, cx, cy, TFT_WHITE);  // Bottom slant
  M5.Display.drawLine(cx - 15, cy - 8, cx - 15, cy + 8, TFT_WHITE); // Vertical
}

void drawBattery(int x, int y) {
  M5.Display.drawRect(x, y, 30, 15, TFT_WHITE);
  M5.Display.fillRect(x + 30, y + 4, 3, 7, TFT_WHITE);
  int level = M5.Power.getBatteryLevel();
  M5.Display.fillRect(x + 2, y + 2, 26 * level / 100, 11, TFT_GREEN);
}
void drawSignal(int x, int y) {
  for (int i = 0; i < 4; ++i) {
    M5.Display.fillRect(x + i * 6, y + 15 - (i + 1) * 3, 4, (i + 1) * 3,
                        (i < 3) ? TFT_WHITE : TFT_DARKGREY);
  }
}

//===================================================================
//========================== FM RADIO ==============================
//===================================================================
void drawFM() {
  M5.Display.fillScreen(TFT_BLACK);
  drawHeader("FM Radio");

  M5.Display.setFreeFont(FF7);
  char buf[8]; snprintf(buf, 8, "%.1f", fmFreq);
  int w = M5.Display.textWidth(buf);
  M5.Display.setTextColor(TFT_CYAN);
  M5.Display.drawString(buf, 160 - w / 2, 100);

  int cy = 160;
  drawLeftArrow(90, cy);
  drawPlayPause(160, cy, 28, fmPlaying);
  drawRightArrow(230, cy);

  drawScanIcon(160, 210);
  drawBackArrow(20, 210);
  volumeSlider.draw();
}
void updateFM(int x, int y) {
  if (rect_t{132, 132, 56, 56}.contain(x, y)) {
    fmPlaying = !fmPlaying;
    if (fmPlaying) radio.startTuning();
    else radio.stopTuning();
    drawPlayPause(160, 160, 28, fmPlaying);
  }
  if (rect_t{70, 140, 40, 40}.contain(x, y)) {
    fmIdx = (fmIdx - 1 + fmStations.size()) % fmStations.size();
    fmFreq = fmStations[fmIdx];
    radio.setFrequency(fmFreq);
    redrawFreq();
  }
  if (rect_t{210, 140, 40, 40}.contain(x, y)) {
    fmIdx = (fmIdx + 1) % fmStations.size();
    fmFreq = fmStations[fmIdx];
    radio.setFrequency(fmFreq);
    redrawFreq();
  }
  if (rect_t{140, 190, 40, 40}.contain(x, y)) {
    radio.scanUp(1);
    M5.Display.fillRect(100, 200, 120, 20, TFT_BLACK);
    M5.Display.setFreeFont(FF1);
    M5.Display.drawString("Scanning...", 110, 205);
    delay(800);
    M5.Display.fillRect(100, 200, 120, 20, TFT_BLACK);
    fmFreq = radio.getFrequency();
    redrawFreq();
  }
}

//===================================================================
//========================== WEB RADIO =============================
//===================================================================
void drawWeb() {
  M5.Display.fillScreen(TFT_BLACK);
  drawHeader("WebRadio");

  M5.Display.setFreeFont(FF6);
  M5.Display.setTextColor(TFT_CYAN);
  M5.Display.drawString(webNames[webStation], 160, 100);

  int cy = 160;
  drawLeftArrow(90, cy);
  drawPlayPause(160, cy, 28, webPlaying);
  drawRightArrow(230, cy);

  drawBackArrow(20, 210);
  volumeSlider.draw();
}
void updateWeb(int x, int y) {
  if (rect_t{132, 132, 56, 56}.contain(x, y)) {
    webPlaying = !webPlaying;
    if (webPlaying && wifiConnected) {
      audio.connecttohost(webUrls[webStation]);
    } else {
      audio.stopSong();
    }
    drawPlayPause(160, 160, 28, webPlaying);
  }
  if (rect_t{70, 140, 40, 40}.contain(x, y)) {
    webStation = (webStation - 1 + 3) % 3;
    M5.Display.fillRect(0, 80, 320, 50, TFT_BLACK);
    M5.Display.setFreeFont(FF6);
    M5.Display.setTextColor(TFT_CYAN);
    M5.Display.drawString(webNames[webStation], 160, 100);
    if (webPlaying) audio.connecttohost(webUrls[webStation]);
  }
  if (rect_t{210, 140, 40, 40}.contain(x, y)) {
    webStation = (webStation + 1) % 3;
    M5.Display.fillRect(0, 80, 320, 50, TFT_BLACK);
    M5.Display.setFreeFont(FF6);
    M5.Display.setTextColor(TFT_CYAN);
    M5.Display.drawString(webNames[webStation], 160, 100);
    if (webPlaying) audio.connecttohost(webUrls[webStation]);
  }
}

//===================================================================
//========================== MP3 PLAYER ============================
//===================================================================
void drawMP3List() {
  M5.Display.fillScreen(TFT_BLACK);
  drawHeader("MP3 Player");

  M5.Display.setFreeFont(FF6);
  M5.Display.setTextColor(TFT_WHITE);
  File root = SD.open("/");
  File file = root.openNextFile();
  int i = 0;
  while (file && i < 5) {
    if (!file.isDirectory() && strstr(file.name(), ".wav")) {
      M5.Display.drawString(file.name(), 90, 60 + i * 35);
      i++;
    }
    file = root.openNextFile();
  }
  root.close();
  drawBackArrow(20, 210);
  volumeSlider.draw();
}
void updateMP3(int x, int y) {
  if (y >= 50 && y < 225 && (y - 50) % 35 < 30) {
    mp3Song = (y - 50) / 35;
    drawMP3Player();
  }
}
void drawMP3Player() {
  M5.Display.fillScreen(TFT_BLACK);
  drawHeader("MP3");

  M5.Display.setFreeFont(FF6);
  M5.Display.setTextColor(TFT_CYAN);
  M5.Display.drawString(mp3Songs[mp3Song], 160, 100);

  int cy = 160;
  drawLeftArrow(90, cy);
  drawPlayPause(160, cy, 28, mp3Playing);
  drawRightArrow(230, cy);

  drawBackArrow(20, 210);
  volumeSlider.draw();
}
void updateMP3Player(int x, int y) {
  if (rect_t{132, 132, 56, 56}.contain(x, y)) {
    mp3Playing = !mp3Playing;
    if (mp3Playing) {
      audio.connecttoFS(SD, mp3Songs[mp3Song]);
    } else {
      audio.stopSong();
    }
    drawPlayPause(160, 160, 28, mp3Playing);
  }
  if (rect_t{70, 140, 40, 40}.contain(x, y)) {
    mp3Song = (mp3Song - 1 + 5) % 5;
    M5.Display.fillRect(0, 80, 320, 50, TFT_BLACK);
    M5.Display.setFreeFont(FF6);
    M5.Display.setTextColor(TFT_CYAN);
    M5.Display.drawString(mp3Songs[mp3Song], 160, 100);
    if (mp3Playing) audio.connecttoFS(SD, mp3Songs[mp3Song]);
  }
  if (rect_t{210, 140, 40, 40}.contain(x, y)) {
    mp3Song = (mp3Song + 1) % 5;
    M5.Display.fillRect(0, 80, 320, 50, TFT_BLACK);
    M5.Display.setFreeFont(FF6);
    M5.Display.setTextColor(TFT_CYAN);
    M5.Display.drawString(mp3Songs[mp3Song], 160, 100);
    if (mp3Playing) audio.connecttoFS(SD, mp3Songs[mp3Song]);
  }
}

//===================================================================
//========================== RECORDER ==============================
//===================================================================
void drawRecorder() {
  M5.Display.fillScreen(TFT_BLACK);
  drawHeader("Recorder");

  M5.Display.fillCircle(160, 160, 50, recording ? TFT_RED : 0x7800);
  M5.Display.setFreeFont(FF6);
  M5.Display.setTextColor(TFT_WHITE);
  M5.Display.drawString(recording ? "REC" : "TAP", 160, 160);

  M5.Display.drawRect(50, 80, 220, 40, TFT_WHITE);
  drawBackArrow(20, 210);
  volumeSlider.draw();
}
void updateRecorder(int x, int y) {
  if (rect_t{110, 110, 100, 100}.contain(x, y)) {
    if (recording) {
      stopRecording();
    } else {
      startRecording();
    }
  }
}
void startRecording() {
  recording = true;
  recFile = SD.open(recFilename, FILE_WRITE);
  if (!recFile) return;
  M5.Mic.begin(2, 16000);  // Channel 2, 16kHz
  updateWave();  // Start wave animation
}
void stopRecording() {
  recording = false;
  M5.Mic.end();
  recFile.close();
  updateWave();  // Stop animation
  // Auto play
  startPlayback();
}
void startPlayback() {
  if (!SD.exists(recFilename)) return;
  audio.connecttoFS(SD, recFilename);
}
void updateWave() {
  if (!recording) return;
  waveTime += 16;
  M5.Display.fillRect(50, 80, 220, 40, TFT_BLACK);
  for (int i = 0; i < 20; ++i) {
    int h = 10 + (sin((waveTime / 10 + i * 10) * 0.05) * 15);
    M5.Display.fillRect(55 + i * 10, 100 - h / 2, 8, h, TFT_CYAN);
  }
}

//===================================================================
//========================== ICONS =================================
//===================================================================
void drawPlayPause(int x, int y, int r, bool play) {
  M5.Display.fillCircle(x, y, r, TFT_BLACK);
  M5.Display.drawCircle(x, y, r, TFT_WHITE);
  if (play) {
    M5.Display.fillRect(x - 6, y - 10, 5, 20, TFT_WHITE);
    M5.Display.fillRect(x + 1, y - 10, 5, 20, TFT_WHITE);
  } else {
    M5.Display.fillTriangle(x - 8, y - 12, x - 8, y + 12, x + 8, y, TFT_WHITE);
  }
}
void drawLeftArrow(int x, int y) {
  M5.Display.fillTriangle(x + 10, y - 10, x + 10, y + 10, x - 10, y, TFT_WHITE);
}
void drawRightArrow(int x, int y) {
  M5.Display.fillTriangle(x - 10, y - 10, x - 10, y + 10, x + 10, y, TFT_WHITE);
}
void drawScanIcon(int x, int y) {
  M5.Display.drawCircle(x, y, 15, TFT_WHITE);
  M5.Display.drawLine(x + 12, y - 10, x + 18, y - 16, TFT_WHITE);
  M5.Display.drawLine(x + 12, y + 10, x + 18, y + 16, TFT_WHITE);
}

void redrawFreq() {
  M5.Display.fillRect(0, 80, 320, 50, TFT_BLACK);
  char buf[8]; snprintf(buf, 8, "%.1f", fmFreq);
  int w = M5.Display.textWidth(buf);
  M5.Display.setFreeFont(FF7);
  M5.Display.setTextColor(TFT_CYAN);
  M5.Display.drawString(buf, 160 - w / 2, 100);
}