#include <stddef.h>
#include <string.h>
#include "view.h"


static const PageRenderPort *s_port = NULL;
/* -------------------------------------------------------------------------
 * 函数名: MenuView_DrawBoundaryBox
 * 功能:
 *   绘制矩形边界框：先清空内部区域，再绘制四条边线。
 *
 * 参数:
 *   view    - 菜单视图对象，提供像素与线段绘制接口
 *   leftX   - 左边界 X
 *   topY    - 上边界 Y
 *   rightX  - 右边界 X
 *   bottomY - 下边界 Y
 *
 * 边界处理:
 *   - 本函数不校验 view/port 空指针，调用方需保证有效。
 *   - 默认要求 leftX <= rightX、topY <= bottomY；
 *     若参数异常，像素循环或画线结果由底层驱动行为决定。
 *
 * 说明:
 *   - 背景填充：逐像素将内部区域置为背景色（get_color_back），
 *     避免残影或旧内容叠加。
 *   - 边框绘制：统一使用字体前景色（get_color_font），绘制上/左/下/右四边。
 *   - 下边框使用 rightX + 1，目的是与其它边界在视觉上闭合对齐。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuView_DrawBoundaryBox(MenuView *view, uint16_t leftX, uint16_t topY, uint16_t rightX, uint16_t bottomY)
{
    uint8_t backColor = s_port->get_color_back();
    uint8_t fontColor = s_port->get_color_font();
    for (uint16_t y = topY; y < bottomY; y++)
    {
        for (uint16_t x = leftX; x < rightX; x++)
        {
            s_port->set_pixel(x, y, backColor);
        }
    }

    s_port->line_h(leftX, rightX, topY, 1, fontColor);
    s_port->line_v(topY, bottomY, leftX, 1, fontColor);
    s_port->line_h(leftX, rightX + 1, bottomY, 1, fontColor);
    s_port->line_v(topY, bottomY, rightX, 1, fontColor);
}

/* -------------------------------------------------------------------------
 * 函数名: MenuView_UpdateSelectionNewLevel
 * 功能:
 *   在“进入新层级”场景下，重绘目标层级窗口内容（边框 + 文本）
 *   并对当前选中项执行反显高亮。
 *
 * 参数:
 *   view         - 菜单视图对象，提供底层渲染接口
 *   currentLevel - 当前层级的“层级框拥有者”节点，提供窗口矩形与数量信息
 *   nextLevel    - 当前层级内的选中节点（也是该层列表起始遍历节点）
 *
 * 边界处理:
 *   - 本函数不做空指针判定，调用方需保证 view/currentLevel/nextLevel 有效。
 *   - 默认要求 currentLevel 与 nextLevel 来自同一层级链路，且 behind 链可遍历。
 *   - 当 currentLevel->rect.wNum 为 0 时，文本循环不执行，
 *     但边框仍会被绘制。
 *
 * 说明:
 *   - 绘制步骤：
 *       1) 使用 currentLevel->rect 绘制层级边框区域
 *       2) 从 nextLevel 起，按 wNum 数量沿 behind 链逐项绘制名称
 *       3) 基于 nextLevel 的 wPos 计算行区域并执行 invert 高亮
 *   - 坐标策略：
 *       wPosX = currentLevel->rect.wSPosX + 4（文本左内边距）
 *       wPosY = currentLevel->rect.wSPosY + item->rect.wPos * LINE_HEIGHT + 3
 *   - 该函数对应“新层级内容补绘”路径，与同层移动的局部反显函数职责分离。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */

 static void MenuView_UpdateSelectionNewLevel(MenuView *view, const tagPMenuItem currentLevel, const tagPMenuItem nextLevel)
 {
      tagPMenuItem ptIndex;
  
      uint16_t wPosX;
      uint16_t wPosY;
      /* 1. 绘制当前层级的边界框 */
      MenuView_DrawBoundaryBox(view, currentLevel->rect.wSPosX, currentLevel->rect.wSPosY, currentLevel->rect.wEPosX, currentLevel->rect.wEPosY);
      /*2. 获取当前层级选中菜单项，并计算X起始坐标*/
      ptIndex = nextLevel;
      /* 计算当前菜单项的 X 坐标，对于下一层级起始 X 坐标都是一致的 */
      wPosX = currentLevel->rect.wSPosX + 4;
  
      /* 3. 绘制当前层级的所有菜单项 */
      for (uint16_t index = 0; index < currentLevel->rect.wNum; index++)
      {
          wPosY = currentLevel->rect.wSPosY + ptIndex->rect.wPos * LINE_HEIGHT + 3;
          s_port->show_str(wPosX, wPosY, ptIndex->menuDef.byName);
          /* 移动到下一个菜单项 */
          ptIndex = ptIndex->links.behind;
      }
  
      /* 4. 绘制当前选中项的反显 */
      wPosY = currentLevel->rect.wSPosY + nextLevel->rect.wPos * LINE_HEIGHT + 3;
      s_port->invert(currentLevel->rect.wSPosX + 2, wPosY - 1, currentLevel->rect.wEPosX - 2, wPosY + LINE_HEIGHT);
}

