#include <esp_now.h>
#include <WiFi.h>
#include <Adafruit_NeoPixel.h>

// === Pin definitions ===
#define MIC_PIN           32   // Analog input for microphone amplifier output (e.g. MAX9814 OUT)
#define SPEAKER_PIN       25   // DAC output for audio amplifier/speaker
#define LED_PIN           23   // Single LED lights up when receiving audio
#define PTT_PIN           34   // Push-to-Talk button (uses INPUT_PULLUP logic)
#define POT_PIN           35   // Linear 10k potentiometer for pitch control (playback speed)
#define AMP_SHUTDOWN_PIN  21   // PAM8302A shutdown pin (active HIGH: amp ON, LOW: amp OFF)
#define NEOPIX_MIC_PIN    19   // Data pin for 24-LED NeoPixel ring (for microphone VU)
#define NEOPIX_MIC_NUM    24
#define NEOPIX_RX_PIN     18   // Data pin for 12-LED NeoPixel ring (for received audio VU)
#define NEOPIX_RX_NUM     12
#define AUDIO_BLOCK      128   // Number of audio samples per packet

Adafruit_NeoPixel micRing(NEOPIX_MIC_NUM, NEOPIX_MIC_PIN, NEO_GRB + NEO_KHZ800);    // Microphone VU (big ring)
Adafruit_NeoPixel rxRing(NEOPIX_RX_NUM, NEOPIX_RX_PIN, NEO_GRBW + NEO_KHZ800);      // Received audio VU (small ring, white for clarity)

// === MAC address of the other ESP32 ===
// CHANGE this to the address of your partner board!
uint8_t peer_mac[6] = { 0xA8, 0x42, 0xE3, 0x90, 0x9D, 0xD0 };

// === Buffers for audio data ===
uint8_t audioBlock[AUDIO_BLOCK];      // For sending audio
volatile bool newBlock = false;       // Set true when a new audio packet arrives
uint8_t rxBlock[AUDIO_BLOCK];         // For receiving audio
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; // For interrupt safety

// === Noise Gate ===
const int noiseThreshold = 10; // Increase to reduce background noise, lower for more sensitivity

// === Amp ON delay timeout ===
unsigned long lastAudioTime = 0;
const unsigned long ampTimeout = 150; // ms after last audio before turning amp OFF

// === VU-Meter helper: classic green → yellow → red (works for both GRB and GRBW) ===
void showVUMeter(Adafruit_NeoPixel &ring, int numPixels, float loudness, float maxLoudness, float brightness = 0.2) {
  int level = map(loudness, 0, maxLoudness, 0, numPixels);
  if (level < 0) level = 0;
  if (level > numPixels) level = numPixels;
  for (int i = 0; i < numPixels; i++) {
    uint32_t color = 0;
    float pos = (float)i / numPixels;
    if (i < level) {
      // Classic VU colors: green, yellow, red (dimmable)
      uint8_t r = 0, g = 0, b = 0, w = 0;
      if (pos < 0.6)      { r = 0;   g = 255; b = 0;   }
      else if (pos < 0.8) { r = 255; g = 255; b = 0;   }
      else                { r = 255; g = 0;   b = 0;   }
      r = uint8_t(r * brightness);
      g = uint8_t(g * brightness);
      b = uint8_t(b * brightness);
      if (numPixels == NEOPIX_RX_NUM) // 12er RX ring: white + color
        color = ring.Color(r, g, b, uint8_t(64 * brightness));
      else // 24er MIC ring: classic color
        color = ring.Color(r, g, b);
    }
    ring.setPixelColor(i, color);
  }
  ring.show();
}

// === ESP-NOW: Called when a packet is received ===
void onReceive(const esp_now_recv_info_t *info, const uint8_t *data, int len) {
  // Only accept audio when NOT sending yourself!
  if (len == AUDIO_BLOCK && !newBlock && digitalRead(PTT_PIN) == LOW) {
    portENTER_CRITICAL_ISR(&mux);
    memcpy(rxBlock, data, AUDIO_BLOCK); // Copy received data to buffer
    newBlock = true;                    // Mark as ready for playback
    portEXIT_CRITICAL_ISR(&mux);
    lastAudioTime = millis();           // Update last audio timestamp
  }
}

void setup() {
  Serial.begin(115200);      // Serial for debug (optional)
  WiFi.mode(WIFI_STA);       // Station mode only
  WiFi.disconnect();
  delay(100);

  // --- Print this board's MAC address for pairing/debug ---
  Serial.print("My MAC address: ");
  Serial.println(WiFi.macAddress());

  // ESP-NOW initialization
  if (esp_now_init() != ESP_OK) {
    Serial.println("ESP-NOW init failed!");
    while (true);
  }

  // Peer setup (MAC of the other board)
  esp_now_del_peer(peer_mac);   // Remove in case it exists
  esp_now_peer_info_t peerInfo;
  memset(&peerInfo, 0, sizeof(peerInfo));
  memcpy(peerInfo.peer_addr, peer_mac, 6);
  peerInfo.channel = 0;
  peerInfo.encrypt = false;
  peerInfo.ifidx = WIFI_IF_STA;
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {
    Serial.println("Adding peer failed!");
    while (true);
  }

  // Register packet receive handler
  esp_now_register_recv_cb(onReceive);

  // Pin setup
  pinMode(PTT_PIN, INPUT_PULLUP); // Button: HIGH when not pressed, LOW when pressed
  pinMode(LED_PIN, OUTPUT);
  pinMode(POT_PIN, INPUT);        // Potentiometer for pitch control
  pinMode(AMP_SHUTDOWN_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);           // LED OFF at startup
  digitalWrite(AMP_SHUTDOWN_PIN, LOW);  // Amp OFF at startup

  // NeoPixel initialization
  micRing.begin();
  micRing.show(); // all LEDs off
  rxRing.begin();
  rxRing.show();  // all LEDs off

  Serial.println("ROBUSTRingPhone ready!");
}

