#include "SPI.h"
#include "mik32_hal_spi.h"

SPI_HandleTypeDef hspi;

bool newConfig = false;
bool isInited = false;
uint32_t currentSpeed = 0;
int8_t currentDataOrder = MSBFIRST;
int8_t currentDataMode = -1;
static uint8_t reverse_bits(uint8_t byte);

// ------------------------------------------------------------------ //

void SPISettings::spiUpdateSettings(uint32_t speedMaximum, uint8_t dataOrder, uint8_t dataMode)
{
  // update config only if something has changed
  if ((currentSpeed != speedMaximum) || (currentDataOrder != dataOrder) || 
      (currentDataMode != dataMode))
  {
    // Find the fastest clock that is less than or equal to the
    // given clock rate. If nothing is slow enough - use the slowest.
    // mik32v2 has the set of deviders, that can be calculate as:
    // div = 2 << (1...7). Value in braсkets is a value for config register
    uint8_t divRegVal = 0; // start from minimal divider (maximum speed)
    while(divRegVal < 7)
    {
      divRegVal++; // values from 1 to 7
      if ((F_CPU/(2 << divRegVal)) <= speedMaximum)
        // find suitable divider
        break;
    }
    // if break didn't call in cycle, it will be the greatest divRegVal (and divider)

    // update params in struct
    hspi.Init.CLKPhase    = dataMode & 0b00000001;
    hspi.Init.CLKPolarity = (dataMode & 0b00000010)>>1;
    hspi.Init.BaudRateDiv = divRegVal;

    currentSpeed = speedMaximum;
    currentDataOrder = dataOrder;
    currentDataMode = dataMode;
    newConfig = true;
  }
}

// ------------------------------------------------------------------ //

SPIClass SPI;
bool SPIClass::spiInUse = false;
uint8_t SPIClass::interruptMode = 0;
uint8_t SPIClass::interruptMask = 0;

void SPIClass::begin()
{
  spi_onBegin();

  // set constant parameters in spi struct
  hspi.Instance         = SPI_1;
  hspi.Init.SPI_Mode    = HAL_SPI_MODE_MASTER;  // only master mode used
  hspi.Init.ThresholdTX = 4;
  hspi.Init.Decoder     = SPI_DECODER_NONE;
  hspi.Init.ManualCS    = SPI_MANUALCS_ON;
  hspi.Init.ChipSelect  = SPI_CS_NONE;
  // adjustable parameters default values as in SPISettings()
  hspi.Init.BaudRateDiv = SPI_CLOCK_DIV8;
  hspi.Init.CLKPhase    = SPI_MODE0 & 0b00000001;
  hspi.Init.CLKPolarity = (SPI_MODE0 & 0b00000010)>>1;

  spiInUse = true;
}

void SPIClass::end() 
{
  if (spiInUse && isInited)
  {
    // turn off spi
    HAL_SPI_Disable(&hspi);

    // deinit spi gpio pins
    if (hspi.Instance == SPI_1)   // only spi1 is using in currunt version
    {
      HAL_GPIO_PinConfig(GPIO_1, (HAL_PinsTypeDef)(GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2), 
                        HAL_GPIO_MODE_GPIO_INPUT, HAL_GPIO_PULL_NONE, HAL_GPIO_DS_2MA);
    }  

    spi_onEnd();

    spiInUse = false;
    isInited = false;
    interruptMode = 0;
    interruptMask = 0;
  }
}

void SPIClass::usingInterrupt(uint8_t interruptNumber)
{
  if(interruptNumber < EXTERNAL_INTERRUPTS_QTY) 
  {
    noInterrupts();                           // prevent transactionBegin
    interruptMask |= (1 << interruptNumber);  // add new interrupt to mask
    if (!interruptMode)
      interruptMode = 1;
    interrupts();
  }
}

void SPIClass::notUsingInterrupt(uint8_t interruptNumber)
{
  if(interruptNumber < EXTERNAL_INTERRUPTS_QTY) 
  {
    noInterrupts();                         // prevent transactionBegin
    interruptMask &= ~(1<<interruptNumber); // delete interrupt from mask
    if (!interruptMask)
      interruptMode = 0;
    interrupts();
  }
}

void SPIClass::beginTransaction(SPISettings settings) 
{
  // disable interrupts in use if necessary
  if (spiInUse && (interruptMode > 0))
  {
    for (uint8_t i = 0; i < EXTERNAL_INTERRUPTS_QTY; i++) 
    {
      if (interruptMask & (1 << i))
        // disable every interrupt by it's number
        disableInterrupt(i);
    }
  }

  // initialize spi bus or update config
  if(spiInUse && ((!isInited) || newConfig))
  {
    // initialize spi with given settings
    if (HAL_SPI_Init(&hspi) != HAL_OK)
      ErrorMsgHandler("SPI.beginTransaction(): initialization error");
    else
      isInited = true;
  }
}

