#include "Drv/pages/YC/view.h"
#include <stdio.h>

static const PageRenderPort *s_port = NULL;

/* -------------------------------------------------------------------------
 * 宏定义：遥测页面布局常量
 * ------------------------------------------------------------------------- */
#define YC_TOP_BAND_MAX_Y       (24u)         /* 顶栏最大 Y 坐标 */
#define YC_CONTENT_BASE_Y       (24u)         /* 内容区起始 Y 坐标 */
#define YC_NAME_X               (6u)          /* 名称显示 X 坐标 */
#define YC_VALUE_X              (100u)        /* 数值显示 X 坐标 */
#define YC_ROW_PAD_X0           (2u)          /* 行内容左侧留白 */
#define YC_ROW_PAD_X1           (2u)          /* 行内容右侧留白 */
#define YC_FOOTER_Y_FROM_BOTTOM (16u)         /* 底栏距底部距离（与 PageRenderer_LcdShowInfoPage 一致）*/
#define YC_FOOTER_GAP_PX        (2u)          /* 内容区与底栏之间的留白间隙 */   

/* -------------------------------------------------------------------------
 * 函数名：YCView_ShowInfoPage
 * 功能:
 *   显示当前页码和总页数信息到底栏。
 *
 * 参数:
 *   wPageNum  - 当前页码（从 1 开始）
 *   wPageMax  - 总页数
 *
 * 边界处理:
 *   - 本函数不做参数合法性校验，直接传递给渲染端口显示。
 *
 * 说明:
 *   - 该函数通过 s_port->show_info_page() 调用底层渲染接口显示页码信息。
 *   - 通常在内容区绘制完成后调用，用于告知用户当前浏览位置。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void YCView_ShowInfoPage(uint16_t wPageNum, uint16_t wPageMax)
{
    s_port->show_info_page(wPageNum, wPageMax);
}

/* -------------------------------------------------------------------------
 * 函数名：YCView_ShowTopName
 * 功能:
 *   在页面顶栏居中显示顶部名称，并绘制装饰性箭头。
 *
 * 参数:
 *   name - 要显示的顶部名称字符串（UTF-8 编码）
 *
 * 边界处理:
 *   - 本函数不对 name 做空指针校验，调用方需保证 name 有效。
 *   - 若名称过长导致居中计算偏移为负数，显示位置可能异常，由底层渲染处理。
 *
 * 说明:
 *   - 首先填充整个屏幕为背景色，实现清屏效果。
 *   - 通过 s_port->get_utf8_len() 和 s_port->get_ascii_width() 计算字符串显示宽度。
 *   - 使用居中公式 (screen_width - text_width) / 2 确定 X 坐标。
 *   - 在 Y=3 位置绘制名称，并在固定位置 (18, 2) 绘制箭头装饰（draw_meitou）。
 *   - 顶栏高度为 0-23 像素，名称显示于第 3 行以确保视觉美观。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void YCView_ShowTopName(const uint8_t *name)
{
    uint16_t wLen = s_port->get_utf8_len(name) * s_port->get_ascii_width();
    s_port->fill_rect(0, 0, s_port->get_size_x() - 1, s_port->get_size_y() - 1, s_port->get_color_back());
    /* 显示顶部名称，居中显示 */
    s_port->show_str((s_port->get_size_x() - wLen) / 2, 3, name);
    s_port->draw_meitou(18, 2);
}

/* -------------------------------------------------------------------------
 * 函数名：YCView_RowY
 * 功能:
 *   根据行号计算该行在屏幕上的实际 Y 坐标。
 *
 * 参数:
 *   row - 行号（从 0 开始）
 *
 * 边界处理:
 *   - 本函数不做行号范围校验，调用方应保证 row 有效。
 *
 * 说明:
 *   - 计算公式：row_y = YC_CONTENT_BASE_Y + row × (字符高度 + 行间距)
 *   - YC_CONTENT_BASE_Y 为内容区起始 Y 坐标（24 像素）。
 *   - 每行高度由 s_port->get_ascii_height() 和 s_port->get_row_space() 决定。
 *   - 该函数确保所有遥测数据显示在内容区内，从第 24 像素开始向下排列。
 *
 * 返回值:
 *   - 该行在屏幕上的 Y 坐标
 * ------------------------------------------------------------------------- */
static uint16_t YCView_RowY(uint8_t row)
{
    uint16_t lineH = s_port->get_ascii_height() + s_port->get_row_space();
    return YC_CONTENT_BASE_Y + (uint16_t)row * lineH;
}

