#include "timer.h"
#include <stdint.h>
#include <stdbool.h>
#include <stm32f4xx.h>
#include "exti.h"
#include "pin.h"
#include "registers.h"
#include "display.h"
#include "delay.h"
#include "uart.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
}

#define DIGITS 4

display_t display;
timer_t display_timer;
uart_t uart;

typedef struct {
  char digits[4];
  uint8_t digits_count;
  uint8_t dot;
  bool error;
} converted_number_t;

converted_number_t convert_number(char* data);

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

  RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN;
  RCC->APB1ENR |= RCC_APB1ENR_TIM3EN | RCC_APB1ENR_USART2EN;
  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

  {
    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, DIGITS, pin_data, pin_sftclk, pin_strobe);
  }

  {
    pin_t pin_rx;
    pin_t pin_tx;

    pin_init(&pin_tx, GPIOA, 2);
    pin_init(&pin_rx, GPIOA, 3);

    pin_into_alternate(&pin_rx, 7);
    pin_into_alternate(&pin_tx, 7);

    usart_init(&uart, USART2, 2);
    usart_configure_speed(&uart, 12000000, 9600);
    usart_configure_uart(&uart, true, USART_WORD_8_BITS, true,
                         USART_PARITY_EVEN, USART_STOP_BIT_ONE);
    usart_configure_receiver(&uart, true);
  }

  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();

  char buffer[64];
  uint16_t idx = 0;

  // Application
  while (1) {
    if (spi_receive(&uart, &buffer[idx], 1) == 0) {
      continue;
    }

    if (buffer[idx] != '\n') {
      idx++;
      continue;
    }

    buffer[idx] = '\0';
    idx = 0;

    converted_number_t converted = convert_number(buffer);

    if (converted.error) {
      display_digits_set(&display, "Err\0");
      display_dots(&display, 0);
    } else {
      uint8_t empty_count = DIGITS - converted.digits_count;
      display_dots(&display, converted.dot << empty_count);
      for (uint8_t digit = 0; digit < DIGITS; digit++) {
        char value = '\0';

        if (digit >= empty_count) {
          value = converted.digits[digit - empty_count];
        }

        display_digit_set(&display, digit, value);
      }

    }

    /* DELAY_US(100); */
  }
}

converted_number_t convert_number(char* data) {
  converted_number_t number;
  bool found_dot = false;
  uint8_t curr_digit = 0;

  number.dot = 0;
  number.error = false;

  while (*data != '\0' && curr_digit < DIGITS) {
    char curr = *(data++);

    // handle first dot
    if (curr == '.' && !found_dot) {
      found_dot = true;

      // if dot first, assume zero.
      if (curr_digit == 0) {
        number.digits[curr_digit++] = '0';
      }

      number.dot = 1 << (curr_digit - 1);
      continue;

      // handle digit
    } else if (curr >= '0' && curr <= '9') {
      number.digits[curr_digit++] = curr;

      // handle others
    } else {
      number.error = true;
      break;
    }

  }

  if (*data != '\0') {
    number.error = true;
  }

  number.digits_count = curr_digit;

  return number;
}

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

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