#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>
#include <locale.h>
#include <time.h>
#include <string.h>
#include <sys/time.h>

#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"

#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "esp_timer.h"
#include "esp_spiffs.h"
#include "esp_sleep.h"

#include "driver/gpio.h"
#include "driver/rtc_io.h"

#define PRINT_DEBUG false
#define WIPE_MEMORY false

#define DHT11_GPIO 0
#define BLINK_GPIO 8

#define DATA_BUFFER_SIZE 100

typedef struct {
    char timestamp[64];
    float humidity;
    float temperature;
} SensorData;

// RTC buffer to store time
RTC_DATA_ATTR time_t rtc_unix_time = 0;
RTC_DATA_ATTR bool rtc_time_initialized = false;

// RTC buffer to store measurements
RTC_DATA_ATTR SensorData rtc_measurement_buffer[DATA_BUFFER_SIZE];
RTC_DATA_ATTR int rtc_buffer_index = 0;

// ############### Base Setup ###############

void set_system_time() {
    if (!rtc_time_initialized) {
        // First boot: use compile time
        struct tm tm = {0};
        char month[4];
        int day, year, hour, min, sec;

        sscanf(__DATE__, "%3s %d %d", month, &day, &year);
        sscanf(__TIME__, "%d:%d:%d", &hour, &min, &sec);

        const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        for (int i = 0; i < 12; i++) {
            if (strcmp(month, months[i]) == 0) {
                tm.tm_mon = i;
                break;
            }
        }
        tm.tm_mday = day;
        tm.tm_year = year - 1900;
        tm.tm_hour = hour;
        tm.tm_min = min;
        tm.tm_sec = sec;
        tm.tm_isdst = -1;

        rtc_unix_time = mktime(&tm);
        rtc_time_initialized = true;
    }

    // Set system time from RTC
    struct timeval tv = { .tv_sec = rtc_unix_time, .tv_usec = 0 };
    settimeofday(&tv, NULL);
}

void get_current_time(char* time_string, size_t size) {
    time_t now;
    struct tm timeinfo;
    time(&now);
    localtime_r(&now, &timeinfo);
    strftime(time_string, size, "%Y-%m-%d %H:%M:%S", &timeinfo);
}