void loop() {
  static int silentMid = 1350;     // Value for silence (use Serial plotter to find)
  static double gain = 0.0075;     // Lower for loud mic, higher for weak mic

  // === SENDING: If PTT button is pressed, sample and transmit audio ===
  if (digitalRead(PTT_PIN) == HIGH) { // HIGH means button is pressed!
    int maxMic = 0;
    int minMic = 4095;
    for (int i = 0; i < AUDIO_BLOCK; i++) {
      int raw = analogRead(MIC_PIN);      // Microphone ADC reading
      int signal = abs(raw - silentMid);  // Distance from silence

      if (raw > maxMic) maxMic = raw;
      if (raw < minMic) minMic = raw;

      int val = (raw - silentMid) * gain + 127;
      if (signal < noiseThreshold) {
        val = 127;
      }
      if (val < 0) val = 0;
      if (val > 255) val = 255;
      audioBlock[i] = val;
      delayMicroseconds(125); // 8kHz sample rate
    }
    esp_now_send(peer_mac, audioBlock, AUDIO_BLOCK); // Transmit audio

    int micLoudness = maxMic - minMic;
    showVUMeter(micRing, NEOPIX_MIC_NUM, micLoudness, 2000, 0.18); // 0.18 = dimmer

    rxRing.clear();
    rxRing.show();
  } else {
    micRing.clear();
    micRing.show();
    rxRing.clear();
    rxRing.show();
  }

  // === RECEIVING: If a new audio block is available and PTT NOT pressed, play it back ===
  if (newBlock && digitalRead(PTT_PIN) == LOW) {
    digitalWrite(LED_PIN, HIGH);
    digitalWrite(AMP_SHUTDOWN_PIN, HIGH); // Enable amplifier for playback
    delay(1);

    portENTER_CRITICAL(&mux);
    newBlock = false;
    portEXIT_CRITICAL(&mux);

    int potVal = analogRead(POT_PIN); // 0...4095
    int playbackDelay = map(potVal, 0, 4095, 90, 220);

    int maxRx = 0;
    int minRx = 255;
    for (int i = 0; i < AUDIO_BLOCK; i++) {
      if (rxBlock[i] > maxRx) maxRx = rxBlock[i];
      if (rxBlock[i] < minRx) minRx = rxBlock[i];
    }
    int rxLoudness = maxRx - minRx;
    showVUMeter(rxRing, NEOPIX_RX_NUM, rxLoudness, 20, 0.14);

    for (int i = 0; i < AUDIO_BLOCK; i++) {
      dacWrite(SPEAKER_PIN, rxBlock[i]);
      delayMicroseconds(playbackDelay);
    }
    lastAudioTime = millis(); // Update last audio timestamp
    digitalWrite(LED_PIN, LOW);
  }

  // --- Amp shutdown logic: turn amp OFF after timeout with no audio received ---
  if (digitalRead(AMP_SHUTDOWN_PIN) == HIGH && (millis() - lastAudioTime > ampTimeout)) {
    digitalWrite(AMP_SHUTDOWN_PIN, LOW); // Mute amp if no audio recently
  }
}

/*
-----------------------------------------
Hardware wiring and project notes:
-----------------------------------------
- MIC_PIN (GPIO32): Connect OUT of your microphone amplifier (e.g., MAX9814, MAX4466).
- SPEAKER_PIN (GPIO25): Connect to audio amp (e.g., PAM8302A IN+).
- LED_PIN (GPIO23): Anode (long leg) of LED to pin, cathode via resistor (e.g., 220Ω) to GND.
- PTT_PIN (GPIO34): One side of pushbutton to this pin, other side to GND.
- POT_PIN (GPIO35): Center of a 10k linear potentiometer to this pin, ends to 3.3V and GND.
- AMP_SHUTDOWN_PIN (GPIO21): Connect to SHDN pin of PAM8302A. HIGH = ON, LOW = OFF.
- NEOPIX_MIC_PIN (GPIO19): Data in for **24-LED NeoPixel ring** (VU meter for microphone, GRB/RGB).
- NEOPIX_RX_PIN  (GPIO18): Data in for **12-LED NeoPixel ring** (VU meter for received audio, GRBW/RGBW).
- Both ESP32s must use this code, but each with the other's MAC in peer_mac[].
- Only one person can transmit at a time (real walkie-talkie behavior).
- "Pitch control": Turn potentiometer to change voice pitch of received audio in real time.
- "VU meter": Rings show audio loudness bar & color gradient in real time.
- Tune gain, silentMid, noiseThreshold, and VU meter scaling for best audio with your parts!
*/
