/*
 * Copyright (c) 2019, Erich Styger
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "platform.h"
#if PL_CONFIG_USE_BUTTONS
#include "buttons.h"
#include "McuButton.h"
#include "McuRTOS.h"
#include "McuDebounce.h"
#include "McuRTT.h"
#include "fsl_port.h" /* for KBI and pull up selection */
#include "Event.h"

/* Buttons */
/* User button SW2: PTA14 */
#define PINS_SW2_GPIO       GPIOA
#define PINS_SW2_PORT       PORTA
#define PINS_SW2_PIN        14U

static McuBtn_Handle_t btnSW2;

bool BTN_SW2ButtonIsPressed(void) {
  return McuBtn_IsOn(btnSW2);
}

static uint32_t GetButtons(void) {
  uint32_t val = 0;

  if (McuBtn_IsOn(btnSW2)) {
    val |= BTN_SW2;
  }
  return val;
}

static void OnDebounceEvent(McuDbnc_EventKinds event, uint32_t buttons);

#define TIMER_PERIOD_MS  20 /* frequency of timer */
static McuDbnc_Desc_t data =
{
	.state = MCUDBMC_STATE_IDLE,
	.timerPeriodMs = TIMER_PERIOD_MS,
	.timer = NULL,
	.debounceTimeMs = 100,
	.repeatTimeMs   = 300,
	.longKeyTimeMs  = 1000,
	.getButtons = GetButtons,
	.onDebounceEvent = OnDebounceEvent,
};

static void OnDebounceEvent(McuDbnc_EventKinds event, uint32_t buttons) {
  switch(event) {
    case MCUDBNC_EVENT_PRESSED:
      SEGGER_printf("pressed: %d\r\n", buttons);
      EVNT_SetEvent(EVNT_SW1_PRESSED);
      break;

    case MCUDBNC_EVENT_PRESSED_REPEAT:
      SEGGER_printf("repeat: %d\r\n", buttons);
      EVNT_SetEvent(EVNT_SW1_PRESSED_REPEAT);
      break;

    case MCUDBNC_EVENT_LONG_PRESSED:
      SEGGER_printf("long pressed: %d\r\n", buttons);
      EVNT_SetEvent(EVNT_SW1_LPRESSED);

    #if PL_CONFIG_USE_GUI_KEY_NAV
      if (buttons&BTN_UP) {
        LV_ButtonEvent(LV_BTN_MASK_UP, LV_MASK_PRESSED_LONG);
      }
      if (buttons&BTN_DOWN) {
        LV_ButtonEvent(LV_BTN_MASK_DOWN, LV_MASK_PRESSED_LONG);
      }
      if (buttons&BTN_LEFT) {
        LV_ButtonEvent(LV_BTN_MASK_LEFT, LV_MASK_PRESSED_LONG);
      }
      if (buttons&BTN_RIGHT) {
        LV_ButtonEvent(LV_BTN_MASK_RIGHT, LV_MASK_PRESSED_LONG);
      }
      if (buttons&BTN_CENTER) {
        LV_ButtonEvent(LV_BTN_MASK_CENTER, LV_MASK_PRESSED_LONG);
      }
    #endif
      break;

    case MCUDBNC_EVENT_LONG_PRESSED_REPEAT:
      SEGGER_printf("long repeat: %d\r\n", buttons);
      EVNT_SetEvent(EVNT_SW1_LPRESSED_REPEAT);
      break;

    case MCUDBNC_EVENT_RELEASED:
      SEGGER_printf("released: %d\r\n", buttons);
      EVNT_SetEvent(EVNT_SW1_RELEASED);

    #if PL_CONFIG_USE_RASPY_UART
      RASPYU_OnJoystickEvent(0);
    #endif
    #if PL_CONFIG_USE_GUI_KEY_NAV
      if (buttons&BTN_UP) {
        LV_ButtonEvent(LV_BTN_MASK_UP, LV_MASK_RELEASED);
      }
      if (buttons&BTN_DOWN) {
        LV_ButtonEvent(LV_BTN_MASK_DOWN, LV_MASK_RELEASED);
      }
      if (buttons&BTN_LEFT) {
        LV_ButtonEvent(LV_BTN_MASK_LEFT, LV_MASK_RELEASED);
      }
      if (buttons&BTN_RIGHT) {
        LV_ButtonEvent(LV_BTN_MASK_RIGHT, LV_MASK_RELEASED);
      }
      if (buttons&BTN_CENTER) {
        LV_ButtonEvent(LV_BTN_MASK_CENTER, LV_MASK_RELEASED);
      }
    #endif
      break;

    default:
    case MCUDBNC_EVENT_END:
      (void)xTimerStop(data.timer, pdMS_TO_TICKS(100)); /* stop timer */
      SEGGER_printf("end: %d\r\n", buttons);
      break;
  }
}

#if McuLib_CONFIG_SDK_USE_FREERTOS
static void vTimerCallbackDebounce(TimerHandle_t pxTimer) {
  /* called with TIMER_PERIOD_MS during debouncing */
  (void)pxTimer; /* not used */
  McuDbnc_Process(&data);
}

