#include <stdint.h>
#include <stm32f4xx.h>
#include "exti.h"
#include "pin.h"
#include "timer.h"
#include "registers.h"
#include "display.h"
#include "delay.h"

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
}

display_t display;

timer_t stopwatch_timer;
timer_t display_timer;
timer_t startstop_debounce_timer;

exti_t startstop_exti;
pin_t startstop_button;
exti_t null_button;

void main()
{
  // Setup
  SystemCoreClockSetHSI();
  systick_configure();

  RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN;
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM4EN;
  RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;

  // The timer clocks are at 12 MHz.
  // For 100 Hz, the prescaler would be 120 000. That is too big,
  // so 200 Hz is chosen. From there it will have to be divided by 2 by application.
  // For 6800 Hz, 1765 prescaler will be used, leading to 6798... Hz

  // Initialize timer for display at interrupt frequency
  //  digit update frequency * digits * 34
  //  aim for 100 Hz * 4 * 17 = 6 800 Hz

  exti_init(&null_button, 4, EXTI, SYSCFG);
  exti_external_interrupt(&null_button, EXTI_GPIOA);
  exti_rising_interrupt(&null_button);
  exti_enable_interrupt(&null_button);

  exti_init(&startstop_exti, 0, EXTI, SYSCFG);
  exti_external_interrupt(&startstop_exti, EXTI_GPIOB);
  exti_rising_interrupt(&startstop_exti);
  exti_enable_interrupt(&startstop_exti);

  pin_init(&startstop_button, GPIOB, 0);
  pin_into_output_pushpull(&startstop_button);
  pin_speed(&startstop_button, LOW_SPEED);

  {
    pin_t pin_data;
    pin_t pin_sftclk;
    pin_t pin_strobe;

    pin_init(&pin_data, GPIOA, 9);
    pin_init(&pin_sftclk, GPIOA, 8);
    pin_init(&pin_strobe, GPIOB, 5);

    pin_into_output(&pin_data);
    pin_into_output(&pin_sftclk);
    pin_into_output(&pin_strobe);

    display_init(&display, 4, pin_data, pin_sftclk, pin_strobe);
    display_dots(&display, 1 << 1);
  }

  timer_init(&stopwatch_timer, TIM2, 2);
  timer_configure(&stopwatch_timer, 0, 60000, 0);
  timer_set_refresh(&stopwatch_timer, 10000*4);

  timer_init(&startstop_debounce_timer, TIM4, 2);
  timer_configure(&startstop_debounce_timer, 0, 60000, 1);
  timer_set_refresh(&startstop_debounce_timer, 10);
  timer_enable_interrupt(&startstop_debounce_timer);

  timer_init(&display_timer, TIM3, 3);
  timer_configure(&display_timer, 0, 500, 0);
  timer_set_refresh(&display_timer, 4);
  timer_enable_interrupt(&display_timer);
  timer_enable(&display_timer);

  __enable_irq();

  // Application
  while (1) {
    uint32_t count = timer_count(&stopwatch_timer) >> 2;

    display_enable_digit(&display, 0,
                         (count % display.max_value) >= 1000);
    display_number_set(&display, count);

    /* DELAY_US(100); */
  }
}

void null_timer(void) {
  timer_set_counter(&stopwatch_timer, 0);
}

void toggle_timer(void) {
  if (timer_is_enabled(&stopwatch_timer)) {
    timer_disable(&stopwatch_timer);
  } else {
    timer_enable(&stopwatch_timer);
  }
}

void EXTI0_handler(void) {
  exti_clear_interrupt(&startstop_exti);
  timer_set_counter(&startstop_debounce_timer, 0);
  timer_enable(&startstop_debounce_timer);
}

void EXTI4_handler(void) {
  exti_clear_interrupt(&null_button);
  null_timer();
}

/* void TIM2_handler(void) { */
/*   // The stopwatch timer */
/* } */

void TIM3_handler(void) {
  timer_clear_interrupt(&display_timer);
  display_update(&display);
}

void TIM4_handler(void) {
  timer_clear_interrupt(&startstop_debounce_timer);
  if (pin_read(&startstop_button)) {
    toggle_timer();
  }
}
