#include "drv_btn.h"
#include <stdbool.h>
#include "stdlib.h"
#include "rtconfig.h"



#define TICKS_INTERVAL    BUTTON_DRV_PERIOD_TIME_MS
#define DEBOUNCE_TICKS    BUTTON_DRV_DEBOUNCE_TICKS

#define SHORT_TICKS       (BUTTON_DRV_SHORT_PRESS_TIME_MS / TICKS_INTERVAL)
#define LONG_TICKS        (BUTTON_DRV_LONG_PRESS_TIME_MS / TICKS_INTERVAL)
#define SERIAL_TICKS      (BUTTON_DRV_LONG_PRESS_HOLD_TIME_MS / TICKS_INTERVAL)
#define TOLERANCE         (TICKS_INTERVAL * 4)  /**< 按键按下时间点回调匹配容差（tick） */    

/**
 * @brief 触发某个事件回调
 *
 * @param evt 事件类型
 * @details 若该事件回调存在，则调用该回调函数。
 */ 
#define BTN_EVENT_CB(evt)                           \
                        do {                        \
                            if (cfg->cb[evt]) {     \
                                cfg->cb[evt](cfg);  \
                            }                       \
                        } while (0)

/**
 * @brief 将时间（ms）换算成 tick 数，若时间为 0 则返回配置值
 * @param time 时间（ms）
 * @param cfg_time 配置时间（ms）
 * @return uint8_t tick 数
 * @details 若时间为 0，则返回配置时间（tick）；否则，将时间（ms）换算成 tick 数。
 */
#define BTN_DRV_TIME_TO_TICKS(time, cfg_time)  (0 == (time)) ? cfg_time : (((time) / TICKS_INTERVAL)) ? ((time) / TICKS_INTERVAL) : 1

static btn_drv_config_t *g_head_cfg = NULL;

/**
 * @brief 按键驱动核心函数，状态机
 *
 * @param btn 按键句柄
 * @details 该函数根据按键的当前状态和采样值，更新按键的状态机和事件。
 */
