#pragma once

#include "soc/soc_caps.h"
#if SOC_I2S_SUPPORTED

#include "Arduino.h"
#include "esp_err.h"
#include "driver/i2s_std.h"
#if SOC_I2S_SUPPORTS_TDM
#include "driver/i2s_tdm.h"
#endif
#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX
#include "driver/i2s_pdm.h"
#endif

typedef esp_err_t (*i2s_channel_read_fn)(i2s_chan_handle_t handle, char * tmp_buf, void *dest, size_t size, size_t *bytes_read, uint32_t timeout_ms);

typedef enum {
    I2S_MODE_STD,
#if SOC_I2S_SUPPORTS_TDM
    I2S_MODE_TDM,
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
    I2S_MODE_PDM_TX,
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
    I2S_MODE_PDM_RX,
#endif
    I2S_MODE_MAX
} i2s_mode_t;

typedef enum {
    I2S_RX_TRANSFORM_NONE,
    I2S_RX_TRANSFORM_32_TO_16,
    I2S_RX_TRANSFORM_16_STEREO_TO_MONO,
    I2S_RX_TRANSFORM_MAX
} i2s_rx_transform_t;

class I2SClass: public Stream {
  public:
    I2SClass();
    ~I2SClass();

    //STD + TDM mode
    void setPins(int8_t bclk, int8_t ws, int8_t dout, int8_t din=-1, int8_t mclk=-1);
    void setInverted(bool bclk, bool ws, bool mclk=false);

    //PDM TX + PDM RX mode
#if SOC_I2S_SUPPORTS_PDM_TX
    void setPinsPdmTx(int8_t clk, int8_t dout0, int8_t dout1=-1);
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
    void setPinsPdmRx(int8_t clk, int8_t din0, int8_t din1=-1, int8_t din2=-1, int8_t din3=-1);
#endif
#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX
    void setInvertedPdm(bool clk);
#endif

    bool begin(i2s_mode_t mode, uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask=-1);
    bool configureTX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask=-1);
    bool configureRX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, i2s_rx_transform_t transform=I2S_RX_TRANSFORM_NONE);
    bool end();

    size_t readBytes(char *buffer, size_t size);
    size_t write(uint8_t *buffer, size_t size);

    i2s_chan_handle_t txChan();
    uint32_t txSampleRate();
    i2s_data_bit_width_t txDataWidth();
    i2s_slot_mode_t txSlotMode();

    i2s_chan_handle_t rxChan();
    uint32_t rxSampleRate();
    i2s_data_bit_width_t rxDataWidth();
    i2s_slot_mode_t rxSlotMode();

    int lastError();

    int available();
    int peek();
    int read();
    size_t write(uint8_t d);

    // Record short PCM WAV to memory with current RX settings. Returns buffer that must be freed by the user.
    uint8_t * recordWAV(size_t rec_seconds, size_t * out_size);
    // Play short PCM WAV from memory
    void playWAV(uint8_t * data, size_t len);
    // Play short MP3 from memory
    bool playMP3(uint8_t *src, size_t src_len);


  private:
    esp_err_t last_error;
    i2s_mode_t _mode;

    i2s_chan_handle_t tx_chan;
    uint32_t tx_sample_rate;
    i2s_data_bit_width_t tx_data_bit_width;
    i2s_slot_mode_t tx_slot_mode;

    i2s_channel_read_fn rx_fn;
    i2s_rx_transform_t rx_transform;
    char * rx_transform_buf;
    size_t rx_transform_buf_len;

    i2s_chan_handle_t rx_chan;
    uint32_t rx_sample_rate;
    i2s_data_bit_width_t rx_data_bit_width;
    i2s_slot_mode_t rx_slot_mode;

    //STD and TDM mode
    int8_t _mclk, _bclk, _ws, _dout, _din;
    bool _mclk_inv, _bclk_inv, _ws_inv;

    //PDM mode
#if SOC_I2S_SUPPORTS_PDM_RX
    int8_t _rx_clk, _rx_din0, _rx_din1, _rx_din2, _rx_din3; //TODO: soc_caps.h 1/4
    bool _rx_clk_inv;
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
    int8_t _tx_clk, _tx_dout0, _tx_dout1;
    bool _tx_clk_inv;
#endif

    bool allocTranformRX(size_t buf_len);
    bool transformRX(i2s_rx_transform_t transform);

    static bool i2sDetachBus(void * bus_pointer);
    bool initSTD(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask);
#if SOC_I2S_SUPPORTS_TDM
    bool initTDM(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask);
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
    bool initPDMtx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch);
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
    bool initPDMrx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch);
#endif
};

#endif /* SOC_I2S_SUPPORTED */