#include "model.h"

#include <string.h>

#include "../../menu/app/menu.h"
#include "utf8.h"

/* -------------------------------------------------------------------------
 * 函数名: MenuModel_Utf8LenCal
 * 功能:
 *   计算 UTF-8 字符串在当前菜单显示规则下的“显示宽度”。
 *
 * 参数:
 *   str - 待计算的 UTF-8 字符串
 *
 * 边界处理:
 *   - 本函数不做空指针校验，调用方需保证 `str` 非空且为 `\0` 结尾。
 *   - 依赖 `utf8_next()` 逐字符解析 UTF-8 编码；若输入非法，结果由底层解析行为决定。
 *
 * 说明:
 *   - 当前显示规则约定：
 *       1) ASCII/单字节字符宽度记为 1
 *       2) 多字节 UTF-8 字符（如中文）宽度记为 2
 *   - 本函数返回的是“显示占位宽度”，不是原始字节长度。
 *   - 该结果会被菜单布局函数用于计算菜单框宽度与字符串显示长度。
 *
 * 返回值:
 *   - UTF-8 字符串的显示宽度
 * ------------------------------------------------------------------------- */
uint8_t MenuModel_Utf8LenCal(uint8_t *str)
{
    uint8_t strLen = 0;
    uint32_t unicode;
    uint8_t index = 0;
    uint8_t n = 0;

    while (str[index] != '\0')
    {
        n = utf8_next(str + index, &unicode);
        strLen += (n > 1) ? 2 : 1;
        index += n;
    }
    return strLen;
}

/* -------------------------------------------------------------------------
 * 函数名: MenuLayout_ItemDisplayLen
 * 功能:
 *    补充右箭头并计算单个菜单项最终用于布局的显示宽度。
 *
 * 参数:
 *   item - 菜单项节点指针
 *
 * 边界处理:
 *   - 本函数不做空指针校验，调用方需保证 `item` 有效。
 *   - 菜单名称最大扫描长度固定为 50，与 `byName` 缓冲区容量保持一致。
 *
 * 说明:
 *   - 先通过 `MenuModel_Utf8LenCal()` 计算菜单名称本身的显示宽度。
 *   - 若该菜单项存在子节点（`links.lower != NULL`），则该项在界面上通常会显示
 *     一个“可下钻”装饰符（例如 `\x10` 右箭头）。
 *   - 如果当前名称末尾尚未包含该装饰符，则额外为布局宽度加 1，
 *     以保证菜单框宽度计算时为装饰符预留空间。
 *   - 本函数只做宽度估算，不修改菜单项内容。
 *
 * 返回值:
 *   - 该菜单项的显示宽度
 * ------------------------------------------------------------------------- */
static uint8_t MenuLayout_ItemDisplayLen(const MenuItem *item)
{
    uint8_t displayLen;
    displayLen = MenuModel_Utf8LenCal((uint8_t *)item->menuDef.byName);
    if (item->links.lower != NULL)
    {
        uint8_t byteLen = 0;
        while ((byteLen < 50) && (item->menuDef.byName[byteLen] != '\0'))
        {
            byteLen++;
        }
        if ((byteLen == 0) || (item->menuDef.byName[byteLen - 1] != '\x10'))
        {
            displayLen += 1;
        }
    }
    return displayLen;
}
/* -------------------------------------------------------------------------
 * 函数名: MenuModel_GetMenuMaxDisplayLen
 * 功能:
 *   统计当前层级菜单项的最大显示宽度，并返回当前层级菜单项中的最大显示宽度。
 *
 * 参数:
 *   ptFirst - 当前层的首节点指针
 *
 * 边界处理:
 *   - 本函数不做空指针校验，调用方需保证 `ptFirst` 有效。
 *
 * 说明:
 *   - 本函数只负责遍历下一层菜单项，并调用 `MenuLayout_ItemDisplayLen()`
 *     计算每个节点的显示宽度。
 *
 * 返回值:
 *   - 当前层级菜单项中的最大显示宽度
 * ------------------------------------------------------------------------- */
uint8_t MenuModel_GetMenuMaxDisplayLen(tagPMenuItem ptFirst)
{
    uint8_t displayLen;
    uint8_t byMaxLen = 0;
    tagPMenuItem ptIndex = ptFirst;

    for (uint16_t index = 0; index < MENU_MODEL_MAX_ITEM; index++)
    {
        displayLen = MenuLayout_ItemDisplayLen(ptIndex);

        if (byMaxLen < displayLen)
        {
            byMaxLen = displayLen;
        }

        ptIndex = ptIndex->links.behind;
        if (ptIndex == ptFirst)
        {
            break;
        }
    }
    return byMaxLen;
}
/* -------------------------------------------------------------------------
 * 函数名: MenuLayout_MapMenuPos
 * 功能:
 *   根据当前菜单项在本层中的原始序号，以及当前层可显示的最大行数，
 *   计算该菜单项在菜单框中的实际显示行号。
 *
 * 参数:
 *   menuPos - 当前菜单项在本层中的原始位置序号（从 0 开始）
 *   itemNum - 当前层菜单项总数
 *   maxNum  - 当前菜单框在可视区域内最多可容纳的显示行数
 *
 * 边界处理:
 *   - 本函数不做参数合法性校验，调用方需保证 `menuPos / itemNum / maxNum` 有效。
 *   - 若 `itemNum <= maxNum`，说明当前层菜单可完整显示，直接返回原始序号。
 *   - 若 `itemNum > maxNum` 且 `menuPos < maxNum`，说明当前项仍落在可直接显示区域，
 *     直接返回原始序号。
 *
 * 说明:
 *   - 当当前层菜单项总数未超过窗口容量时，菜单项在框内的显示行号与其原始序号一致。
 *   - 当菜单项总数超过窗口容量时，需要把靠后的菜单项映射到有限的可视行范围内。
 *   - 当前实现策略为：
 *       1) 若当前项仍位于前 `maxNum - 1` 个可显示位置内，则保持不变
 *       2) 若当前项已落到窗口容量之外，则将其映射到靠近窗口底部的可视区域
 *   - 该函数的作用是为后续 Y 坐标计算提供“菜单框内部显示行号”，
 *     避免菜单项过多时直接按原始序号计算导致超出可显示范围。
 *
 * 返回值:
 *   - 当前菜单项在菜单框中的实际显示行号
 * ------------------------------------------------------------------------- */
