#include "ost_hal.h"

#include "samd21.h"
#include "uart.h"
#include "hal_gpio.h"
#include "spi_master.h"
#include "uart.h"

#include "string.h"
#include "debug.h"
#include "st7789.h"
#include "spi_display.h"

#include "arduino_platform.h"

static volatile uint32_t msTicks = 0;

//-----------------------------------------------------------------------------
#define PERIOD_FAST 1200
#define PERIOD_SLOW 500

HAL_GPIO_PIN(LED, B, 8) // Built-in LED
HAL_GPIO_PIN(BUTTON, A, 15)

HAL_GPIO_PIN(CD, A, 27);

//-----------------------------------------------------------------------------
void timer_set_period(uint16_t i)
{
  TC3->COUNT16.CC[0].reg = ((F_CPU / 1000ul) * i) / 256;
  TC3->COUNT16.COUNT.reg = 0;
}

//-----------------------------------------------------------------------------
void ost_tasker_timer_callback();

void TC3_Handler(void)
{
  if (TC3->COUNT16.INTFLAG.reg & TC_INTFLAG_MC(1))
  {
    // HAL_GPIO_LED_toggle();
    ost_tasker_timer_callback();
    TC3->COUNT16.INTFLAG.reg = TC_INTFLAG_MC(1);
  }
}

// Setup TC4/TC5 in 32-bit mode with 100kHz tick
void timer_32_init()
{

  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(30) | // Divide the 48MHz system clock by 3 = 1.6MHz
                     GCLK_GENDIV_ID(5);    // Set division on Generic Clock Generator (GCLK) 5
  while (GCLK->STATUS.bit.SYNCBUSY)
    ; // Wait for synchronization

  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |         // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |       // Enable GCLK 5
                      GCLK_GENCTRL_SRC_DFLL48M | // Set the clock source to 48MHz
                      GCLK_GENCTRL_ID(5);        // Set clock source on GCLK 5
  while (GCLK->STATUS.bit.SYNCBUSY)
    ; // Wait for synchronization

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |     // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK5 | // ....on GCLK5
                      GCLK_CLKCTRL_ID_TC4_TC5; // Feed the GCLK5 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY)
    ; // Wait for synchronization

  TC4->COUNT32.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 1.6MHz/16 = 100kHz
                            TC_CTRLA_PRESCSYNC_PRESC | // Reload timer on next prescaler clock
                            TC_CTRLA_MODE_COUNT32;     // Set the TC4 timer to 32-bit mode in conjuction with timer TC5

  TC4->COUNT32.CTRLA.bit.ENABLE = 1; // Enable TC4
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY)
    ; // Wait for synchronization

  TC4->COUNT32.READREQ.reg = TC_READREQ_RCONT |     // Enable a continuous read request
                             TC_READREQ_ADDR(0x10); // Offset of the 32-bit COUNT register
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY)
    ; // Wait for (read) synchronization
}

//-----------------------------------------------------------------------------
static void timer_init(void)
{
  PM->APBCMASK.reg |= PM_APBCMASK_TC3;

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(TC3_GCLK_ID) |
                      GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(0);

  TC3->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16 | TC_CTRLA_WAVEGEN_MFRQ |
                           TC_CTRLA_PRESCALER_DIV256 | TC_CTRLA_PRESCSYNC_RESYNC;

  TC3->COUNT16.COUNT.reg = 0;

  // timer_set_period(PERIOD_SLOW);

  TC3->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;

  TC3->COUNT16.INTENSET.reg = TC_INTENSET_MC(1);
  NVIC_EnableIRQ(TC3_IRQn);
}

void arduino_mkrzero_init(void);

// ----------------------------------------------------------------------------
// SYSTEM HAL
// ----------------------------------------------------------------------------

#if 0
// Cortex-M0 does not have DWT register accessible from CPU

// Register base address
#define DWT_CR *(uint32_t *)0xE0001000
#define DWT_CYCCNT *(uint32_t *)0xE0001004
#define DEM_CR *(uint32_t *)0xE000EDFC
 
