#include "Drv/pages/YC/presenter.h"
#include "Drv/pages/page_manager.h"
#include "Drv/key.h"
#include <stddef.h>
#include <stdio.h>
#include <string.h>

/* -------------------------------------------------------------------------
 * 静态变量：桩测试相位计数器
 * ------------------------------------------------------------------------- */
static uint32_t s_ycStubPhase;          /* 桩测试相位计数器，用于生成模拟数据的时间变化 */

/* -------------------------------------------------------------------------
 * 函数名：Presenter_StubTick
 * 功能:
 *   递增桩测试相位计数器，使模拟数据产生时间维度的动态变化。
 *
 * 参数:
 *   无
 *
 * 边界处理:
 *   - 本函数不对计数器进行溢出检查，依赖 uint32_t 自动回绕特性。
 *
 * 说明:
 *   - 这是一个桩测试辅助函数，仅在编译接入阶段使用。
 *   - 通过递增 s_ycStubPhase 实现模拟数据的时变效果。
 *   - Presenter_FormatStubValue() 使用该变量生成不同时间点的模拟值。
 *   - 真实数据接入后此函数将被移除或替换为真实采样逻辑。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void Presenter_StubTick(void)
{
    s_ycStubPhase++;
}

/* -------------------------------------------------------------------------
 * 函数名：Presenter_FormatStubValue
 * 功能:
 *   按表索引与时间相位生成可显示遥测字符串（桩函数，用于测试阶段模拟数据）。
 *
 * 参数:
 *   tableIndex - 遥测量常量表的索引，用于确定基础数值
 *   width      - 数据显示宽度（包含小数点长度），当前未使用
 *   dotBits    - 小数位数，控制输出字符串的小数精度
 *   outBuf     - 输出缓冲区指针（输出），存放格式化后的字符串
 *   outSize    - 输出缓冲区大小（字节）
 *
 * 边界处理:
 *   - 若 outBuf 为 NULL 或 outSize 为 0，函数直接返回，不做任何操作。
 *   - 若 dotBits > 6，则自动截断为 6 位小数，避免输出过长。
 *   - width 参数在当前实现中未使用，通过 (void)width 消除编译器警告。
 *
 * 说明:
 *   - 这是一个桩函数（stub），用于在真实采样数据接入前提供测试支持。
 *   - 数值生成算法：
 *       1) 基础值 = tableIndex × 137 + s_ycStubPhase × 3
 *          - tableIndex 来自遥测量常量表索引
 *          - s_ycStubPhase 为静态变量，通过 YCView_StubTick() 递增实现时间相位变化
 *       2) 显示值 = (基础值 % 100000) / 1000.0
 *          - 取模确保数值范围在 0-99.999 之间
 *          - 除以 1000.0 转换为带小数的浮点数
 *   - 支持跨平台字符串格式化：
 *       - MSVC 编译器使用 sprintf_s()（安全版）
 *       - 其他编译器使用 snprintf()
 *   - 此函数后续将被真实采样数据格式化逻辑替换。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void Presenter_FormatStubValue(uint16_t tableIndex, uint8_t width, uint8_t dotBits,
                                   uint8_t *outBuf, size_t outSize)
{
    if ((outBuf == NULL) || (outSize == 0))
    {
        return;
    }
    /* 生成模拟数值：基于表索引和相位计数 */
    uint32_t base = (uint32_t)tableIndex * 137u + s_ycStubPhase * 3u;
    double v = (double)(base % 100000u) / 1000.0;
    /* 限制小数位数不超过 6 位 */
    if (dotBits > 6u)
    {
        dotBits = 6u;
    }
#if defined(_MSC_VER)
    (void)width;
    (void)sprintf_s((char *)outBuf, outSize, "%.*f", dotBits, v);
#else
    (void)width;
    (void)snprintf((char *)outBuf, outSize, "%.*f", (int)dotBits, v);
#endif
}

static uint16_t Presenter_ClampCursorRow(uint16_t cursorRow, uint16_t rows)
{
    if (rows == 0u)
    {
        return 0u;
    }
    if (cursorRow >= rows)
    {
        return (uint16_t)(rows - 1u);
    }
    return cursorRow;
}

