//***********************************************************************************************************
//! @file        cli_protocol.c
//! @brief       Реализация протокола CLI с адресацией (board_id)
//! @version     v1.1.0
//! @date        07.04.2026
//***********************************************************************************************************

#include "cli_protocol.h"
#include "ring_buffer.h"
#include "lmcal.h"
#include "tcp.h"
#include "fpga_cmn.h"
#include "flash_mem.h"
#include "submodule.h"
#include <string.h>
#include <stdio.h>

#define CLI_CMD_PREFIX          'T'
#define CLI_RESPONSE_PREFIX     't'
#define CLI_ERROR_PREFIX        'e'
#define CLI_MSG_TERMINATOR      '\r'

#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;
static u8 fifo_rx_buf[CLI_CMD_BUFFER_SIZE];

static char cli_cmd_buffer[CLI_CMD_BUFFER_SIZE];
static u16 cli_cmd_index = 0;

static bool cli_connected = false;
static u32 cli_last_activity = 0;
static u32 (*cli_get_tick)(void) = NULL;

static cli_device_info_t cli_device_info;
static char cli_password[16] = CLI_DEFAULT_PASSWORD;

//-----------------------------------------------------------------------------------------------------------
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];
}

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;
}

void cli_send_string(const char* str)
{
    if (!str) return;
    while (*str) lmcal_uart_write(CLI_UART_CHANNEL, (u16)*str++);
}

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;
    byte_to_hex(cmd, (char*)&buf[idx]); idx += 2;
    byte_to_hex(cli_device_info.board_id, (char*)&buf[idx]); idx += 2;
    for (u8 i = 0; i < len; i++) {
        byte_to_hex(data[i], (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]);
}

static void send_error(u8 cmd, u8 err)
{
    u8 buf[8];
    u16 idx = 0;
    buf[idx++] = CLI_ERROR_PREFIX;
    byte_to_hex(cmd, (char*)&buf[idx]); idx += 2;
    byte_to_hex(cli_device_info.board_id, (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]);
}

static void handle_cmd_open(const u8* data, u8 len)
{
    if (cli_connected) {
        send_error(CLI_CMD_OPEN, CLI_ERR_INTERNAL);
        return;
    }
    if (len > 0) {
        if (len != strlen(cli_password)) {
            send_error(CLI_CMD_OPEN, CLI_ERR_INTERNAL);
            return;
        }
        for (u8 i = 0; i < len; i++) {
            if (data[i] != cli_password[i]) {
                send_error(CLI_CMD_OPEN, CLI_ERR_INTERNAL);
                return;
            }
        }
    }
    cli_connected = true;
    if (cli_get_tick) cli_last_activity = cli_get_tick();
    send_response(CLI_CMD_OPEN, NULL, 0);
}

static void handle_cmd_close(void)
{
    cli_connected = false;
    send_response(CLI_CMD_CLOSE, NULL, 0);
    cli_cmd_index = 0;
    memset(cli_cmd_buffer, 0, sizeof(cli_cmd_buffer));
}

static void handle_cmd_ping(const u8* data, u8 len)
{
    if (!cli_connected) {
        send_error(CLI_CMD_PING, CLI_ERR_INTERNAL);
        return;
    }
    send_response(CLI_CMD_PING, data, len);
}

static void handle_cmd_get_info(void)
{
    if (!cli_connected) {
        send_error(CLI_CMD_GET_INFO, CLI_ERR_INTERNAL);
        return;
    }
    u8 buf[CLI_DATA_MAX_SIZE];
    u16 idx = 0;
    u8 len;
    buf[idx++] = cli_device_info.board_id;
    len = strlen(cli_device_info.name);
    if (len > 31) len = 31;
    buf[idx++] = len;
    memcpy(&buf[idx], cli_device_info.name, len);
    idx += len;
    len = strlen(cli_device_info.serial);
    if (len > 15) len = 15;
    buf[idx++] = len;
    memcpy(&buf[idx], cli_device_info.serial, len);
    idx += len;
    char ver[16];
    snprintf(ver, sizeof(ver), "%lu", tsp_dev_info.mcu_ver);
    len = strlen(ver);
    if (len > 15) len = 15;
    buf[idx++] = len;
    memcpy(&buf[idx], ver, len);
    idx += len;
    send_response(CLI_CMD_GET_INFO, buf, idx);
}