static uint16_t MenuLayout_MapMenuPos(uint16_t menuPos, uint16_t itemNum, uint8_t maxNum)
{
    if ((itemNum > maxNum) && (menuPos >= maxNum))
    {
        return (uint16_t)(maxNum - (itemNum - menuPos));
    }
    return menuPos;
}

/* -------------------------------------------------------------------------
 * 函数名: MenuLayout_TryFlipUp
 * 功能:
 *   当子菜单默认向下展开可能越界时，尝试将菜单框改为“向上展开”。
 *
 * 参数:
 *   startY   - 菜单框起始 Y 坐标（输入/输出）
 *   endY     - 菜单框结束 Y 坐标（输入/输出）
 *   itemNum  - 当前子菜单项数量
 *   flipMinY - 允许上翻后的最小 Y 边界
 *
 * 边界处理:
 *   - 本函数不做空指针校验，调用方需保证 `startY / endY` 有效。
 *   - 若根据 `itemNum` 推导出的上翻后起点小于等于 `flipMinY`，
 *     则认为顶部空间不足，上翻失败。
 *   - 若候选起点超出 `MENU_YMAX`，则视为异常坐标，上翻失败。
 *
 * 说明:
 *   - 本函数的目标是在“尽量保持父子菜单行对齐”的前提下，
 *     把原本会向下越界的子菜单框整体上移。
 *   - 计算步骤：
 *       1) 以当前 `startY` 为锚点，反推出上翻后的候选顶部 `candidateEndY`
 *       2) 判断该候选顶部是否落在允许范围 `(flipMinY, MENU_YMAX)` 内
 *       3) 若可上翻，则将 `startY` 更新为候选顶部
 *       4) 同时使用旧 `startY` 反推出新的 `endY`
 *   - 返回成功后，调用方可直接使用更新后的 `startY / endY`；
 *     若失败，则由调用方继续执行 clamp/probe 等后备布局策略。
 *
 * 返回值:
 *   - 1 : 上翻成功
 *   - 0 : 上翻失败
 * ------------------------------------------------------------------------- */