void print_chip_info() {
    esp_chip_info_t chip_info;
    uint32_t flash_size;
    esp_chip_info(&chip_info);
    printf("This is %s chip with %d CPU core(s), %s%s%s%s, ",
           CONFIG_IDF_TARGET,
           chip_info.cores,
           (chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "",
           (chip_info.features & CHIP_FEATURE_BT) ? "BT" : "",
           (chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "",
           (chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : "");

    unsigned major_rev = chip_info.revision / 100;
    unsigned minor_rev = chip_info.revision % 100;
    printf("silicon revision v%d.%d, ", major_rev, minor_rev);
    if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
        printf("Get flash size failed");
        return;
    }
    printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024),
           (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
    printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size());
}

// ############### Status indicator ###############

void init_led() {
    gpio_reset_pin(BLINK_GPIO);
    gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
    gpio_set_level(BLINK_GPIO, 1);
}

void led_flicker(int times, bool fast) {
    for(u_int8_t i = 0; i < times; i++) {
        gpio_set_level(BLINK_GPIO, 0);
        vTaskDelay(pdMS_TO_TICKS(fast ? 250 : 500));
        gpio_set_level(BLINK_GPIO, 1);
        vTaskDelay(pdMS_TO_TICKS(fast ? 250 : 500));
    }
}

// ############### Measurement ###############

void read_dht11(float *humidity, float *temperature) {
    uint8_t data[5] = {0};

    gpio_set_direction(DHT11_GPIO, GPIO_MODE_OUTPUT);
    gpio_set_level(DHT11_GPIO, 0);
    vTaskDelay(pdMS_TO_TICKS(20));
    gpio_set_level(DHT11_GPIO, 1);
    gpio_set_direction(DHT11_GPIO, GPIO_MODE_INPUT);

    while (gpio_get_level(DHT11_GPIO) == 1) vTaskDelay(pdMS_TO_TICKS(0.1));
    while (gpio_get_level(DHT11_GPIO) == 0) vTaskDelay(pdMS_TO_TICKS(0.1));

    for (int i = 0; i < 40; i++) {
        while (gpio_get_level(DHT11_GPIO) == 1) vTaskDelay(pdMS_TO_TICKS(0.1));
        while (gpio_get_level(DHT11_GPIO) == 0) vTaskDelay(pdMS_TO_TICKS(0.1));

        uint32_t pulse_start = esp_timer_get_time();
        while (gpio_get_level(DHT11_GPIO) == 1) vTaskDelay(pdMS_TO_TICKS(0.1));
        uint32_t pulse_end = esp_timer_get_time();

        uint8_t bit = (pulse_end - pulse_start) > 50 ? 1 : 0;
        data[i / 8] <<= 1;
        data[i / 8] |= bit;
    }

    uint8_t humidity_int = data[0];
    uint8_t humidity_dec = data[1];
    int8_t temperature_int = (data[2]);
    uint8_t temperature_dec = data[3];
    uint8_t checksum = data[4];

    uint8_t sum = humidity_int + humidity_dec + temperature_int + temperature_dec;
    if (sum != checksum) {
        #if PRINT_DEBUG
        printf("Checksum failed! Retrying...\n");
        #endif
        vTaskDelay(pdMS_TO_TICKS(1000));
        read_dht11(humidity, temperature);
        return;
    }

    *humidity = (float)humidity_int + (float)humidity_dec / 10.0f;
    *temperature = (float)temperature_int + (float)temperature_dec / 10.0f;
}

void make_measurement() {
    float humidity, temperature;
    read_dht11(&humidity, &temperature);

    char time_string[64] = {0};
    get_current_time(time_string, sizeof(time_string));

    #if PRINT_DEBUG
        printf("Measurement: [%s, %.1f%%, %.1f°C]\n", time_string, humidity, temperature);
    #endif

    // Store in RTC buffer
    if (rtc_buffer_index < DATA_BUFFER_SIZE) {
        strncpy(rtc_measurement_buffer[rtc_buffer_index].timestamp, time_string, sizeof(time_string));
        rtc_measurement_buffer[rtc_buffer_index].humidity = humidity;
        rtc_measurement_buffer[rtc_buffer_index].temperature = temperature;
        rtc_buffer_index++;
    }
}

// ############### IO operations ###############

void init_filesystem() {
    esp_vfs_spiffs_conf_t conf = {
        .base_path = "/spiffs",
        .partition_label = "storage",
        .max_files = 5,
        .format_if_mount_failed = true
    };
    esp_err_t ret = esp_vfs_spiffs_register(&conf);
    if (ret != ESP_OK) {
        #if PRINT_DEBUG
        printf("Failed to initialize SPIFFS (%s)\n", esp_err_to_name(ret));
        #endif
        return;
    }
    #if PRINT_DEBUG
    printf("SPIFFS initialized successfully\n");
    #endif
}

void save_buffer_to_persistent(){
    FILE *f = fopen("/spiffs/measurements.csv", "a");
    if (f == NULL) {
        printf("Failed to open CSV file!\n");
        return;
    }

    // Write header if file is empty
    fseek(f, 0, SEEK_END);
    if (ftell(f) == 0) {
        fprintf(f, "Timestamp,Humidity,Temperature\n");
    }

    // Write all buffered measurements
    for (int i = 0; i < rtc_buffer_index; i++) {
        fprintf(f, "%s,%.1f,%.1f\n",
                rtc_measurement_buffer[i].timestamp,
                rtc_measurement_buffer[i].humidity,
                rtc_measurement_buffer[i].temperature);
    }
    fclose(f);
    rtc_buffer_index = 0; // Reset buffer
    #if PRINT_DEBUG
    printf("CSV file updated with %d measurements!\n", DATA_BUFFER_SIZE);
    #endif
}

void clear_file(){
    led_flicker(10, false);
    FILE *f = fopen("/spiffs/measurements.csv", "w");
    if (f) {
        fprintf(f, "Timestamp,Humidity,Temperature\n");
        fclose(f);
    }
}

void readout_persistent_as_csv(){
    FILE *f = fopen("/spiffs/measurements.csv", "r");
    if (f) {
        char line[256];
        while (fgets(line, sizeof(line), f)) {
            printf("%s", line);
        }
        fclose(f);
    }
}

// ############### Buffer operations ###############

void readout_buffer_as_csv(){
    for (int i = 0; i < rtc_buffer_index; i++) {
        printf("%s,%.1f,%.1f\n",
            rtc_measurement_buffer[i].timestamp,
            rtc_measurement_buffer[i].humidity,
            rtc_measurement_buffer[i].temperature);
    }
}

// ############### Main thread ###############

const uint16_t sleep_time_seconds = 15 * 60; // Measurement frequency

void app_main() {
    setlocale(LC_ALL, "C");
    set_system_time();
    
    init_led();
    init_filesystem();

    led_flicker(3, true);

    #if PRINT_DEBUG
    printf("DHT11 temperature and humidity sensor\n");
    char start_time[64] = {0};
    get_current_time(start_time, sizeof(start_time));
    printf("Program started at: %s\n", start_time);
    print_chip_info();
    #endif

    make_measurement();
    if (rtc_buffer_index >= DATA_BUFFER_SIZE) {
        save_buffer_to_persistent();
    }
    readout_persistent_as_csv();
    readout_buffer_as_csv();
    #if WIPE_MEMORY
    clear_file();
    #endif

    rtc_unix_time += sleep_time_seconds;
    esp_sleep_enable_timer_wakeup(sleep_time_seconds * 1000000); 
    esp_deep_sleep_start();
}