/******************************************************************************
 * @file        key.c
 * @brief       按键驱动模块 - 基于 MultiButton 库的高可移植性实现
 * @details     本文件实现了基于 MultiButton 库的按键驱动功能，支持多按键配置、
 *              按键状态检测、按键事件回调等功能。采用配置表方式管理按键，
 *              便于扩展和维护。支持单次点击事件检测。
 * @author      阜阳师范大学物电学院
 * @version     V0.1
 * @date        2026.1.19
 * @note        依赖库: MultiButton
 *              支持按键: ENTER, UP, DOWN, LEFT, RIGHT, ESC, ADD, DEC, RESET
 *              按键模式: 输入模式，无上拉/下拉
 *              特殊处理: PB3 需要禁用 JTAG 才能作为普通 GPIO 使用
 ******************************************************************************/

#include "key.h"
#include "160160D.h"

/* ============================================================================
 * 按键配置结构体定义
 * ============================================================================ */
/**
 * @brief 按键配置结构体
 * @details 用于存储每个按键的硬件配置信息，包括 GPIO 端口、引脚和按键类型
 */
typedef struct {
    uint16_t pin;              /**< GPIO 引脚编号（如 GPIO_PIN_12） */
    GPIO_TypeDef* port;        /**< GPIO 端口（如 GPIOD） */
    KEY_TYPE key_type;         /**< 按键类型标识（如 KEY_ENTER） */
} KeyConfig_t;

/* ============================================================================
 * 按键硬件引脚映射表
 * ============================================================================ */
/**
 * @brief 按键硬件引脚映射表
 * @details 详细引脚映射关系如下：
 * | 按键名称 | 端口 | 引脚 | 按键类型 | 功能说明 |
 * |---------|------|------|----------|----------|
 * | KEY_ENTER | GPIOD | PD12 | KEY_ENTER | 确认键 |
 * | KEY_UP | GPIOD | PD11 | KEY_UP | 上键 |
 * | KEY_DOWN | GPIOD | PD10 | KEY_DOWN | 下键 |
 * | KEY_LEFT | GPIOD | PD13 | KEY_LEFT | 左键 |
 * | KEY_RIGHT | GPIOD | PD8 | KEY_RIGHT | 右键 |
 * | KEY_ESC | GPIOB | PB15 | KEY_ESC | 取消键 |
 * | KEY_ADD | GPIOB | PB3 | KEY_ADD | 加键（需禁用 JTAG） |
 * | KEY_RESET | GPIOD | PD15 | KEY_RESET | 复位键 |
 * @note PB3 引脚默认被 JTAG 占用，需要禁用 JTAG 才能作为普通 GPIO 使用
 */
static const KeyConfig_t key_configs[] = {
    {GPIO_PIN_12, GPIOD, KEY_ENTER},       /**< 确认键：PD12 */
    {GPIO_PIN_11, GPIOD, KEY_UP},          /**< 上键：PD11 */
    {GPIO_PIN_10, GPIOD, KEY_DOWN},        /**< 下键：PD10 */
    {GPIO_PIN_13, GPIOD, KEY_LEFT},       /**< 左键：PD13 */
    {GPIO_PIN_8,  GPIOD, KEY_RIGHT},      /**< 右键：PD8 */
    {GPIO_PIN_15, GPIOB, KEY_ESC},        /**< 取消键：PB15 */
    {GPIO_PIN_3,  GPIOB, KEY_ADD},        /**< 加键：PB3（需禁用 JTAG） */
    {GPIO_PIN_15, GPIOD, KEY_RESET},      /**< 复位键：PD15 */
};

#define KEY_COUNT         (sizeof(key_configs) / sizeof(key_configs[0]))  /**< 按键总数 */

/* ============================================================================
 * 全局变量定义
 * ============================================================================ */
static Button btn_handles[KEY_COUNT];      /**< MultiButton 按键句柄数组 */
static KeyCallback key_callback = NULL;    /**< 业务逻辑层注册的回调函数指针 */

/* ============================================================================
 * 内部函数实现
 * ============================================================================ */

/**
 * @brief  读取按键 GPIO 电平状态
 * @param  button_id 按键 ID（对应 key_configs 数组索引，0 到 KEY_COUNT-1）
 * @note   该函数由 MultiButton 库调用，用于读取按键的硬件状态
 *          返回值：0 表示按下（低电平），1 表示释放（高电平）
 * @retval 0: 按键按下（低电平）
 * @retval 1: 按键释放（高电平）
 */
static uint8_t button_read_level(uint8_t button_id)
{
    uint8_t pin_bit = HAL_GPIO_ReadPin(key_configs[button_id].port, key_configs[button_id].pin);
    return pin_bit;
}

/**
 * @brief  统一的按键事件回调函数（内部使用）
 * @param  btn MultiButton 库的按键句柄指针
 * @note   当 MultiButton 库检测到按键事件时，会调用此函数
 *          此函数将按键事件转换为业务逻辑层的按键类型，并调用注册的回调函数
 *          处理流程：
 *          1. 检查按键句柄和回调函数是否有效
 *          2. 从按键句柄中获取 button_id
 *          3. 通过 button_id 查找对应的按键类型
 *          4. 调用业务逻辑层注册的回调函数
 * @retval 无
 */