static void StartDebounce(uint32_t buttons, bool fromISR) {
  BaseType_t res;
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;

  if (data.state==MCUDBMC_STATE_IDLE) {
    data.scanValue = buttons;
    data.state = MCUDBMC_STATE_START;
    McuDbnc_Process(&data);
    if (fromISR) {
      res = xTimerStartFromISR(data.timer, &xHigherPriorityTaskWoken);
    } else {
      res = xTimerStart(data.timer, pdMS_TO_TICKS(100));
    }
#ifdef NDEBUG
    (void)res; /* avoid compiler warning about unused variable */
#endif
    assert(res==pdPASS);
    if (fromISR) {
      portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
  }
}
#endif

#if !PL_CONFIG_USE_KBI
#if McuLib_CONFIG_SDK_USE_FREERTOS
static void PollButtons(void) {
  if (McuBtn_IsOn(btnSW2)) {
    StartDebounce(BTN_SW2, false);
  }
}

static void BtnTask(void *pv) {
  for(;;) {
    PollButtons();
    vTaskDelay(pdMS_TO_TICKS(50));
  }
}
#endif
#else
void PORTA_IRQHandler(void) { /* SW2 is on PORTA (PINS_SW2_PORT) */
  uint32_t flags;

  flags = GPIO_PortGetInterruptFlags(PINS_SW2_GPIO);
  if (flags&(1U<<PINS_SW2_PIN)) {
    GPIO_PortClearInterruptFlags(PINS_SW2_GPIO, 1U<<PINS_SW2_PIN);
    StartDebounce(BTN_SW2, true);
  }
  __DSB();
}
#endif

/* because SDK 2.3.0 does not provide this: */
static inline port_pin_config_t PORT_GetPinConfig(PORT_Type *base, uint32_t pin) {
  uint32_t addr = (uint32_t)&base->PCR[pin];
  return *(port_pin_config_t *)(addr);
}

void BTN_EnablePullup(void) {
  /* V2 has no pull-up on the board, need to turn on internal pull-up */
  McuBtn_EnablePullResistor(btnSW2);
}

void BTN_Init(void) {
  McuBtn_Config_t btnConfig;

  McuBtn_GetDefaultConfig(&btnConfig);
  btnConfig.isLowActive = true;

  btnConfig.hw.gpio = PINS_SW2_GPIO;
  btnConfig.hw.port = PINS_SW2_PORT;
  btnConfig.hw.pin = PINS_SW2_PIN;
  btnSW2 = McuBtn_InitButton(&btnConfig);
#if PL_IS_INTRO_ZUMO_K22_V2
  /* has no pull-ups on the board, need to turn on internal pull-ups */
  port_pin_config_t portConfig;

  portConfig = PORT_GetPinConfig(PINS_SW2_PORT, PINS_SW2_PIN); /* get current config */
  portConfig.pullSelect = kPORT_PullUp;
  PORT_SetPinConfig(PINS_SW2_PORT, PINS_SW2_PIN, &portConfig);
#endif
#if !PL_LOCAL_CONFIG_USE_RESET_PIN
      /* SW1: enable and turn on pull-up resistor for PTA14 (push button) */
//      PORT_PDD_SetPinPullSelect(PORTA_BASE_PTR, 14, PORT_PDD_PULL_UP);
//      PORT_PDD_SetPinPullEnable(PORTA_BASE_PTR, 14, PORT_PDD_PULL_ENABLE);
  /* this is not implemented yet for the SDK: need to disable reset functionality and enable pull-ups */
#endif
#if PL_CONFIG_USE_KBI
  PORT_SetPinInterruptConfig(PINS_SW2_PORT, PINS_SW2_PIN, kPORT_InterruptFallingEdge);
  /* SW2 is on Port A */
  NVIC_SetPriority(PORTA_IRQn, configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY);
  EnableIRQ(PORTA_IRQn);
#elif McuLib_CONFIG_SDK_USE_FREERTOS /* use task for button polling */
  if (xTaskCreate(
      BtnTask,  /* pointer to the task */
      "Btn", /* task name for kernel awareness debugging */
      500/sizeof(StackType_t), /* task stack size */
      (void*)NULL, /* optional task startup argument */
      tskIDLE_PRIORITY+4,  /* initial priority */
      (TaskHandle_t*)NULL /* optional task handle to create */
    ) != pdPASS)
  {
    for(;;){} /* error! probably out of memory */
  }
#endif
#if McuLib_CONFIG_SDK_USE_FREERTOS
  data.timer = xTimerCreate(
        "tmrDbnc", /* name */
        pdMS_TO_TICKS(TIMER_PERIOD_MS), /* period/time */
        pdTRUE, /* auto reload */
        (void*)2, /* timer ID */
        vTimerCallbackDebounce); /* callback */
  if (data.timer==NULL) {
    for(;;); /* failure! */
  }
#endif
}

void BTN_Deinit(void) {
#if PL_CONFIG_USE_KBI
  #if TINYK22_HAT_VERSION==3
    DisableIRQ(PORTB_IRQn); /* all buttons are on Port B */
  #elif TINYK22_HAT_VERSION==4 || TINYK22_HAT_VERSION==5
    DisableIRQ(PORTA_IRQn); /* left and right are on Port A */
    DisableIRQ(PORTB_IRQn); /* up, down, push are on Port B */
  #endif
#endif
  btnSW2 = McuBtn_DeinitButton(btnSW2);
}
#endif /* PL_CONFIG_USE_BUTTONS */
