/***************************************************
   StudyOptimizer.ino

   Example Blynk-based firmware for an ESP32 that:
    - Tracks total elapsed time since study session start
    - Tracks how much of that time the user is actually
      present at the desk (using a proximity sensor)
    - Lets the user set a "StudyTill" target time (HH:MM format)
    - Shows how many hours remain (in binary) on an LED strip
    - Plays a "panic button" MP3 when triggered from Blynk
    - Sends a "tooloud" event if microphone input exceeds threshold
 ***************************************************/
#define BLYNK_TEMPLATE_ID "TMPL6iqFYLVI8"
#define BLYNK_TEMPLATE_NAME "Study Buddy"
#define BLYNK_AUTH_TOKEN "wyzrlF76I3b3RL6Jfue4meve5CgPWoja"
// -------------------------
//   Includes / Definitions
// -------------------------
#include <Arduino.h>
#include <WiFi.h>
#include <BlynkSimpleEsp32.h>    // Blynk library for ESP32
#include "SpiffPlayer.h"         // Provided in this project
#include "TimeVisualizer.h"      // Provided in this project
#include "Log.h"                 // Provided in this project

// --- Blynk Auth / WiFi Credentials (fill these in yourself) ---
char ssid[] = "Infi";
char pass[] = "yossishamai";

// --- Blynk Virtual Pins ---
#define VPIN_PANIC_BUTTON   V0   // button that triggers MP3 alarm
#define VPIN_DND_TOGGLE     V1   // "Do Not Disturb" toggle (blue LED)
#define VPIN_TIME_ELAPSED   V2   // total session time (seconds)
#define VPIN_TIME_STUDIED   V3   // active study time (seconds, user present)
#define VPIN_STUDY_TILL     V4   // study goal (target time HH:MM)

// --- Hardware Pins (adjust to your board) ---
#define TRIG_PIN       5    // HC-SR04 trigger pin
#define ECHO_PIN       18   // HC-SR04 echo pin
#define MIC_PIN        34   // analog microphone input pin
#define LED_PIN        2    // Blue LED for Do Not Disturb indicator

// Example thresholds for sensor logic (tweak to your setup)
#define PROXIMITY_DISTANCE_CM 50   // presence if distance < 50 cm
#define NOISE_THRESHOLD       4000 // microphone loudness threshold

// -------------------------
//   Global Objects
// -------------------------
SpiffPlayer player("/panic.mp3");   // MP3 player for panic alarm (file in SPIFFS)
TimeVisualizer timeVis;             // Visualizer for remaining time (LED strip)

// Track elapsed time since start of session
unsigned long studyStartMillis = 0;

// Track how many seconds user is actually at the desk
unsigned long timeStudiedSeconds = 0;
bool wasStudying = false;
unsigned long lastProxCheckMillis = 0;

// Target study end time (set via Blynk in HH:MM)
int  targetHour = 0;
int  targetMinute = 0;
bool targetTimeSet = false;
bool targetReachedNotified = false;

// Audio playback state for panic button
bool soundPlaying = false;         // true if panic.mp3 is currently playing

// Time synchronization status
bool timeSyncInitialized = false;  // true if NTP time was obtained at startup

// -------------------------
//   Forward Declarations
// -------------------------
bool isUserPresent();
void updateTimeLeftOnStrip();
void checkMicrophone();

// -------------------------------------
//   Blynk Functions for Virtual Pins
// -------------------------------------
BLYNK_WRITE(VPIN_PANIC_BUTTON) {
  // param.asInt() == 1 if button pressed, 0 if released
  int pressed = param.asInt();
  if (pressed == 1) {
    if (!soundPlaying) {
      LOG_INFO("BLYNK_WRITE(V0)", "Panic button pressed -> playing audio");
      if (player.setup("/panic.mp3")) {
        soundPlaying = true;
        LOG_INFO("Audio", "panic.mp3 playback started");
      } else {
        LOG_ERROR("Audio", "Failed to start panic.mp3 playback");
      }
    } else {
      LOG_WARNING("BLYNK_WRITE(V0)", "Panic button pressed while audio already playing");
    }
  } else {
    if (soundPlaying) {
      LOG_INFO("BLYNK_WRITE(V0)", "Panic button released -> stopping audio");
      player.stop();
      soundPlaying = false;
    } else {
      LOG_INFO("BLYNK_WRITE(V0)", "Panic button released (no audio playing)");
    }
  }
}

BLYNK_WRITE(VPIN_DND_TOGGLE) {
  // Toggle Do Not Disturb LED on/off
  int dndState = param.asInt();
  digitalWrite(LED_PIN, dndState ? HIGH : LOW);
  LOG_INFO("BLYNK_WRITE(V1)", "Do Not Disturb LED turned %s", dndState ? "ON" : "OFF");
}

BLYNK_WRITE(VPIN_STUDY_TILL) {
  // Expecting a target time in "HH:MM" format
  const char* timeStr = param.asStr();
  unsigned long totalSeconds = atoi(timeStr);
  int hh = totalSeconds / 3600;
  int mm = (totalSeconds % 3600) / 60;
  if (timeStr) {
    targetHour = hh + 1;
    targetMinute = mm;
    targetTimeSet = true;
    targetReachedNotified = false;
    LOG_INFO("BLYNK_WRITE(V4)", "Study target time set to %02d:%02d", targetHour, targetMinute);
  } else {
    LOG_WARNING("BLYNK_WRITE(V4)", "Invalid StudyTill format: %s", timeStr ? timeStr : "null");
  }
}

