//***********************************************************************************************************
//! @file        cli_protocol.c
//! @brief       Реализация протокола CLI с адресацией (board_id)
//! @version     v1.2.1
//! @date        20.04.2026
//!
//! @details     Протокол предназначен для тестирования и диагностики встроенного ПО устройств,
//!              подключенных к ПК через UART (COM-порт). ПК выступает мастером, устройство — подчинённым.
//!              Команды передаются в текстовом виде
//!              
//!              Расширение протокола: новые команды регистрируются через cli_register_command().
//!
//!              Максимальный размер поля данных: задается в конфигурации
//!
//***********************************************************************************************************

#include "cli_protocol.h"
#include "ring_buffer.h"
#include "lmcal.h"
// #include <string.h>


//******************************************** Определения типов ********************************************

// Коды ошибок по протоколу
typedef enum {
    CLI_ERR_NONE         = 0x00U,   // Нет ошибки
    CLI_ERR_UNKNOWN_CMD  = 0x01U,   // Неизвестная команда
    CLI_ERR_INVALID_ARG  = 0x02U,   // Неверный формат или значение аргумента
    CLI_ERR_INTERNAL     = 0x03U,   // Внутренняя ошибка устройства
    CLI_ERR_CON          = 0x04U,   // Устройство не подключено
} cli_error_t;



//************************************ Локальные константы **************************************************
#define CLI_CMD_PREFIX          'T'     // Префикс команды
#define CLI_RESPONSE_PREFIX     't'     // Префикс успешного ответа
#define CLI_ERROR_PREFIX        'e'     // Префикс ошибки
#define CLI_MSG_TERMINATOR      '\r'    // Символ конца сообщения

// Макрос для преобразования ASCII HEX-символа в 4-битное значение (0-15), 0xFF при ошибке
#define HEX_CHAR_TO_NIBBLE(c)  (((c) >= '0' && (c) <= '9') ? ((c) - '0') :      \
                                ((c) >= 'A' && (c) <= 'F') ? ((c) - 'A' + 10) : \
                                ((c) >= 'a' && (c) <= 'f') ? ((c) - 'a' + 10) : 0xFF)



//************************************ Локальные переменные *************************************************
static ringbuf_t fifo_rx;                           // Кольцевой буфер приёма байт из UART
static u8 fifo_rx_buf[CLI_CMD_BUFFER_SIZE];         // Память для кольцевого буфера

static u8 cli_connected = 0;

static cli_device_info_t cli_device_info;

static cli_cmd_handler_t cmd_handlers[CLI_MAX_COMMANDS];

static char buf_tmp[CLI_DATA_MAX_SIZE];
static u8 data_size;


//***********************************************************************************************************
//******************************** Определения локальных (приватных) функций ********************************
//***********************************************************************************************************

// Преобразование байта в два HEX-символа (старший и младший полубайт)
static inline void byte_to_hex(u8 byte, char* hex)
{
    const char* d = "0123456789ABCDEF";
    hex[0] = d[(byte >> 4) & 0x0F];
    hex[1] = d[byte & 0x0F];
}



// Преобразование двух HEX-символов в байт. Возвращает 0xFF при ошибке.
static u8 hex_to_byte(const char* hex)
{
    u8 h = HEX_CHAR_TO_NIBBLE(hex[0]);
    u8 l = HEX_CHAR_TO_NIBBLE(hex[1]);
    return (h == 0xFF || l == 0xFF) ? 0xFF : (h << 4) | l;
}



// Безопасное копирование строки с гарантированным завершающим нулём.
// Аналог strncpy, но всегда добавляет '\0' в конце буфера.
static void safe_strncpy(char* dst, const char* src, size_t dst_size)
{
    if (dst == NULL || src == NULL || dst_size == 0) return;
    strncpy(dst, src, dst_size - 1);
    dst[dst_size - 1] = '\0';
}



