#include "gas_sensor.h"
#include "variables.h"
#include "main.h"
#include <string.h>

#define GAS_SENSOR_SLOT_COUNT         2U
#define GAS_SENSOR_QUERY_LEN          8U
#define GAS_SENSOR_RESP_LEN           25U
#define GAS_SENSOR_QUERY_PERIOD_MS    250U
#define GAS_SENSOR_RESP_TIMEOUT_MS    120U
#define GAS_SENSOR_MISS_THRESHOLD     3U

typedef struct
{
    UART_HandleTypeDef *huart;
    volatile GasSlotState_t *pub;
    uint8_t rx_buf[GAS_SENSOR_RESP_LEN];
    uint8_t rx_idx;
    uint8_t waiting_resp;
    uint32_t query_tick;
} GasSlotCtx_t;

static GasSlotCtx_t g_slots[GAS_SENSOR_SLOT_COUNT];
static uint8_t g_next_slot = 0U;
static uint32_t g_last_query_tick = 0U;

static const uint8_t g_modbus_query[GAS_SENSOR_QUERY_LEN] = {
    0x01U, 0x03U, 0x00U, 0x00U, 0x00U, 0x0AU, 0xC5U, 0xCDU
};

static uint16_t gas_modbus_crc16(const uint8_t *data, uint16_t len)
{
    uint16_t crc = 0xFFFFU;

    for (uint16_t i = 0U; i < len; i++)
    {
        crc ^= data[i];
        for (uint8_t j = 0U; j < 8U; j++)
        {
            if ((crc & 0x0001U) != 0U)
            {
                crc >>= 1U;
                crc ^= 0xA001U;
            }
            else
            {
                crc >>= 1U;
            }
        }
    }

    return crc;
}

static void gas_slot_reset_public(volatile GasSlotState_t *slot)
{
    slot->present = 0U;
    slot->gas_type = GAS_SENSOR_EMPTY_TYPE;
    slot->conc_raw = 0U;
    slot->fmt_reg = 0U;
    slot->state = 0xFFU;
}

static GasSlotCtx_t *gas_slot_from_huart(UART_HandleTypeDef *huart)
{
    if (huart == NULL)
    {
        return NULL;
    }

    if (huart->Instance == USART2)
    {
        return &g_slots[0];
    }

    if (huart->Instance == USART3)
    {
        return &g_slots[1];
    }

    return NULL;
}

static void gas_slot_finish_wait(GasSlotCtx_t *slot)
{
    if (slot == NULL)
    {
        return;
    }

    slot->waiting_resp = 0U;
    slot->rx_idx = 0U;
}

static void gas_slot_record_miss(GasSlotCtx_t *slot)
{
    volatile GasSlotState_t *pub = NULL;

    if (slot == NULL)
    {
        return;
    }

    pub = slot->pub;
    pub->err_count++;

    if (pub->miss_cnt < 0xFFU)
    {
        pub->miss_cnt++;
    }

    if (pub->miss_cnt >= GAS_SENSOR_MISS_THRESHOLD)
    {
        gas_slot_reset_public(pub);
    }

    gas_slot_finish_wait(slot);
}

static uint8_t gas_slot_parse_frame(GasSlotCtx_t *slot)
{
    volatile GasSlotState_t *pub = slot->pub;
    const uint16_t crc_calc = gas_modbus_crc16(slot->rx_buf, GAS_SENSOR_RESP_LEN - 2U);
    const uint16_t crc_recv = ((uint16_t)slot->rx_buf[GAS_SENSOR_RESP_LEN - 1U] << 8) |
                              slot->rx_buf[GAS_SENSOR_RESP_LEN - 2U];

    if ((slot->rx_buf[0] != 0x01U) ||
        (slot->rx_buf[1] != 0x03U) ||
        (slot->rx_buf[2] != 0x14U) ||
        (crc_calc != crc_recv))
    {
        gas_slot_record_miss(slot);
        return 0U;
    }

    pub->fmt_reg = ((uint16_t)slot->rx_buf[3] << 8) | slot->rx_buf[4];
    pub->conc_raw = ((uint16_t)slot->rx_buf[5] << 8) | slot->rx_buf[6];
    pub->state = slot->rx_buf[14];
    pub->gas_type = slot->rx_buf[19];
    pub->present = 1U;
    pub->miss_cnt = 0U;
    pub->last_ok_tick = HAL_GetTick();
    pub->ok_count++;

    gas_slot_finish_wait(slot);
    return 1U;
}