/* -------------------------------------------------------------------------
 * 函数名: MenuView_ShowTopLevel
 * 功能:
 *   绘制菜单页顶部区域（0 级标题栏）：清空标题背景、绘制装饰线，
 *   并按传入模式显示顶部提示文案。
 *
 * 参数:
 *   view - 菜单视图对象，提供渲染端口能力
 *   mode - 当前需要显示的运行模式（MenuMode）
 *
 * 边界处理:
 *   - 本函数不做 view/port 空指针校验，调用方需保证对象已正确初始化。
 *   - 模式值若不在已定义枚举分支内，走默认分支显示“当前模式: 无模式”。
 *
 * 说明:
 *   - 先清空顶部区域（y: 0~32），避免旧文案残留。
 *   - 再调用 s_port->draw_meitou(16, 2) 绘制标题装饰线，保持页面视觉一致性。
 *   - 模式文案映射:
 *       MODE_OVERFLOW_PROTECTION   -> "当前模式: 过流保护"
 *       MODE_LOCAL_FEEDER_SEGMENT  -> "就地馈线: 分段模式"
 *       MODE_LOCAL_FEEDER_CONTACT  -> "就地馈线: 联络模式"
 *       其他/未设置                -> "当前模式: 无模式"
 *   - 本函数只负责“顶部区域”渲染，不处理菜单列表区内容刷新。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuView_ShowTopLevel(MenuView *view, MenuMode mode)
{
    uint16_t lcdSizeX = s_port->get_size_x();
    s_port->fill_rect(0, 0, lcdSizeX - 1, 32, s_port->get_color_back());
    s_port->draw_meitou(16, 2);
    /* 在 0 级菜单标题栏下方显示当前运行模式提示文字 */
    if (mode == MODE_OVERFLOW_PROTECTION)
    {
        s_port->show_str(16, 20, (uint8_t *)"当前模式: 过流保护");
    }
    else if (mode == MODE_LOCAL_FEEDER_SEGMENT)
    {
        s_port->show_str(16, 20, (uint8_t *)"就地馈线: 分段模式");
    }
    else if (mode == MODE_LOCAL_FEEDER_CONTACT)
    {
        s_port->show_str(16, 20, (uint8_t *)"就地馈线: 联络模式");
    }
    else
    {
        s_port->show_str(16, 20, (uint8_t *)"当前模式: 无模式");
    }
}

/* -------------------------------------------------------------------------
 * 函数名: MenuView_UpdateSelectionSameLevel
 * 功能:
 *   在“同层同父节点”移动场景下执行局部刷新，仅更新旧/新选中行的反显状态，
 *   避免整块区域重绘带来的闪烁。
 *
 * 参数:
 *   view    - 菜单视图对象，提供底层 invert 绘制能力
 *   oldItem - 变更前的选中菜单项（旧焦点）
 *   newItem - 变更后的选中菜单项（新焦点）
 *
 * 边界处理:
 *   - 本函数不做空指针与父链校验，调用方需保证：
 *       1) view/s_port 有效
 *       2) oldItem/newItem 非空
 *       3) oldItem->links.higher 非空且与 newItem 属于同一父节点
 *   - 若上述前置条件不满足，坐标计算与反显结果将不可预期。
 *
 * 说明:
 *   - 该函数是 Presenter 在“同层同父移动”场景（典型 U/D）的优化渲染路径。
 *   - 通过两次 invert 完成“旧选中恢复 + 新选中高亮”：
 *       1) 对旧行区域 invert：撤销旧高亮
 *       2) 对新行区域 invert：施加新高亮
 *   - 坐标基准来自 oldItem 的父节点（levelRectOwner = oldItem->links.higher）：
 *       posY = levelRectOwner->rect.wSPosY + (item->rect.wPos - 1) * LINE_HEIGHT + 3
 *   - X 方向使用边框内缩（+2/-2），避免覆盖边框线条，保持视觉完整性。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuView_UpdateSelectionSameLevel(MenuView *view, const tagPMenuItem oldItem, const tagPMenuItem newItem)
{
    tagPMenuItem levelRectOwner = oldItem->links.higher;

    uint16_t leftX = (uint16_t)(levelRectOwner->rect.wSPosX + 2);
    uint16_t rightX = (uint16_t)(levelRectOwner->rect.wEPosX - 2);
    uint16_t oldPosY = (uint16_t)(levelRectOwner->rect.wSPosY + oldItem->rect.wPos * LINE_HEIGHT + 3);
    uint16_t newPosY = (uint16_t)(levelRectOwner->rect.wSPosY + newItem->rect.wPos * LINE_HEIGHT + 3);

    /* 旧选中行反显恢复 + 新选中行反显高亮 */
    s_port->invert(leftX, (uint16_t)(oldPosY - 1), rightX, (uint16_t)(oldPosY + LINE_HEIGHT));
    s_port->invert(leftX, (uint16_t)(newPosY - 1), rightX, (uint16_t)(newPosY + LINE_HEIGHT));
}

