/* Includes ----------------------------------------------------------------- */
#include "bsp_spi.h"

#include "main.h"
#include "spi.h"

/* Private define ----------------------------------------------------------- */
#define OLED_SPI SPI1
#define IMU_SPI SPI5
/* #define XXX_SPI SPIX */

/* Private macro ------------------------------------------------------------ */
#define IMU_SPI_NSS_Reset() \
  HAL_GPIO_WritePin(SPI5_NSS_GPIO_Port, SPI5_NSS_Pin, GPIO_PIN_RESET)
#define IMU_SPI_NSS_Set() \
  HAL_GPIO_WritePin(SPI5_NSS_GPIO_Port, SPI5_NSS_Pin, GPIO_PIN_SET)

/* Private typedef ---------------------------------------------------------- */
/* Private variables -------------------------------------------------------- */
static struct {
  struct {
    void (*TxCpltCallback)(void);       /* SPI Tx Completed callback */
    void (*RxCpltCallback)(void);       /* SPI Rx Completed callback */
    void (*TxRxCpltCallback)(void);     /* SPI TxRx Completed callback */
    void (*TxHalfCpltCallback)(void);   /* SPI Tx Half Completed callback */
    void (*RxHalfCpltCallback)(void);   /* SPI Rx Half Completed callback */
    void (*TxRxHalfCpltCallback)(void); /* SPI TxRx Half Completed callback */
    void (*ErrorCallback)(void);        /* SPI Error callback */
    void (*AbortCpltCallback)(void);    /* SPI Abort callback */
  } oled;

  struct {
    void (*TxCpltCallback)(void);       /* SPI Tx Completed callback */
    void (*RxCpltCallback)(void);       /* SPI Rx Completed callback */
    void (*TxRxCpltCallback)(void);     /* SPI TxRx Completed callback */
    void (*TxHalfCpltCallback)(void);   /* SPI Tx Half Completed callback */
    void (*RxHalfCpltCallback)(void);   /* SPI Rx Half Completed callback */
    void (*TxRxHalfCpltCallback)(void); /* SPI TxRx Half Completed callback */
    void (*ErrorCallback)(void);        /* SPI Error callback */
    void (*AbortCpltCallback)(void);    /* SPI Abort callback */
  } imu;
} bsp_spi_callback;

/* Private function  -------------------------------------------------------- */
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {
  if (hspi->Instance == OLED_SPI) {
    if (bsp_spi_callback.oled.TxCpltCallback != NULL) {
      bsp_spi_callback.oled.TxCpltCallback();
    }
  } else if (hspi->Instance == IMU_SPI) {
    IMU_SPI_NSS_Set();
    if (bsp_spi_callback.imu.TxCpltCallback != NULL) {
      bsp_spi_callback.imu.TxCpltCallback();
    }
  }
}

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {
  if (hspi->Instance == OLED_SPI) {
    if (bsp_spi_callback.oled.RxCpltCallback != NULL) {
      bsp_spi_callback.oled.RxCpltCallback();
    }
  } else if (hspi->Instance == IMU_SPI) {
    IMU_SPI_NSS_Set();
    if (bsp_spi_callback.imu.RxCpltCallback != NULL) {
      bsp_spi_callback.imu.RxCpltCallback();
    }
  }
}

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
  if (hspi->Instance == OLED_SPI) {
    if (bsp_spi_callback.oled.TxRxCpltCallback != NULL) {
      bsp_spi_callback.oled.TxRxCpltCallback();
    }
  } else if (hspi->Instance == IMU_SPI) {
    if (bsp_spi_callback.imu.TxRxCpltCallback != NULL) {
      bsp_spi_callback.imu.TxRxCpltCallback();
    }
  }
}

void HAL_SPI_TxHalfCpltCallback(SPI_HandleTypeDef *hspi) {
  if (hspi->Instance == OLED_SPI) {
    if (bsp_spi_callback.oled.TxHalfCpltCallback != NULL) {
      bsp_spi_callback.oled.TxHalfCpltCallback();
    }
  } else if (hspi->Instance == IMU_SPI) {
    if (bsp_spi_callback.imu.TxHalfCpltCallback != NULL) {
      bsp_spi_callback.imu.TxHalfCpltCallback();
    }
  }
}

