/*
 * 模块名称：Remote Display
 * 模块功能：实现 RemoDispBus 协议，提供远程 LCD 显示与按键转发功能
 * 适用平台：通用嵌入式平台
 * 作者：王建锋
 * 创建日期：2026-05-11
 * 修改记录：
 *     2026-05-11  王建锋  创建初始版本
 */
#include "remoteDisplay.h"
#include <stdio.h>
#include <string.h>
#include "Drv/lcd/lcd.h"
#include "TCP/tcp.h"
#include "Drv/key.h"
#include "remoteDisplayProtocol.h"

/* 回复帧最大长度：头4 + 最大 payload(4+LCD_DISPLAYMEMORYSIZE) + crc1 */
#define REMO_REPLY_BUF_SIZE  (4 + 4 + LCD_DISPLAYMEMORYSIZE + 1)

/*
 * 函数功能：计算数据的XOR校验和
 * 入口参数：p_data - 数据缓冲区指针  const uint8_t*  非NULL
 *           len    - 数据长度      unsigned int    0 - PROTOCOL_MAX_DATA_LEN
 * 返回值：校验和值  uint8_t  0 - 255
 * 限定条件：p_data指针必须指向有效的内存区域，长度至少为len字节
 * 函数说明：1. 采用XOR算法计算校验和
 *           2. 将所有数据字节进行异或运算
 *           3. 当len为0时，返回0
 */
static uint8_t calc_crc(const uint8_t *p_data, unsigned int len) {
    uint8_t crc = 0;
    unsigned int i;

    if (p_data == NULL) {
        return 0;
    }

    for (i = 0; i < len; i++) {
        crc ^= p_data[i];
    }

    return crc;
}

/*
 * 函数功能：构造设备回复帧并发送
 * 入口参数：client - 客户端 socket  int  有效的 socket 描述符
 *           cmd - 命令字  uint8_t  参考 RemoDispBus 命令定义
 *           data - 回复数据指针  const uint8_t*  可为 NULL
 *           data_len - 回复数据长度  unsigned int  0 - PROTOCOL_MAX_DATA_LEN
 * 出口参数：无
 * 返回值：0 - 发送成功
 *        -1 - 发送失败或参数非法
 * 限定条件：client 已连接且有效
 * 函数说明：构造 [TAG_DEVICE][cmd][len_hi][len_lo][data][crc] 帧并一次性发送
 */
static int send_reply(int client, uint8_t cmd, const uint8_t* data, unsigned int data_len) {
    uint8_t buf[REMO_REPLY_BUF_SIZE];
    unsigned int total = 4 + data_len + 1;

    if (total > REMO_REPLY_BUF_SIZE)
        return -1;
    buf[0] = TAG_DEVICE;
    buf[1] = cmd & 0xFF;
    buf[2] = (data_len >> 8) & 0xFF;
    buf[3] = data_len & 0xFF;
    if (data_len)
        memcpy(buf + 4, data, data_len);
    buf[4 + data_len] = calc_crc(data, data_len);

    return TcpClient_Send(client, (const char*)buf, total) == (int)total ? 0 : -1;
}

/*
 * 函数功能：处理 CMD_LCDMEM 请求并回复显存数据
 * 入口参数：client - 客户端 socket  int  有效的 socket 描述符
 *           req_data - 请求数据指针  const uint8_t*  期望至少 4 个字节
 *           req_len - 请求数据长度  unsigned int  0 - PROTOCOL_MAX_DATA_LEN
 * 出口参数：无
 * 返回值：无
 * 限定条件：req_data 可为 NULL，但 req_len 必须有效
 * 函数说明：解析大端起始地址，读取显存并通过 send_reply 发送结果
 */