static uint8_t MenuLayout_TryFlipUp(uint16_t *startY, uint16_t *endY, uint16_t itemNum, uint16_t flipMinY)
{
    uint16_t candidateEndY;
    uint16_t temp;

    candidateEndY = (uint16_t)(*startY - (itemNum - 1) * LINE_HEIGHT - MENU_YADD);
    if ((candidateEndY > flipMinY) && (candidateEndY < MENU_YMAX))
    {
        temp = *startY;
        *startY = candidateEndY;
        *endY = (uint16_t)(temp + LINE_HEIGHT);
        return 1;
    }
    return 0;
}
/* -------------------------------------------------------------------------
 * 函数名: MenuLayout_FallbackProbeUp
 * 功能:
 *   当子菜单无法直接放入可视区域时，按“逐行向上试探”的方式寻找可容纳位置。
 *
 * 参数:
 *   ptIndex - 当前菜单路径节点数组（输入）
 *   itemNum - 当前子菜单项数量
 *   startY  - 菜单框起始 Y 坐标（输出）
 *   endY    - 菜单框结束 Y 坐标（输出）
 *
 * 边界处理:
 *   - 本函数不做空指针校验，调用方需保证 `ptIndex / startY / endY` 有效。
 *   - 默认要求 `ptIndex[0]` 已指向 0 级菜单节点，且其 `rect.wSPosY` 已完成布局计算。
 *   - 当试探上移后 `startY` 小于 1 行高度时，直接钳制为 0，避免出现负坐标。
 *   - 若多次上移后仍无法完全放入 `MENU_YMAX` 范围，本函数保留最后一次试探结果，
 *     由调用方或后续绘制逻辑继续处理。
 *
 * 说明:
 *   - 该函数是介于“直接上翻失败”和“强制贴顶”之间的一种更平滑兜底策略。
 *   - 处理步骤：
 *       1) 先以 0 级菜单顶部 `ptIndex[0]->rect.wSPosY` 作为初始起点
 *       2) 按 `itemNum * LINE_HEIGHT + MENU_YADD` 计算当前理论底部
 *       3) 若底部仍超出 `MENU_YMAX`，则每次向上移动 1 个 `LINE_HEIGHT`
 *       4) 每移动一次都重新计算 `endY`，直到菜单框进入可显示区域或试探结束
 *   - 这种策略比直接贴顶更保守，尽量保留与原始父菜单位置的相对关系，
 *     同时逐步寻找一个更自然的显示位置。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuLayout_FallbackProbeUp(uint16_t w0LevelSPosY, uint16_t itemNum, uint16_t *startY, uint16_t *endY)
{
    *startY = w0LevelSPosY;
    *endY = (uint16_t)(*startY + itemNum * LINE_HEIGHT + MENU_YADD);
    for (uint16_t i = 1; i < (uint16_t)(w0LevelSPosY / LINE_HEIGHT + 1); i++)
    {
        if (*endY > MENU_YMAX)
        {
            *startY = (uint16_t)(w0LevelSPosY - LINE_HEIGHT * i);
            if (*startY < LINE_HEIGHT)
            {
                *startY = 0;
            }
            *endY = (uint16_t)(*startY + itemNum * LINE_HEIGHT + MENU_YADD);
        }
        else
        {
            break;
        }
    }
}

/* -------------------------------------------------------------------------
 * 函数名: MenuView_SubPosCal
 * 功能:
 *   以递归方式计算从当前层首节点开始的所有子菜单框位置，
 *   完成当前层以及更深层级菜单的矩形布局。
 *
 * 参数:
 *   ptFirst      - 当前层首节点指针
 *   ptParent     - 当前层所属父菜单框节点
 *   w0LevelSPosY - 0 级菜单框顶部 Y 坐标，用于越界时的回退布局基准
 *
 * 边界处理:
 *   - 若 `ptFirst == NULL`，说明当前层不存在有效菜单链，直接返回。
 *   - 本函数不对 `ptParent` 做空指针校验，调用方需保证其有效，
 *     且其 `rect` 已完成布局计算。
 *   - 当前层遍历仍使用 `MENU_MODEL_MAX_ITEM` 作为保护上限，
 *     防止异常链表导致死循环。
 *   - 当子菜单框底部超出 `MENU_YMAX` 时，优先尝试上翻；
 *     若上翻失败，则退化到 `MenuLayout_FallbackProbeUp()` 继续寻找可显示位置。
 *
 * 说明:
 *   - 本函数是子菜单布局的递归核心，处理流程如下：
 *       1) 以 `ptFirst` 为起点遍历当前层所有节点
 *       2) 对每个存在子菜单的节点，先取其下一层首节点 `ptNextFirst`
 *       3) 通过 `MenuModel_GetMenuMaxDisplayLen()` 计算该下一层菜单的最大显示宽度
 *       4) 根据父菜单框右边界，计算当前子菜单框的 X 坐标
 *       5) 根据当前节点在本层中的 0 基序号 `wPos`、父层项数 `wNum`，
 *          计算当前子菜单框的理论 Y 坐标
 *       6) 若理论底部越界，则尝试上翻或执行回退探测
 *       7) 当前节点对应子菜单框位置确定后，
 *          再递归处理其更深一层的子菜单布局
 *   - `ptParent->rect.wNum` 表示父节点直接子节点总数，
 *     用于把当前节点的原始层内 0 基序号映射到菜单框可显示行号。
 *   - `w0LevelSPosY` 作为递归过程中统一的顶部参考，
 *     保证深层菜单在空间不足时仍能回退到稳定的显示区域。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
static void MenuView_SubPosCal(tagPMenuItem ptFirst, tagPMenuItem ptParent, uint16_t w0LevelSPosY)
{
    uint16_t wSPosY;
    uint16_t wEPosY;
    uint8_t byMaxLen;
    uint8_t byMaxNum;
    uint16_t byMenuPos;
    uint16_t byItemNum;

    tagPMenuItem ptNextFirst;
    tagPMenuItem ptIndex;


    if (ptFirst == NULL)
    {
        return;
    }

    byMaxNum = (MENU_YMAX - MENU_YMIN) / LINE_HEIGHT;
    ptIndex = ptFirst;
    for (uint16_t wLoop = 0; wLoop < MENU_MODEL_MAX_ITEM; wLoop++)
    {
        if (ptIndex->links.lower != NULL)
        {
            ptNextFirst = ptIndex->links.lower;  /* 保存下一层的第一个节点 */

            byMaxLen = MenuModel_GetMenuMaxDisplayLen(ptNextFirst); /* 获取下一层菜单项中的最大显示宽度 */

            /* 计算子菜单框的 X 坐标 */
            ptIndex->rect.wSPosX = ptParent->rect.wEPosX;   /* 子菜单框左边界紧贴父菜单框右边界 */
            /* 子菜单框右边界由最大显示宽度 * 单字符宽度 + 额外边距推导 */
            ptIndex->rect.wEPosX = (uint16_t)(ptIndex->rect.wSPosX + byMaxLen * MENU_WITDTH + MENU_XADD);


            /* 计算子菜单框的 Y 坐标 */
            byMenuPos = ptIndex->rect.wPos;  /* 当前菜单项的 0 基序号 */
            byItemNum = ptParent->rect.wNum;  /* 父菜单项的项数 */
            byMenuPos = MenuLayout_MapMenuPos(byMenuPos, byItemNum, byMaxNum);

            wSPosY = ptParent->rect.wSPosY + byMenuPos * LINE_HEIGHT;
            byItemNum = ptIndex->rect.wNum;
            wEPosY = wSPosY + byItemNum * LINE_HEIGHT + MENU_YADD;

            if (wEPosY < MENU_YMAX)
            {
                ptIndex->rect.wSPosY = wSPosY;
                ptIndex->rect.wEPosY = wEPosY;
            }
            else
            {
                if (!MenuLayout_TryFlipUp(&wSPosY, &wEPosY, byItemNum, MENU_YMIN))
                {
                    MenuLayout_FallbackProbeUp(w0LevelSPosY, byItemNum, &wSPosY, &wEPosY);
                }
                ptIndex->rect.wSPosY = wSPosY;
                ptIndex->rect.wEPosY = wEPosY;
            }
            MenuView_SubPosCal(ptNextFirst, ptIndex, w0LevelSPosY);
        }
        ptIndex = ptIndex->links.behind;
        if (ptIndex == ptFirst)
        {
            break;
        }
    }
}
/* -------------------------------------------------------------------------
 * 函数名: MenuModel_PositionCal
 * 功能:
 *   计算整棵菜单树的初始显示位置，完成 0 级菜单以及后续子菜单层级的矩形布局。
 *
 * 参数:
 *   ptMenuHead   - 菜单头节点（0 级起点）
 *   by0LevelNum  - 0 级菜单数量
 *
 * 边界处理:
 *   - 若 ptMenuHead 为 NULL、config 为 NULL 或 by0LevelNum 为 0，则直接返回。
 *   - 若 by0LevelNum 超过本函数内部固定工作数组容量 4，则直接返回。
 *   - 当 0 级菜单数量大于 LCD 宽度时，byInterval 可能被截断为 0；
 *     当前通过断言暴露异常输入，并保持返回保护。
 *
 * 说明:
 *   - 本函数是菜单布局计算入口：
 *       1) 先建立 0 级菜单的遍历起点、当前位置与 0 基序号
 *       2) 根据 lcdSizeX / by0LevelNum 计算 0 级菜单的水平分布间隔
 *       3) 逐个遍历 0 级菜单项
 *       4) 对存在子菜单的节点，计算其子菜单框体矩形
 *       5) 递归调用 MenuView_Sub1PosCal() 继续完成更深层布局
 *   - 本函数只计算 rect，不负责实际绘制。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
void MenuModel_PositionCal(tagPMenuItem ptMenuHead, uint16_t by0LevelNum)
{
    tagPMenuItem ptFirst = ptMenuHead;
    tagPMenuItem ptIndex = ptMenuHead;
    uint8_t byMaxLen;
    uint8_t byInterval;
    tagPMenuItem ptNextFirst;

    ASSERT((ptMenuHead == NULL) || (by0LevelNum == 0));
    if ((ptMenuHead == NULL) || (by0LevelNum == 0))
    {
        return;
    }

    byInterval = MENU_SIZE_X / by0LevelNum;

    for (uint16_t wLoop = 0; wLoop < by0LevelNum; wLoop++)
    {
        if (ptIndex->links.lower != NULL)
        {
            ptNextFirst = ptIndex->links.lower;  /* 保存下一层的第一个节点 */
            byMaxLen = MenuModel_GetMenuMaxDisplayLen(ptNextFirst);
            ptIndex->rect.wSPosX = ptIndex->rect.wPos * byInterval;
            ptIndex->rect.wSPosY = MENU_SIZE_Y - LINE_HEIGHT - ptIndex->rect.wNum * LINE_HEIGHT - MENU_YADD;
            ptIndex->rect.wEPosX = ptIndex->rect.wPos * byInterval + byMaxLen * MENU_WITDTH + MENU_XADD;
            ptIndex->rect.wEPosY = MENU_SIZE_Y - LINE_HEIGHT;
            
            MenuView_SubPosCal(ptNextFirst, ptIndex, ptIndex->rect.wSPosY);
        }
        ptIndex = ptIndex->links.behind;
        if (ptIndex == ptFirst)
        {
            break;
        }
    }
}