static void btn_drv_event_handle(btn_drv_config_t *cfg)
{
    if (cfg == NULL || cfg->interface == NULL || cfg->interface->get_key_level == NULL) {
        return;
    }
    uint8_t read_gpio_level = cfg->interface->get_key_level(cfg->interface);

    if ((cfg->state) > 0) {
        cfg->ticks++;
    }

    if (read_gpio_level != cfg->button_level) {
        if (++(cfg->debounce_cnt) >= DEBOUNCE_TICKS) {
            cfg->button_level = read_gpio_level;
            cfg->debounce_cnt = 0;
        }
    } else {
        cfg->debounce_cnt = 0;
    }

    switch (cfg->state) {
    case BTN_DRV_STATE_PRESS_DOWN:
        if (cfg->button_level == BTN_DRV_LEVEL_ACTIVE) {
            cfg->event = (uint8_t)BTN_DRV_EVENT_PRESS_DOWN;
            BTN_EVENT_CB(BTN_DRV_EVENT_PRESS_DOWN);
            cfg->ticks = 0;
            cfg->repeat = 1;
            cfg->state = BTN_DRV_STATE_PRESS_UP;
        } else {
            cfg->event = (uint8_t)BTN_DRV_EVENT_NONE_PRESS;
        }
        break;
    case BTN_DRV_STATE_PRESS_UP:
        if (cfg->button_level != BTN_DRV_LEVEL_ACTIVE) {
            cfg->event = (uint8_t)BTN_DRV_EVENT_PRESS_UP;
            BTN_EVENT_CB(BTN_DRV_EVENT_PRESS_UP);
            cfg->ticks = 0;
            cfg->state = BTN_DRV_STATE_REPEAT_DOWN;
        } else if (cfg->ticks >= cfg->long_press_ticks) {
            cfg->event = (uint8_t)BTN_DRV_EVENT_LONG_PRESS_START;
            BTN_EVENT_CB(BTN_DRV_EVENT_LONG_PRESS_START);
            cfg->ticks = 0;
            cfg->state = BTN_DRV_STATE_LONG_PRESS_UP;
        }
        break;
    case BTN_DRV_STATE_REPEAT_DOWN:
        if (cfg->button_level == BTN_DRV_LEVEL_ACTIVE) {
            cfg->event = (uint8_t)BTN_DRV_EVENT_PRESS_DOWN;
            BTN_EVENT_CB(BTN_DRV_EVENT_PRESS_DOWN);
            cfg->event = (uint8_t)BTN_DRV_EVENT_PRESS_REPEAT;
            cfg->repeat ++;
            BTN_EVENT_CB(BTN_DRV_EVENT_PRESS_REPEAT);
            cfg->ticks = 0;
            cfg->state = BTN_DRV_STATE_REPEAT_UP;
        } else if (cfg->ticks > cfg->short_press_ticks) {
            if (cfg->repeat == 1) {
                cfg->event = (uint8_t)BTN_DRV_EVENT_SINGLE_CLICK;
                BTN_EVENT_CB(BTN_DRV_EVENT_SINGLE_CLICK);
            } else if (cfg->repeat == 2) {
                cfg->event = (uint8_t)BTN_DRV_EVENT_DOUBLE_CLICK;
                BTN_EVENT_CB(BTN_DRV_EVENT_DOUBLE_CLICK);
            }
            cfg->event = (uint8_t)BTN_DRV_EVENT_PRESS_REPEAT_DONE;
            BTN_EVENT_CB(BTN_DRV_EVENT_PRESS_REPEAT_DONE);
            cfg->repeat = 0;
            cfg->state = BTN_DRV_STATE_PRESS_DOWN;
            cfg->event = (uint8_t)BTN_DRV_EVENT_PRESS_END;
            BTN_EVENT_CB(BTN_DRV_EVENT_PRESS_END);
        }
        break;
    case BTN_DRV_STATE_REPEAT_UP:
        if (cfg->button_level != BTN_DRV_LEVEL_ACTIVE) {
            cfg->event = (uint8_t)BTN_DRV_EVENT_PRESS_UP;
            BTN_EVENT_CB(BTN_DRV_EVENT_PRESS_UP);
            if (cfg->ticks < cfg->short_press_ticks) {
                cfg->ticks = 0;
                cfg->state = BTN_DRV_STATE_REPEAT_DOWN;
            } else {
                cfg->state = BTN_DRV_STATE_PRESS_DOWN;
                cfg->event = (uint8_t)BTN_DRV_EVENT_PRESS_END;
                BTN_EVENT_CB(BTN_DRV_EVENT_PRESS_END);
            }
        }
        break;
    case BTN_DRV_STATE_LONG_PRESS_UP:
        if (cfg->button_level == BTN_DRV_LEVEL_ACTIVE) {
            if (cfg->ticks >= (cfg->long_press_hold_cnt + 1) * SERIAL_TICKS + cfg->long_press_ticks) {
                cfg->event = (uint8_t)BTN_DRV_EVENT_LONG_PRESS_HOLD;
                cfg->long_press_hold_cnt ++;
                BTN_EVENT_CB(BTN_DRV_EVENT_LONG_PRESS_HOLD);
            }
        } else {
            cfg->event = (uint8_t)BTN_DRV_EVENT_PRESS_UP;
            BTN_EVENT_CB(BTN_DRV_EVENT_PRESS_UP);
            cfg->state = BTN_DRV_STATE_PRESS_DOWN;
            cfg->long_press_hold_cnt = 0;
            cfg->event = (uint8_t)BTN_DRV_EVENT_PRESS_END;
            BTN_EVENT_CB(BTN_DRV_EVENT_PRESS_END);
        }
        break;
    }
}

/**
 * @brief 按键事件处理回调函数
 * 
 * @param args 按键设备实例指针
 * @details 该函数遍历全局按键链表，对每个按键实例调用事件处理函数。
 */