/* -------------------------------------------------------------------------
 * 函数名: MenuView_FullRefresh
 * 功能:
 *   执行菜单页整页刷新：清空工作区、重绘顶部标题区域、按当前层级路径重绘各级菜单框。
 *
 * 参数:
 *   view     - 菜单视图对象，提供布局参数与绘制能力
 *   menuCtrl - 菜单运行时控制结构，提供当前层级与路由链信息
 *   mode     - 顶部标题显示使用的运行模式
 *
 * 边界处理:
 *   - 本函数不做空指针校验，调用方需保证 view/menuCtrl/s_port 均有效。
 *   - 当 byClass 为 0 时，for 循环不进入，仅刷新顶部区域。
 *   - 默认要求 ptRoute[index] 与 ptRoute[index + 1] 在有效层级范围内可访问。
 *
 * 说明:
 *   - 刷新步骤：
 *       1) 使用背景色清空菜单工作区域（MENU_YMIN ~ menuYMax）
 *       2) 调用 MenuView_ShowTopLevel() 重绘顶部模式文案与装饰
 *       3) 按当前菜单层级（byClass）逐层调用 MenuView_UpdateSelectionNewLevel()
 *          重绘各层边框、文本和选中态
 *   - 该函数用于“整页一致性恢复”场景（如跨层回退、顶层切换等）。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuView_FullRefresh(MenuView *view, const tagMenuCtrl *menuCtrl, MenuMode mode)
{
    uint8_t backColor = s_port->get_color_back();
    uint16_t lcdSizeX = s_port->get_size_x();

    s_port->fill_rect(0, MENU_YMIN, (uint16_t)(lcdSizeX - 1), MENU_YMAX, backColor);

    MenuView_ShowTopLevel(view, mode);
    for (uint8_t index = 0; index < menuCtrl->ptCurrent->menuDef.byClass; index++)
    {
        MenuView_UpdateSelectionNewLevel(view, menuCtrl->ptRoute[index], menuCtrl->ptRoute[index + 1]);
    }
}
/* -------------------------------------------------------------------------
 * 函数名: MenuView_Init
 * 功能:
 *   初始化菜单视图对象 MenuView，准备渲染端口引用与布局参数，
 *   并设置当前模式状态的默认值以及渲染入口函数指针。
 *
 * 参数:
 *   view - 菜单视图实例地址（由调用方分配，函数内填充其字段）
 *
 * 边界处理:
 *   - 本函数不校验 view 空指针，调用方需保证 view 非空。
 *   - 渲染端口的物理 init 不在视图内执行；
 *     由入口（main）统一完成屏幕初始化（符合 PageRenderPort 共享策略）。
 *
 * 说明:
 *   - 初始化内容包含：
 *       1) s_port = PageRenderer_Lcd()：绑定 LCD 渲染端口表指针
 *       4) 设置当前模式为 MODE_NONE，避免进入绘制前读取未定义值
 *       5) 视图层仅保留绘制原子动作，刷新策略由 Presenter 调度
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
void MenuView_Init(MenuView *view)
{
    s_port = PageRenderer_Lcd();
    view->update_selection_new_level = MenuView_UpdateSelectionNewLevel;
    view->update_selection_same_level = MenuView_UpdateSelectionSameLevel;
    view->full_refresh = MenuView_FullRefresh;
}

#ifdef UNIT_TEST
const PageRenderPort *MenuView_GetPortForTest(void)
{
    return s_port;
}
#endif