static void button_callback(Button* btn)
{
    if (btn != NULL && key_callback != NULL)
    {
        /* 通过 button_id 获取对应的按键类型 */
        uint8_t button_id = btn->button_id;
        if (button_id < KEY_COUNT)
        {
            /* 调用业务逻辑层注册的回调函数 */
            key_callback(key_configs[button_id].key_type);
        }
    }
}

/**
 * @brief  使能指定 GPIO 端口的时钟
 * @param  port GPIO 端口指针（如 GPIOA, GPIOB, GPIOD 等）
 * @note   使用 switch-case 结构实现，代码简洁清晰
 *          支持所有 GPIO 端口（A-G）
 *          在初始化 GPIO 之前必须使能对应的时钟
 * @retval 无
 */
static void KEY_GPIO_ClockEnable(GPIO_TypeDef *port)
{
    switch ((uint32_t)port) {
        case (uint32_t)GPIOA: __HAL_RCC_GPIOA_CLK_ENABLE(); break;  /**< 使能 GPIOA 时钟 */
        case (uint32_t)GPIOB: __HAL_RCC_GPIOB_CLK_ENABLE(); break;  /**< 使能 GPIOB 时钟 */
        case (uint32_t)GPIOC: __HAL_RCC_GPIOC_CLK_ENABLE(); break;  /**< 使能 GPIOC 时钟 */
        case (uint32_t)GPIOD: __HAL_RCC_GPIOD_CLK_ENABLE(); break;  /**< 使能 GPIOD 时钟 */
        case (uint32_t)GPIOE: __HAL_RCC_GPIOE_CLK_ENABLE(); break;  /**< 使能 GPIOE 时钟 */
        case (uint32_t)GPIOF: __HAL_RCC_GPIOF_CLK_ENABLE(); break;  /**< 使能 GPIOF 时钟 */
        case (uint32_t)GPIOG: __HAL_RCC_GPIOG_CLK_ENABLE(); break;  /**< 使能 GPIOG 时钟 */
        default: break;
    }
}

/* ============================================================================
 * 外部接口函数实现
 * ============================================================================ */

/**
 * @brief  按键硬件初始化函数
 * @note   初始化流程：
 *          1. 使能 AFIO 时钟（修改 AFIO 寄存器前必须）
 *          2. 禁用 JTAG，保留 SWD（释放 PA15、PB3、PB4 作为普通 GPIO）
 *          3. 遍历按键配置表，对每个按键进行初始化：
 *             a. 使能对应 GPIO 端口时钟
 *             b. 配置 GPIO 为输入模式，无上拉/下拉，高速模式
 *             c. 初始化 MultiButton 按键句柄
 *             d. 绑定单次点击事件到回调函数
 *             e. 启动按键检测
 * @note   特殊处理：由于使用了 PB3 引脚，需要禁用 JTAG 功能
 *          JTAG 禁用后，PA13、PA14 仍可用于 SWD 调试
 * @retval 无
 */
void Key_Init(void)
{
    /* ========== AFIO 和 JTAG 配置 ========== */
    __HAL_RCC_AFIO_CLK_ENABLE();      /**< 使能 AFIO 时钟（修改 AFIO 寄存器前必须） */
    
    /* 禁用 JTAG，保留 SWD（PA13、PA14 仍可用于调试） */
    /* 这会释放 PA15、PB3、PB4 作为普通 GPIO */
    __HAL_AFIO_REMAP_SWJ_NOJTAG();    
    
    /* ========== GPIO 和按键初始化 ========== */
    GPIO_InitTypeDef gpio_init_struct = {0};
    
    /* 批量初始化所有按键 */
    for (uint8_t button_id = 0; button_id < KEY_COUNT; button_id++)
    {
        /* 使能对应 GPIO 端口时钟 */
        KEY_GPIO_ClockEnable(key_configs[button_id].port);
        
        /* 配置 GPIO 为输入模式 */
        gpio_init_struct.Pin = key_configs[button_id].pin;
        gpio_init_struct.Mode = GPIO_MODE_INPUT;      /**< 输入模式 */
        gpio_init_struct.Pull = GPIO_NOPULL;          /**< 无上拉/下拉（外部电路处理） */
        gpio_init_struct.Speed = GPIO_SPEED_HIGH;     /**< 高速模式，提高响应速度 */
        HAL_GPIO_Init(key_configs[button_id].port, &gpio_init_struct);
        
        /* 初始化 MultiButton 按键句柄 */
        button_init(&btn_handles[button_id], button_read_level, 0, button_id);
        
        /* 绑定单次点击事件到回调函数 */
        button_attach(&btn_handles[button_id], BTN_SINGLE_CLICK, button_callback);
        
        /* 启动按键检测 */
        button_start(&btn_handles[button_id]);
    }
}

/**
 * @brief  注册按键回调函数（供业务逻辑层调用）
 * @param  callback 按键回调函数指针，当检测到按键事件时会调用此函数
 * @note   业务逻辑层通过此函数注册按键处理回调
 *          回调函数参数为 KEY_TYPE 类型，表示被按下的按键类型
 *          如果传入 NULL，则取消已注册的回调函数
 * @retval 无
 */
void Key_RegisterCallback(KeyCallback callback)
{
    key_callback = callback;
}