// Define the required enable bit
#define DEM_CR_TRCENA (1 << 24)
#define DWT_CR_CYCCNTENA (1 << 0)


static void cortex_m_cycle_counter_init()
{
  DEM_CR |= (uint32_t)DEM_CR_TRCENA;
  DWT_CYCCNT = (uint32_t)0u;
  DWT_CR |= (uint32_t)DWT_CR_CYCCNTENA;
}

static uint32_t cortex_m_cycle_get_counter()
{
    return (uint32_t)DWT_CYCCNT;
}

static void cortex_m_delay_ms(volatile uint32_t milliseconds)
{
  uint32_t au32_initial_ticks = cortex_m_cycle_get_counter();
  uint32_t au32_ticks = (F_CPU / 1000000);
  milliseconds *= au32_ticks;
  while ((cortex_m_cycle_get_counter() - au32_initial_ticks) < milliseconds);
}
#endif

// Set up RTC for counting
void setupRTC()
{
  // configure the 32768 Hz oscillator
  // SYSCTRL->XOSC32K.reg =  SYSCTRL_XOSC32K_ONDEMAND |
  //                         SYSCTRL_XOSC32K_RUNSTDBY |
  //                         SYSCTRL_XOSC32K_EN32K |
  //                         SYSCTRL_XOSC32K_XTALEN |
  //                         SYSCTRL_XOSC32K_STARTUP(6) |
  //                         SYSCTRL_XOSC32K_ENABLE;

  // attach peripheral clock to 32768 Hz oscillator (1 tick = 1/32768 second)
  // GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(0);
  // GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_ID(2));
  GCLK->CLKCTRL.reg = (uint32_t)((GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK5 | (RTC_GCLK_ID << GCLK_CLKCTRL_ID_Pos)));

  // disable RTC if it is enabled, to allow reconfiguration
  RTC->MODE0.CTRL.reg &= ~RTC_MODE0_CTRL_ENABLE;

  // trigger RTC software reset
  RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_SWRST;

  // configure RTC in mode 0 (32-bit)
  RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_PRESCALER_DIV1 | RTC_MODE0_CTRL_MODE_COUNT32;

  // set match_clear bit(7) to clear counter for periodic interrupts
  // this will probably screw up anything else using the RTC
  // See Table 18-1. MODE0 - Mode Register Summary
  RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_MATCHCLR;

  // enter freq correction here as sign (bit7) and magnitude (bits 6-0)
  RTC_FREQCORR_VALUE(0x00); // adjust if necessary

  // initialize counter & compare values
  RTC->MODE0.COUNT.reg = 0;
  RTC->MODE0.COMP[0].reg = 32768 * 5; // 32768 * 2 seconds

  // enable the CMP0 interrupt in the RTC
  RTC->MODE0.INTENSET.reg |= RTC_MODE0_INTENSET_CMP0;

  // re-enable RTC after reconfiguration and initial scheduling
  RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_ENABLE;

  // enable RTC interrupt in controller
  NVIC_SetPriority(RTC_IRQn, 0x00);
  NVIC_EnableIRQ(RTC_IRQn);

  // change USB/EIC priority to 0x01 to prevent preempting the RTC
  // NVIC_SetPriority(USB_IRQn, 0x01);
  // NVIC_SetPriority(EIC_IRQn, 0x01);

  // enable continuous synchronization
  while (RTC->MODE0.STATUS.bit.SYNCBUSY)
    ;
  RTC->MODE0.READREQ.reg = RTC_READREQ_RREQ | RTC_READREQ_RCONT | 0x0010;
  while (RTC->MODE0.STATUS.bit.SYNCBUSY)
    ;
} // end setupRTC

void RTC_Handler(void)
{
  if (RTC->MODE0.INTFLAG.bit.CMP0)
  {
    HAL_GPIO_LED_toggle();
    RTC->MODE0.INTFLAG.reg = 0xFF; // Clear all interrupts
    // RTC->MODE0.INTFLAG.reg |= 0x81; // Clear CMP0 and OVF interrupts only
  }
}