static void handle_cmd_lcdmem(int client, const uint8_t* req_data, unsigned int req_len) {
    const uint8_t *framebuffer = Lcd_GetFrameBuffer();
    unsigned int start_addr = 0;

    if (req_len >= 4)
        start_addr = ((unsigned int)req_data[0] << 24) | ((unsigned int)req_data[1] << 16)
                   | ((unsigned int)req_data[2] << 8) | req_data[3];

    /* 回复 payload = [4B 地址大端][显存]，Python 取 payload[4:] 为位图 */
    uint8_t payload[4 + LCD_DISPLAYMEMORYSIZE];
    payload[0] = (start_addr >> 24) & 0xFF;
    payload[1] = (start_addr >> 16) & 0xFF;
    payload[2] = (start_addr >> 8) & 0xFF;
    payload[3] = start_addr & 0xFF;

    unsigned int copy_len = LCD_DISPLAYMEMORYSIZE;
    if (start_addr < LCD_DISPLAYMEMORYSIZE) 
    {
        unsigned int offset = start_addr;
        copy_len = LCD_DISPLAYMEMORYSIZE - offset;
        memcpy(payload + 4, framebuffer + offset, copy_len);
    } 
    else 
    {
        copy_len = 0;
    }
    send_reply(client, CMD_LCDMEM, payload, 4 + copy_len);
}

/*
 * 函数功能：处理 CMD_INIT 请求并回复 LCD 参数
 * 入口参数：client - 客户端 socket  int  有效的 socket 描述符
 * 出口参数：无
 * 返回值：无
 * 限定条件：client 已连接且有效
 * 函数说明：回复 LCD 宽度、高度和显存大小的 8 字节信息
 */
static void handle_cmd_init(int client) {
    uint8_t data[8];
    data[0] = (LCD_SIZE_X >> 8) & 0xFF;
    data[1] = LCD_SIZE_X & 0xFF;
    data[2] = (LCD_SIZE_Y >> 8) & 0xFF;
    data[3] = LCD_SIZE_Y & 0xFF;
    data[4] = (LCD_DISPLAYMEMORYSIZE >> 24) & 0xFF;
    data[5] = (LCD_DISPLAYMEMORYSIZE >> 16) & 0xFF;
    data[6] = (LCD_DISPLAYMEMORYSIZE >> 8) & 0xFF;
    data[7] = LCD_DISPLAYMEMORYSIZE & 0xFF;
    send_reply(client, CMD_INIT, data, 8);
}

/*
 * 函数功能：处理 CMD_KEY 请求并更新远程按键信号
 * 入口参数：client - 客户端 socket  int  有效的 socket 描述符
 *           data - 键值数据指针  const uint8_t*  期望至少 1 字节
 *           len - 数据长度  unsigned int  0 - PROTOCOL_MAX_DATA_LEN
 * 出口参数：无
 * 返回值：无
 * 限定条件：data 必须包含至少 1 个字节键值
 * 函数说明：将按键值写入全局远程按键结构，供主循环处理
 */
static void handle_cmd_key(int client, const uint8_t* data, unsigned int len) {
#ifdef DEBUG
    printf("handle_cmd_key: 0x%02X\n", data[0]);
#endif
    g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NEW;
    g_tRemoteKey.byKeyValue = data[0];
}

/*
 * 函数功能：处理 CMD_KEEPLIVE 请求并回复空帧
 * 入口参数：client - 客户端 socket  int  有效的 socket 描述符
 * 出口参数：无
 * 返回值：无
 * 限定条件：client 已连接且有效
 * 函数说明：发送保活响应帧，无数据部分
 */
static void handle_cmd_keeplive(int client) {
    send_reply(client, CMD_KEEPLIVE, (const uint8_t*)NULL, 0);
}
/*
 * 函数功能：TCP 服务器线程入口函数
 * 入口参数：arg - 线程参数指针  void*  实际类型为 server_thread_arg_t*
 * 出口参数：无
 * 返回值：无
 * 限定条件：arg 必须指向有效的 server_thread_arg_t 实例
 * 函数说明：创建 TCP 监听 socket，接受客户端连接并按 RemoDispBus 协议处理请求
 */
