#include "buttons.h"

#include <stdbool.h>

#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define BTN_D0_GPIO 0 /* D0/BOOT, active LOW */
#define BTN_D1_GPIO 1 /* D1, active HIGH */
#define BTN_D2_GPIO 2 /* D2, active HIGH */

#define BTN_DEBOUNCE_MS 30
#define BTN_LONG_MS 1000
#define BTN_POLL_MS 10

static bool s_buttons_inited = false;

static bool button_pressed(btn_id_t id)
{
    if (id == BTN_D0) {
        return gpio_get_level(BTN_D0_GPIO) == 0;
    }
    if (id == BTN_D1) {
        return gpio_get_level(BTN_D1_GPIO) == 1;
    }
    if (id == BTN_D2) {
        return gpio_get_level(BTN_D2_GPIO) == 1;
    }
    return false;
}

static bool any_pressed(void)
{
    return button_pressed(BTN_D0) || button_pressed(BTN_D1) || button_pressed(BTN_D2);
}

void buttons_init(void)
{
    if (s_buttons_inited) {
        return;
    }

    gpio_config_t d0_cfg = {
        .pin_bit_mask = 1ULL << BTN_D0_GPIO,
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };
    gpio_config(&d0_cfg);

    gpio_config_t d1d2_cfg = {
        .pin_bit_mask = (1ULL << BTN_D1_GPIO) | (1ULL << BTN_D2_GPIO),
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_ENABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };
    gpio_config(&d1d2_cfg);

    s_buttons_inited = true;
}

btn_event_t buttons_poll(void)
{
    btn_event_t evt = {.id = BTN_NONE, .kind = BTN_EVT_NONE};

    if (!s_buttons_inited) {
        buttons_init();
    }

    if (button_pressed(BTN_D0)) {
        evt.id = BTN_D0;
        evt.kind = BTN_EVT_PRESS;
        return evt;
    }
    if (button_pressed(BTN_D1)) {
        evt.id = BTN_D1;
        evt.kind = BTN_EVT_PRESS;
        return evt;
    }
    if (button_pressed(BTN_D2)) {
        evt.id = BTN_D2;
        evt.kind = BTN_EVT_PRESS;
        return evt;
    }

    return evt;
}

btn_event_t buttons_wait(uint32_t timeout_ms)
{
    btn_event_t evt = {.id = BTN_NONE, .kind = BTN_EVT_NONE};
    uint32_t elapsed = 0;
    const bool wait_forever = (timeout_ms == 0xFFFFFFFFu);

    if (!s_buttons_inited) {
        buttons_init();
    }

    while (any_pressed()) {
        vTaskDelay(pdMS_TO_TICKS(BTN_POLL_MS));
        if (!wait_forever) {
            elapsed += BTN_POLL_MS;
            if (elapsed >= timeout_ms) {
                return evt;
            }
        }
    }

    while (wait_forever || elapsed < timeout_ms) {
        btn_id_t id = BTN_NONE;

        if (button_pressed(BTN_D0)) {
            id = BTN_D0;
        } else if (button_pressed(BTN_D1)) {
            id = BTN_D1;
        } else if (button_pressed(BTN_D2)) {
            id = BTN_D2;
        }

        if (id != BTN_NONE) {
            uint32_t held_ms = 0;

            vTaskDelay(pdMS_TO_TICKS(BTN_DEBOUNCE_MS));
            if (!button_pressed(id)) {
                continue;
            }

            while (button_pressed(id)) {
                vTaskDelay(pdMS_TO_TICKS(BTN_POLL_MS));
                held_ms += BTN_POLL_MS;
                if (!wait_forever) {
                    elapsed += BTN_POLL_MS;
                    if (elapsed >= timeout_ms) {
                        return evt;
                    }
                }
            }

            vTaskDelay(pdMS_TO_TICKS(BTN_DEBOUNCE_MS));
            evt.id = id;
            evt.kind = (held_ms >= BTN_LONG_MS) ? BTN_EVT_LONG_PRESS : BTN_EVT_PRESS;
            return evt;
        }

        vTaskDelay(pdMS_TO_TICKS(BTN_POLL_MS));
        if (!wait_forever) {
            elapsed += BTN_POLL_MS;
        }
    }

    return evt;
}
