/*
 * Created on: Jul 27, 2024
 *
 * generic button handler like I do on most of my projects
 *
 *
 * to use the buttons:
 *
 * - set REG_BTN_DEBOUNCE_TIME to desired debounce time in 5ms increments
 *   (default is set in BTN_DEBOUNCE)
 *
 * - set button flags for the buttons you want the interrupt line to respond
 *   to in REG_BTNx_INT_ENABLE for each button
 *   flags are BTN_PUSH / BTN_HOLD / BTN_RELEASE bits
 *
 * - enable button line interrupt if you want the interrupt line to go high
 *   when a button interrupt happens
 *   this is INT_BTN bit in REG_INTR_FLAGS
 *
 * - if using interrupts, check flag; clear the flag by writing BTN_INT bit
 *   to REG_INTR_FLAGS_CLEAR
 *
 * - read the button states from REG_BTN_PUSHED, REG_BTN_HELD, REG_BTN_RELEASED
 *   these registers are clear-on-read so will always have fresh information
 *
 * - alternatively, work the mask directly by reading REG_BTNx_MASK
 *
 */

#include "btn.h"

#include "intr_out.h"
#include "../i2c_slave.h"

#include <ch32v00x.h>




struct Btn btn[BTN_COUNT] = {0};

static uint8_t set_interrupt = 0;



static void update_i2c_reg(uint8_t idx)
{
    if (idx > BTN_COUNT) return;

    // set raw masks
    i2cs_reg[REG_BTN1_MASK + idx] = btn[idx]._mask;

    // set interrupt line high if interrupt is enabled for the button
    if (btn[idx]._mask & i2cs_reg[REG_BTN1_INT_ENABLE + idx]) {
        set_interrupt = 1;
    }
}

void btn_push_cb(uint8_t idx)
{
    // indicate which button is being pushed in dedicated register
    i2cs_reg[REG_BTN_PUSHED] |= (1 << idx);
    i2cs_reg[REG_BTN_PUSHED_LATCHED] |= (1 << idx);

    update_i2c_reg(idx);
}

void btn_hold_cb(uint8_t idx)
{
    // indicate which button is being held in dedicated register
    i2cs_reg[REG_BTN_HELD] |= (1 << idx);

    // before updating general registers
    update_i2c_reg(idx);
}

void btn_release_cb(uint8_t idx)
{
    // indicate which button is being released in dedicated register
    i2cs_reg[REG_BTN_RELEASED] |= (1 << idx);
    i2cs_reg[REG_BTN_PUSHED_LATCHED] &= ~(1 << idx);

    update_i2c_reg(idx);
}


void btn_init()
{
    uint8_t i;

    EXTI_InitTypeDef exti = {0};

    // it is assumed that GPIO is already configured for these pins

    // configure default setup
    for (i = 0; i < BTN_COUNT; i++) {
        btn[i]._mask = BTN_RELEASE;
    }

    btn[0]._pintype = BTN1_PIN;
    btn[1]._pintype = BTN2_PIN;
    btn[2]._pintype = BTN3_PIN;
    btn[3]._pintype = BTN4_PIN;
    btn[4]._pintype = BTN5_PIN;

    GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource0);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource4);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource5);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource6);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource7);

    for (i = 0; i < BTN_COUNT; i++) {
        // check buttons, and ignore if held
        if (!(BTN_PORT->INDR & (1 << btn[i]._pintype))) {
            btn[i]._mask |= BTN_IGNORE;
        }

        // set initial callbacks
        btn[i].cb_push = btn_push_cb;
        btn[i].cb_hold = btn_hold_cb;
        btn[i].cb_release = btn_release_cb;

        // set up exti line to fire interrupt when button is pushed
        exti.EXTI_Line = (1 << btn[i]._pintype);
        exti.EXTI_Mode = EXTI_Mode_Interrupt;
        exti.EXTI_Trigger = EXTI_Trigger_Falling;
        exti.EXTI_LineCmd = ENABLE;
        EXTI_Init(&exti);
    }

    // set default debounce
    i2cs_reg[REG_BTN_DEBOUNCE_TIME] = BTN_DEBOUNCE;
}

void btn_poll()
{
    uint8_t i;
    uint8_t r;
    uint8_t ignore;

    uint8_t pushed;

    set_interrupt = 0;

    for (i = 0; i < BTN_COUNT; i++) {
        pushed = 0;

        ignore = btn[i]._mask & BTN_IGNORE;
        r = BTN_PORT->INDR & (1 << (btn[i]._pintype & BTN_PINTYPE_MASK));

        // active low type buttons
        if (!r) pushed = 1;
        // active high type buttons
        // if (r && !(btn[i]._pintype & BTN_ACTIVE_LO)) pushed = 1;

        if (pushed) {
            // hold counter
            if (btn[i]._count < 0xffff) btn[i]._count++;

            // pushed long enough?
            if (btn[i]._count < i2cs_reg[REG_BTN_DEBOUNCE_TIME]) continue;

            // first push?
            if (!(btn[i]._mask & BTN_PUSH)) {
                btn[i]._mask = BTN_PUSH | ignore;
                if (!ignore) {
                    if (btn[i].cb_push) btn[i].cb_push(i);
                    btn[i]._mask |= (BTN_PUSH << 4);
                }
            } else

            // held to count limit
            if (btn[i].hold && (btn[i]._count >= btn[i].hold)) {
                // if button is not repeatable, do not retrigger
                if ((btn[i]._mask & BTN_HOLD) && !btn[i].repeat) continue;

                btn[i]._mask |= BTN_HOLD;
                // call callback only if not in ignore state
                if (!ignore) {
                    if (btn[i].cb_hold) btn[i].cb_hold(i);
                    btn[i]._mask |= (BTN_HOLD << 4);
                }

                // apply repeat rate to count
                if (btn[i].repeat > btn[i]._count) {
                    btn[i]._count = 0;
                } else btn[i]._count -= btn[i].repeat;
            }
        } else {
            // is not pushed
            if (!(btn[i]._mask & BTN_RELEASE)) {
                // note: release will remove ignore status
                btn[i]._mask = BTN_RELEASE;
                // call callback only if not in ignore state
                if (btn[i].cb_release && !ignore) {
                    btn[i].cb_release(i);
                    btn[i]._mask |= (BTN_RELEASE << 4);
                }
            }
            btn[i]._count = 0;
        }
    }

    // any button event with enabled interrupt flags will set button interrupt flag
    if (set_interrupt) {
        i2cs_append_flag(INT_BTN);
    }
}