// Формирование и отправка успешного ответа на команду.
// Формат: xx[cmd][data...]\r
static void send_response(u8 cmd, const u8* data, u8 len)
{
    u8 buf[CLI_CMD_BUFFER_SIZE];
    u16 idx = 0;
    buf[idx++] = CLI_RESPONSE_PREFIX;                       // 't'
    buf[idx++] = CLI_RESPONSE_PREFIX;                       // 't'

    byte_to_hex(cmd, (char*)&buf[idx]); idx += 2;          // код команды

    for (u8 i = 0; i < len; i++) {
        buf[idx++] = data[i];
        // byte_to_hex(data[i], (char*)&buf[idx]);             // данные в HEX
        // idx += 2;
    }

    buf[idx++] = CLI_MSG_TERMINATOR;                        // '\r'

    for (u16 i = 0; i < idx; i++) {
        lmcal_uart_write(CLI_UART_CHANNEL, buf[i]);
    }
}



// Формирование и отправка сообщения об ошибке.
// Формат: xx[cmd][err]\r
static void send_error(u8 cmd, u8 err)
{
    u8 buf[8];
    u16 idx = 0;

    buf[idx++] = CLI_ERROR_PREFIX;                          // 'e'
    buf[idx++] = CLI_ERROR_PREFIX;                          // 'e'

    byte_to_hex(cmd, (char*)&buf[idx]); idx += 2;

    byte_to_hex(err, (char*)&buf[idx]); idx += 2;           // код ошибки

    buf[idx++] = CLI_MSG_TERMINATOR;

    for (u16 i = 0; i < idx; i++) {
        lmcal_uart_write(CLI_UART_CHANNEL, buf[i]);
    }
}


//--------------------------------------------------------------------------



// Обработчик команды открытия соединения (CLI_CMD_OPEN = 0x00)
// Данные могут содержать пароль. Если пароль задан, проверяем его.
static i8 handle_cmd_open(const u8* data, u8 len)
{
    i8 err = CLI_HND_OK_RX;
    cli_connected = 1;
    return err;
}



// Обработчик команды закрытия соединения (CLI_CMD_CLOSE = 0x01)
static i8 handle_cmd_close(const u8* data, u8 len)
{
    i8 err = CLI_HND_OK_RX;
    cli_connected = 0;
    return err;
}



// Обработчик команды проверки соединения (CLI_CMD_ECHO = 0x02)
// Возвращает эхо переданных данных.
// data в hex
static i8 handle_cmd_echo(const u8* data, u8 len)
{
    i8 err = CLI_HND_OK_TX;
    
    u8 tmp[CLI_DATA_MAX_SIZE];
    memcpy(tmp, data, len);

    cli_prepare_tx_data(tmp, len);
    return err;
}



// Обработчик команды получения информации об устройстве (CLI_CMD_GET_INFO = 0x0A)
// Формат ответа: длина имени, имя, длина серийного, серийный,
//                длина версии, версия (ASCII).
static i8 handle_cmd_get_info(const u8* data, u8 len)
{
    i8 err = CLI_HND_OK_RX;
    return err;

    // (void)data; (void)len;
    // if (!cli_connected) {
    //     send_error(CLI_CMD_GET_INFO, CLI_ERR_INTERNAL);
    //     return;
    // }
    // u8 buf[CLI_DATA_MAX_SIZE];
    // u16 idx = 0;
    // u8 str_len;

    // buf[idx++] = cli_device_info.board_id;

    // str_len = strlen(cli_device_info.name);
    // if (str_len > 31) str_len = 31;
    // buf[idx++] = str_len;
    // memcpy(&buf[idx], cli_device_info.name, str_len);
    // idx += str_len;

    // str_len = strlen(cli_device_info.serial);
    // if (str_len > 15) str_len = 15;
    // buf[idx++] = str_len;
    // memcpy(&buf[idx], cli_device_info.serial, str_len);
    // idx += str_len;

    // char ver[16] = "0";
    // // snprintf(ver, sizeof(ver), "%lu", tcp_dev_info.mcu_ver);
    // str_len = strlen(ver);
    // if (str_len > 15) str_len = 15;
    // buf[idx++] = str_len;
    // memcpy(&buf[idx], ver, str_len);
    // idx += str_len;

    // send_response(CLI_CMD_GET_INFO, buf, idx);
}



// Колбек прерывания по приёму байта из UART.
static void uart_rx_cb(void)
{
    u16 rx = lmcal_uart_read(CLI_UART_CHANNEL);
    rb_add_item(&fifo_rx, (u8*)&rx);
}