void ost_system_delay_ms(uint32_t delay)
{
  uint32_t t0 = TC4->COUNT32.COUNT.reg;

  delay *= 100;
  bool quit = false;
  do
  {
    uint32_t t1 = TC4->COUNT32.COUNT.reg;
    uint32_t diff = t1 - t0;
    quit = diff >= delay;
  } while (!quit);
}

void ost_system_initialize()
{
  arduino_mkrzero_init();

  // cortex_m_cycle_counter_init();

  // SysTick_Config(F_CPU / 1000);

  //  timer_init();
  timer_32_init();
  uart_init(115200);

  HAL_GPIO_LED_out();
  HAL_GPIO_LED_clr();

  //  timer_set_period(PERIOD_FAST);

  // Initialize SPI for SD Card
  spi_init(250000, 0);

  // SPI for display
  spi_display_init(10000000, 0);

  HAL_GPIO_CD_in();
  HAL_GPIO_CD_pullup();

  //   setupRTC();

  //   sd_card_test();

  /*
    uart_puts("\r\nHello, world!\r\n");

    HAL_GPIO_LED_out();
    HAL_GPIO_LED_clr();

    HAL_GPIO_BUTTON_in();
    HAL_GPIO_BUTTON_pullup();

    while (1)
    {
      if (HAL_GPIO_BUTTON_read())
        cnt = 0;
      else if (cnt < 5001)
        cnt++;

      if (5000 == cnt)
      {
        fast = !fast;
        timer_set_period(fast ? PERIOD_FAST : PERIOD_SLOW);
        uart_putc('.');
      }
    }*/
}

void system_putc(char ch)
{
  uart_putc(ch);
}

// void SysTick_Handler()
// {
//     msTicks++;
// }

void system_led_write(uint8_t value)
{
  HAL_GPIO_LED_write(value);
}

// ----------------------------------------------------------------------------
// SDCARD HAL
// ----------------------------------------------------------------------------
void sdcard_set_slow_clock()
{
  spi_init(100000, 0);
}

void sdcard_set_fast_clock()
{
  spi_init(800000, 0);
}

void ost_hal_sdcard_cs_high()
{
  spi_ss(1);
}

void ost_hal_sdcard_cs_low()
{
  spi_ss(0);
}

uint8_t ost_hal_sdcard_spi_transfer(uint8_t dat)
{
  return spi_transfer(dat);
}

void sdcard_spi_recv_multi(uint8_t *buff, uint32_t btr)
{
  for (uint32_t i = 0; i < btr; i++)
  {
    buff[i] = spi_transfer(buff[i]);
  }
}

// ----------------------------------------------------------------------------
// DISPLAY HAL
// ----------------------------------------------------------------------------
#define DC_PIN GPIO_PIN_1
#define DC_GPIO_PORT GPIOA
#define DC_GPIO_CLK RCU_GPIOA

void ost_display_initialize()
{
  ST7789_Init();
  ST7789_Fill_Color(MAGENTA);
}

void ost_display_dc_high()
{
  spi_display_dc(1);
}

void ost_display_dc_low()
{
  spi_display_dc(0);
}

void ost_display_ss_high()
{
  spi_display_ss(1);
}

void ost_display_ss_low()
{
  spi_display_ss(0);
}

void ost_display_draw_h_line(uint16_t y, uint8_t *pixels, uint8_t *palette)
{
  ST7789_Fill_Line(y, pixels, palette);
}

uint8_t ost_display_transfer_byte(uint8_t dat)
{
  return spi_display_transfer(dat);
}

void ost_display_transfer_multi(uint8_t *buff, uint32_t btr)
{
  for (uint32_t i = 0; i < btr; i++)
  {
    buff[i] = spi_display_transfer(buff[i]);
  }
}