static void gas_slot_send_query(GasSlotCtx_t *slot)
{
    if (slot == NULL)
    {
        return;
    }

    slot->rx_idx = 0U;
    slot->waiting_resp = 1U;
    slot->query_tick = HAL_GetTick();

    if (HAL_UART_Transmit(slot->huart, (uint8_t *)g_modbus_query, GAS_SENSOR_QUERY_LEN, 20U) != HAL_OK)
    {
        gas_slot_record_miss(slot);
    }
}

void GasSensor_Init(void)
{
    (void)memset(g_slots, 0, sizeof(g_slots));

    g_slots[0].huart = &huart2;
    g_slots[0].pub = &gas_slot1;
    g_slots[1].huart = &huart3;
    g_slots[1].pub = &gas_slot2;

    gas_slot_reset_public(&gas_slot1);
    gas_slot1.miss_cnt = GAS_SENSOR_MISS_THRESHOLD;
    gas_slot_reset_public(&gas_slot2);
    gas_slot2.miss_cnt = GAS_SENSOR_MISS_THRESHOLD;

    g_next_slot = 0U;
    g_last_query_tick = HAL_GetTick();
}

void GasSensor_Service(void)
{
    const uint32_t now = HAL_GetTick();

    for (uint8_t i = 0U; i < GAS_SENSOR_SLOT_COUNT; i++)
    {
        if ((g_slots[i].waiting_resp != 0U) && ((now - g_slots[i].query_tick) >= GAS_SENSOR_RESP_TIMEOUT_MS))
        {
            gas_slot_record_miss(&g_slots[i]);
        }
    }

    if ((now - g_last_query_tick) >= GAS_SENSOR_QUERY_PERIOD_MS)
    {
        gas_slot_send_query(&g_slots[g_next_slot]);
        g_next_slot = (uint8_t)((g_next_slot + 1U) % GAS_SENSOR_SLOT_COUNT);
        g_last_query_tick = now;
    }
}

void GasSensor_RxByteIRQ(UART_HandleTypeDef *huart, uint8_t byte)
{
    GasSlotCtx_t *slot = gas_slot_from_huart(huart);

    if ((slot == NULL) || (slot->waiting_resp == 0U))
    {
        return;
    }

    switch (slot->rx_idx)
    {
        case 0U:
            if (byte == 0x01U)
            {
                slot->rx_buf[slot->rx_idx++] = byte;
            }
            return;

        case 1U:
            if (byte == 0x03U)
            {
                slot->rx_buf[slot->rx_idx++] = byte;
            }
            else if (byte == 0x01U)
            {
                slot->rx_buf[0] = byte;
                slot->rx_idx = 1U;
            }
            else
            {
                slot->rx_idx = 0U;
            }
            return;

        case 2U:
            if (byte == 0x14U)
            {
                slot->rx_buf[slot->rx_idx++] = byte;
            }
            else if (byte == 0x01U)
            {
                slot->rx_buf[0] = byte;
                slot->rx_idx = 1U;
            }
            else
            {
                slot->rx_idx = 0U;
            }
            return;

        default:
            break;
    }

    if (slot->rx_idx < GAS_SENSOR_RESP_LEN)
    {
        slot->rx_buf[slot->rx_idx++] = byte;
    }

    if (slot->rx_idx >= GAS_SENSOR_RESP_LEN)
    {
        (void)gas_slot_parse_frame(slot);
    }
}

void GasSensor_RxErrorIRQ(UART_HandleTypeDef *huart)
{
    GasSlotCtx_t *slot = gas_slot_from_huart(huart);

    if (slot == NULL)
    {
        return;
    }

    if (slot->waiting_resp != 0U)
    {
        gas_slot_record_miss(slot);
    }
    else
    {
        slot->pub->err_count++;
        slot->rx_idx = 0U;
    }
}