// Обработка буфера, 4 первых символа должны содержать код и длину данных
static i8 parse_cmd(char* buf, u8* cmd, u8* data)
{
    i8 err = 0;

    u8 cmd_tmp = hex_to_byte(&buf[0]);
    u8 data_tmp = hex_to_byte(&buf[2]);

    if ((cmd_tmp != 0xFF) && (data_tmp != 0xFF))
    {
        *cmd = cmd_tmp;
        *data = data_tmp;
    }
    else
    {
        err = -1;
    }

    return err;
}



// Преобразование буфера символов в буфер с hex
// Принимает: указатель на строку и кол-во символов
// Возвращает: буфер с байтами и кол-во байт
static void parse_data(char* buf, u8* size)
{
    u8 tmp;
    u8 tmp_size = *size / 2;

    for (u8 i=0, j=0; i < *size; i+=2, j++)
    {
        tmp = hex_to_byte(&buf[i]);
        buf[j] = tmp;
    }

    *size = tmp_size;
}


// Чтение байта из ФИФО. При ошибке возвращает -1
static u8 read_fifo(u8* byte)
{
    i8 err=0;

    if (rb_get_items_qty(&fifo_rx) > 0) 
    {
        rb_get_item(&fifo_rx, byte);
    }
    else
    {
        err = -1;
    }

    return err;
}



//************************************ Публичные функции ****************************************************

fun_res_t cli_protocol_init(void)
{
    fifo_rx.item_size = sizeof(u8);

    rb_init(&fifo_rx, fifo_rx_buf, sizeof(fifo_rx_buf));
    rb_clear(&fifo_rx);

    lmcal_uart_set_callback(CLI_UART_CHANNEL, CB_INT_RX_CALLBACK, uart_rx_cb);
    lmcal_uart_enable(CLI_UART_CHANNEL, 1);

    cli_connected = 0;
        
    safe_strncpy(cli_device_info.name, "CLI Device", sizeof(cli_device_info.name));
    safe_strncpy(cli_device_info.serial, "00000000", sizeof(cli_device_info.serial));
    safe_strncpy(cli_device_info.version, "1.0.0", sizeof(cli_device_info.version));

    // Инициализация встроенных обработчиков
    memset(cmd_handlers, 0, sizeof(cmd_handlers));
    const cli_command_t builtin[] = {
        { CLI_CMD_OPEN,      handle_cmd_open },
        { CLI_CMD_CLOSE,     handle_cmd_close },
        { CLI_CMD_ECHO,      handle_cmd_echo },
        { CLI_CMD_GET_INFO,  handle_cmd_get_info },
    };

    for (size_t i = 0; i < sizeof(builtin)/sizeof(builtin[0]); i++) {
        cmd_handlers[builtin[i].code] = builtin[i].handler;
    }

    return ERR_OK;
}



fun_res_t cli_protocol_deinit(void)
{
    lmcal_uart_set_callback(CLI_UART_CHANNEL, CB_INT_RX_CALLBACK, NULL);
    cli_connected = 0;
    return ERR_OK;
}