/* -------------------------------------------------------------------------
 * 函数名：YCView_DrawRowNameAndValue
 * 功能:
 *   在指定行绘制遥测项的名称和数值。
 *
 * 参数:
 *   name    - 名称字符串指针
 *   value   - 数值字符串指针
 *   row     - 目标行号（从 0 开始）
 *   drawName - 是否显示名称的标志，非 0 表示显示，0 表示不显示
 *
 * 边界处理:
 *   - 本函数不对 name/value 做空指针校验，调用方需保证字符串有效。
 *   - 若 drawName 为 0，则跳过名称绘制，仅显示数值。
 *
 * 说明:
 *   - 根据 drawName 标志决定是否在左侧区域（YC_NAME_X）显示名称。
 *   - 数值始终在右侧固定位置（YC_VALUE_X）显示。
 *   - Y 坐标通过 YCView_RowY(row) 计算，确保内容对齐到指定行。
 *   - 该函数是遥测数据显示的核心绘制单元，被 YCView_PaintContent() 调用。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void YCView_DrawRowNameAndValue(const uint8_t *name, const uint8_t *value, uint8_t row, uint8_t drawName)
{
    if (drawName != 0u)
    {
        s_port->show_str(YC_NAME_X, YCView_RowY(row), name);
    }
    s_port->show_str(YC_VALUE_X, YCView_RowY(row), value);
}

/* -------------------------------------------------------------------------
 * 函数名：YCView_ClearContentArea
 * 功能:
 *   清空内容区域（不含顶栏和底栏），填充背景色并准备重绘。
 *
 * 参数:
 *   无
 *
 * 边界处理:
 *   - 若内容区上边界 y0 大于等于下边界 y1，或屏幕宽度为 0，不执行填充操作。
 *   - 自动将下边界限制在屏幕有效范围内（sy-1）。
 *
 * 说明:
 *   - 内容区范围：从 YC_CONTENT_BASE_Y-2 到 YC_FOOTER_Y_FROM_BOTTOM
 *   - 上边界预留 2 像素用于视觉缓冲，避免与顶栏过于接近。
 *   - 下边界通过 YC_FOOTER_Y_FROM_BOTTOM 计算，确保不覆盖底栏页码信息。
 *   - 清空后内容区处于待绘制状态，由调用方继续执行数据项绘制。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void YCView_ClearContentArea(void)
{
    uint16_t sx = s_port->get_size_x();
    uint16_t sy = s_port->get_size_y();
    uint16_t y0 = YC_CONTENT_BASE_Y - 2u;
    uint16_t y1 = sy - YC_FOOTER_Y_FROM_BOTTOM;
    if ((y1 > y0) && (sx > 0u))
    {
        s_port->fill_rect(0, y0, sx - 1u, y1, s_port->get_color_back());
    }
}

/* -------------------------------------------------------------------------
 * 函数名：YCView_ApplyCursorInvert
 * 功能:
 *   对当前光标所在行进行反显高亮处理。
 *
 * 参数:
 *   input - 视图输入结构体，包含页码、光标位置等信息
 *
 * 边界处理:
 *   - 若数据为空（input->empty != 0）或没有可显示的行数，直接返回。
 *   - 若计算的反显区域越界，自动调整至有效范围内。
 *   - 若反显上边界大于等于下边界，不执行反显操作。
 *
 * 说明:
 *   - 光标行 Y 坐标通过 YCView_RowY(input->cursor_row) 计算。
 *   - 反显区域高度为单行高度（lineH-1），确保覆盖整个字符区域。
 *   - X 方向留白：左侧 YC_ROW_PAD_X0，右侧 sx-1-YC_ROW_PAD_X1，避免触及屏幕边缘。
 *   - 下边界受 YC_FOOTER_Y_FROM_BOTTOM 限制，防止覆盖底栏页码。
 *   - 该函数在绘制页码前调用，避免因页码绘制导致光标高亮消失。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void YCView_ApplyCursorInvert(const yc_view_input_t *input)
{
    uint16_t sx = s_port->get_size_x();
    uint16_t rows = input->rows_on_page;
    uint16_t lineH = s_port->get_ascii_height() + s_port->get_row_space();
    uint16_t y0;
    uint16_t y1;
    uint16_t yMax;

    if ((input->empty != 0u) || (rows == 0u))
    {
        return;
    }
    y0 = YCView_RowY((uint8_t)input->cursor_row);
    y1 = y0 + lineH - 1u;
    yMax =  s_port->get_size_y() - YC_FOOTER_Y_FROM_BOTTOM;
    if (y1 > yMax)
    {
        y1 = yMax;
    }
    if (y1 < y0)
    {
        return;
    }
    s_port->invert(YC_ROW_PAD_X0, y0, sx - 1u - YC_ROW_PAD_X1, y1);
}

/* -------------------------------------------------------------------------
 * 函数名：YCView_PaintContent
 * 功能:
 *   绘制内容区，包括清空背景、显示遥测数据项、光标高亮和页码信息。
 *
 * 参数:
 *   input - 视图输入结构体，包含页面索引、行数据等信息
 *
 * 边界处理:
 *   - 若数据为空（input->empty != 0），显示"无遥测数据"提示并返回。
 *   - 跳过无效行（input->row_valid[row] == 0）。
 *   - 循环次数受 input->rows_on_page 和 YC_VIEW_ROWS_MAX 双重限制。
 *
 * 说明:
 *   - 绘制流程：
 *       1) 调用 YCView_ClearContentArea() 清空内容区背景
 *       2) 若数据为空，显示提示信息并返回
 *       3) 遍历有效行，逐行调用 YCView_DrawRowNameAndValue() 绘制名称和数值
 *       4) 先反显光标行（YCView_ApplyCursorInvert），再绘制页码
 *          - 顺序重要：避免页码绘制覆盖光标高亮效果
 *       5) 最后显示页码信息（YCView_ShowInfoPage）
 *   - 该函数是遥测页面内容绘制的核心入口，被 YCView_DrawFull() 和 YCView_DrawValues() 调用。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void YCView_PaintContent(const yc_view_input_t *input)
{
    uint8_t row;
    uint16_t rows;

    YCView_ClearContentArea();

    if (input->empty != 0u)
    {
        s_port->show_str(6u, YC_CONTENT_BASE_Y + 20u, (const uint8_t *)"无遥测数据");
        return;
    }

    rows = input->rows_on_page;
    for (row = 0; row < (uint8_t)rows && row < YC_VIEW_ROWS_MAX; row++)
    {
        if (input->row_valid[row] == 0u)
        {
            continue;
        }
        YCView_DrawRowNameAndValue(input->row_name[row], input->row_value[row], row, 1u);
    }

    /* 先反显光标，再绘页码，避免 invert 覆盖底栏「第 x 页」造成花屏 */
    YCView_ApplyCursorInvert(input);
    YCView_ShowInfoPage(input->page, input->page_max);
}

