#include "spi_dma.h"

#include "ch32v003fun.h"
#include "debug.h"

volatile transfer_state_t tx_state = IDLE;
volatile transfer_state_t rx_state = IDLE;

static const uint8_t tx_dummy_byte = 0xFF;
static uint8_t rx_dummy;  // Static RX dummy buffer

static inline void wait_for_transfer_complete(void) {
  while (tx_state != TX_DONE || rx_state != RX_DONE);
}

// static inline void spi_wait_not_busy(void) {
//   while ((SPI1->STATR & SPI_STATR_BSY) != 0);
// }

void set_transfer_states(void) {
  tx_state = TRANSMITTING;
  rx_state = RECEIVING;
}

void clear_transfer_states(void) {
  tx_state = IDLE;
  rx_state = IDLE;
}

void DMA1_HandleIrq(uint32_t channel_interrupt,
                    volatile transfer_state_t* state,
                    transfer_state_t active_state,
                    transfer_state_t done_state) {
  if (DMA1->INTFR & channel_interrupt) {  // check if DMA interrupt occurred
    DMA1->INTFCR = channel_interrupt;     // clear the interrupt flag
    if (*state == active_state) {
      *state = done_state;
    }
  }
}
void DMA1_Channel3_IRQHandler(void) __attribute__((interrupt));
void DMA1_Channel2_IRQHandler(void) __attribute__((interrupt));

void DMA1_Channel3_IRQHandler(void) {
  DMA1_HandleIrq(DMA1_IT_TC3, &tx_state, TRANSMITTING, TX_DONE);
}

void DMA1_Channel2_IRQHandler(void) {
  DMA1_HandleIrq(DMA1_IT_TC2, &rx_state, RECEIVING, RX_DONE);
}

void configure_dma(DMA_Channel_TypeDef* tx_channel, uint32_t tx_addr,
                   DMA_Channel_TypeDef* rx_channel, uint32_t rx_addr,
                   int rx_circular, uint16_t len) {
  // disable DMA channels
  tx_channel->CFGR &= ~DMA_CFGR1_EN;
  rx_channel->CFGR &= ~DMA_CFGR1_EN;

  // set memory addresses and transfer count
  tx_channel->MADDR = tx_addr;
  tx_channel->CNTR = len;
  rx_channel->MADDR = rx_addr;
  rx_channel->CNTR = len;

  // set or clear the circular mode for RX
  rx_channel->CFGR =
      (rx_channel->CFGR & ~DMA_CFGR1_CIRC) | (rx_circular ? DMA_CFGR1_CIRC : 0);

  // enable DMA channels
  tx_channel->CFGR |= DMA_CFGR1_EN;
  rx_channel->CFGR |= DMA_CFGR1_EN;
}

void spidma_read_buffer(uint8_t* buf, uint16_t len) {
  set_transfer_states();
  configure_dma(DMA1_Channel3, (uint32_t)&tx_dummy_byte, DMA1_Channel2,
                (uint32_t)buf, 0, len);

  wait_for_transfer_complete();
  clear_transfer_states();
}

void spidma_write_buffer(uint8_t* buf, uint16_t len) {
  set_transfer_states();
  configure_dma(DMA1_Channel3, (uint32_t)buf, DMA1_Channel2, (uint32_t)rx_dummy,
                1, len);

  wait_for_transfer_complete();
  clear_transfer_states();
}

uint8_t spi_transfer(uint8_t data) {
  while (!(SPI1->STATR & SPI_STATR_TXE));
  SPI1->DATAR = data;

  while (!(SPI1->STATR & SPI_STATR_RXNE));
  return SPI1->DATAR;
}

uint8_t spi_read_byte() { return spi_transfer(tx_dummy_byte); }

void spi_write_byte(uint8_t byte) { spi_transfer(byte); }

void spi_select(void) {
  GPIOA->BCR = (1 << 4);  // Set PA4 (CS) low
}

void spi_unselect(void) {
  GPIOA->BSHR = (1 << 4);  // Set PA4 (CS) high
}

void init_spidma(void) {
  // Enable clock for GPIOA and SPI1
  RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1;
  // Enable clock for DMA1
  RCC->AHBPCENR |= RCC_AHBPeriph_DMA1;

  // SPI1 Pin Configuration
  // CS on PA4, 10MHz Output, open-drain
  GPIOA->CFGLR &= ~(0xf << (4 * 4));
  GPIOA->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD) << (4 * 4);
  // SCK on PA5, 10MHz Output, alt func, push-pull
  GPIOA->CFGLR &= ~(0xf << (4 * 5));
  GPIOA->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 5);
  // MOSI on PA7, 10MHz input, floating
  GPIOA->CFGLR &= ~(0xf << (4 * 6));
  GPIOA->CFGLR |= (GPIO_CNF_IN_FLOATING) << (4 * 6);
  // MISO on PA6, 10MHz Output, alt func, push-pull
  GPIOA->CFGLR &= ~(0xf << (4 * 7));
  GPIOA->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 7);

  // Reset and Configure SPI1
  SPI1->CTLR1 = 0;  // Clear CTLR1 initially

  SPI1->CTLR1 = SPI_Mode_Master |                  // Master mode
                SPI_Direction_2Lines_FullDuplex |  // Full duplex
                SPI_DataSize_8b |                  // 8-bit data frame format
                SPI_CPOL_Low |                     // Clock polarity
                SPI_CPHA_1Edge |                   // Clock phase
                SPI_BaudRatePrescaler_8 |          // Baud rate prescaler
                SPI_NSS_Soft;                      // Software NSS management

  // Enable TX and RX DMA
  SPI1->CTLR2 = SPI_CTLR2_TXDMAEN | SPI_CTLR2_RXDMAEN;

  SPI1->I2SCFGR &= SPI_Mode_Select;  // Disable I2S mode
  SPI1->CTLR1 |= SPI_CTLR1_SPE;      // Enable SPI

  // DMA setup
  DMA1_Channel3->PADDR = (uint32_t)&SPI1->DATAR;  // TX Channel
  DMA1_Channel3->CFGR = DMA_M2M_Disable | DMA_Priority_VeryHigh |
                        DMA_MemoryDataSize_Byte | DMA_PeripheralDataSize_Byte |
                        DMA_MemoryInc_Enable | DMA_PeripheralInc_Disable |
                        DMA_Mode_Normal | DMA_DIR_PeripheralDST | DMA_IT_TC;

  DMA1_Channel2->PADDR = (uint32_t)&SPI1->DATAR;  // RX Channel
  DMA1_Channel2->CFGR = DMA_M2M_Disable | DMA_Priority_VeryHigh |
                        DMA_MemoryDataSize_Byte | DMA_PeripheralDataSize_Byte |
                        DMA_MemoryInc_Enable | DMA_PeripheralInc_Disable |
                        DMA_Mode_Normal | DMA_DIR_PeripheralSRC | DMA_IT_TC;

  NVIC_SetPriority(DMA1_Channel2_IRQn, 0);
  NVIC_SetPriority(DMA1_Channel3_IRQn, 0);
  NVIC_EnableIRQ(DMA1_Channel3_IRQn);
  NVIC_EnableIRQ(DMA1_Channel2_IRQn);
}