/*
 * Arduino Zero board initialization
 *
 * Good to know:
 *   - At reset, ResetHandler did the system clock configuration. Core is running at 48MHz.
 *   - Watchdog is disabled by default, unless someone plays with NVM User page
 *   - During reset, all PORT lines are configured as inputs with input buffers, output buffers and pull disabled.
 */
void arduino_mkrzero_init(void)
{
  // // Set Systick to 1ms interval, common to all Cortex-M variants
  // if ( SysTick_Config( SystemCoreClock / 1000 ) )
  // {
  //   // Capture error
  //   while ( 1 ) ;
  // }
  NVIC_SetPriority(SysTick_IRQn, (1 << __NVIC_PRIO_BITS) - 2); /* set Priority for Systick Interrupt (2nd lowest) */

  // Clock PORT for Digital I/O
  PM->APBBMASK.reg |= PM_APBBMASK_PORT;
  //
  //  // Clock EIC for I/O interrupts
  PM->APBAMASK.reg |= PM_APBAMASK_EIC;

  // Clock SERCOM for Serial
  PM->APBCMASK.reg |= PM_APBCMASK_SERCOM0 | PM_APBCMASK_SERCOM1 | PM_APBCMASK_SERCOM2 | PM_APBCMASK_SERCOM3 | PM_APBCMASK_SERCOM4 | PM_APBCMASK_SERCOM5;

  // Clock TC/TCC for Pulse and Analog
  PM->APBCMASK.reg |= PM_APBCMASK_TCC0 | PM_APBCMASK_TCC1 | PM_APBCMASK_TCC2 | PM_APBCMASK_TC3 | PM_APBCMASK_TC4 | PM_APBCMASK_TC5;

  // Clock ADC/DAC for Analog
  PM->APBCMASK.reg |= PM_APBCMASK_ADC | PM_APBCMASK_DAC;

// Defining VERY_LOW_POWER breaks Arduino APIs since all pins are considered INPUT at startup
// However, it really lowers the power consumption by a factor of 20 in low power mode (0.03mA vs 0.6mA)
#ifdef VERY_LOW_POWER
  // Setup all pins (digital and analog) in INPUT mode (default is nothing)
  for (uint32_t ul = 0; ul < NUM_DIGITAL_PINS; ul++)
  {
    pinMode(ul, INPUT);
  }
#endif

  // Initialize Analog Controller
  // Setting clock
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
    ;

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_ADC) | // Generic Clock ADC
                      GCLK_CLKCTRL_GEN_GCLK0 |   // Generic Clock Generator 0 is source
                      GCLK_CLKCTRL_CLKEN;

  while (ADC->STATUS.bit.SYNCBUSY == 1)
    ; // Wait for synchronization of registers between the clock domains

  ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV512 | // Divide Clock by 512.
                   ADC_CTRLB_RESSEL_10BIT;      // 10 bits resolution as default

  ADC->SAMPCTRL.reg = 0x3f; // Set max Sampling Time Length

  while (ADC->STATUS.bit.SYNCBUSY == 1)
    ; // Wait for synchronization of registers between the clock domains

  ADC->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND; // No Negative input (Internal Ground)

  // Averaging (see datasheet table in AVGCTRL register description)
  ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 |  // 1 sample only (no oversampling nor averaging)
                     ADC_AVGCTRL_ADJRES(0x0ul); // Adjusting result by 0

  // analogReference( AR_DEFAULT ) ; // Analog Reference is AREF pin (3.3v)

  // Initialize DAC
  // Setting clock
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
    ;
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_DAC) | // Generic Clock ADC
                      GCLK_CLKCTRL_GEN_GCLK0 |   // Generic Clock Generator 0 is source
                      GCLK_CLKCTRL_CLKEN;

  while (DAC->STATUS.bit.SYNCBUSY == 1)
    ;                                      // Wait for synchronization of registers between the clock domains
  DAC->CTRLB.reg = DAC_CTRLB_REFSEL_AVCC | // Using the 3.3V reference
                   DAC_CTRLB_EOEN;         // External Output Enable (Vout)
}
