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

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>

#include "main.h"
#include "app.h"

#include "task_remote.h"
#include "task_system_attribute.h"
#include "task_system_interface.h"
#include "logger.h"

#define G_TASK_REMOTE_TICK_INI            0ul
#define G_TASK_REMOTE_TEMP_UPDATE_RATE_MS 1000ul

// Public variables
//// this variable is incremented by 1 on every ms by HAL_SysTick
volatile uint32_t g_task_remote_tick_cnt;
queue_t task_remote_rx_queue;
uint8_t task_remote_uart_rx_byte;
uint8_t task_remote_uart_tx_dma_buffer[G_TASK_REMOTE_QUEUE_LEN];

// Private types declaration
typedef enum {
    TX_DATA_STATUS_OK             = 'A',
    TX_DATA_MAIN_OUTPUT_TOGGLE_OK = 'B',
    TX_DATA_TIMER_TOGGLE_OK       = 'C',
    TX_DATA_TIMER_SET_VALUES_OK   = 'D',
    TX_DATA_TEMP_STATUS_OK        = 'E',
    TX_DATA_TEMP_SET_OK           = 'F'
} uart_tx_data_e;

typedef enum {
    RX_DATA_STATUS             = 'A',
    RX_DATA_MAIN_OUTPUT_TOGGLE = 'B',
    RX_DATA_TIMER_TOGGLE       = 'C',
    RX_DATA_TIMER_SET_VALUES   = 'D',
    RX_DATA_TEMP_STATUS        = 'E',
    RX_DATA_TEMP_SET           = 'F'
} uart_rx_data_e;

// Private variables definition
uint32_t g_task_remote_tick;
time_t system_time;
static queue_t task_remote_tx_queue;

// Public functions declaration
void task_remote_queue_enqueue(queue_t *queue, uint8_t value);

// Private functions declaration
static void task_remote_rx_data_handler(void *parameters);
static void task_remote_queue_init(queue_t *queue);
static uint8_t task_remote_queue_peek(queue_t *queue);
static uint8_t task_remote_queue_count(queue_t *queue);
static uint8_t task_remote_queue_dequeue(queue_t *queue);
static uint8_t task_remote_queue_is_empty(queue_t *queue);

// Public funtions implementation
void task_remote_init(void *parameters)
{
    LOGGER_INFO("Initializing `task_remote`...");

    task_remote_queue_init(&task_remote_tx_queue);
    task_remote_queue_init(&task_remote_rx_queue);

    g_task_remote_tick = G_TASK_REMOTE_TICK_INI;
    HAL_UART_Receive_IT(&huart3, &task_remote_uart_rx_byte, 1);

    LOGGER_INFO("Done initializing `task_remote`");
}

void task_remote_update(void *parameters)
{
    bool b_time_update_required = false;
    shared_data_type *p_shared_data = (shared_data_type *)parameters;

    __asm("CPSID i");
    if (G_TASK_REMOTE_TICK_INI < g_task_remote_tick_cnt)
    {
        g_task_remote_tick_cnt--;
        b_time_update_required = true;
    }
    __asm("CPSIE i");

    while (b_time_update_required)
    {
        __asm("CPSID i");
        if (G_TASK_REMOTE_TICK_INI < g_task_remote_tick_cnt)
        {
            g_task_remote_tick_cnt--;
            b_time_update_required = true;
        }
        else
        {
            b_time_update_required = false;
        }
        __asm("CPSIE i");

        if (!task_remote_queue_is_empty(&task_remote_rx_queue))
        {
            task_remote_rx_data_handler(parameters);
        }

        // Send temp updated values to remote module
        if (0 == g_task_remote_tick)
        {
            g_task_remote_tick = G_TASK_REMOTE_TEMP_UPDATE_RATE_MS;
            task_remote_queue_enqueue(&task_remote_tx_queue, TX_DATA_TEMP_STATUS_OK);
            task_remote_queue_enqueue(&task_remote_tx_queue, p_shared_data->temp_sensor);
            task_remote_queue_enqueue(&task_remote_tx_queue, p_shared_data->temp_set_point);
        }
        else
        {
            g_task_remote_tick--;
        }

        if(!task_remote_queue_is_empty(&task_remote_tx_queue))
        {
            uint8_t i;
            for (i = 0; !task_remote_queue_is_empty(&task_remote_tx_queue); ++i)
            {
                task_remote_uart_tx_dma_buffer[i] = task_remote_queue_dequeue(&task_remote_tx_queue);
            }
            HAL_UART_Transmit_DMA(&huart3, task_remote_uart_tx_dma_buffer, i);
        }
    }
}

void task_remote_queue_enqueue(queue_t *queue, uint8_t value)
{
    queue->count++;
    queue->data[queue->head++] = value;
}

