/*
  HardwareSPI.h - Hardware SPI interface for Arduino
  Copyright (c) 2018 Arduino LLC.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#pragma once

#include "Common.h"
#include <inttypes.h>
#include "Stream.h"
#include <pthread.h>
#include "drv_spi.h"

#define SPI_HAS_TRANSACTION
#define SPI_MSBFIRST (1)
#define SPI_LSBFIRST (0)

#define MSB_32_SET(var, val)                                        \
    do {                                                            \
        uint8_t *d = (uint8_t *)&(val);                             \
        (var) = d[3] | (d[2] << 8) | (d[1] << 16) | (d[0] << 24);   \
    } while (0)

#define MSB_24_SET(var, val)                                        \
    do {                                                            \
        uint8_t *d = (uint8_t *)&(val);                             \
        (var) = d[2] | (d[1] << 8) | (d[0] << 16);                  \
    } while (0)

#define MSB_16_SET(var, val)                                        \
    do {                                                            \
        (var) = (((val) & 0xFF00) >> 8) | (((val) & 0xFF) << 8);    \
    } while (0)

#define MSB_PIX_SET(var, val)                                       \
    do {                                                            \
        uint8_t *d = (uint8_t *)&(val);                             \
        (var) = d[1] | (d[0] << 8) | (d[3] << 16) | (d[2] << 24);   \
    } while (0)

namespace arduino {

typedef enum {
    SPI_MODE0 = 0,
    SPI_MODE1 = 1,
    SPI_MODE2 = 2,
    SPI_MODE3 = 3,
} SPIMode;


class SPISettings {
public:
    SPISettings() : _clock(1000000), _bitOrder(SPI_MSBFIRST), _dataMode(SPI_MODE0) {}
    SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) : _clock(clock), _bitOrder(bitOrder), _dataMode(dataMode) {}
    uint32_t _clock;
    uint8_t _bitOrder;
    uint8_t _dataMode;
};

class SPIClass {
private:
    int8_t _spi_num;
    drv_spi_inst_t _spi;
    bool _use_hw_ss;
    int8_t _sck;
    int8_t _miso;
    int8_t _mosi;
    int8_t _ss;
    uint8_t _dataMode;
    uint8_t _bitOrder;
    uint32_t _freq;
#if !CONFIG_DISABLE_HAL_LOCKS
    pthread_mutex_t paramLock;
#endif
    void writePattern_(const uint8_t *data, uint8_t size, uint8_t repeat);

public:
    SPIClass(uint8_t spi_bus = 0);
    ~SPIClass();
    bool begin(int8_t sck = -1, int8_t miso = -1, int8_t mosi = -1, int8_t ss = -1);
    void end();

    void setHwCs(bool use);
    void setSSInvert(bool invert);  //use before setHwCS for change to be used by setHwCs
    void setBitOrder(uint8_t bitOrder);
    void setDataMode(uint8_t dataMode);
    void setFrequency(uint32_t freq);

    void beginTransaction(SPISettings settings);
    void endTransaction(void);
    void transfer(void *data, uint32_t size);
    uint8_t transfer(uint8_t data);
    uint16_t transfer16(uint16_t data);
    uint32_t transfer32(uint32_t data);

    void transferBytes(const uint8_t *data, uint8_t *out, uint32_t size);
    void transferBits(uint32_t data, uint32_t *out, uint8_t bits);

    void write(uint8_t data);
    void write16(uint16_t data);
    void write32(uint32_t data);
    void writeBytes(const uint8_t *data, uint32_t size);
    void writePixels(const void *data, uint32_t size);  //ili9341 compatible
    void writePattern(const uint8_t *data, uint8_t size, uint32_t repeat);

    drv_spi_inst_t bus() {
        return _spi;
    }
    int8_t pinSS() {
        return _ss;
    }
};

extern SPIClass spi0;
extern SPIClass spi1;
extern SPIClass spi2;

}
