/*
 * Copyright (c) 2023 Juan Manuel Cruz <jcruz@fi.uba.ar> <jcruz@frba.utn.edu.ar>.
 * Copyright (c) 2026 Martin Javier Klöckner <mklockner@fi.uba.ar>
 *
 * All rights reserved
 *
 * See file `LICENSE` for full details
 */

#include "main.h"

#include "logger.h"
#include "dwt.h"

#include "app.h"
#include "task_menu_attribute.h"
#include "task_menu_interface.h"
#include "display_i2c.h"

#define G_TASK_MEN_TICK_INI                0ul
#define G_TASK_MEN_TICK_CNT_INI            0ul
#define G_TASK_MEN_DISPLAY_UPDATE_RATE_MS  1000ul

static task_menu_dta_t task_menu_dta = {
    G_TASK_MEN_TICK_INI, ST_MEN_IDLE, EV_MEN_ST_IDLE, false
};

// Private functions declaration
static void task_menu_statechart(void *parameters);
static void task_menu_set_state(task_menu_st_t new_state);
static uint8_t task_menu_count_digits(int16_t n);

// Private variables
static bool g_display_update;
static task_menu_st_t g_menu_prev_state;
static int16_t temp_sensor_prev;
static uint8_t temp_ctrl_pwm_dc_prev;

// Public variables
volatile uint32_t g_task_menu_tick_cnt;

// External variables
extern uint16_t g_ds18b20_temp;
extern uint16_t g_ds18b20_update_count;
extern uint16_t g_ds18b20_dma_error_count;