static void handle_cmd_read_mem(const u8* data, u8 len)
{
    if (!cli_connected) {
        send_error(CLI_CMD_READ_MEM, CLI_ERR_INTERNAL);
        return;
    }
    if (len != 5) {
        send_error(CLI_CMD_READ_MEM, CLI_ERR_INVALID_ARG);
        return;
    }
    u32 addr = 0;
    for (u8 i = 0; i < 4; i++) addr = (addr << 8) | data[i];
    u8 size = data[4];
    if (size == 0 || size > CLI_DATA_MAX_SIZE) {
        send_error(CLI_CMD_READ_MEM, CLI_ERR_INVALID_ARG);
        return;
    }
    if ((addr >= 0x08000000 && addr < 0x08200000) ||
        (addr >= 0x20000000 && addr < 0x20020000)) {
        u8 buf[CLI_DATA_MAX_SIZE];
        for (u8 i = 0; i < size; i++) buf[i] = *(volatile u8*)(addr + i);
        send_response(CLI_CMD_READ_MEM, buf, size);
    } else {
        send_error(CLI_CMD_READ_MEM, CLI_ERR_INTERNAL);
    }
}

static void handle_cmd_write_mem(const u8* data, u8 len)
{
    if (!cli_connected) {
        send_error(CLI_CMD_WRITE_MEM, CLI_ERR_INTERNAL);
        return;
    }
    if (len < 5) {
        send_error(CLI_CMD_WRITE_MEM, CLI_ERR_INVALID_ARG);
        return;
    }
    u32 addr = 0;
    for (u8 i = 0; i < 4; i++) addr = (addr << 8) | data[i];
    u8 size = len - 4;
    if (size > CLI_DATA_MAX_SIZE) {
        send_error(CLI_CMD_WRITE_MEM, CLI_ERR_INVALID_ARG);
        return;
    }
    if ((addr >= 0x20000000 && addr < 0x20020000)) {
        for (u8 i = 0; i < size; i++) *(volatile u8*)(addr + i) = data[4 + i];
        send_response(CLI_CMD_WRITE_MEM, NULL, 0);
    } else {
        send_error(CLI_CMD_WRITE_MEM, CLI_ERR_INTERNAL);
    }
}

static void parse_and_execute(void)
{
    char* buf = cli_cmd_buffer;
    u16 len = cli_cmd_index;
    if (len < 6) { cli_cmd_index = 0; return; }
    if (buf[0] != CLI_CMD_PREFIX) { cli_cmd_index = 0; return; }
    if (buf[len - 1] != CLI_MSG_TERMINATOR) return;

    u8 cmd = hex_to_byte(&buf[1]);
    if (cmd == 0xFF) { cli_cmd_index = 0; return; }

    u8 board_id = hex_to_byte(&buf[3]);
    if (board_id == 0xFF) { cli_cmd_index = 0; return; } // broadcast ignored
    if (board_id != cli_device_info.board_id) { cli_cmd_index = 0; return; }

    u16 data_start = 5;
    u16 chars = len - 1 - data_start;
    u8 data[CLI_DATA_MAX_SIZE + 4];
    u8 data_len = 0;
    if (chars > 0) {
        if (chars % 2 != 0) { send_error(cmd, CLI_ERR_INVALID_ARG); cli_cmd_index = 0; return; }
        data_len = chars / 2;
        if (data_len > CLI_DATA_MAX_SIZE + 4) { send_error(cmd, CLI_ERR_INVALID_ARG); cli_cmd_index = 0; return; }
        for (u8 i = 0; i < data_len; i++) {
            data[i] = hex_to_byte(&buf[data_start + i * 2]);
            if (data[i] == 0xFF) { send_error(cmd, CLI_ERR_INVALID_ARG); cli_cmd_index = 0; return; }
        }
    }

    if (cli_get_tick) cli_last_activity = cli_get_tick();

    switch (cmd) {
        case CLI_CMD_OPEN:      handle_cmd_open(data, data_len); break;
        case CLI_CMD_CLOSE:     handle_cmd_close(); break;
        case CLI_CMD_PING:      handle_cmd_ping(data, data_len); break;
        case CLI_CMD_GET_INFO:  handle_cmd_get_info(); break;
        case CLI_CMD_READ_MEM:  handle_cmd_read_mem(data, data_len); break;
        case CLI_CMD_WRITE_MEM: handle_cmd_write_mem(data, data_len); break;
        default:                send_error(cmd, CLI_ERR_UNKNOWN_CMD); break;
    }
    cli_cmd_index = 0;
}