enum _STR_FLAG_ {
    EN_STR_FLAG_FUNSET = 0,
    EN_STR_FLAG_OKCANCEL,
    EN_VALUE_FLAG_SLOTTYPE,
    EN_VALUE_FLAG_YESNO,
    EN_VALUE_FLAG_CHN_NUM,
    EN_VALUE_FLAG_YXSET,
    EN_VALUE_FLAG_YKSTEP,
    EN_VALUE_FLAG_YKACTION,
    EN_VALUE_FLAG_CHK,
    EN_VALUE_FLAG_BEUSED,
    EN_VALUE_FLAG_PORTTYPE,
    EN_VALUE_FLAG_MAPNAME,
    EN_VALUE_FLAG_RECTITLE,
    EN_VALUE_FLAG_BHINFO,
    EN_VALUE_FLAG_BHSET,
    EN_VALUE_FLAG_OKCANCEL,
    EN_VALUE_FLAG_FILETYPE,
    EN_VALUE_FLAG_ROLE,
    EN_VALUE_FLAG_COMMSTATE,
    EN_VALUE_FLAG_ALARM,
    EN_VALUE_FLAG_YXTYPE,
    EN_STR_FLAG_MENUALAUTO,
    EN_STR_FLAG_LINKBREAK,
    EN_STR_FLAG_DONEUNDONE,
    EN_STR_FLAG_ACTBACK,
    EN_STR_FLAG_RESUMEBREAK,
    EN_STR_FLAG_USESTOP,
    EN_STR_FLAG_BAUDRATE,
    EN_STR_FLAG_VERIFY,
    EN_STR_FLAG_PROTOL,
    EN_STR_FLAG_RECORD,
    EN_STR_FLAG_MESSAGE,
    EN_STR_FLAG_COM,
    EN_STR_FLAG_MODE,
    EN_STR_FLAG_COMBAUD,
    EN_STR_FLAG_DATABIT,
    EN_STR_FLAG_STOPBIT,
    EN_STR_FLAG_COMTYPE,
    EN_STR_FLAG_SAMEV,
    EN_STR_FLAG_PT,
    EN_STR_FLAG_CT,
    EN_STR_FLAG_TRANSTYPE,
    EN_STR_FLAG_LINEPHS,
    EN_STR_FLAG_RETOUT,
    EN_STR_FLAG_YXDI,
    EN_STR_FLAG_YKPUL,
    EN_STR_FLAG_WAVE,
    EN_STR_FLAG_MEMTYPE,
    EN_STR_FLAG_CHANNEL,
    EN_STR_FLAG_POWERTYPE,
    EN_STR_FLAG_ZEROTYPE,
    EN_STR_FLAG_YCCOMMTYPE,
    EN_STR_FLAG_YXCOMMTYPE,
    EN_STR_FLAG_CTRLWORD,
    EN_STR_FLAG_BUSNAME,
    EN_STR_FLAG_UNITSHOWMODE,
    EN_STR_FLAG_BAYMODE,
    EN_STR_FLAG_BASEANA,
    EN_STR_FLAG_BASEPHASE,
    EN_STR_FLAG_BAYCODE,
    EN_STR_FLAG_SET_NUM,
    EN_STR_FLAG_DCTYPE,
    EN_STR_FLAG_DCINTYPE,
    EN_STR_FLAG_DWINTYPE,
    EN_STR_FLAG_PTCT,
    EN_STR_FLAG_HARMOTYPE,
    EN_STR_FLAG_YXSIN_DI,
    EN_STR_FLAG_SHOWANATYPE,
    EN_STR_FLAG_ANAPOLARTYPE,
    EN_STR_FLAG_WIRDIAGRAMTYPE
} enumStrType;