void HAL_SPI_RxHalfCpltCallback(SPI_HandleTypeDef *hspi) {
  if (hspi->Instance == OLED_SPI) {
    if (bsp_spi_callback.oled.RxHalfCpltCallback != NULL) {
      bsp_spi_callback.oled.RxHalfCpltCallback();
    }
  } else if (hspi->Instance == IMU_SPI) {
    if (bsp_spi_callback.imu.RxHalfCpltCallback != NULL) {
      bsp_spi_callback.imu.RxHalfCpltCallback();
    }
  }
}

void HAL_SPI_TxRxHalfCpltCallback(SPI_HandleTypeDef *hspi) {
  if (hspi->Instance == OLED_SPI) {
    if (bsp_spi_callback.oled.TxRxHalfCpltCallback != NULL) {
      bsp_spi_callback.oled.TxRxHalfCpltCallback();
    }
  } else if (hspi->Instance == IMU_SPI) {
    if (bsp_spi_callback.imu.TxRxHalfCpltCallback != NULL) {
      bsp_spi_callback.imu.TxRxHalfCpltCallback();
    }
  }
}

void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) {
  if (hspi->Instance == OLED_SPI) {
    if (bsp_spi_callback.oled.ErrorCallback != NULL) {
      bsp_spi_callback.oled.ErrorCallback();
    }
  } else if (hspi->Instance == IMU_SPI) {
    if (bsp_spi_callback.imu.ErrorCallback != NULL) {
      bsp_spi_callback.imu.ErrorCallback();
    }
  }
}

void HAL_SPI_AbortCpltCallback(SPI_HandleTypeDef *hspi) {
  if (hspi->Instance == OLED_SPI) {
    if (bsp_spi_callback.oled.AbortCpltCallback != NULL) {
      bsp_spi_callback.oled.AbortCpltCallback();
    }
  } else if (hspi->Instance == IMU_SPI) {
    if (bsp_spi_callback.imu.AbortCpltCallback != NULL) {
      bsp_spi_callback.imu.AbortCpltCallback();
    }
  }
  /*
  else if (hspi->Instance == XXX_SPI) {
          if (bsp_spi_callback.xxx.AbortCpltCallback != NULL) {
                  bsp_spi_callback.xxx.AbortCpltCallback();
          }
  }
  */
}