/* -------------------------------------------------------------------------
 * 函数名：Presenter_BuildViewInput
 * 功能:
 *   从领域 Model 构建视图输入快照，供 View 层绘制使用。
 * 
 * 设计原则:
 *   - Presenter 负责将 Model 数据转换为 View 可识别的格式。
 *   - View 不直接访问 Yc_Table_*系列函数，保持职责分离。
 *   - 本函数生成 yc_view_input_t 结构体，包含所有绘制所需信息。
 *
 * 参数:
 *   m      - Model 对象指针，提供当前页面状态和数据源
 *   out    - 输出指针，用于填充 yc_view_input_t 结构体
 *
 * 边界处理:
 *   - 若 m 或 out 为 NULL，直接返回不做任何操作。
 *   - 若数据为空（m->empty != 0），设置 rows_on_page=0 并提前返回。
 *   - 遍历范围受 YC_VIEW_ROWS_MAX 限制，防止数组越界。
 *   - 索引查找失败时（tidx==0xFFFFu），标记该行无效（row_valid=0）。
 *
 * 说明:
 *   - 构建流程：
 *       1) 清空输出结构体（memset）
 *       2) 复制基础页面信息：top_name、empty、page、page_max、cursor_row
 *       3) 若数据为空，设置 rows_on_page=0 并返回
 *       4) 遍历当前页有效行：
 *          - 计算全局序号 ord = YC_Model_OrdinalForRow()
 *          - 查找对应表索引 tidx = YC_Model_TableIndexForOrdinal()
 *          - 若索引无效，跳过该行（row_valid=0）
 *          - 否则标记有效（row_valid=1），复制名称和数值字符串
 *   - 数值来源：桩模式下使用 Presenter_FormatStubValue() 生成模拟数据。
 *   - 真实接入后，此处的数值填充将被真实采样值替换。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void Presenter_BuildViewInput(const model_t *m, uint16_t cursorRow, yc_view_input_t *out)
{
    uint16_t rows;
    uint16_t ord;
    uint16_t tidx;

    /* 参数有效性检查 */
    if ((m == NULL) || (out == NULL))
    {
        return;
    }

    /* 清空输出结构体并填充基础页面信息 */
    (void)memset(out, 0, sizeof(*out));
    out->top_name = m->topName;
    out->empty = m->empty;
    out->page = m->page;
    out->page_max = m->group;
    
    rows = YC_Model_RowsOnCurrentPage(m);
    out->rows_on_page = rows;

    out->cursor_row = Presenter_ClampCursorRow(cursorRow, rows);

    /* 数据为空时的特殊处理 */
    if (m->empty != 0u)
    {
        out->rows_on_page = 0u;
        return;
    }

    /* 遍历当前页有效行，构建显示数据 */
   
    for (uint8_t row = 0u; row < (uint8_t)rows && row < YC_VIEW_ROWS_MAX; row++)
    {
        /* 计算全局序号并查找对应表索引 */
        ord = YC_Model_OrdinalForRow(m, row);
        tidx = YC_Model_TableIndexForOrdinal(m->attrib, ord);
        out->row_valid[row] = 0u;     /* 默认标记为无效 */
        if (tidx == 0xFFFFu)
        {
            continue;                 /* 索引无效，跳过此行 */
        }
        out->row_valid[row] = 1u;     /* 标记为有效行 */
        /* 获取名称指针 */
        out->row_name[row] = YC_GetNamePtr(m, tidx);

        if (m->attrib == EN_INPUT_RLY_ALL)
        {
            uint8_t inputType;
            uint8_t inputState;
            inputType = YC_Input_GetType(tidx);
            inputState = YC_Input_GetState(m->cpuPara, tidx);
            (void)YC_Input_FormatState(inputType, inputState, out->row_value[row], YC_VIEW_VALUE_BUF_MAX);
        }
        else
        {
            Presenter_FormatStubValue(tidx, YC_Table_GetWidth(tidx), YC_Table_GetDotBits(tidx),
                                      out->row_value[row], YC_VIEW_VALUE_BUF_MAX);
        }
    }
}

