#include <Arduino.h>
#include <esp_wifi.h>
#include <driver/rtc_io.h>

#include "main.h"
#include "config.h"
#include "bluetooth.h"
#include "storage.h"
#include "temperature.h"

// The delay to perform when waiting idle (not sleeping) for next measurement 
constexpr uint32_t delayWaitingForNextMeasurementMS = 50;

// Indicate when the next temperature measurement should be performed
// Time is measured in seconds since power-on via RTC clock
RTC_DATA_ATTR uint32_t nextTimeToMeasureTemperatureSeconds = 0;

// The unique ID generated whenever the device is powered on
RTC_DATA_ATTR uint32_t uniqueID;

// Indicate the time until the device should stay awake
// Updated when button is pressed
uint32_t sleepStartAfterButtonPressOrConnection = 0;

uint8_t wakeupCauseByte = 0;

uint8_t getWakeupCause() {
    return wakeupCauseByte;
}

uint32_t getUniqueID() {
    return uniqueID;
}

/**
 * @brief Check if enough time has passed for the next temperature measurement
 * 
 * @return true A measurement should be performed
 * @return false Wait until more time has elapsed
 */
bool shouldMeasureTemperature() {
    return time(NULL) >= nextTimeToMeasureTemperatureSeconds;
}

/**
 * @brief Set the next time when temperatures should be measured
 */
void setNextTemperatureMeasurementInterval() {
    nextTimeToMeasureTemperatureSeconds += temperatureMeasurementIntervalSeconds;
}

/**
 * @brief Check how long to wait until the next temperature measurement
 * 
 * @return uint32_t The number of seconds to wait
 */
uint32_t secondsUntilNextTemperatureMeasurement() {
    // Time in seconds since power on
    time_t currentTime = time(NULL);
    if (currentTime >= nextTimeToMeasureTemperatureSeconds) {
        return 0;
    }
    uint32_t seconds = nextTimeToMeasureTemperatureSeconds - currentTime;
    return min(seconds, temperatureMeasurementIntervalSeconds);
}

/**
 * @brief Start sleep if next measurement is sufficiently in the future
 * 
 * If the measurement is less than one second in the future, don't do anything.
 * If the device sleeps, then execution starts again in the `setup()` function.
 */
void deepSleepUntilNextTemperatureMeasurement() {
    uint32_t seconds = secondsUntilNextTemperatureMeasurement();
    if (seconds == 0) {
        // The time until next measurement is too short,
        // so don't sleep and just wait a little
        // May run the loop multiple times until the time is elapsed
        return;
    }
    Serial.print("Sleeping for ");
    Serial.print(seconds);
    Serial.println(" seconds");
    bluetoothStop();
    esp_deep_sleep(seconds * 1000000);
}

/**
 * @brief Check if the button is pressed
 *  
 * This indicates that the device should stay awake and that Bluetooth should be enabled.
 * 
 * @return true The button is pressed
 * @return false The button is not pressed
 */
bool wakeupButtonIsPressed() {
    return gpio_get_level(wakeupButtonPin) == 0;
}

/**
 * @brief Shift the timestamp into the future where the device is allowed to go to sleep.
 * 
 * Updates are performed when the wake button was pressed or a device is connected via bluetooth.
 */
void updateStayAwakeTime() {
    sleepStartAfterButtonPressOrConnection = time(NULL) + wakeupDurationAfterButtonPress;
}

/**
 * @brief Check if the device should go to sleep
 * 
 * Sleeping is allowed if no bluetooth connection exists, or if the last wake button press
 * was far enough in the past.
 * @return true Allow the device to go to deep sleep
 * @return false Keep the device awake
 */
bool shouldGoToSleep() {
    return time(NULL) > sleepStartAfterButtonPressOrConnection;
}

void enableLED() {
    // Configure LED pin
    // Equivalent to: pinMode(1, OUTPUT);
    gpio_config_t ledConfig;
    ledConfig.pin_bit_mask = (1ULL << 1);
    ledConfig.mode = GPIO_MODE_OUTPUT;
    gpio_config(&ledConfig);

    // Enable LED, as long as ESP is awake
    // Equivalent to: digitalWrite(1, LOW);
    gpio_set_level(GPIO_NUM_1, 0); // For this led, low means on
}

void setup() {
    esp_sleep_wakeup_cause_t wakeupCause = esp_sleep_get_wakeup_cause();
    wakeupCauseByte = static_cast<uint8_t>(wakeupCause);
    bool isFirstStart = wakeupCause == ESP_SLEEP_WAKEUP_UNDEFINED;
    
    // No need for serial output in production
    //Serial.begin(serialBaudRate);

    if (isFirstStart) {
        // Power on, generate unique ID on device start
        uniqueID = random();
    }
    
    // LED useless inside case
    // enableLED();
    
    // Configure button pin to wake up ESP from deep sleep on low signal with pullup
    rtc_gpio_pullup_en(wakeupButtonPin);
    esp_sleep_enable_ext0_wakeup(wakeupButtonPin, 0);

    // Enable EEPROM to persist measurements
    // Only needed if sufficient measurements are in RTC memory
    storageConfigure(isFirstStart);

    // Configure bluetooth if wake button was pressed
    if (wakeupCause == ESP_SLEEP_WAKEUP_EXT0 || isFirstStart) {
        Serial.println("First start or button press");
        bluetoothConfigure();
        updateStayAwakeTime();
    }

    // Configure the temperature sensors
    // Happens only once per power cycle
    temperatureConfigure();

    Serial.println("Setup complete");
}

void loop() {
    if (shouldMeasureTemperature()) {
        Serial.println("Measuring");

        Temperature samples[temperatureSensorCount];
        temperaturePerformUpdate(samples);
        saveTemperatures(samples);
        setNextTemperatureMeasurementInterval();
    }

    if (wakeupButtonIsPressed() || bluetoothIsConnected()) {
        bluetoothConfigure(); // Only done once internally
        updateStayAwakeTime();
    }

    if (shouldGoToSleep()) {
        // May return, if less then one second to wait
        // Otherwise control flow starts with setup() again
        deepSleepUntilNextTemperatureMeasurement();
    }
    delay(delayWaitingForNextMeasurementMS);
}