#include "spi.h"
#include "registers.h"
#include "stm32f401xe.h"

void spi_init(spi_t* spi, pin_t csn, SPI_TypeDef* peripheral, uint8_t idx) {
  spi->periph = peripheral;
  spi->idx = idx;
  spi->csn = csn;
}

void spi_master_configure_speed(spi_t *spi,
                                uint8_t divider) {
  reg_write_bits(&spi->periph->CR1, divider << SPI_CR1_BR_Pos, SPI_CR1_BR_Msk);
}

void spi_master_configure(spi_t *spi, bool sw_nss, bool clock_polarity,
                          bool clock_phase, spi_frame_orientation_t orientation, spi_frame_format_t format) {
  reg_write_bits(&spi->periph->CR1,
                 (sw_nss << SPI_CR1_SSM_Pos) | (format << SPI_CR1_DFF_Pos) |
                     (orientation << SPI_CR1_LSBFIRST_Pos) |
                     (0 << SPI_CR1_BIDIMODE_Pos) | (1 << SPI_CR1_MSTR_Pos) |
                 (clock_polarity << SPI_CR1_CPOL_Pos) | (clock_phase << SPI_CR1_CPHA_Pos),
                 SPI_CR1_SSM_Msk | SPI_CR1_DFF_Msk | SPI_CR1_LSBFIRST_Msk | SPI_CR1_BIDIMODE_Msk | SPI_CR1_MSTR_Msk | SPI_CR1_CPOL_Msk | SPI_CR1_CPHA_Msk);

  spi->periph->CR2 |= SPI_CR2_SSOE | (1 << 3);
}
void spi_master_enable(spi_t *spi, bool enable) {
  reg_write_bits(&spi->periph->CR1, enable << SPI_CR1_SPE_Pos, SPI_CR1_SPE);
}

void spi_slave_configure(spi_t *spi, bool sw_nss, bool clock_polarity,
                          bool clock_phase, spi_frame_orientation_t orientation, spi_frame_format_t format) {
  reg_write_bits(&spi->periph->CR1,
                 (sw_nss << SPI_CR1_SSM_Pos) | (format << SPI_CR1_DFF_Pos) |
                     (orientation << SPI_CR1_LSBFIRST_Pos) |
                     (0 << SPI_CR1_BIDIMODE_Pos) | (0 << SPI_CR1_MSTR_Pos) |
                 (clock_polarity << SPI_CR1_CPOL_Pos) | (clock_phase << SPI_CR1_CPHA_Pos),
                 SPI_CR1_SSM_Msk | SPI_CR1_DFF_Msk | SPI_CR1_LSBFIRST_Msk | SPI_CR1_BIDIMODE_Msk | SPI_CR1_MSTR_Msk | SPI_CR1_CPOL_Msk | SPI_CR1_CPHA_Msk);
}
void spi_slave_enable(spi_t *spi, bool enable) {
  reg_write_bits(&spi->periph->CR1, enable << SPI_CR1_SPE_Pos, SPI_CR1_SPE);
}

uint16_t spi_transmit(spi_t *spi, uint16_t *data, uint16_t size) {
  pin_reset(&spi->csn);
  for (int16_t i = 0; i < size; i++) {
    while (!(spi->periph->SR & SPI_SR_TXE));
    uint16_t word = *(data++);
    spi->periph->DR = word;
  }

  return size;
}
uint16_t spi_receive(spi_t *spi, uint16_t *buffer, uint16_t max_size) {
  uint16_t size = 0;

  while (size < max_size && (spi->periph->SR & SPI_SR_RXNE)) {
    *(buffer + (size++)) = (uint16_t)(spi->periph->DR & SPI_DR_DR_Msk);
  }

  return size;

}

void spi_pulse_csn(spi_t *spi) {
  pin_set(&spi->csn);
  /* pin_reset(&spi->csn); */
}

bool spi_can_transmit(spi_t *spi) {
  return (spi->periph->SR & SPI_SR_TXE) != 0;
}
uint16_t spi_can_receive(spi_t *spi) {
  return ((spi->periph->SR & SPI_SR_RXNE) != 0) ? 1 : 0;
}

bool spi_enable_interrupt(spi_t *spi, bool tx, bool rx) {
  reg_write_bits(&spi->periph->CR2,
                 (tx << SPI_CR2_TXEIE_Pos) | (rx << SPI_CR2_RXNEIE_Pos),
                 SPI_CR2_TXEIE | SPI_CR2_RXNEIE);

  // TODO: proper irq by idx
  NVIC_SetPriority(SPI1_IRQn, 1);
  NVIC_EnableIRQ(SPI1_IRQn);
}
