#ifndef Adafruit_SPIDevice_h
#define Adafruit_SPIDevice_h

#include <Arduino.h>

#if !defined(SPI_INTERFACES_COUNT) || (defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0))
// HW SPI available
#include <SPI.h>
#define BUSIO_HAS_HW_SPI
#else
// SW SPI ONLY
enum
{
    SPI_MODE0,
    SPI_MODE1,
    SPI_MODE2,
    _SPI_MODE4
};
typedef uint8_t SPIClass;
#endif

// some modern SPI definitions don't have BitOrder enum
#if (defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR)) || defined(ESP8266) || defined(TEENSYDUINO) ||                \
    defined(SPARK) || defined(ARDUINO_ARCH_SPRESENSE) || defined(MEGATINYCORE) || defined(DXCORE) ||                   \
    defined(ARDUINO_AVR_ATmega4809) || defined(ARDUINO_AVR_ATmega4808) || defined(ARDUINO_AVR_ATmega3209) ||           \
    defined(ARDUINO_AVR_ATmega3208) || defined(ARDUINO_AVR_ATmega1609) || defined(ARDUINO_AVR_ATmega1608) ||           \
    defined(ARDUINO_AVR_ATmega809) || defined(ARDUINO_AVR_ATmega808) || defined(ARDUINO_ARCH_ARC32) ||                 \
    defined(ARDUINO_ARCH_XMC)

typedef enum _BitOrder
{
    SPI_BITORDER_MSBFIRST = MSBFIRST,
    SPI_BITORDER_LSBFIRST = LSBFIRST,
} BusIOBitOrder;

#elif defined(ESP32) || defined(__ASR6501__) || defined(__ASR6502__)

// some modern SPI definitions don't have BitOrder enum and have different SPI
// mode defines
typedef enum _BitOrder
{
    SPI_BITORDER_MSBFIRST = SPI_MSBFIRST,
    SPI_BITORDER_LSBFIRST = SPI_LSBFIRST,
} BusIOBitOrder;

#else
// Some platforms have a BitOrder enum but its named MSBFIRST/LSBFIRST
#define SPI_BITORDER_MSBFIRST MSBFIRST
#define SPI_BITORDER_LSBFIRST LSBFIRST
typedef BitOrder BusIOBitOrder;
#endif

#if defined(__IMXRT1062__) // Teensy 4.x
// *Warning* I disabled the usage of FAST_PINIO as the set/clear operations
// used in the cpp file are not atomic and can effect multiple IO pins
// and if an interrupt happens in between the time the code reads the register
//  and writes out the updated value, that changes one or more other IO pins
// on that same IO port, those change will be clobbered when the updated
// values are written back.  A fast version can be implemented that uses the
// ports set and clear registers which are atomic.
// typedef volatile uint32_t BusIO_PortReg;
// typedef uint32_t BusIO_PortMask;
// #define BUSIO_USE_FAST_PINIO

#elif defined(__MBED__) || defined(__ZEPHYR__)
// Boards based on RTOS cores like mbed or Zephyr are not going to expose the
// low level registers needed for fast pin manipulation
#undef BUSIO_USE_FAST_PINIO

#elif defined(ARDUINO_ARCH_XMC)
#undef BUSIO_USE_FAST_PINIO

#elif defined(__AVR__) || defined(TEENSYDUINO)
typedef volatile uint8_t BusIO_PortReg;
typedef uint8_t BusIO_PortMask;
#define BUSIO_USE_FAST_PINIO

#elif defined(ESP8266) || defined(ESP32) || defined(__SAM3X8E__) || defined(ARDUINO_ARCH_SAMD)
typedef volatile uint32_t BusIO_PortReg;
typedef uint32_t BusIO_PortMask;
#define BUSIO_USE_FAST_PINIO

#elif (defined(__arm__) || defined(ARDUINO_FEATHER52)) && !defined(ARDUINO_ARCH_RP2040) && !defined(ARDUINO_SILABS) && \
    !defined(ARDUINO_UNOR4_MINIMA) && !defined(ARDUINO_UNOR4_WIFI)
typedef volatile uint32_t BusIO_PortReg;
typedef uint32_t BusIO_PortMask;
#if !defined(__ASR6501__) && !defined(__ASR6502__)
#define BUSIO_USE_FAST_PINIO
#endif

#else
#undef BUSIO_USE_FAST_PINIO
#endif

/**! The class which defines how we will talk to this device over SPI **/
class Adafruit_SPIDevice
{
  public:
#ifdef BUSIO_HAS_HW_SPI
    Adafruit_SPIDevice(int8_t cspin, uint32_t freq = 1000000, BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST,
                       uint8_t dataMode = SPI_MODE0, SPIClass *theSPI = &SPI);
#else
    Adafruit_SPIDevice(int8_t cspin, uint32_t freq = 1000000, BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST,
                       uint8_t dataMode = SPI_MODE0, SPIClass *theSPI = nullptr);
#endif
    Adafruit_SPIDevice(int8_t cspin, int8_t sck, int8_t miso, int8_t mosi, uint32_t freq = 1000000,
                       BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST, uint8_t dataMode = SPI_MODE0);
    ~Adafruit_SPIDevice();

    bool begin(void);
    bool read(uint8_t *buffer, size_t len, uint8_t sendvalue = 0xFF);
    bool write(const uint8_t *buffer, size_t len, const uint8_t *prefix_buffer = nullptr, size_t prefix_len = 0);
    bool write_then_read(const uint8_t *write_buffer, size_t write_len, uint8_t *read_buffer, size_t read_len,
                         uint8_t sendvalue = 0xFF);
    bool write_and_read(uint8_t *buffer, size_t len);

    uint8_t transfer(uint8_t send);
    void transfer(uint8_t *buffer, size_t len);
    void beginTransaction(void);
    void endTransaction(void);
    void beginTransactionWithAssertingCS();
    void endTransactionWithDeassertingCS();

  private:
#ifdef BUSIO_HAS_HW_SPI
    SPIClass *_spi = nullptr;
    SPISettings *_spiSetting = nullptr;
#else
    uint8_t *_spi = nullptr;
    uint8_t *_spiSetting = nullptr;
#endif
    uint32_t _freq;
    BusIOBitOrder _dataOrder;
    uint8_t _dataMode;
    void setChipSelect(int value);

    int8_t _cs, _sck, _mosi, _miso;
#ifdef BUSIO_USE_FAST_PINIO
    BusIO_PortReg *mosiPort, *clkPort, *misoPort, *csPort;
    BusIO_PortMask mosiPinMask, misoPinMask, clkPinMask, csPinMask;
#endif
    bool _begun;
};

#endif // Adafruit_SPIDevice_h