static void uart_rx_cb(void)
{
    u16 rx = lmcal_uart_read(CLI_UART_CHANNEL);
    u8 ch = (u8)(rx & 0xFF);
    rb_add_item(&fifo_rx, &ch);
}

//-----------------------------------------------------------------------------------------------------------
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);

    cli_cmd_index = 0;
    cli_connected = false;
    cli_last_activity = 0;
    cli_get_tick = NULL;
    memset(cli_cmd_buffer, 0, sizeof(cli_cmd_buffer));

    strcpy(cli_device_info.name, "CLI Device");
    strcpy(cli_device_info.serial, "00000000");
    strcpy(cli_device_info.version, "1.0.0");
    cli_device_info.board_id = 0;

    cli_send_string("\r\n>----- LTA PROTOCOL ENABLE -----<\r\n");
    cli_send_string("> T00 - Open connection (T0001, T00011357)\r\n");
    cli_send_string("> T01 - Close connection (T0101)\r\n");
    cli_send_string("> T02 - Ping (T0201)\r\n");
    cli_send_string("> T0A - Get device info (T0A01)\r\n");
    cli_send_string("> T14 - Read memory (T14012000000004)\r\n");
    cli_send_string("> T15 - Write memory (T150120000000AABBCCDD)\r\n");
    cli_send_string("> ");
    return ERR_OK;
}

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

void cli_protocol_process(void)
{
    while (rb_get_items_qty(&fifo_rx) > 0) {
        u8 ch;
        if (rb_get_item(&fifo_rx, &ch) != RB_RES_OK) continue;
        if (cli_cmd_index == 0 && (ch == '\r' || ch == '\n')) continue;
        if (ch == '\r' || ch == '\n') {
            if (cli_cmd_index > 0) {
                cli_cmd_buffer[cli_cmd_index] = CLI_MSG_TERMINATOR;
                cli_cmd_index++;
                parse_and_execute();
                cli_send_string("> ");
            }
            cli_cmd_index = 0;
        } else if (cli_cmd_index < CLI_CMD_BUFFER_SIZE - 1) {
            cli_cmd_buffer[cli_cmd_index++] = ch;
        } else {
            cli_cmd_index = 0;
            memset(cli_cmd_buffer, 0, sizeof(cli_cmd_buffer));
        }
    }
    if (cli_connected && cli_get_tick) {
        if ((cli_get_tick() - cli_last_activity) > CLI_CONNECTION_TIMEOUT_MS)
            cli_connected = false;
    }
}

fun_res_t cli_protocol_set_device_info(const char* name, const char* serial, const char* ver, u8 board_id)
{
    if (name) strncpy(cli_device_info.name, name, sizeof(cli_device_info.name)-1);
    if (serial) strncpy(cli_device_info.serial, serial, sizeof(cli_device_info.serial)-1);
    if (ver) strncpy(cli_device_info.version, ver, sizeof(cli_device_info.version)-1);
    cli_device_info.board_id = board_id;
    return ERR_OK;
}

fun_res_t cli_protocol_set_board_id(u8 board_id)
{
    cli_device_info.board_id = board_id;
    return ERR_OK;
}

u8 cli_protocol_get_board_id(void) { return cli_device_info.board_id; }

void cli_protocol_set_password(const char* pass)
{
    if (pass) strncpy(cli_password, pass, sizeof(cli_password)-1);
}

bool cli_protocol_verify_password(const char* pass)
{
    if (!pass) return false;
    return (strcmp(pass, cli_password) == 0);
}