/* -------------------------------------------------------------------------
 * 函数名：Presenter_Refresh
 * 功能:
 *   完整刷新页面显示，重绘整个画面（包括顶栏和内容区）。
 *
 * 参数:
 *   presenter - Presenter 对象指针，包含 Model 和 View 的绑定关系
 *
 * 边界处理:
 *   - 若 presenter、presenter->view 或 presenter->model 为 NULL，直接返回。
 *   - 不做额外的有效性校验，依赖调用方保证参数正确性。
 *
 * 说明:
 *   - 完整刷新流程：
 *       1) 调用 Presenter_BuildViewInput() 从 Model 构建 View 输入快照
 *       2) 通过 presenter->view->draw_full() 回调执行完整页面绘制
 *   - 使用 draw_full 而非 draw_values，确保顶栏和内容区全部重绘。
 *   - 该函数在以下场景调用：
 *       - 用户翻页（page 变更）后需要刷新显示
 *       - 光标移动后需要同步更新高亮效果
 *       - 页面首次加载时的完整绘制
 *   - 相比 draw_values，此函数开销较大但能确保画面一致性。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void Presenter_Refresh(presenter_t *presenter)
{
    yc_view_input_t input;

    /* 参数有效性检查 */
    if ((presenter == NULL) || (presenter->view == NULL) || (presenter->model == NULL))
    {
        return;
    }

    /* 构建 View 输入并触发完整绘制 */
    Presenter_BuildViewInput(presenter->model, presenter->cursor_row, &input);
    presenter->view->draw_full(presenter->view, &input);
}

/* -------------------------------------------------------------------------
 * 函数名：Presenter_RefreshValues
 * 功能:
 *   仅刷新内容区数值显示（不重绘顶栏），用于主循环中的周期性更新。
 *
 * 参数:
 *   presenter - Presenter 对象指针，包含 Model 和 View 的绑定关系
 *
 * 边界处理:
 *   - 若 presenter、presenter->view 或 presenter->model 为 NULL，直接返回。
 *   - 若数据为空（model->empty != 0），跳过刷新避免无效绘制。
 *
 * 说明:
 *   - 数值刷新流程：
 *       1) 检查数据有效性，空数据时不执行刷新
 *       2) 调用 Presenter_StubTick() 推进桩测试相位（模拟数据更新）
 *       3) 构建 View 输入快照
 *       4) 通过 presenter->view->draw_values() 回调仅重绘内容区
 *   - 使用 draw_values 而非 draw_full，避免重复绘制顶栏减少开销。
 *   - 该函数在以下场景调用：
 *       - 主循环节拍触发（周期性数值刷新）
 *       - 桩模式下模拟数据需要更新显示
 *   - 相比 Presenter_Refresh()，此函数更高效适合高频调用。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void Presenter_RefreshValues(presenter_t *presenter)
{
    yc_view_input_t input;

    /* 参数有效性检查 */
    if ((presenter == NULL) || (presenter->view == NULL) || (presenter->model == NULL))
    {
        return;
    }
    /* 数据为空时跳过刷新 */
    if (presenter->model->empty != 0u)
    {
        return;
    }
    /* 推进桩测试相位并触发内容区刷新 */
    Presenter_StubTick();
    Presenter_BuildViewInput(presenter->model, presenter->cursor_row, &input);
    presenter->view->draw_values(presenter->view, &input);
}

