/*
 * Copyright (c) 2026 Martin Javier Klöckner <mklockner@fi.uba.ar>
 *
 * See file `LICENSE` for full details
 */

#include <stdbool.h>

#include "main.h"
#include "systick.h"
#include "ds18b20.h"
#include "stm32f1xx_ll_usart.h"
#include "logger.h"

#define DS18B20_SKIP_ROM_CMD                   0xCC
#define DS18B20_CONVERT_T_CMD                  0x44
#define DS18B20_READ_SCRATCHPAD_CMD            0xBE
#define DS18B20_WRITE_SCRATCHPAD_CMD           0x4E
#define DS18B20_WRITE_SCRATCHPAD_TO_EEPROM_CMD 0x48

#define G_DS18B20_RESOLUTION_BITS 9
#define G_DS18B20_CONV_TIME_MS    100ul // ~100ms for 9 bit resolution
#define G_DS18B20_TICK_INI        0ul
#define G_RESET_BAUDRATE          9600

// TODO: Handle temp sensor not responding with 115200. Idea: Use first 115200
//       until the sensor no longer responds, in thta case try with different
//       speeds, such as 96500.
#define G_DATA_BAUDRATE        96500

#define G_DS18B20_ASYNC_QUEUE_LEN    256
#define G_DS18B20_ASYNC_RESET_DEL_MS 100ul

// Private data structures definition
typedef enum {
    DS18B20_QUEUE_BYTE_TX,
    DS18B20_QUEUE_BYTE_RX,
    DS18B20_QUEUE_READ_LSB,
    DS18B20_QUEUE_READ_MSB,
    DS18B20_QUEUE_SET_BAUD_SLOW,
    DS18B20_QUEUE_SET_BAUD_FAST,
    DS18B20_QUEUE_RESET,
    DS18B20_QUEUE_DELAY,
} ds18b20_data_type_e;

typedef struct {
    ds18b20_data_type_e type;
    uint16_t data;
} ds18b20_data_t;

static struct {
    uint8_t head;
    uint8_t tail;
    uint8_t count;
    ds18b20_data_t queue[G_DS18B20_ASYNC_QUEUE_LEN];
} ds18b20_async_queue;

// Public variables
bool b_ds18b20_dma_tx_done, b_ds18b20_dma_rx_done;

// Private global variables
static uint32_t g_ds18b20_tick;
static uint16_t g_ds18b20_temp, g_ds18b20_update_count;
static uint8_t g_temp_lsb, g_temp_msb;
static uint8_t g_ds18b20_tx_buffer[8], g_ds18b20_rx_buffer[8];
static bool b_ds18b20_rx_data_processed, b_ds18b20_async_temp_ready;

// Private functions declaration
//// Blocking calls
static bool ds18b20_reset(void);
static void ds18b20_write(uint8_t data);
static void ds18b20_set_resolution(uint8_t resolution);
static uint8_t ds18b20_get_rx_byte(void);
static void ds18b20_byte_to_array_of_bytes(uint8_t byte, uint8_t *array);
//// Asynchronous calls
static void ds18b20_dma_write(ds18b20_data_t data);
static void ds18b20_async_reset(void);
static void ds18b20_async_enqueue(ds18b20_data_t data);
static ds18b20_data_t ds18b20_async_dequeue(void);
static ds18b20_data_t ds18b20_async_queue_tail(void);

static bool ds18b20_reset(void)
{
    uint8_t data = 0xF0;

    __HAL_UART_DISABLE(&huart1);
    LL_USART_SetBaudRate(USART1, HAL_RCC_GetPCLK2Freq(), G_RESET_BAUDRATE);
    __HAL_UART_ENABLE(&huart1);

    HAL_UART_Transmit(&huart1, &data, 1, 100);
    if ((HAL_UART_Receive(&huart1, &data, 1, 100) != HAL_OK) || (data == 0xF0))
    {
        // Initialization failed
        LOGGER_ERROR("Couldn't read temperature sensor");
        return true;
    }

    __HAL_UART_DISABLE(&huart1);
    LL_USART_SetBaudRate(USART1, HAL_RCC_GetPCLK2Freq(), G_DATA_BAUDRATE);
    __HAL_UART_ENABLE(&huart1);
    return false;
}