/* Exported functions ------------------------------------------------------- */
int8_t BSP_SPI_RegisterCallback(BSP_SPI_t spi, BSP_SPI_Callback_t type,
                             void (*callback)(void)) {
  if (callback == NULL) return -1;

  switch (spi) {
    case BSP_SPI_IMU:
      switch (type) {
        case BSP_SPI_TX_COMPLETE_CB:
          bsp_spi_callback.imu.TxCpltCallback = callback;
          break;
        case BSP_SPI_RX_COMPLETE_CB:
          bsp_spi_callback.imu.RxCpltCallback = callback;
          break;
        case BSP_SPI_TX_RX_COMPLETE_CB:
          bsp_spi_callback.imu.TxRxCpltCallback = callback;
          break;
        case BSP_SPI_TX_HALF_COMPLETE_CB:
          bsp_spi_callback.imu.TxHalfCpltCallback = callback;
          break;
        case BSP_SPI_RX_HALF_COMPLETE_CB:
          bsp_spi_callback.imu.RxHalfCpltCallback = callback;
          break;
        case BSP_SPI_TX_RX_HALF_COMPLETE_CB:
          bsp_spi_callback.imu.TxRxHalfCpltCallback = callback;
          break;
        case BSP_SPI_ERROR_CB:
          bsp_spi_callback.imu.ErrorCallback = callback;
          break;
        case BSP_SPI_ABORT_CB:
          bsp_spi_callback.imu.AbortCpltCallback = callback;
          break;
        default:
          return -1;
      }
      break;

    case BSP_SPI_OLED:
      switch (type) {
        case BSP_SPI_TX_COMPLETE_CB:
          bsp_spi_callback.oled.TxCpltCallback = callback;
          break;
        case BSP_SPI_RX_COMPLETE_CB:
          bsp_spi_callback.oled.RxCpltCallback = callback;
          break;
        case BSP_SPI_TX_RX_COMPLETE_CB:
          bsp_spi_callback.oled.TxRxCpltCallback = callback;
          break;
        case BSP_SPI_TX_HALF_COMPLETE_CB:
          bsp_spi_callback.oled.TxHalfCpltCallback = callback;
          break;
        case BSP_SPI_RX_HALF_COMPLETE_CB:
          bsp_spi_callback.oled.RxHalfCpltCallback = callback;
          break;
        case BSP_SPI_TX_RX_HALF_COMPLETE_CB:
          bsp_spi_callback.oled.TxRxHalfCpltCallback = callback;
          break;
        case BSP_SPI_ERROR_CB:
          bsp_spi_callback.oled.ErrorCallback = callback;
          break;
        case BSP_SPI_ABORT_CB:
          bsp_spi_callback.oled.AbortCpltCallback = callback;
          break;
        default:
          return -1;
      }
      break;
      /*
      case BSP_SPI_XXX:
              switch (type) {
                      case BSP_SPI_TX_COMPLETE_CB:
                              bsp_spi_callback.xxx.TxCpltCallback = callback;
                              break;
                      case BSP_SPI_RX_COMPLETE_CB:
                              bsp_spi_callback.xxx.RxCpltCallback = callback;
                              break;
                      case BSP_SPI_TX_RX_COMPLETE_CB:
                              bsp_spi_callback.xxx.TxRxCpltCallback = callback;
                              break;
                      case BSP_SPI_TX_HALF_COMPLETE_CB:
                              bsp_spi_callback.xxx.TxHalfCpltCallback =
      callback; break; case BSP_SPI_RX_HALF_COMPLETE_CB:
                              bsp_spi_callback.xxx.RxHalfCpltCallback =
      callback; break; case BSP_SPI_TX_RX_HALF_COMPLETE_CB:
                              bsp_spi_callback.xxx.TxRxHalfCpltCallback =
      callback; break; case BSP_SPI_ERROR_CB: bsp_spi_callback.xxx.ErrorCallback
      = callback; break; case BSP_SPI_ABORT_CB:
                              bsp_spi_callback.xxx.AbortCpltCallback = callback;
                              break;
                      default:
                              return -1;
              }
              break;
              */
  }
  return 0;
}

int8_t BSP_SPI_Transmit(BSP_SPI_t spi, uint8_t *data, uint16_t len) {
  if (data == NULL) return -1;

  switch (spi) {
    case BSP_SPI_IMU:
      /* Do NOT use hardware NSS. It doesn't implement the same logic. */
      IMU_SPI_NSS_Reset();
      HAL_SPI_Transmit(&hspi5, data, len, 55);
      break;

    case BSP_SPI_OLED:
      HAL_SPI_Transmit(&hspi1, data, len, 55);
      // HAL_SPI_Transmit_DMA(&hspi1, data, len);
      break;

      /*
      case BSP_SPI_XXX:
              HAL_SPI_Transmit_DMA(&hspix, data, len);
              break;
      */
  }
  return 0;
}

int8_t BSP_SPI_Receive(BSP_SPI_t spi, uint8_t *data, uint16_t len) {
  if (data == NULL) return -1;

  switch (spi) {
    case BSP_SPI_IMU:
      IMU_SPI_NSS_Reset();
      if (len > 1u) {
        HAL_SPI_Receive_DMA(&hspi5, data, len);
      } else {
        HAL_SPI_Receive(&hspi5, data, len, 55);
      }
      break;

    case BSP_SPI_OLED:
      return -1;

      /*
      case BSP_SPI_XXX:
              HAL_SPI_Receive(&hspix, data, len);
              break;
      */
  }
  return 0;
}