#include <stdint.h>
#include <stm32f4xx.h>
#include "exti.h"
#include "pin.h"
#include "timer.h"
#include "stm32f401xe.h"

#define LED_PIN 5
#define LED_GPIO GPIOA
#define BUTTON_PIN 13
#define BUTTON_GPIO GPIOC
#define BUTTON_GPIO_ID 2 // A 0 B 1 C 2

void hard_fault_handler() {
  while(1) {}
}

void usage_fault_handler() {
  while(1) {}
}

void nmi_handler() {
  while(1) {}
}

void bus_fault_handler() {
  while(1) {}
}


/*----------------------------------------------------------------------------
 * SystemCoreClockConfigure: configure SystemCoreClock using HSI
                             (HSE is not populated on Nucleo board)
 *----------------------------------------------------------------------------*/
void SystemCoreClockSetHSI(void) {

  RCC->CR |= ((uint32_t)RCC_CR_HSION);                     // Enable HSI
  while ((RCC->CR & RCC_CR_HSIRDY) == 0);                  // Wait for HSI Ready

  RCC->CFGR = RCC_CFGR_SW_HSI;                             // HSI is system clock
  while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI);  // Wait for HSI used as system clock

  FLASH->ACR  = FLASH_ACR_PRFTEN;                          // Enable Prefetch Buffer
  FLASH->ACR |= FLASH_ACR_ICEN;                            // Instruction cache enable
  FLASH->ACR |= FLASH_ACR_DCEN;                            // Data cache enable
  FLASH->ACR |= FLASH_ACR_LATENCY_5WS;                     // Flash 5 wait state

  RCC->CFGR |= RCC_CFGR_HPRE_DIV1;                         // HCLK = SYSCLK
  RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;                        // APB1 = HCLK/4
  RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;                        // APB2 = HCLK/2

  RCC->CR &= ~RCC_CR_PLLON;                                // Disable PLL

  // HSI = 16 MHz
  // PLL configuration:  VCO = HSI/M * N,  Sysclk = VCO/P
  // => Sysclk = 48 MHz, APB1 = 12 MHz, APB2 = 24 MHz
  // Since divider for APB1 is != 1, timer clock is 24 MHz
  RCC->PLLCFGR = ( 16ul                   |                // PLL_M =  16
                 (384ul <<  6)            |                // PLL_N = 384
                 (  3ul << 16)            |                // PLL_P =   8
                 (RCC_PLLCFGR_PLLSRC_HSI) |                // PLL_SRC = HSI
                 (  8ul << 24)             );              // PLL_Q =   8

  RCC->CR |= RCC_CR_PLLON;                                 // Enable PLL
  while((RCC->CR & RCC_CR_PLLRDY) == 0) __NOP();           // Wait till PLL is ready

  RCC->CFGR &= ~RCC_CFGR_SW;                               // Select PLL as system clock source
  RCC->CFGR |=  RCC_CFGR_SW_PLL;
  while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);  // Wait till PLL is system clock src
}

pin_t led;
exti_t button_exti;
pin_t button;
timer_t toggle_timer;
timer_t debounce_timer;

// the timer runs at 200 Hz
/* uint16_t timer_values[3] = { 20, 100, 300 }; */
uint16_t timer_values[3] = { 40, 200, 600 };
uint8_t current_timer_value = 1;

void main()
{
  // TODO: enable tim2, exti, syscfg
  SystemCoreClockSetHSI();
  RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOCEN;
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN;
  RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;

  volatile uint32_t dummy;
  dummy = (RCC->AHB1ENR);
  dummy = (RCC->AHB1ENR);
  dummy = (RCC->APB1ENR);
  dummy = (RCC->APB1ENR);
  dummy = (RCC->APB2ENR);
  dummy = (RCC->APB2ENR);

  pin_init(&led, LED_GPIO, LED_PIN);
  pin_into_output_pushpull(&led);
  pin_speed(&led, LOW_SPEED);

  pin_init(&button, BUTTON_GPIO, BUTTON_PIN);
  pin_into_input(&button);

  exti_init(&button_exti, BUTTON_PIN, EXTI, SYSCFG);
  exti_external_interrupt(&button_exti, BUTTON_GPIO_ID);
  exti_falling_interrupt(&button_exti);
  exti_clear_interrupt(&button_exti);
  exti_enable_interrupt(&button_exti);

  // configure toggle timer
  timer_init(&toggle_timer, TIM2, 2);
  timer_set_refresh(&toggle_timer, timer_values[0]);
  timer_configure(&toggle_timer, 1, 60000, 0);
  timer_enable_interrupt(&toggle_timer);
  timer_enable(&toggle_timer);

  // configure debounce timer
  timer_init(&debounce_timer, TIM3, 3);
  timer_set_refresh(&debounce_timer, 10);
  timer_configure(&debounce_timer, 1, 60000, 1);
  timer_enable_interrupt(&debounce_timer);

  __enable_irq();
  while(1) { __WFI(); }
}

// Toggle led timer
void TIM2_handler(void)
{
  timer_clear_interrupt(&toggle_timer);
  pin_toggle(&led);
}

// Debounce button timer
void TIM3_handler(void)
{
  timer_clear_interrupt(&debounce_timer);
  if (!pin_read(&button)) {
    timer_set_refresh(&toggle_timer,
                      timer_values[current_timer_value++]);
    current_timer_value %= sizeof(timer_values) / sizeof(uint16_t);
  }
}

void EXTI15_10_handler(void)
{
  if (exti_is_interrupt(&button_exti)) {
    exti_clear_interrupt(&button_exti);
    timer_set_counter(&debounce_timer, 0);
    timer_enable(&debounce_timer);
  }
}