const tagMenuModel menuTab[] =
{
    {   0,  "主界面",           "",               0,                  0x0000,             0,                  (FUNCPTR)Menu_NonPfunc              },
    {   1,  "装置信息",    "查看装置信息",         0,                  0x0000,             EN_ANA_0,           (FUNCPTR)MenuProc_See_AppInfo       },
    {   1,  "实时数据",    "装置实时数据",         0,                  0x0000,             0,                  (FUNCPTR)Menu_NonPfunc              },
    {   2,  "交流量",      "查看遥测交流量",       EN_MEA_AC,          0x0000,             EN_ANA_0,           (FUNCPTR)MenuProc_See_YC            },
    {   2,  "直流量",      "查看遥测直流量",       EN_MEA_DC,          0x0000,             EN_ANA_0,           (FUNCPTR)MenuProc_See_YC            },
    {   2,  "遥信量",      "查看遥信开入量",       EN_INPUT_RLY_ALL,   0x0000,             EN_INPUT_0,         (FUNCPTR)MenuProc_See_YC         },
    {   1,  "参数定值",    "保护参数查看与修改",    0,                  0x0000,             0,                  (FUNCPTR)Menu_NonPfunc              },
    {   2,  "整定",        "整定装置保护参数",     0,                  0x0000,             0,                  (FUNCPTR)Menu_NonPfunc              },
    {   3,  "参数",        "查看设备参数定值",     EN_FIGURE_SET,      CN_USER_PWD,        EN_SIDE_BASIC,      (FUNCPTR)MenuProc_Set_Value         },
    {   3,  "定值",        "设置装置数值定值",     EN_FIGURE_SET,      CN_USER_PWD,        EN_SIDE_DEF,        (FUNCPTR)MenuProc_Set_Value         },
    {   3,  "控制字",      "设置装置控制字",       EN_SOFT_SET,        CN_USER_PWD,        EN_SIDE_DEF,        (FUNCPTR)MenuProc_Set_Value         },
    {   3,  "软压板",      "设置软压板",           0,                  CN_USER_PWD,        EN_SOFT_PRO,        (FUNCPTR)MenuProc_Set_Soft          },
    {   2,  "查看",        "查看装置保护参数",     0,                  0x0000,             0,                  (FUNCPTR)Menu_NonPfunc              },
    {   3,  "参数",        "设置设备参数定值",     EN_FIGURE_SET,      0x0000,             EN_SIDE_BASIC,      (FUNCPTR)MenuProc_See_Set           },
    {   3,  "定值",        "查看数值型定值",       EN_FIGURE_SET,      0x0000,             EN_SIDE_DEF,        (FUNCPTR)MenuProc_See_Set           },
    {   3,  "控制字",      "查看控制字定值",       EN_SOFT_SET,        0x0000,             EN_SIDE_DEF,        (FUNCPTR)MenuProc_See_Set           },
    {   3,  "软压板",      "查看软压板",           0,                  0x0000,             EN_SOFT_PRO,        (FUNCPTR)MenuProc_See_Soft          },
    {   1,  "三遥设置",    "",                     0,                  0x0000,             0,                  (FUNCPTR)Menu_NonPfunc              },
    {   2,  "遥测死区",    "设置遥测量死区门槛",   0,                  CN_USER_PWD,        0,                  (FUNCPTR)MenuProc_YC_SetSqValue     },
    {   2,  "遥测系数",    "设置遥测量微调系数",   EN_MEA_ADJ,         CN_USER_PWD,        0,                  (FUNCPTR)MenuProc_YC_SetAdjCoe      },
    {   2,  "遥信类型",    "设置遥信类型",         0,                  CN_USER_PWD,        0,                  (FUNCPTR)MenuProc_YX_SetCommType    },
    {   2,  "遥信防抖",    "设置遥信防抖时间",     0,                  CN_USER_PWD,        0,                  (FUNCPTR)MenuProc_YX_SetWidth       },
    {   2,  "双点遥信",    "设置双点遥信虚端子",   0,                  CN_USER_PWD,        0,                  (FUNCPTR)MenuProc_YX_SetTwin        },
    {   1,  "装置维护",    "",                     0,                  0x0000,             0,                  (FUNCPTR)Menu_NonPfunc              },
    {   2,  "时钟设置",    "设置系统时钟",         0,                  CN_USER_PWD,        0,                  (FUNCPTR)MenuProc_Cfg_Time          },
    {   2,  "强制复归",    "可复归未返回事件",     EN_REV_FORCE,       CN_USER_PWD,        0,                  (FUNCPTR)MenuProc_Cfg_RevEvent      },
    {   2,  "手动录波",    "启动手动录波",         0,                  CN_USER_PWD,        0,                  (FUNCPTR)MenuProc_Cfg_ManualWave    },
    {   2,  "清除记录",    "",                     0,                  CN_USER_PWD,        0,                  (FUNCPTR)MenuProc_Cfg_ClrRec        },
    {   1,  "通讯参数",    "",                     0,                  0x0000,             0,                  (FUNCPTR)Menu_NonPfunc              },
    {   2,  "通讯设置",    "外部通讯设置",         0,                  CN_USER_PWD,        0,                  (FUNCPTR)MenuProc_Cfg_ComPara       },
    {   2,  "网口设置",    "",                     0,                  CN_USER_PWD,        0,                  (FUNCPTR)MenuProc_Cfg_EditIP        },
    {   2,  "SNTP设置",    "",                     0,                  CN_USER_PWD,        0,                  (FUNCPTR)MenuProc_Cfg_EditSntp      },
    {   1,  "记录查询",    "查看各种装置记录",     0,                  0x0000,             0,                  (FUNCPTR)Menu_NonPfunc              },
    {   2,  "SOE记录",     "",                     0,                  0x0000,             0,                  (FUNCPTR)MenuProc_See_RecSOE        },
    {   2,  "事故记录",    "",                     0,                  0x0000,             0,                  (FUNCPTR)MenuProc_See_RecAct        },
    {   2,  "操作记录",    "",                     0,                  0x0000,             0,                  (FUNCPTR)MenuProc_See_RecOpt        },
    {   2,  "保护告警",    "",                     0,                  0x0000,             0,                  (FUNCPTR)MenuProc_See_RecAlm        },
    {   2,  "保护启动",    "",                     0,                  0x0000,             0,                  (FUNCPTR)MenuProc_See_RecStart      },
    {   2,  "遥控记录",    "",                     0,                  0x0000,             0,                  (FUNCPTR)MenuProc_See_RecYK         },
    {   2,  "自检记录",    "",                     0,                  0x0000,             0,                  (FUNCPTR)MenuProc_See_RecChk        },
    {   2,  "运行记录",    "",                     0,                  0x0000,             0,                  (FUNCPTR)MenuProc_See_RecRun        },
    {   2,  "运行报告",    "",                     0,                  0x0000,             0,                  (FUNCPTR)MenuProc_See_RecFault      },
    {   0,  "厂家设置",    "设置装置相关参数",     0,                  CN_COP_PWD,         0,                  (FUNCPTR)Menu_NonPfunc              },
    {   1,  "元件配置",    "配置元件配置",         0,                  CN_COP_PWD,         EN_FACTORY_PASSWORD,(FUNCPTR)MenuProc_Cfg_CellConf      },
    {   1,  "恢复默认",    "恢复默认元件定值参数", 0,                  CN_COP_PWD,         0,                  (FUNCPTR)Menu_NonPfunc              },
    {   2,  "全部恢复",    "全部参数恢复默认",     0,                  CN_COP_PWD,         EN_NO_USER_PWD,     (FUNCPTR)MenuProc_AllInf_Default    },
    {   2,  "默认参数",    "当前参数恢复默认",     0,                  CN_COP_PWD,         EN_NO_USER_PWD,     (FUNCPTR)MenuProc_Para_Default      },
    {   2,  "默认定值",    "当前定值区恢复默认",   0,                  CN_COP_PWD,         EN_NO_USER_PWD,     (FUNCPTR)MenuProc_Set_Default       },
    {   2,  "软压板",      "当前软压板恢复默认",   0,                  CN_COP_PWD,         EN_NO_USER_PWD,     (FUNCPTR)MenuProc_Resume_Soft       },
    {   2,  "元件配置",    "元件配置恢复默认",     0,                  CN_COP_PWD,         EN_NO_USER_PWD,     (FUNCPTR)MenuProc_Cfg_CellDef       },
    {   1,  "交流显示",    "交流显示方式设置",     0,                  CN_COP_PWD,         0,                  (FUNCPTR)MenuProc_Cfg_ShowAnaType   },
    {   1,  "装置调试",    "调试装置",             0,                  0x0000,             0,                  (FUNCPTR)Menu_NonPfunc              },
    {   2,  "虚拟遥信",    "设置虚拟遥信值",       0,                  CN_COP_PWD,         0,                  (FUNCPTR)MenuProc_Dbg_XuYX          },
    {   2,  "交流虚遥测",  "设置虚拟交流遥测值",   EN_MEA_AC,          CN_COP_PWD,         EN_ANA_0,           (FUNCPTR)MenuProc_Dbg_XuYC          },
    {   2,  "直流虚遥测",  "设置虚拟直流遥测值",   EN_MEA_DC,          CN_COP_PWD,         EN_ANA_0,           (FUNCPTR)MenuProc_Dbg_XuYC          },
    {   2,  "电度虚遥测",  "设置虚拟电度遥测值",   EN_MEA_POWER,       CN_COP_PWD,         EN_ANA_0,           (FUNCPTR)MenuProc_Dbg_XuYC          },
    {   2,  "动作虚事件",  "设置虚拟动作事件",     EN_ACT_REC,         CN_COP_PWD,         0,                  (FUNCPTR)MenuProc_Dbg_XuEvent       },
    {   2,  "告警虚事件",  "设置虚拟告警事件",     EN_ALM_REC,         CN_COP_PWD,         0,                  (FUNCPTR)MenuProc_Dbg_XuEvent       },
    {   2,  "动作出口",    "进入此菜单保护退出",   EN_OUTPUT_TRIP,     CN_COP_PWD,         EN_INPUT_0,         (FUNCPTR)MenuProc_Dbg_Relay         },
    {   2,  "信号出口",    "进入此菜单保护退出",   EN_OUTPUT_SIGN,     CN_COP_PWD,         0,                  (FUNCPTR)MenuProc_Dbg_Relay         },
    {   1,  "版本信息",    "查看板件版本信息",     0,                  CN_COP_PWD,         0,                  (FUNCPTR)MenuProc_See_VersionBoard },
};