// Private function implementation
static void task_remote_rx_data_handler(void *parameters)
{
    shared_data_type *p_shared_data = (shared_data_type *)parameters;

    uint8_t query_type = task_remote_queue_peek(&task_remote_rx_queue);

    switch(query_type)
    {
        case RX_DATA_STATUS:
            task_remote_queue_dequeue(&task_remote_rx_queue); // skip query type

            task_remote_queue_enqueue(&task_remote_tx_queue, TX_DATA_STATUS_OK);
            task_remote_queue_enqueue(&task_remote_tx_queue,
                    p_shared_data->temp_ctrl_enabled);

            HAL_RTC_GetTime(&hrtc, &s_time, RTC_FORMAT_BIN);
            HAL_RTC_GetDate(&hrtc, &s_date, RTC_FORMAT_BIN);

            struct tm time_tm = {
                .tm_year = s_date.Year + 100,
                .tm_mon  = s_date.Month - 1,
                .tm_mday = s_date.Date,
                .tm_hour = s_time.Hours,
                .tm_min  = s_time.Minutes,
                .tm_sec  = s_time.Seconds,
            };

            system_time = mktime(&time_tm);

            // LSB first
            task_remote_queue_enqueue(&task_remote_tx_queue,
                    (uint8_t)(system_time & 0xFF));
            task_remote_queue_enqueue(&task_remote_tx_queue,
                    (uint8_t)((system_time >> 8) & 0xFF));
            task_remote_queue_enqueue(&task_remote_tx_queue,
                    (uint8_t)((system_time >> 16) & 0xFF));
            task_remote_queue_enqueue(&task_remote_tx_queue,
                    (uint8_t)((system_time >> 24) & 0xFF));

            task_remote_queue_enqueue(&task_remote_tx_queue, p_shared_data->timer->enabled);
            task_remote_queue_enqueue(&task_remote_tx_queue, p_shared_data->timer->start_hour);
            task_remote_queue_enqueue(&task_remote_tx_queue, p_shared_data->timer->start_min);
            task_remote_queue_enqueue(&task_remote_tx_queue, p_shared_data->timer->end_hour);
            task_remote_queue_enqueue(&task_remote_tx_queue, p_shared_data->timer->end_min);

            task_remote_queue_enqueue(&task_remote_tx_queue, p_shared_data->temp_sensor);
            task_remote_queue_enqueue(&task_remote_tx_queue, p_shared_data->temp_set_point);

        break;

        case RX_DATA_MAIN_OUTPUT_TOGGLE:

            task_remote_queue_dequeue(&task_remote_rx_queue); // skip query type
            task_system_interface_put_event(EV_SYS_REM_TEMP_CTRL_ENABLED);
            task_remote_queue_enqueue(&task_remote_tx_queue,
                    TX_DATA_MAIN_OUTPUT_TOGGLE_OK);

        break;

        case RX_DATA_TIMER_TOGGLE:

            task_remote_queue_dequeue(&task_remote_rx_queue); // skip query type
            task_system_interface_put_event(EV_SYS_REM_TIMER_TOGGLE);
            task_remote_queue_enqueue(&task_remote_tx_queue,
                    TX_DATA_TIMER_TOGGLE_OK);

        break;

        case RX_DATA_TIMER_SET_VALUES:

            if (task_remote_queue_count(&task_remote_rx_queue) < 5)
            {
                // wait for additional data
                break;
            }

            task_remote_queue_dequeue(&task_remote_rx_queue); // skip query type

            p_shared_data->timer->start_hour = task_remote_queue_dequeue(&task_remote_rx_queue);
            p_shared_data->timer->start_min  = task_remote_queue_dequeue(&task_remote_rx_queue);
            p_shared_data->timer->end_hour   = task_remote_queue_dequeue(&task_remote_rx_queue);
            p_shared_data->timer->end_min    = task_remote_queue_dequeue(&task_remote_rx_queue);

            task_remote_queue_enqueue(&task_remote_tx_queue, TX_DATA_TIMER_SET_VALUES_OK);

        break;

        case RX_DATA_TEMP_STATUS:

            task_remote_queue_dequeue(&task_remote_rx_queue); // skip query type

            task_remote_queue_enqueue(&task_remote_tx_queue, TX_DATA_TEMP_STATUS_OK);
            task_remote_queue_enqueue(&task_remote_tx_queue, p_shared_data->temp_sensor);
            task_remote_queue_enqueue(&task_remote_tx_queue, p_shared_data->temp_set_point);

        break;

        case RX_DATA_TEMP_SET:

            if (task_remote_queue_count(&task_remote_rx_queue) < 2)
            {
                // wait for additional data
                break;
            }

            task_remote_queue_dequeue(&task_remote_rx_queue); // skip query type
            p_shared_data->temp_set_point = task_remote_queue_dequeue(&task_remote_rx_queue);
            task_remote_queue_enqueue(&task_remote_tx_queue, TX_DATA_TEMP_SET_OK);
            task_system_interface_put_event(EV_SYS_REM_TEMP_SET);

        break;

        default: 
            task_remote_queue_dequeue(&task_remote_rx_queue);
        break;
    }
}

static void task_remote_queue_init(queue_t *queue)
{
    queue->head = 0;
    queue->tail = 0;
    queue->count = 0;

    for (uint8_t i = 0; i < G_TASK_REMOTE_QUEUE_LEN; ++i)
    {
        queue->data[i] = 0;
    }
}

static uint8_t task_remote_queue_peek(queue_t *queue)
{
    return queue->data[queue->tail];
}

static uint8_t task_remote_queue_count(queue_t *queue)
{
    return queue->count;
}

static uint8_t task_remote_queue_dequeue(queue_t *queue)
{
    if(task_remote_queue_is_empty(queue))
    {
        return task_remote_queue_peek(queue);
    }

    queue->count--;
    return queue->data[queue->tail++];
}

static uint8_t task_remote_queue_is_empty(queue_t *queue)
{
    return (queue->count == 0);
}