void cli_protocol_process(void)
{
    static u8 buf_index;
    // Текущая команда и размер данных для приема
    static u8 cur_cmd;
    
    
    static enum {
        RESET,          // Сброс
        WAIT_START,     // Ожидание первого символа команды
        WAIT_CMD,       // Прием аргументов команды
        WAIT_DATA,      // Приём данных команды
        WAIT_CR,        // Ожидание \r
        EXECUTE,        // Выполнение
    } state = RESET;

    u8 tmp;
    i8 err;
    
    switch (state) 
    {
        case RESET:
            buf_index = 0;
            cur_cmd = 0;
            data_size = 0;

            state = WAIT_START;
        break;


        case WAIT_START:
            if (read_fifo(&tmp) == 0)
            {
                if (tmp == CLI_CMD_PREFIX)
                {
                    state = WAIT_CMD;
                    buf_index = 0;
                } 
            }
        break;


        case WAIT_CMD:
            if (read_fifo(&tmp) == 0)
            {
                buf_tmp[buf_index++] = tmp;
                
                if (buf_index == 4)
                {
                    if (parse_cmd(buf_tmp, &cur_cmd, &data_size) == 0)
                    {
                        if ((data_size > 0) && 
                            (CLI_DATA_MAX_SIZE >= data_size) &&
                            (data_size % 2 == 0)) 
                            state = WAIT_DATA;
                        else state = WAIT_CR;
                        
                        buf_index = 0;
                    }
                    else
                    {
                        // Ошибка в формате команды
                        state = RESET;
                    }
                }
            }
        break;


        case WAIT_DATA:
            if (data_size > buf_index)
            {
                if (read_fifo(&tmp) == 0)
                {
                    buf_tmp[buf_index++] = tmp;
                }
            }
            else
            {
                state = WAIT_CR;
            }
        break;


        case WAIT_CR:
            if (read_fifo(&tmp) == 0)
            {
                if (tmp == CLI_MSG_TERMINATOR)
                {
                    if (cmd_handlers[cur_cmd] != NULL)
                    {
                        state = EXECUTE;

                        // Преобразование данных из строки в hex
                        if (data_size > 0) parse_data(buf_tmp, &data_size);
                    }
                    else
                    {
                        // Данная команда не зарегестрирована
                        send_error(cur_cmd, CLI_ERR_UNKNOWN_CMD);
                        state = RESET;
                    }
                } 
                else 
                {
                    state = RESET;
                }
            }
        break;

        
        case EXECUTE:
            #if 0
            if (cli_connected == 0)
            {
                if (cur_cmd != CLI_CMD_OPEN)
                {
                    send_error(cur_cmd, CLI_ERR_CON);
                    state = WAIT_START;
                    break;
                }
            }
            #endif

            err = cmd_handlers[cur_cmd](buf_tmp, data_size);

            switch(err)
            {
                case(CLI_HND_OK_RX):
                    send_response(cur_cmd, NULL, 0);
                    state = WAIT_START;
                break;

                case(CLI_HND_OK_TX):
                    send_response(cur_cmd, buf_tmp, data_size);
                    state = WAIT_START;
                break;

                case(CLI_HND_ERR_ARG):
                    send_error(cur_cmd, CLI_ERR_INVALID_ARG);
                    state = WAIT_START;
                break;

                case(CLI_HND_ERR_INT):
                    send_error(cur_cmd, CLI_ERR_INTERNAL);
                    state = WAIT_START;
                break;

                case(CLI_HND_BUSY):
                    // Ждем, когда хзндл выполнится
                break;

                default:
                    // хэндл вернул недопустимый ответ
                    send_error(cur_cmd, CLI_ERR_INTERNAL);
                    state = WAIT_START;
                break;
            }
        break;
        

        default:
            while(1);
        break;
    }
}



void cli_prepare_tx_data(u8* hex_data, u8 size)
{
    if (hex_data == NULL) return;
        
    u8 tmp[3];

    for (u8 i=0, j=0; i < size; i++, j+=2) {
        // Преобразуем каждый байт в два hex‑символа (с ведущими нулями)
        sprintf(tmp, "%02X", hex_data[i]);
        buf_tmp[j]   = tmp[0];
        buf_tmp[j+1] = tmp[1];
    }
    
    data_size = size * 2;
}



fun_res_t cli_protocol_set_device_info(const char* name, const char* serial, const char* ver)
{
    if (name)   safe_strncpy(cli_device_info.name, name, sizeof(cli_device_info.name));
    if (serial) safe_strncpy(cli_device_info.serial, serial, sizeof(cli_device_info.serial));
    if (ver)    safe_strncpy(cli_device_info.version, ver, sizeof(cli_device_info.version));
    return ERR_OK;
}



fun_res_t cli_register_command(const cli_command_t* cmd)
{
    if (cmd == NULL || cmd->handler == NULL || (CLI_INT_CMD_NUM > cmd->code)) 
    {
        return ERR_INVALID_ARGUMENT;
    }

    if (cmd_handlers[cmd->code] != NULL) 
    {
        return ERR_RUNTIME_ERROR;
    }

    cmd_handlers[cmd->code] = cmd->handler;
    return ERR_OK;
}



void cli_unregister_command(u8 code)
{
    if (CLI_INT_CMD_NUM > code)
    {
        cmd_handlers[code] = NULL;
    }
}



//*********************************************** КОНЕЦ ФАЙЛА ***********************************************