/* -------------------------------------------------------------------------
 * 函数名：YCView_DrawFull
 * 功能:
 *   完整绘制遥测页面，包括顶栏和内容区。
 *
 * 参数:
 *   view  - 视图对象指针（当前未使用）
 *   input - 视图输入结构体，包含顶部名称和行数据等信息
 *
 * 边界处理:
 *   - 若 input 或 s_port 为 NULL，直接返回。
 *   - 通过 (void)view 消除未使用参数警告。
 *
 * 说明:
 *   - 完整绘制流程：先显示顶栏名称（YCView_ShowTopName），再绘制内容区（YCView_PaintContent）。
 *   - 整屏擦除操作在 MenuPage_OnExit() 统一完成，此处无需重复清屏。
 *   - 该函数用于首次加载页面或需要完整重绘的场景。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void YCView_DrawFull(view_t *view, const yc_view_input_t *input)
{
    (void)view;
    if ((input == NULL) || (s_port == NULL))
    {
        return;
    }
    /* 整屏擦除在 MenuPage_OnExit 统一完成，此处直接绘制顶栏与内容 */
    YCView_ShowTopName(input->top_name);
    YCView_PaintContent(input);
}

/* -------------------------------------------------------------------------
 * 函数名：YCView_DrawValues
 * 功能:
 *   仅重绘内容区数值（不重绘顶栏），用于主循环中的数值刷新。
 *
 * 参数:
 *   view  - 视图对象指针（当前未使用）
 *   input - 视图输入结构体，包含页面和行数据信息
 *
 * 边界处理:
 *   - 若 input 或 s_port 为 NULL，直接返回。
 *   - 通过 (void)view 消除未使用参数警告。
 *
 * 说明:
 *   - 该函数是主循环节拍的核心调用点，用于周期性刷新遥测数值显示。
 *   - 仅重绘内容区（YCView_PaintContent），顶栏保持不变以减少绘制开销。
 *   - 在桩模式下，此函数会触发 YCView_FormatStubValue() 生成模拟数据并更新显示。
 *   - 相比 YCView_DrawFull()，此函数更高效，适合高频刷新场景。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void YCView_DrawValues(view_t *view, const yc_view_input_t *input)
{
    (void)view;
    if ((input == NULL) || (s_port == NULL))
    {
        return;
    }
    /* 主循环节拍：重绘内容区以更新桩数值（不重画面顶栏） */
    YCView_PaintContent(input);
}

/* -------------------------------------------------------------------------
 * 函数名：View_Init
 * 功能:
 *   初始化遥测页面视图，绑定渲染端口和回调函数。
 *
 * 参数:
 *   view - 视图对象指针，待初始化的视图结构体
 *
 * 边界处理:
 *   - 本函数不对 view 做空指针校验，调用方需保证 view 有效。
 *
 * 说明:
 *   - 初始化流程：
 *       1) 获取 LCD 渲染端口（s_port = PageRenderer_Lcd()）
 *       2) 设置 show_top_name 为 NULL，表示不使用自定义顶栏显示函数
 *       3) 绑定 show_info_page 回调到 YCView_ShowInfoPage
 *       4) 绑定 draw_full 回调到 YCView_DrawFull（完整绘制入口）
 *       5) 绑定 draw_values 回调到 YCView_DrawValues（数值刷新入口）
 *   - 初始化后，视图对象即可被页面管理器使用。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
void View_Init(view_t *view)
{
    s_port = PageRenderer_Lcd();
    view->show_top_name = NULL;
    view->show_info_page = YCView_ShowInfoPage;
    view->draw_full = YCView_DrawFull;
    view->draw_values = YCView_DrawValues;
}