// Public functions
void task_menu_init(void *parameters)
{
    task_menu_dta_t *p_task_menu_dta;
    shared_data_type *p_shared_data = (shared_data_type *)parameters;

    LOGGER_INFO("Initializing `task_menu`...");

    task_menu_interface_init_event_queue();

    g_menu_prev_state = ST_MEN_IDLE;
    p_task_menu_dta = &task_menu_dta;
    p_task_menu_dta->state = ST_MEN_IDLE;
    p_task_menu_dta->event = EV_MEN_ST_IDLE;
    p_task_menu_dta->event_flag = false;
    temp_sensor_prev = p_shared_data->temp_sensor;
    temp_ctrl_pwm_dc_prev = p_shared_data->temp_ctrl_pwm_dc;

    display_i2c_init();
    display_i2c_set_cursor_pos(0, 0);
    display_i2c_write_string("Initializing...");
    g_display_update = true;

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

void task_menu_update(void *parameters)
{
    bool b_time_update_required = false;

    __asm("CPSID i");
    if (G_TASK_MEN_TICK_CNT_INI < g_task_menu_tick_cnt)
    {
        /* Update Tick Counter */
        g_task_menu_tick_cnt--;
        b_time_update_required = true;
    }
    __asm("CPSIE i");

    while (b_time_update_required)
    {
        display_i2c_async_tick();
        task_menu_statechart(parameters);

        __asm("CPSID i");
        if (G_TASK_MEN_TICK_CNT_INI < g_task_menu_tick_cnt)
        {
            g_task_menu_tick_cnt--;
            b_time_update_required = true;
        }
        else
        {
            b_time_update_required = false;
        }
        __asm("CPSIE i");
    }
}

static void task_menu_set_state(task_menu_st_t new_state)
{
    task_menu_dta_t *p_task_menu_dta;
    p_task_menu_dta = &task_menu_dta;

    g_menu_prev_state = p_task_menu_dta->state;
    p_task_menu_dta->state = new_state;

    g_display_update = true;

    switch(new_state)
    {
        case ST_MEN_CLOCK_SET:
            p_task_menu_dta->tick = G_TASK_MEN_DISPLAY_UPDATE_RATE_MS;
        break;
        default: break;
    }
}

static uint8_t task_menu_count_digits(int16_t n)
{
    uint8_t count = 0;

    if (n == 0) return 1;
    if (n < 0) n = -n;

    while (n > 0) {
        n /= 10;
        count++;
    }

    return count;
}

static void task_menu_statechart(void *parameters)
{
    task_menu_dta_t *p_task_menu_dta;
    shared_data_type *p_shared_data = (shared_data_type *)parameters;
    char menu_row_str[32];

    p_task_menu_dta = &task_menu_dta;

    if (true == task_menu_interface_any_event())
    {
        p_task_menu_dta->event_flag = true;
        p_task_menu_dta->event = task_menu_interface_get_event();
    }

    if (true == p_task_menu_dta->event_flag)
    {
        p_task_menu_dta->event_flag = false;
        switch (p_task_menu_dta->event)
        {
            case EV_MEN_ST_IDLE:
            case EV_MEN_INACTIVE_TIMEOUT:
                task_menu_set_state(ST_MEN_IDLE);
            break;

            case EV_MEN_DISPLAY_UPDATE:
                g_display_update = true;
            break;

            case EV_MEN_ST_CLOCK_SET:
                task_menu_set_state(ST_MEN_CLOCK_SET);
            break;

            case EV_MEN_ST_TEMP_SET:
                task_menu_set_state(ST_MEN_TEMP_SET);
            break;

            case EV_MEN_ST_TIMER_SET_STATUS:
                task_menu_set_state(ST_MEN_TIMER_SET_STATUS);
            break;

            case EV_MEN_ST_TIMER_SET_START:
                task_menu_set_state(ST_MEN_TIMER_SET_START);
            break;

            case EV_MEN_ST_TIMER_SET_END:
                task_menu_set_state(ST_MEN_TIMER_SET_END);
            break;

            default: break;
        }
    }

    switch (p_task_menu_dta->state)
    {
        case ST_MEN_IDLE:

            if (0 == p_task_menu_dta->tick)
            {
                if ((p_shared_data->temp_sensor != temp_sensor_prev) ||
                        (p_shared_data->temp_ctrl_pwm_dc != temp_ctrl_pwm_dc_prev))
                {
                    g_display_update = true;
                }
                temp_sensor_prev = p_shared_data->temp_sensor;
                temp_ctrl_pwm_dc_prev = p_shared_data->temp_ctrl_pwm_dc;
                p_task_menu_dta->tick = G_TASK_MEN_DISPLAY_UPDATE_RATE_MS;
            }
            else 
            {
                p_task_menu_dta->tick--;
            }

            if (g_display_update)
            {
                g_display_update = false;
                display_i2c_async_clear();
                memset(menu_row_str, '\0', sizeof(menu_row_str));

                if (p_shared_data->temp_ctrl_enabled)
                {
                    display_i2c_async_write_string("On");
                    display_i2c_async_set_cursor_pos(10, 0);
                    sprintf(menu_row_str, "%5d%%",
                            p_shared_data->temp_ctrl_pwm_dc);
                    display_i2c_async_write_string(menu_row_str);
                }
                else
                {
                    sprintf(menu_row_str, "Off");
                    display_i2c_async_write_string(menu_row_str);
                }

                display_i2c_async_set_cursor_pos(0, 1);
                memset(menu_row_str, '\0', sizeof(menu_row_str));
                uint8_t aux = (8 - task_menu_count_digits(p_shared_data->temp_set_point));
                aux = ((aux < 0) ? 0 : ((aux > 8) ? 8 : aux));
                sprintf(menu_row_str, "%s %*d(%d)%cC",
                        p_shared_data->timer->enabled ? "[P]" : "   ", aux,
                        p_shared_data->temp_sensor,
                        p_shared_data->temp_set_point,
                        G_DISPLAY_CHAR_DEGREE_CODE);
                display_i2c_async_write_string(menu_row_str);
            }

        break;

        case ST_MEN_CLOCK_SET:

            if (0 == p_task_menu_dta->tick)
            {
                g_display_update = true;
                p_task_menu_dta->tick = G_TASK_MEN_DISPLAY_UPDATE_RATE_MS;
            }
            else 
            {
                p_task_menu_dta->tick--;
            }

            if (g_display_update)
            {
                g_display_update = false;
                display_i2c_async_clear();
                display_i2c_async_write_string("Clock>");
                display_i2c_async_set_cursor_pos(8, 0);

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

                memset(menu_row_str, '\0', sizeof(menu_row_str));
                sprintf(menu_row_str, "%2d:%02d:%02d",
                        s_time.Hours, s_time.Minutes, s_time.Seconds);
                display_i2c_async_write_string(menu_row_str);
            }

        break;

        case ST_MEN_TEMP_SET:

            if (g_display_update)
            {
                g_display_update = false;
                display_i2c_async_clear();
                display_i2c_async_write_string("Temp>");
                memset(menu_row_str, '\0', sizeof(menu_row_str));
                sprintf(menu_row_str, "%9d%cC",
                        p_shared_data->temp_set_point, G_DISPLAY_CHAR_DEGREE_CODE);
                display_i2c_async_write_string(menu_row_str);
            }

        break;

        case ST_MEN_TIMER_SET_STATUS:

            if (g_display_update)
            {
                g_display_update = false;
                display_i2c_async_clear();
                display_i2c_async_write_string("Timer>");
                display_i2c_async_set_cursor_pos(13, 0);
                memset(menu_row_str, '\0', sizeof(menu_row_str));
                sprintf(menu_row_str, "%3s", 
                        p_shared_data->timer->enabled ? "On" : "Off");
                display_i2c_async_write_string(menu_row_str);

                display_i2c_async_set_cursor_pos(3, 1);
                memset(menu_row_str, '\0', sizeof(menu_row_str));
                sprintf(menu_row_str, "%s%2d:%02d - %d:%02d",
                        (p_shared_data->timer->end_hour >= 10) ? "" : " ",
                        p_shared_data->timer->start_hour,
                        p_shared_data->timer->start_min,
                        p_shared_data->timer->end_hour,
                        p_shared_data->timer->end_min);
                display_i2c_async_write_string(menu_row_str);
            }

        break;

        case ST_MEN_TIMER_SET_START:

            if (g_display_update)
            {
                g_display_update = false;
                display_i2c_async_clear();
                display_i2c_async_write_string("Timer>start>");
                display_i2c_async_set_cursor_pos(11, 1);
                memset(menu_row_str, '\0', sizeof(menu_row_str));
                sprintf(menu_row_str, "%2d:%02d",
                        p_shared_data->timer->start_hour,
                        p_shared_data->timer->start_min);
                display_i2c_async_write_string(menu_row_str);
            }

        break;

        case ST_MEN_TIMER_SET_END:

            if (g_display_update)
            {
                g_display_update = false;
                display_i2c_async_clear();
                display_i2c_async_write_string("Timer>end>");
                display_i2c_async_set_cursor_pos(11, 1);
                memset(menu_row_str, '\0', sizeof(menu_row_str));
                sprintf(menu_row_str, "%2d:%02d",
                        p_shared_data->timer->end_hour,
                        p_shared_data->timer->end_min);
                display_i2c_async_write_string(menu_row_str);
            }

        break;

        default:

            p_task_menu_dta->tick  = G_TASK_MEN_TICK_INI;
            p_task_menu_dta->state = ST_MEN_IDLE;
            p_task_menu_dta->event = EV_MEN_ST_IDLE;
            p_task_menu_dta->event_flag  = false;
            g_display_update = false;

        break;
    }
}
