/*
 * Control buttons driver.
 */

#include "nrf.h"
#include "nrf_error.h"
#include "boards.h"
#include "nrf_delay.h"

#include "nrf_mesh_defines.h"
#include "timer.h"
#include "utils.h"
#include "nrfx_gpiote.h"
#include "app_timer.h"
#include "app_error.h"

#include "log.h"

#include "button.h"



/* Type definitions */

typedef struct btn_state_s {
    nrfx_gpiote_pin_t pin;
    bool debounce;
    bool expecting_second_click;
    bool skip_release;
    int32_t period_cnt;
    app_timer_id_t timer_id;
    app_timer_t timer_id_data;
} btn_state_t;



/*** Global variables */

static nrfx_gpiote_pin_t m_btn_list[BUTTONS_NUMBER] = BUTTONS_LIST;
static btn_state_t m_btn_state[BUTTONS_NUMBER];
static btn_handler_cb_t m_btn_handler_cb;

/* Since SDK does not allow to create APP_TIMERs as members of a structure or
   as arrays, we create one timer instance to use it as a template for
   initializing built-in timers. */
APP_TIMER_DEF(m_btn_timer);



/*** Internal fuctions */

static btn_state_t *btn_get_state(nrfx_gpiote_pin_t btn_pin)
{
    int i;

    for (i = 0; i < ARRAY_SIZE(m_btn_state); i++) {
        if (m_btn_state[i].pin == btn_pin) {
            return &m_btn_state[i];
        }
    }

    return NULL;
}


static void btn_event_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
    btn_state_t *p_btn_state = btn_get_state(pin);
    APP_ERROR_CHECK_BOOL(p_btn_state != NULL);

    if (!p_btn_state->debounce) {
        p_btn_state->debounce = true;
        app_timer_stop(p_btn_state->timer_id);
        app_timer_start(p_btn_state->timer_id, APP_TIMER_TICKS(BTN_DEBOUNCE_INTERVAL_MS), p_btn_state);
    }
}


static void btn_timeout_handler(void * p_context)
{
    btn_state_t *p_btn_state = (btn_state_t *)p_context;
    APP_ERROR_CHECK_BOOL(p_btn_state != NULL);
    bool is_press;

#if BUTTONS_ACTIVE_STATE == 0
    is_press = !nrfx_gpiote_in_is_set(p_btn_state->pin);
#else /* BUTTONS_ACTIVE_STATE */
    is_press = nrfx_gpiote_in_is_set(p_btn_state->pin);
#endif /* BUTTONS_ACTIVE_STATE */

    if (p_btn_state->debounce) {
        // post processing debounce state
        p_btn_state->debounce = false;

        if (is_press) {
            // start long-press processing
            p_btn_state->period_cnt = 0;
            app_timer_start(p_btn_state->timer_id, APP_TIMER_TICKS(BTN_PRESS_PERIOD_MS), p_btn_state);
        } else  {
            // button release
            if (!p_btn_state->skip_release) {
                if (p_btn_state->expecting_second_click) {
                    p_btn_state->expecting_second_click = false;
                    m_btn_handler_cb(p_btn_state->pin, BTN_EVT_DOUBLE_CLICK);
                } else {
                    p_btn_state->expecting_second_click = true;
                    UNUSED_VARIABLE(app_timer_start(p_btn_state->timer_id,
                                                    APP_TIMER_TICKS(BTN_DOUBLE_CLICK_MS),
                                                    p_btn_state));
                }
            } else {
                p_btn_state->skip_release = false;
            }
        }
    } else if (p_btn_state->expecting_second_click) {
        p_btn_state->expecting_second_click = false;
        m_btn_handler_cb(p_btn_state->pin, BTN_EVT_CLICK);
    } else {
        // process long-press
        if (is_press) {
            p_btn_state->period_cnt++;
            if (p_btn_state->period_cnt >= BTN_PRESS_TIMEOUT_CNT) {
                m_btn_handler_cb(p_btn_state->pin, BTN_EVT_TIMEOUT);
            } else {
                if (p_btn_state->period_cnt == BTN_LONG_PRESS_CNT) {
                    p_btn_state->skip_release = true;
                    m_btn_handler_cb(p_btn_state->pin, BTN_EVT_LONG_PRESS);
                }
                app_timer_start(p_btn_state->timer_id, APP_TIMER_TICKS(BTN_PRESS_PERIOD_MS), p_btn_state);
            }
        }
    }
}



/*** Interface functions */

/* initialize buttons driver */
void btn_init(btn_handler_cb_t cb)
{
    /* mark timers main structure as unused */
    const app_timer_id_t m_timer0 __attribute__((unused)) = m_btn_timer;

    APP_ERROR_CHECK_BOOL(GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS >= BUTTONS_NUMBER);
    APP_ERROR_CHECK_BOOL(cb != NULL);

    m_btn_handler_cb = cb;

    if (!nrfx_gpiote_is_init()) {
        APP_ERROR_CHECK(nrfx_gpiote_init());
    }

    nrfx_gpiote_in_config_t in_config = {
        .sense = NRF_GPIOTE_POLARITY_TOGGLE,
        .pull = BUTTON_PULL,
        .is_watcher = false,
        .hi_accuracy = false,
        .skip_gpio_setup = false
    };

    for (uint32_t i = 0; i < BUTTONS_NUMBER; ++i) {
        m_btn_state[i].pin = m_btn_list[i];
        m_btn_state[i].debounce = false;
        m_btn_state[i].expecting_second_click = false;
        m_btn_state[i].skip_release = false;
        m_btn_state[i].period_cnt = 0;
        APP_ERROR_CHECK(nrfx_gpiote_in_init(m_btn_list[i], &in_config, btn_event_handler));
        nrfx_gpiote_in_event_enable(m_btn_list[i], true);

        memcpy(&m_btn_state[i].timer_id_data, &m_btn_timer_data, sizeof(m_btn_timer_data));
        m_btn_state[i].timer_id = &m_btn_state[i].timer_id_data;
        APP_ERROR_CHECK(app_timer_create(&m_btn_state[i].timer_id, APP_TIMER_MODE_SINGLE_SHOT, btn_timeout_handler));
    }
}