static void ds18b20_set_resolution(uint8_t resolution)
{
    uint8_t config;

    switch(resolution)
    {
        case 9:  config = 0x1F; break;
        case 10: config = 0x3F; break;
        case 11: config = 0x5F; break;
        case 12: config = 0x7F; break;
        default: config = 0x7F;
    }

    ds18b20_reset();
    ds18b20_write(DS18B20_SKIP_ROM_CMD);
    ds18b20_write(DS18B20_WRITE_SCRATCHPAD_CMD);

    ds18b20_write(0x00); // Alarm TH byte
    ds18b20_write(0x00); // Alarm TL byte
    ds18b20_write(config);

    ds18b20_reset();
    ds18b20_write(DS18B20_SKIP_ROM_CMD);
    ds18b20_write(DS18B20_WRITE_SCRATCHPAD_TO_EEPROM_CMD);
}

void ds18b20_init(void)
{
    if (ds18b20_reset() == 0)
    {
        LOGGER_INFO("Temperature sensor initialized successfully");
    }
    ds18b20_set_resolution(G_DS18B20_RESOLUTION_BITS);

    g_ds18b20_tick = G_DS18B20_TICK_INI;
    g_ds18b20_update_count = 0;
    g_temp_lsb = g_temp_msb = 0xFF;

    b_ds18b20_dma_tx_done = true;
    b_ds18b20_dma_rx_done = true;
    b_ds18b20_rx_data_processed = true;
    b_ds18b20_async_temp_ready = false;
    ds18b20_async_queue.head = 0;
    ds18b20_async_queue.tail = 0;
    ds18b20_async_queue.count = 0;
}

static void ds18b20_write(uint8_t data)
{
    uint8_t buffer[8];
    ds18b20_byte_to_array_of_bytes(data, buffer);
    HAL_UART_Transmit(&huart1, buffer, 8, 100);
}

static void ds18b20_async_reset(void)
{
    ds18b20_async_enqueue((ds18b20_data_t){DS18B20_QUEUE_SET_BAUD_SLOW});
    ds18b20_async_enqueue((ds18b20_data_t){DS18B20_QUEUE_RESET});
    ds18b20_async_enqueue((ds18b20_data_t){DS18B20_QUEUE_SET_BAUD_FAST});
}

static void ds18b20_dma_write(ds18b20_data_t data)
{
    switch (data.type)
    {
        case DS18B20_QUEUE_BYTE_TX:
            ds18b20_byte_to_array_of_bytes(data.data, g_ds18b20_tx_buffer);
            b_ds18b20_dma_tx_done = false;
            HAL_UART_Transmit_DMA(&huart1, g_ds18b20_tx_buffer, 8);
        break;

        case DS18B20_QUEUE_BYTE_RX:
            memset(g_ds18b20_rx_buffer, 0, sizeof(g_ds18b20_rx_buffer));
            b_ds18b20_dma_rx_done = false;
            HAL_UARTEx_ReceiveToIdle_DMA(&huart1, g_ds18b20_rx_buffer, 8);
        break;

        case DS18B20_QUEUE_READ_LSB:
        case DS18B20_QUEUE_READ_MSB:
            b_ds18b20_dma_tx_done = false;
            b_ds18b20_dma_rx_done = false;
            b_ds18b20_rx_data_processed = false;
            memset(g_ds18b20_rx_buffer, 0, sizeof(g_ds18b20_rx_buffer));
            ds18b20_byte_to_array_of_bytes(0xFF, g_ds18b20_tx_buffer);

            HAL_UARTEx_ReceiveToIdle_DMA(&huart1, g_ds18b20_rx_buffer, 8);
            HAL_UART_Transmit_DMA(&huart1, g_ds18b20_tx_buffer, 8);
        break;

        case DS18B20_QUEUE_DELAY:
            g_ds18b20_tick = data.data;
        break;

        case DS18B20_QUEUE_SET_BAUD_SLOW:
            __HAL_UART_DISABLE(&huart1);
            LL_USART_SetBaudRate(USART1, HAL_RCC_GetPCLK2Freq(), G_RESET_BAUDRATE);
            __HAL_UART_ENABLE(&huart1);
        break;

        case DS18B20_QUEUE_SET_BAUD_FAST:
            __HAL_UART_DISABLE(&huart1);
            LL_USART_SetBaudRate(USART1, HAL_RCC_GetPCLK2Freq(), G_DATA_BAUDRATE);
            __HAL_UART_ENABLE(&huart1);
        break;

        case DS18B20_QUEUE_RESET:
            b_ds18b20_dma_tx_done = false;
            b_ds18b20_dma_rx_done = true;
            b_ds18b20_rx_data_processed = false;
            memset(g_ds18b20_rx_buffer, 0, sizeof(g_ds18b20_rx_buffer));
            g_ds18b20_tx_buffer[0] = 0xF0;
            HAL_UARTEx_ReceiveToIdle_DMA(&huart1, g_ds18b20_rx_buffer, 1);
            HAL_UART_Transmit_DMA(&huart1, g_ds18b20_tx_buffer, 1);
        break;

        default: break;
    }
}