/* -------------------------------------------------------------------------
 * 函数名：Presenter_HandleInput
 * 功能:
 *   处理用户键盘输入，执行相应的页面导航和控制操作。
 *
 * 参数:
 *   presenter - Presenter 对象指针，包含 Model 和 View 的绑定关系
 *   keyVal    - 按键值（KEY_ESC、KEY_U、KEY_D等）
 *
 * 边界处理:
 *   - 若 presenter、presenter->model 或 presenter->view 为 NULL，直接返回。
 *   - 数据为空时仅响应 ESC 键（退出页面），其他按键忽略。
 *   - 光标移动时检查边界，确保不超出有效范围。
 *   - 翻页操作检查页码范围，防止越界。
 *
 * 说明:
 *   - 按键处理逻辑：
 *       KEY_ESC: 弹出当前页面，返回上一级菜单
 *       KEY_U:   光标上移（若不在第一行）
 *       KEY_D:   光标下移（若不在最后一行）
 *       KEY_L:   上一页（若不在第一页）
 *       KEY_R:   下一页（若不在最后一页）
 *       KEY_F1/F2: 复归事件预留接口（后续接入通讯层）
 *       KEY_ENT: 确认操作预留接口（当前未实现）
 *       其他按键：忽略处理
 *   - 光标/翻页操作后调用 Presenter_Refresh() 刷新显示。
 *   - 数据为空时仅允许 ESC 退出，防止无效导航。
 *   - 该函数作为 presenter->handle_input 回调被页面管理器调用。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void Presenter_HandleInput(presenter_t *presenter, uint8_t keyVal)
{
    model_t *m;

    /* 参数有效性检查 */
    if ((presenter == NULL) || (presenter->model == NULL) || (presenter->view == NULL))
    {
        return;
    }

    m = presenter->model;

    /* 数据为空时的特殊处理：仅响应 ESC 退出 */
    if (m->empty != 0u)
    {
        if (keyVal == KEY_ESC)
        {
            (void)PageManager_Pop();
        }
        return;
    }

    /* 按键分发处理 */
    switch (keyVal)
    {
        case KEY_ESC:
            /* 退出页面，返回上一级菜单 */
            (void)PageManager_Pop();
            break;
        case KEY_U:
            /* 光标上移：若不在第一行则减小光标位置 */
            if (presenter->cursor_row > 0u)
            {
                presenter->cursor_row--;
            }
            Presenter_Refresh(presenter);     /* 刷新显示更新高亮 */
            break;
        case KEY_D:
            /* 光标下移：若不在最后一行则增加光标位置 */
            {
                uint16_t rows = YC_Model_RowsOnCurrentPage(m);
                if ((rows > 0u) && (presenter->cursor_row + 1u < rows))
                {
                    presenter->cursor_row++;
                }
                Presenter_Refresh(presenter);     /* 刷新显示更新高亮 */
                break;
            }
        case KEY_L:
            /* 上一页：若不在第一页则减小页码，并校正光标 */
            if (m->page > 1u)
            {
                m->page--;
                presenter->cursor_row = Presenter_ClampCursorRow(presenter->cursor_row, YC_Model_RowsOnCurrentPage(m)); /* 确保光标在有效范围 */
                Presenter_Refresh(presenter);     /* 刷新显示 */
            }
            break;
        case KEY_R:
            /* 下一页：若不在最后一页则增加页码，并校正光标 */
            if (m->page < m->group)
            {
                m->page++;
                presenter->cursor_row = Presenter_ClampCursorRow(presenter->cursor_row, YC_Model_RowsOnCurrentPage(m)); /* 确保光标在有效范围 */
                Presenter_Refresh(presenter);     /* 刷新显示 */
            }
            break;
        case KEY_F1:
        case KEY_F2:
            /* 复归事件：预留接口，后续接入通讯层实现保护复归功能 */
            break;
        case KEY_ENT:
            /* 确认操作：预留接口，当前未实现具体功能 */
            break;
        default:
            /* 未知按键：忽略处理 */
            break;
    }
}

/* -------------------------------------------------------------------------
 * 函数名：Presenter_Init
 * 功能:
 *   初始化 Presenter 实例，完成依赖绑定、对外接口挂接以及初始导航状态装配。
 *
 * 参数:
 *   presenter - 待初始化的 Presenter 实例指针
 *   model     - Model 实例指针，提供菜单数据与状态管理
 *   view      - View 实例指针，提供绘制能力与渲染接口
 *
 * 边界处理:
 *   - 若 presenter、model 或 view 任一为 NULL，直接返回不做初始化。
 *   - 使用 memset 将 presenter 结构体清零后再填充有效字段。
 *
 * 说明:
 *   - 初始化流程：
 *       1) 参数有效性校验（presenter/model/view均非空）
 *       2) 清空 presenter 结构体到零值
 *       3) 绑定 Model 引用（presenter->model = model）
 *       4) 绑定 View 引用（presenter->view = view）
 *       5) 挂接输入处理回调（handle_input -> Presenter_HandleInput）
 *       6) 挂接完整刷新回调（refresh -> Presenter_Refresh）
 *       7) 挂接数值刷新回调（refresh_values -> Presenter_RefreshValues）
 *   - Presenter 作为 Model 与 View 之间的桥梁，负责：
 *       - 将 Model 数据转换为 View 可识别的格式
 *       - 响应键盘输入并更新 Model 状态
 *       - 触发 View 刷新以显示最新数据
 *   - 初始化后 Presenter 即可被页面管理器使用。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
void Presenter_Init(presenter_t *presenter, model_t *model, view_t *view)
{
    /* 参数有效性检查 */
    if ((presenter == NULL) || (model == NULL) || (view == NULL))
    {
        return;
    }

    /* 清空结构体并绑定依赖 */
    memset(presenter, 0, sizeof(*presenter));
    presenter->model = model;
    presenter->view = view;
    presenter->cursor_row = 0u;
    
    /* 挂接回调函数接口 */
    presenter->handle_input = Presenter_HandleInput;
    presenter->refresh = Presenter_Refresh;
    presenter->refresh_values = Presenter_RefreshValues;
}