uint8_t SPIClass::transfer(uint8_t data) 
{
  uint8_t rxByte = 0;

  if (isInited)
  {
    // reverse bits if LSB mode needed
    if (currentDataOrder == LSBFIRST)
      data = reverse_bits(data);

    // send and recieve data
    HAL_StatusTypeDef SPI_Status = HAL_SPI_Exchange(&hspi, &data, &rxByte, 1, SPI_TIMEOUT_DEFAULT*2);
    if (SPI_Status != HAL_OK)
      HAL_SPI_ClearError(&hspi);
    
    // reverse bits again if LSB mode needed
    if (currentDataOrder == LSBFIRST)
      rxByte = reverse_bits(rxByte);
  }
  return rxByte;
}

uint16_t SPIClass::transfer16(uint16_t data) 
{
  uint8_t buf[2];
  uint16_t rxVal = 0;
  if (isInited)
  {
    // prepare data for send
    if (currentDataOrder == LSBFIRST)
    {
      // least significant byte is forward and reverse bits inside each byte
      buf[0] = reverse_bits(data&0xFF);
      buf[1] = reverse_bits((data>>8)&0xFF);
    }
    else
    {
      // most significant byte is forward
      buf[0] = (data>>8)&0xFF;
      buf[1] = data&0xFF;
    }

    // send and recieve data
    HAL_StatusTypeDef SPI_Status = HAL_SPI_Exchange(&hspi, buf, buf, 2, SPI_TIMEOUT_DEFAULT*2);
    if (SPI_Status != HAL_OK)
      HAL_SPI_ClearError(&hspi);

    // process the received data
    if (currentDataOrder == LSBFIRST)
      // reverse bits for LSB mode
      rxVal = ( ((uint16_t)reverse_bits(buf[1]) ) << 8) | reverse_bits(buf[0]);
    else
      rxVal = ( ((uint16_t)buf[0]) << 8) | buf[1];
  }
  return rxVal;
}

void SPIClass::transfer(void *buf, size_t count) 
{
  if (count == 0) 
    return;

  if (isInited)
  {
    uint8_t *p = (uint8_t *)buf; 
    
    // reverse bits in buffer if LSB mode needed
    if (currentDataOrder == LSBFIRST)
    {
      for (uint32_t i = 0; i < count; i++)
        *(p+i) = reverse_bits(*(p+i));
    }

    // send and recieve data using the same buffer
    HAL_StatusTypeDef SPI_Status = HAL_SPI_Exchange(&hspi, (uint8_t*)buf, (uint8_t*)buf, count, SPI_TIMEOUT_DEFAULT*2);
    if (SPI_Status != HAL_OK)
      HAL_SPI_ClearError(&hspi);

    // reverse bits if LSB mode needed
    if (currentDataOrder == LSBFIRST)
    {
      p = (uint8_t *)buf; // return to buf beginning
      for (uint32_t i = 0; i < count; i++)
        *(p+i) = reverse_bits(*(p+i));
    }
  }
}

void SPIClass::endTransaction(void) 
{
  // enable interrupts in use
  if (spiInUse && (interruptMode > 0))
  {
    for (uint8_t i = 0; i < EXTERNAL_INTERRUPTS_QTY; i++) 
    {
      if (interruptMask & (1 << i))
        // enable every interrupt in use by it's number
        enableInterrupt(i);
    }
  }
}

// ------------------------------------ //
void SPIClass::setBitOrder(uint8_t bitOrder) 
{
  if (spiInUse)
    currentDataOrder = bitOrder;
  else
    ErrorMsgHandler("SPI.setBitOrder():SPI.begin() need to be called first");
}

void SPIClass::setDataMode(uint8_t dataMode) 
{
  if (spiInUse)
  {
    hspi.Init.CLKPhase    = (dataMode&0b00000001);
    hspi.Init.CLKPolarity = (dataMode&0b00000010)>>1;
    HAL_SPI_Set_Clock_Mode(&hspi);
    currentDataMode = dataMode;
  }
  else
    ErrorMsgHandler("SPI.setDataMode():SPI.begin() need to be called first");
}

void SPIClass::setClockDivider(uint8_t clockDiv) 
{
  if (spiInUse)
  {
    // if divider is valid
    if ((clockDiv == SPI_CLOCK_DIV4)  || (clockDiv == SPI_CLOCK_DIV8)   || 
        (clockDiv == SPI_CLOCK_DIV16) || (clockDiv == SPI_CLOCK_DIV32)  || 
        (clockDiv == SPI_CLOCK_DIV64) || (clockDiv == SPI_CLOCK_DIV128) || 
        (clockDiv == SPI_CLOCK_DIV256))
    {
      hspi.Init.BaudRateDiv = clockDiv;
      currentSpeed = F_CPU >> (clockDiv+1);
      HAL_SPI_Set_Clock_Divider(&hspi);
    }
    else
      ErrorMsgHandler("SPI.setClockDivider(): Invalid clock devider");
  }
  else
    ErrorMsgHandler("SPI.setClockDivider():SPI.begin() need to be called first");
}

static uint8_t reverse_bits(uint8_t byte) 
{
  byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4;
  byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2;
  byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1;
  return byte;
}