void tcp_server_thread_fn(void* arg) {
    server_thread_arg_t* ctx = (server_thread_arg_t*)arg;
    int server_sock = TcpServer_Listen((uint16_t)ctx->port);
    if (server_sock == TCP_INVALID_SOCKET) {
        fprintf(stderr, "[TCP Server] TcpServer_Listen(%d) failed\n", ctx->port);
        return;
    }
    *ctx->p_server_sock = server_sock;
    printf("[TCP Server] RemoDispBus 监听端口 %d，等待客户端连接...\n", ctx->port);

    /* ========== 外层循环：主线程未请求退出时，持续等待并接受新客户端 ========== */
    while (!*ctx->p_quit) {
        /* 阻塞等待一个客户端连接；主线程关闭 server_sock 时 Accept 会失败并返回 INVALID */
        int client = TcpServer_Accept(server_sock);
        if (client == TCP_INVALID_SOCKET) {
            if (*ctx->p_quit) {
                break;
            }
            continue;
        }
        printf("[TCP Server] 客户端连接成功\n");

        /* 为当前客户端初始化独立的协议解析实例 */ 
        protocol_instance_t proto_ins;
        protocol_init(&proto_ins);

        /* ========== 内层循环：处理当前连接上的 RemoDispBus 请求，直到断开或退出 ========== */
        while (!*ctx->p_quit) {
            uint8_t recv_byte;  /* 逐字节接收 */ 
            int n = TcpClient_Recv(client, (uint8_t*)&recv_byte, 1);
            if (n > 0) {  /* 成功收到1字节 */
                /* 喂给状态机逐字节处理 */
                protocol_byte_handler(&proto_ins, recv_byte);
                /* 检查是否有完整帧解析完成 */
                if (proto_ins.frame_done_flag == 1) {
                    /* 根据解析到的CMD分发处理 */
                    switch (proto_ins.recv_cmd) 
                    {
                        case CMD_KEEPLIVE:
                            handle_cmd_keeplive(client);
                            break;
                        case CMD_INIT:
                            handle_cmd_init(client);
                            break;
                        case CMD_KEY:
                            handle_cmd_key(client, proto_ins.recv_buf, proto_ins.expect_data_len);
                            break;
                        case CMD_LCDMEM:
                            handle_cmd_lcdmem(client, proto_ins.recv_buf, proto_ins.expect_data_len);
                            break;
                        default:
                            send_reply(client, proto_ins.recv_cmd, (const uint8_t*)NULL, 0);
                            break;
                    }
                    /* 处理完成后，复位协议实例，准备接收下一帧 */
                    protocol_init(&proto_ins);
                }
            }
            else {  /* n==0（客户端关闭）或 n<0（读取错误）*/ 
                printf("[TCP Server] 客户端关闭连接\n");
                break;
            }
        }
        /* 当前客户端处理结束，关闭该连接；外层循环继续 Accept 下一个客户端 */
        TcpClient_Close(client);
    }

    TcpServer_Close(server_sock);
    *ctx->p_server_sock = TCP_INVALID_SOCKET;
    printf("[TCP Server] 已退出\n");
}
/*
 * 函数功能：启动 TCP 服务并创建服务器线程
 * 入口参数：out_server_th - 输出线程句柄  thread_handle_t*  非空
 *           io_server_arg - 输入输出服务器线程参数  server_thread_arg_t*  非空
 * 出口参数：out_server_th - 输出创建的线程句柄
 * 返回值：0 - 成功
 *        1 - 失败（已调用 Tcp_Cleanup）
 * 限定条件：io_server_arg 必须初始化并提供有效端口、socket 和退出标志指针
 * 函数说明：初始化 TCP 子系统并创建服务器线程，若失败则清理资源
 */
int StartTcpServerThread(thread_handle_t *out_server_th, server_thread_arg_t *io_server_arg) {
     if (Tcp_Init() != 0) {
         fprintf(stderr, "Tcp_Init failed\n");
         return 1;
     }
 
     if (Thread_Create(tcp_server_thread_fn, io_server_arg, out_server_th) != 0) {
         fprintf(stderr, "Thread_Create(server) failed\n");
         Tcp_Cleanup();
         return 1;
     }
     Sleep(200);
 
     return 0;
 }