static void ds18b20_byte_to_array_of_bytes(uint8_t byte, uint8_t *array)
{
    for (uint8_t i = 0; i < 8; ++i)
    {
        array[i] = ((byte & (1 << i)) ? 0xFF : 0x00);
    }
}

static uint8_t ds18b20_get_rx_byte(void)
{
    uint8_t received_value = 0;

    for (uint8_t i = 0; i < 8; ++i)
    {
        received_value |= (((g_ds18b20_rx_buffer[i] == 0xFF) ? 1 : 0) << i);
    }

    return received_value;
}

void ds18b20_async_tick(void)
{
    if (!b_ds18b20_rx_data_processed)
    {
        b_ds18b20_rx_data_processed = true;
        switch(ds18b20_async_queue_tail().type)
        {
            case DS18B20_QUEUE_READ_LSB:
                g_temp_lsb = ds18b20_get_rx_byte();
            break;
            case DS18B20_QUEUE_READ_MSB:
                g_temp_msb = ds18b20_get_rx_byte();
                g_ds18b20_temp = ((g_temp_msb << 8) | g_temp_lsb) / 16;
                b_ds18b20_async_temp_ready = true;
                g_ds18b20_update_count++;
            break;
            case DS18B20_QUEUE_RESET:
                // TODO: Read presence byte
            break;
            default: break;
        }
    }
    else
    {
        if (0 < g_ds18b20_tick)
        {
            g_ds18b20_tick--;
        }
        else
        {
            if ((0 != ds18b20_async_queue.count)
                    && b_ds18b20_dma_tx_done
                    && b_ds18b20_dma_rx_done)
            {
                ds18b20_dma_write(ds18b20_async_dequeue());
            }
        }
    }
}

void ds18b20_async_read_temp(void)
{
    b_ds18b20_async_temp_ready = false;

    ds18b20_async_reset();
    ds18b20_async_enqueue((ds18b20_data_t){
            DS18B20_QUEUE_BYTE_TX, DS18B20_SKIP_ROM_CMD});
    ds18b20_async_enqueue((ds18b20_data_t){
            DS18B20_QUEUE_BYTE_TX, DS18B20_CONVERT_T_CMD});
    ds18b20_async_enqueue((ds18b20_data_t){
            DS18B20_QUEUE_DELAY, G_DS18B20_CONV_TIME_MS});

    ds18b20_async_reset();
    ds18b20_async_enqueue((ds18b20_data_t){
            DS18B20_QUEUE_BYTE_TX, DS18B20_SKIP_ROM_CMD});
    ds18b20_async_enqueue((ds18b20_data_t){
            DS18B20_QUEUE_BYTE_TX, DS18B20_READ_SCRATCHPAD_CMD});
    ds18b20_async_enqueue((ds18b20_data_t){DS18B20_QUEUE_READ_LSB});
    ds18b20_async_enqueue((ds18b20_data_t){DS18B20_QUEUE_READ_MSB});
}

bool ds18b20_async_temp_ready(void)
{
    return b_ds18b20_async_temp_ready;
}

int16_t ds18b20_async_get_temp(void)
{
    return g_ds18b20_temp;
}

static void ds18b20_async_enqueue(ds18b20_data_t data)
{
    ds18b20_async_queue.count++;
    ds18b20_async_queue.queue[ds18b20_async_queue.head++] = data;
}

#if 0
static ds18b20_data_t ds18b20_async_dequeue(void)
{
    ds18b20_async_queue.count--;
    ds18b20_data_t ret = ds18b20_async_queue.queue[ds18b20_async_queue.tail];

    if ((DS18B20_QUEUE_READ_LSB != ret.type) || (DS18B20_QUEUE_READ_LSB != ret.type))
    {
        ds18b20_async_queue.tail++;
    }

    return res;
}
#endif

static ds18b20_data_t ds18b20_async_dequeue(void)
{
    ds18b20_async_queue.count--;
    return ds18b20_async_queue.queue[ds18b20_async_queue.tail++];
}

static ds18b20_data_t ds18b20_async_queue_tail(void)
{
    return ds18b20_async_queue.queue[(ds18b20_async_queue.tail) - 1];
}
