#include <libopencm3/cm3/nvic.h>
#include <libopencm3/cm3/systick.h>
#include <libopencm3/cm3/scb.h>
#include <libopencm3/stm32/rcc.h>
#include <stddef.h>
#include <stdio.h>

#include "adc.h"
#include "buttons.h"
#include "usb.h"
#include "ringbuffer.h"
#include "uart.h"
#include "encoder.h"

static const uint32_t button_time = 100;

volatile uint32_t tick = 0;
RINGBUFFER_STORAGE(usb_to_uart_buf, 64)
RINGBUFFER_STORAGE(uart_to_usb_buf, 64)
RINGBUFFER_STORAGE(comm_in_buf, 64)
RINGBUFFER_STORAGE(comm_out_buf, 64)

extern uint32_t *_board_dfu_dbl_tap;

static void sys_tick_setup(void);
static void fast_reset(void);
static void start_bootloader(void);
static void erase_app(void);

static void sys_tick_setup() {
  systick_set_reload(168000/2-1);
  systick_set_clocksource(STK_CSR_CLKSOURCE_AHB);
  systick_counter_enable();
  systick_interrupt_enable();
}

void sys_tick_handler() {
  tick++;
}

void fast_reset() {
  *_board_dfu_dbl_tap = 0xf02669ef;
  scb_reset_system();
}
void start_bootloader() {
  *_board_dfu_dbl_tap = 0xf01669ef;
  scb_reset_system();
}
void erase_app() {
  *_board_dfu_dbl_tap = 0xf5e80ab4;
  scb_reset_system();
}

int main(void) {
#if 0
  rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_84MHZ]);
#else
  rcc_clock_setup_pll(&rcc_hse_12mhz_3v3[RCC_CLOCK_3V3_84MHZ]);
#endif

  RINGBUFFER_INIT(uart_to_usb_buf, 64);
  RINGBUFFER_INIT(usb_to_uart_buf, 64);
  RINGBUFFER_INIT(comm_in_buf, 64);
  RINGBUFFER_INIT(comm_out_buf, 64);


  sys_tick_setup();

  while(tick < 100)
    ;

  adc_setup();
  uart_setup();
  usb_setup();

  buttons_setup();
  encoder_setup();

  nvic_enable_irq(NVIC_USART3_IRQ);

  uint32_t last_tick = tick;
  int last_pos = 0;
  int watchdog = 1;
  int watchdog_counter = 0;
  int print_changes = 0;
  while (1) {
    /* Handle control messages through the comms CDC */
    char buf[64];
    unsigned buf_len = 0;

    const bool no_comms = watchdog && ringbuffer_empty(usb_to_uart_buf) && ringbuffer_empty(comm_in_buf);
    if (!no_comms) {
      watchdog_counter = 0;
    }
    while (tick != last_tick) {
      buttons_tick();
      last_tick++;
      watchdog_counter += no_comms;
    }
    if (watchdog_counter >= 35000) {
      fast_reset();
    }

    for (char c; ringbuffer_get(comm_in_buf, (void *)&c, 1);) {
      switch (c) {
        case 'B': // Battery
          printf("%lu", (unsigned long)adc_bat_voltage());
          break;
        case 'O': // Open
          buttons_open(button_time);
          break;
        case 'C': // Close
          buttons_close(button_time);
          break;
        case 'R': // Report
          printf("pos: %d", encoder_get());
          printf("tick: %lu", tick);
          break;
        case 'r': // toggle reporting
          print_changes = !print_changes;
          printf("pos reporting: %d", print_changes);
          break;
        case 'W': // toggle watchdog
          watchdog = !watchdog;
          printf("watchdog: %d", watchdog);
          break;
        case 'F': // Flash
          start_bootloader();
          break;
        case 'E': // Erase
          erase_app();
          break;
        case 'S': // reStart
          fast_reset();
          break;
      }
    }

    int pos = encoder_get();
    if (pos != last_pos && ringbuffer_empty(comm_out_buf)) {
      if (print_changes) {
        printf("pos: %d", pos);
      }
      last_pos = pos;
    }


    /* Send replies */
    if ((buf_len = ringbuffer_peek(comm_out_buf, &buf[0], sizeof buf))) {
      if (usb_write_cdcacm(ACM_COMM, (void *)buf, buf_len, 1)) {
        ringbuffer_skip(comm_out_buf, buf_len);
      }
    }

    /* Send any available data to the NFC CDC */
    if (!ringbuffer_empty(uart_to_usb_buf)) {
      unsigned len = ringbuffer_peek(uart_to_usb_buf, &buf[0], sizeof buf);
      if (usb_write_cdcacm(ACM_NFC, &buf[0], len, 1)) {
        ringbuffer_skip(uart_to_usb_buf, len);
      }
    }
    if (!ringbuffer_empty(usb_to_uart_buf)) {
      usart_enable_tx_interrupt(USART3);
    }

    usbd_poll(g_usbd_dev);
    __asm__("wfi");
  }
}

void usart3_isr() {
  while (USART_SR(USART3) & USART_SR_RXNE) {
    uint8_t c = usart_recv(USART3);
    ringbuffer_add(uart_to_usb_buf, (void *)&c, 1);
  }
  while (USART_SR(USART3) & USART_SR_TXE) {
    if (ringbuffer_empty(usb_to_uart_buf)) {
      usart_disable_tx_interrupt(USART3);
      break;
    } else {
      uint8_t c;
      ringbuffer_get(usb_to_uart_buf, &c, 1);
      usart_send(USART3, c);
    }
  }
}