/* -------------------------------------------------------------------------
 * 函数名: MenuModel_CountTopLevel
 * 功能:
 *   统计静态菜单定义表中 0 级菜单项的数量，用于后续顶部菜单布局计算。
 *
 * 参数:
 *   maxItem - 需要参与统计的菜单项总数
 *
 * 边界处理:
 *   - 本函数默认 `maxItem` 不超过 `menuTab` 实际长度，调用方需保证该前提成立。
 *   - 当 `maxItem == 0` 时，for 循环不进入，函数返回 0。
 *
 * 说明:
 *   - 在当前菜单模型中，`byClass == 0` 表示顶层菜单节点。
 *   - 本函数通过顺序扫描 `menuTab[0 ~ maxItem-1]`，
 *     统计所有 0 级节点数量，并将结果返回给布局初始化流程。
 *   - 返回值通常会被用于计算顶层菜单在 LCD 上的水平分布间隔。
 *
 * 返回值:
 *   - 静态菜单表中的 0 级菜单数量
 * ------------------------------------------------------------------------- */
uint16_t MenuModel_CountTopLevel(uint16_t maxItem)
{
    uint8_t by0LevelNum = 0;
    uint32_t wLoop;

    for (wLoop = 0; wLoop < maxItem; wLoop++)
    {
        if (menuTab[wLoop].byClass == 0)
        {
            by0LevelNum++;
        }
    }
    return by0LevelNum;
}