void btn_drv_event_callback(void *args)
{
    for (btn_drv_config_t * cfg = g_head_cfg; cfg; cfg = cfg->next) {
        btn_drv_event_handle(cfg);
    }   
}

/**
 * @brief 注册按键事件回调函数
 * 
 * @param handle 按键句柄
 * @param event 要注册的事件类型
 * @param cb 事件回调函数指针
 * @param usr_data 用户自定义数据指针
 * @return bool true 成功，false 失败
 * @details 该函数用于注册指定按键实例上的某个事件回调。
 */
bool btn_drv_event_register(btn_drv_handle_t handle, btn_drv_event_e event, btn_drv_cb_t cb)
{
    btn_drv_config_t *cfg = (btn_drv_config_t *)handle;

    if (cfg == NULL || cb == NULL || event >= BTN_DRV_EVENT_MAX) {
        return false;
    }
    if (cfg->cb[event]) {
        return false;
    }

    cfg->cb[event] = cb;

    return true;
}

/** 
 * @brief 注销按键事件回调
 * 
 * @param handle 按键句柄
 * @param event 要注销的事件类型
 * @return bool true 成功，false 失败
 * @details 该函数用于注销指定按键实例上的某个事件回调。
 */
bool btn_drv_event_unregister(btn_drv_handle_t handle, btn_drv_event_e event)
{
    btn_drv_config_t *cfg = (btn_drv_config_t *)handle;

    if (cfg == NULL || event >= BTN_DRV_EVENT_MAX) {
        return false;
    }
    if (cfg->cb[event] == NULL) {
        return false;
    }

    cfg->cb[event] = NULL;

    return true;
}

/**
 * @brief 创建一个按键驱动实例
 *
 * @param params 按键驱动配置参数
 * @param driver 按键驱动接口
 * @param button 输出的按键驱动句柄指针
 * @return bool true 成功，false 失败
 * @details 该函数根据配置参数和驱动接口创建一个按键驱动实例，并将其插入到全局按键链表中。
 */
bool btn_drv_create(const btn_drv_params_t *params, const btn_drv_interface_t *interface, btn_drv_handle_t *handle)
{
    if (interface == NULL || interface->get_key_level == NULL || handle == NULL) {
        return false;
    }
    btn_drv_config_t *cfg = (btn_drv_config_t *)calloc(1, sizeof(btn_drv_config_t));
    if (cfg == NULL) {
        return false;
    }

    cfg->interface = (btn_drv_interface_t *)interface;
    cfg->long_press_ticks = BTN_DRV_TIME_TO_TICKS(params ? params->long_press_time : 0, LONG_TICKS);
    cfg->short_press_ticks = BTN_DRV_TIME_TO_TICKS(params ? params->short_press_time : 0, SHORT_TICKS);

    cfg->event = BTN_DRV_EVENT_NONE_PRESS;
    cfg->button_level = BTN_DRV_LEVEL_INACTIVE;

    cfg->next = g_head_cfg;
    g_head_cfg = cfg;

    *handle = (btn_drv_handle_t)cfg;

    return true;
}

/**
 * @brief 删除一个按键设备实例
 * @details 该函数根据按键句柄删除指定按键实例，释放其占用的资源。
 * @param handle 按键句柄
 * @return rt_err_t RT_EOK 成功，-RT_ERROR 失败
 */ 
bool btn_drv_delete(btn_drv_handle_t handle)
{
    if (handle == NULL) {
        return false;
    }

    btn_drv_config_t *cfg = (btn_drv_config_t *)handle;
    if (cfg->interface && cfg->interface->del) {
        cfg->interface->del(cfg->interface);
    }

    btn_drv_config_t **curr;
    for (curr = &g_head_cfg; *curr;) {
        btn_drv_config_t *entry = *curr;
        if (entry == cfg) {
            *curr = entry->next;
            free(entry);
        } else {
            curr = &entry->next;
        }
    }

    return true;
}


