#include "temp.h"
#include "driver/gpio.h"
#include "esp_rom_sys.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <math.h>

#define OW_PIN GPIO_NUM_35

static float cached_temp = 25.0f;
static portMUX_TYPE ow_mux = portMUX_INITIALIZER_UNLOCKED;

static void ow_low(void)
{
    gpio_set_level(OW_PIN, 0);
    gpio_set_direction(OW_PIN, GPIO_MODE_OUTPUT);
}

static void ow_release(void)
{
    gpio_set_direction(OW_PIN, GPIO_MODE_INPUT);
}

static int ow_reset(void)
{
    ow_low();
    esp_rom_delay_us(480);

    taskENTER_CRITICAL(&ow_mux);
    ow_release();
    esp_rom_delay_us(70);
    int presence = !gpio_get_level(OW_PIN);
    taskEXIT_CRITICAL(&ow_mux);

    esp_rom_delay_us(410);
    return presence;
}

static void ow_write_bit(int bit)
{
    taskENTER_CRITICAL(&ow_mux);
    ow_low();
    if (bit) {
        esp_rom_delay_us(6);
        ow_release();
        taskEXIT_CRITICAL(&ow_mux);
        esp_rom_delay_us(54);
    } else {
        esp_rom_delay_us(60);
        ow_release();
        taskEXIT_CRITICAL(&ow_mux);
        esp_rom_delay_us(1);
    }
}

static int ow_read_bit(void)
{
    taskENTER_CRITICAL(&ow_mux);
    ow_low();
    esp_rom_delay_us(2);
    ow_release();
    esp_rom_delay_us(12);
    int bit = gpio_get_level(OW_PIN);
    taskEXIT_CRITICAL(&ow_mux);
    esp_rom_delay_us(46);
    return bit;
}

static void ow_write_byte(uint8_t byte)
{
    for (int i = 0; i < 8; i++) {
        ow_write_bit(byte & 1);
        byte >>= 1;
    }
}

static uint8_t ow_read_byte(void)
{
    uint8_t byte = 0;
    for (int i = 0; i < 8; i++)
        if (ow_read_bit())
            byte |= (1 << i);
    return byte;
}

static float ds18b20_read(void)
{
    if (!ow_reset()) return NAN;
    ow_write_byte(0xCC); /* Skip ROM */
    ow_write_byte(0x44); /* Convert T */
    vTaskDelay(pdMS_TO_TICKS(750));

    if (!ow_reset()) return NAN;
    ow_write_byte(0xCC);
    ow_write_byte(0xBE); /* Read Scratchpad */

    uint8_t lsb = ow_read_byte();
    uint8_t msb = ow_read_byte();

    int16_t raw = (msb << 8) | lsb;
    return raw / 16.0f;
}

static void temp_task(void *arg)
{
    (void)arg;
    for (;;) {
        float t = ds18b20_read();
        if (!isnan(t))
            cached_temp = t;
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

void temp_init(void)
{
    gpio_config_t io = {
        .pin_bit_mask = (1ULL << OW_PIN),
        .mode         = GPIO_MODE_INPUT,
        .pull_up_en   = GPIO_PULLUP_ENABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type    = GPIO_INTR_DISABLE,
    };
    gpio_config(&io);

    xTaskCreate(temp_task, "temp", 2048, NULL, 3, NULL);
}

float temp_get(void)
{
    return cached_temp;
}