/* -------------------------------------------------------------------------
 * 函数名: MenuModel_BuildTree
 * 功能:
 *   根据按层级顺序排列的静态菜单定义表，构建菜单运行时树，并建立
 *   `higher / lower / before / behind` 四类链路关系。
 *
 * 参数:
 *   menuItems   - 运行时菜单节点数组（输出）
 *   modelTab    - 静态菜单定义表（输入）
 *   modelCount  - 菜单项总数量
 *
 * 边界处理:
 *   - 本函数不做 menuItems/modelTab 空指针校验，调用方需保证输入有效。
 *   - 默认要求 modelCount > 0，且 `modelTab` 已按菜单显示顺序正确排列：
 *     同层节点连续，子节点紧跟在父节点之后。
 *   - 当前函数内部使用固定深度数组 `ptFirst[4] / ptLast[4]`，
 *     因此默认支持的最大层级索引为 3。
 *
 * 说明:
 *   - 构建流程：
 *       1) 先初始化 `menuItems[0]`，作为整棵树的起点
 *       2) 使用 `ptFirst[level] / ptLast[level]` 记录每一层当前的首节点和尾节点
 *       3) 依次读取后续静态定义项，复制 `menuDef` 到运行时节点
 *       4) 比较“当前节点层级”和“下一个节点层级”，决定链路挂接方式：
 *          a) byCurClass < byNextClass
 *             表示进入更深一层，`ptNextNode` 是 `ptCurrent` 的第一个子节点：
 *             - `ptCurrent->links.lower = ptNextNode`
 *             - `ptNextNode->links.higher = ptCurrent`
 *             - 该层的 `ptFirst/ptLast` 都初始化为 `ptNextNode`
 *          b) byCurClass == byNextClass
 *             表示接入同层兄弟节点：
 *             - `ptNextNode->links.before = ptCurrent`
 *             - `ptCurrent->links.behind = ptNextNode`
 *             - `ptNextNode->links.higher` 继承当前节点的父节点
 *             - 更新该层 `ptLast`
 *          c) byCurClass > byNextClass
 *             表示从更深层回退到某个上层后，再接入该层的新兄弟节点：
 *             - `ptNextNode->links.before = ptLast[byNextClass]`
 *             - `ptNextNode->links.higher = ptFirst[byNextClass]->links.higher`
 *               即：新节点的父节点与该层第一个节点保持一致
 *             - 将原尾节点的 `behind` 指向新节点，并更新该层 `ptLast`
 *             - 对回退过程中已经结束的更深层链表做首尾闭环
 *       5) 全部节点处理完成后，再将 `0 ~ byCurClass` 各层链表统一闭环
 *   - 构建结果：
 *       `higher` - 父节点
 *       `lower`  - 第一个子节点
 *       `before` - 同层前一个节点
 *       `behind` - 同层后一个节点
 *   - 同层链表最终被组织为环形结构，便于菜单上下移动时循环导航。
 *   - 本函数只负责树与链表关系构建，不负责菜单位置计算和显示名称装饰。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
void MenuModel_BuildTree(tagMenuItem *menuItems, const tagMenuModel *modelTab, uint32_t modelCount)
{
    tagPMenuItem ptFirst[MENU_MODEL_MAX_LEVEL]; /* 每层第一个节点 */
    tagPMenuItem ptLast[MENU_MODEL_MAX_LEVEL];  /* 每层最后一个节点 */
    tagPMenuItem ptCurrent;  /* 当前节点 */
    tagPMenuItem ptNextNode; /* 下一个节点 */

    uint8_t byCurClass;   /* 当前节点层级 */
    uint8_t byNextClass;  /* 下一个节点层级 */

    /* 初始化第 0 个节点，作为整棵树的起点 */
    memset(&menuItems[0], 0, sizeof(menuItems[0]));
    /* 初始化第 0 个节点的业务字段 */
    memcpy(&menuItems[0].menuDef, &modelTab[0], sizeof(menuItems[0].menuDef));

    /* 初始化第 0 个节点的链路 */
    ptFirst[0] = &menuItems[0];
    ptLast[0] = ptFirst[0];
    ptCurrent = &menuItems[0];
    
    /* 构建菜单树 */
    for (uint16_t index = 1; index < modelCount; index++)
    {
        /* 初始化第 index 个节点的业务字段 */
        ptNextNode = &menuItems[index];
        memset(ptNextNode, 0, sizeof(*ptNextNode));
        memcpy(&ptNextNode->menuDef, &modelTab[index], sizeof(ptNextNode->menuDef));

        byCurClass = ptCurrent->menuDef.byClass;
        byNextClass = ptNextNode->menuDef.byClass;

        if (byCurClass < byNextClass) /* 当前节点层级小于下一个节点层级 */
        {
            ptCurrent->links.lower = ptNextNode;
            ptNextNode->links.higher = ptCurrent;
            /* 更新该层 first/last */
            ptFirst[byNextClass] = ptNextNode;
            ptLast[byNextClass] = ptNextNode;
        }
        else if (byCurClass == byNextClass) /* 当前节点层级等于下一个节点层级 */
        {
            ptNextNode->links.before = ptCurrent;
            ptNextNode->links.higher = ptCurrent->links.higher;
            ptCurrent->links.behind = ptNextNode;
            /* 更新该层 last */
            ptLast[byNextClass] = ptNextNode;
        }
        else /* 当前节点层级大于下一个节点层级，回退层级*/
        {
            ptNextNode->links.before = ptLast[byNextClass];
            ptNextNode->links.higher = ptFirst[byNextClass]->links.higher;
            /* 更新该层 last 的 behind */
            ptLast[byNextClass]->links.behind = ptNextNode;
            ptLast[byNextClass] = ptNextNode;

            /* 形成回环链表 */
            for (uint16_t index2 = byCurClass; index2 > byNextClass; index2--)
            {
                ptLast[index2]->links.behind = ptFirst[index2];
                ptFirst[index2]->links.before = ptLast[index2];
            }
        }
        /* 更新当前节点 */
        ptCurrent = ptNextNode;
    }
    /* 形成回环链表 */
    byCurClass = ptCurrent->menuDef.byClass;
    for (uint16_t index = 0; index <= byCurClass; index++)
    {
        ptLast[index]->links.behind = ptFirst[index];
        ptFirst[index]->links.before = ptLast[index];
    }
}