BLYNK_CONNECTED() {
  // Sync toggle and target values when connected to Blynk
  LOG_INFO("Blynk", "Connected to Blynk cloud");
  Blynk.syncVirtual(VPIN_DND_TOGGLE);
  Blynk.syncVirtual(VPIN_STUDY_TILL);
}

// -------------------------------------
//   Setup
// -------------------------------------
void setup() {
  Serial.begin(115200);
  LOG_INFO("setup", "Starting StudyOptimizer system...");

  // Synchronize time via NTP at startup
  timeSyncInitialized = timeVis.setup(ssid, pass);
  if (!timeSyncInitialized) {
    LOG_WARNING("setup", "TimeVisualizer init for NTP time failed, continuing without time sync");
  }
  // Re-enable WiFi (TimeVisualizer may disconnect WiFi after sync)
  WiFi.mode(WIFI_STA);
  LOG_INFO("WiFi", "Connecting to WiFi and Blynk...");
  Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);

  // Initialize SPIFFS audio player and load "panic.mp3"
  if (!player.setup()) {
    LOG_WARNING("setup", "Failed to initialize SpiffPlayer. MP3 playback won't work.");
  } else {
    player.stop();  // stop playback if it started during setup
  }

  // Setup sensor and indicator pins
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  pinMode(MIC_PIN, INPUT);

  // Mark the start time of the study session
  studyStartMillis = millis();

  LOG_INFO("setup", "StudyOptimizer setup complete.");
}

unsigned long lastMicCheckMillis = 0;
// -------------------------------------
//   Main Loop
// -------------------------------------
unsigned long lastElapsedSeconds = 0;
unsigned long sendtime = 0;
void loop() {
  // Let Blynk handle its background tasks
  Blynk.run();

  // Service the MP3 player (for streaming audio)
  // player.loop();
  
  unsigned long currentMillis = millis();
  unsigned long elapsedSeconds = (currentMillis - studyStartMillis) / 1000UL;

  // 2) Check proximity sensor (every 1s) and update active study time, then update LED strip
  if (currentMillis - lastProxCheckMillis >= 1000) {
    lastProxCheckMillis = currentMillis;
    bool present = isUserPresent();
    
    Blynk.virtualWrite(VPIN_TIME_ELAPSED, elapsedSeconds);
    if (present) {
        if (!wasStudying) {
          Blynk.logEvent("startstudying", "Someone seems to want to start studying O.O");
        }
        wasStudying = true;
        timeStudiedSeconds += elapsedSeconds - lastElapsedSeconds;
        Blynk.virtualWrite(VPIN_TIME_STUDIED, timeStudiedSeconds);
    } else {
      if (wasStudying) {
         Blynk.logEvent("stoppedstudying", "Damn bro you still got time left O.O");
      }
      // User is absent this second
      wasStudying = false;
    }
    lastElapsedSeconds = elapsedSeconds;
    // Update LED strip display (hours left until target time)
    updateTimeLeftOnStrip();
  }

      // Audio playback handler: keep looping the audio if playing  
    if (soundPlaying) {  
        // Continue playing the MP3; loop() returns false when playback is finished  
        if (!player.loop()) {  
            soundPlaying = false;  
            LOG_INFO("Audio", "'Be quiet' audio finished playing");  
        }  
    }  

  // 3) Check microphone level every ~1.5s
  if (currentMillis - lastMicCheckMillis >= 5000) {
    lastMicCheckMillis = currentMillis;
    checkMicrophone();
  }

  // 4) If you want NTP-based animations from TimeVisualizer, you could also call:
  // timeVis.loop();
}

// -------------------------------------
//   Proximity Sensor Reading
// -------------------------------------
bool isUserPresent() {
  // Trigger the ultrasonic sensor and measure echo pulse duration
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  long duration = pulseIn(ECHO_PIN, HIGH, 30000);  // 30ms timeout to avoid blocking too long
  float distanceCm = duration * 0.034f / 2.0f;
  return (distanceCm > 0 && distanceCm < PROXIMITY_DISTANCE_CM);
}

// -------------------------------------
//   Update LED Strip for Hours Left
// -------------------------------------
void updateTimeLeftOnStrip() {
  // Only proceed if time is synced and a target time is set
  if (!timeSyncInitialized) {
    LOG_INFO("updateTimeLeftOnStrip", "time not synced");
    return;
  }
  if (!targetTimeSet) {
    // No target -> ensure LED strip is off
    LOG_INFO("updateTimeLeftOnStrip", "No targettimeset");
    timeVis.setTimeDifference(0);
    return;
  }
  // Get current local time
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    LOG_ERROR("updateTimeLeftOnStrip", "Failed to obtain local time");
    return;
  }
  long timeleft = (targetHour - timeinfo.tm_hour) * 100 + targetMinute - timeinfo.tm_min;
  LOG_INFO("updateTimeLeftOnStrip", "time left %i", timeleft);
  // Display the remaining full hours in binary on the LED strip
  timeVis.setTimeDifference(timeleft);
  // If target time has been reached or passed, send one-time notification
  if (!targetReachedNotified && timeleft <= 0) {
    targetReachedNotified = true;
    LOG_INFO("updateTimeLeftOnStrip", "Study target time reached -> event logged");
  }
}

// -------------------------------------
//   Check Microphone & Send Event
// -------------------------------------
void checkMicrophone() {
  int micValue = analogRead(MIC_PIN);
  LOG_INFO("checkMicrophone", "micValue = %d", micValue);

  if (micValue > NOISE_THRESHOLD) {
    // Log an event on Blynk (requires an event named "tooloud" configured on dashboard)
    Blynk.logEvent("tooloud", "Noise threshold exceeded!");
    LOG_INFO("checkMicrophone", "Too loud -> event logged (tooloud)");
  }
}