/* -------------------------------------------------------------------------
 * 函数名: MenuModel_DecorateDisplayNames
 * 功能:
 *   为存在子菜单的菜单项名称追加显示装饰符，提示该节点可继续进入下一级。
 *
 * 参数:
 *   menuItems   - 菜单项数组首地址
 *   modelCount  - 菜单项总数量
 *
 * 边界处理:
 *   - 本函数不做 menuItems 空指针校验，调用方需保证数组有效。
 *   - 若菜单项没有子节点（links.lower == NULL），直接跳过，不追加装饰符。
 *   - 通过 `sizeof(byName) - 1` 预留字符串结尾 `\0`，避免越界写入。
 *   - 若名称已以 `\x10` 结尾，则不重复追加。
 *
 * 说明:
 *   - `\x10` 在当前菜单显示逻辑中作为“右箭头/可下钻”标记，
 *     用于提示用户该项后面还有子菜单。
 *   - 处理流程：
 *       1) 遍历所有菜单项
 *       2) 判断是否存在子菜单
 *       3) 统计当前名称长度（不超过缓冲区上限）
 *       4) 若末尾尚未追加装饰符，则写入 `\x10` 和字符串结束符 `\0`
 *   - 该函数只修改显示名称，不改变菜单树链路关系和业务属性。
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
void MenuModel_DecorateDisplayNames(tagMenuItem *menuItems, uint32_t modelCount)
{
    for (uint32_t i = 0; i < modelCount; i++)
    {
        uint16_t nameCapacity = (uint16_t)sizeof(menuItems[i].menuDef.byName);
        uint16_t maxTextLenWithoutNull = (uint16_t)(nameCapacity - 1);
        uint16_t len = 0;
        /* 如果没有子菜单，直接跳过 */
        if (menuItems[i].links.lower == NULL)
        {
            continue;
        }
        /* 计算名称长度 */
        while ((len < maxTextLenWithoutNull) && (menuItems[i].menuDef.byName[len] != '\0'))
        {
            len++;
        }
        /* 补充右箭头 */
        if ((len == 0) || (menuItems[i].menuDef.byName[len - 1] != '\x10'))
        {
            if (len < maxTextLenWithoutNull)
            {
                menuItems[i].menuDef.byName[len] = '\x10';
                menuItems[i].menuDef.byName[len + 1] = '\0';
            }
        }
    }
}
/* -------------------------------------------------------------------------
 * 函数名: MenuModel_IndexMenuItems
 * 功能:
 *   统计整棵菜单树中所有菜单项的 `wPos` 与 `wNum`。
 *
 * 参数:
 *   menuItems  - 菜单项数组
 *   modelCount - 菜单项总数
 *
 * 边界处理:
 *   - 若 `menuItems == NULL` 或 `modelCount == 0`，则直接返回。
 *
 * 说明:
 *   - 本函数分两部分初始化：
 *       1) 先遍历 0 级同层链表，为所有顶层节点写入 `rect.wPos`
 *       2) 再遍历每个节点：
 *          a) 若无子节点，则其 `rect.wNum = 0`
 *          b) 若有子节点，则沿子层 `behind` 链统计子节点总数
 *          c) 同时为该组子节点写入各自的 `rect.wPos`
 *          d) 最后把子节点总数写回当前父节点的 `rect.wNum`
 *   - 因此：
 *       `wPos` 表示“当前节点在本层中的 0 基序号”
 *       `wNum` 表示“当前节点拥有的直接子节点总数”
 *
 * 返回值:
 *   - 无
 * ------------------------------------------------------------------------- */
void MenuModel_IndexMenuItems(tagMenuItem *menuItems, uint32_t modelCount)
{
    tagPMenuItem current;
    tagPMenuItem parent;
    tagPMenuItem child;
    uint16_t count;

    if ((menuItems == NULL) || (modelCount == 0))
    {
        return;
    }

    /* 1. 记录 0 级节点的位置和子节点数量 */
    current = &menuItems[0];
    count = 0;
    for (uint16_t index = 0; index < modelCount; index++)
    {
        current->rect.wPos = count;
        current = current->links.behind;
        if ((current == NULL) || (current == &menuItems[0]))
        {
            break;
        }
        count++;
    }

    /* 2. 记录其他层级的位置和子节点数量 */
    for (uint32_t i = 0; i < modelCount; i++)
    {
        parent = &menuItems[i];
        current = parent->links.lower;
        /* a. 如果当前节点没有子节点，说明是最深一层，记录当前节点子节点数量为 0 */
        if (current == NULL)
        {
            parent->rect.wNum = 0;
            continue;
        }

        /*b. 若有子节点，则沿子层 `behind` 链统计子节点总数 */
        child = current;
        count = 0;

        for (uint16_t index = 0; index < MENU_MODEL_MAX_ITEM; index++)
        {
            /*c. 为该组子节点写入各自的 `rect.wPos` */
            child->rect.wPos = count;
            count++;

            child = child->links.behind;
            if ((child == NULL) || (child == current))
            {
                break;
            }
        }
        /* d. 最后把子节点总数写回当前父节点的 `rect.wNum` */
        parent->rect.wNum = count;
    }
}
 

void MenuModel_Init(menu_model_t *model)
{
    uint16_t by0LevelNum;
    model->maxItem = (uint16_t)(sizeof(menuTab) / sizeof(tagMenuModel));
    memset(model->menuItems, 0, sizeof(model->menuItems));

    by0LevelNum = MenuModel_CountTopLevel(model->maxItem);
    MenuModel_BuildTree(model->menuItems, menuTab, model->maxItem);
    MenuModel_DecorateDisplayNames(model->menuItems, model->maxItem);
    MenuModel_IndexMenuItems(model->menuItems, model->maxItem);
    MenuModel_PositionCal(&model->menuItems[0], by0LevelNum);
}