/*!
 * @file Adafruit_VS1053.h
 */

#ifndef ADAFRUIT_VS1053_H
#define ADAFRUIT_VS1053_H

#if (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#include <pins_arduino.h>
#endif

#if !defined(ARDUINO_STM32_FEATHER) && !defined(ARDUINO_UNOR4_WIFI) &&         \
    !defined(ARDUINO_UNOR4_MINIMA)
#include "pins_arduino.h"
#include "wiring_private.h"
#endif

#include <Adafruit_SPIDevice.h>

#if defined(PREFER_SDFAT_LIBRARY)
#include <SdFat.h>
extern SdFat SD;
#else
#include <SD.h>
#endif

// define here the size of a register!
#if defined(ARDUINO_STM32_FEATHER)
typedef volatile uint32 RwReg;
typedef uint32_t PortMask;
#elif defined(ARDUINO_ARCH_AVR)
typedef volatile uint8_t RwReg;
typedef uint8_t PortMask;
#elif defined(__arm__)
#if defined(TEENSYDUINO)
typedef volatile uint8_t RwReg;
typedef uint8_t PortMask;
#else
typedef volatile uint32_t RwReg;
typedef uint32_t PortMask;
#endif
#elif defined(ESP8266) || defined(ESP32)
typedef volatile uint32_t RwReg;
typedef uint32_t PortMask;
#elif defined(__ARDUINO_ARC__)
typedef volatile uint32_t RwReg;
typedef uint32_t PortMask;
#else
typedef volatile uint8_t RwReg; //!< 1-byte read-write register
typedef uint8_t PortMask; //!< Type definition for a bitmask that is used to
                          //!< specify the bit width
#endif

typedef volatile RwReg PortReg; //!< Type definition/alias used to specify the
                                //!< port register that a pin is in

#define VS1053_FILEPLAYER_TIMER0_INT                                           \
  255 //!< Allows useInterrupt to accept pins 0 to 254
#define VS1053_FILEPLAYER_PIN_INT                                              \
  5 //!< Allows useInterrupt to accept pins 0 to 4

#define VS1053_SCI_READ 0x03  //!< Serial read address
#define VS1053_SCI_WRITE 0x02 //!< Serial write address

#define VS1053_REG_MODE 0x00       //!< Mode control
#define VS1053_REG_STATUS 0x01     //!< Status of VS1053b
#define VS1053_REG_BASS 0x02       //!< Built-in bass/treble control
#define VS1053_REG_CLOCKF 0x03     //!< Clock frequency + multiplier
#define VS1053_REG_DECODETIME 0x04 //!< Decode time in seconds
#define VS1053_REG_AUDATA 0x05     //!< Misc. audio data
#define VS1053_REG_WRAM 0x06       //!< RAM write/read
#define VS1053_REG_WRAMADDR 0x07   //!< Base address for RAM write/read
#define VS1053_REG_HDAT0 0x08      //!< Stream header data 0
#define VS1053_REG_HDAT1 0x09      //!< Stream header data 1
#define VS1053_REG_VOLUME 0x0B     //!< Volume control

#define VS1053_GPIO_DDR 0xC017   //!< Direction
#define VS1053_GPIO_IDATA 0xC018 //!< Values read from pins
#define VS1053_GPIO_ODATA 0xC019 //!< Values set to the pins

#define VS1053_INT_ENABLE 0xC01A //!< Interrupt enable

#define VS1053_MODE_SM_DIFF                                                    \
  0x0001 //!< Differential, 0: normal in-phase audio, 1: left channel inverted
#define VS1053_MODE_SM_LAYER12 0x0002  //!< Allow MPEG layers I & II
#define VS1053_MODE_SM_RESET 0x0004    //!< Soft reset
#define VS1053_MODE_SM_CANCEL 0x0008   //!< Cancel decoding current file
#define VS1053_MODE_SM_EARSPKLO 0x0010 //!< EarSpeaker low setting
#define VS1053_MODE_SM_TESTS 0x0020    //!< Allow SDI tests
#define VS1053_MODE_SM_STREAM 0x0040   //!< Stream mode
#define VS1053_MODE_SM_SDINEW 0x0800   //!< VS1002 native SPI modes
#define VS1053_MODE_SM_ADPCM 0x1000    //!< PCM/ADPCM recording active
#define VS1053_MODE_SM_LINE1 0x4000 //!< MIC/LINE1 selector, 0: MICP, 1: LINE1
#define VS1053_MODE_SM_CLKRANGE                                                \
  0x8000 //!< Input clock range, 0: 12..13 MHz, 1: 24..26 MHz

#define VS1053_SCI_AIADDR                                                      \
  0x0A //!< Indicates the start address of the application code written earlier
       //!< with SCI_WRAMADDR and SCI_WRAM registers.
#define VS1053_SCI_AICTRL0                                                     \
  0x0C //!< SCI_AICTRL register 0. Used to access the user's application program
#define VS1053_SCI_AICTRL1                                                     \
  0x0D //!< SCI_AICTRL register 1. Used to access the user's application program
#define VS1053_SCI_AICTRL2                                                     \
  0x0E //!< SCI_AICTRL register 2. Used to access the user's application program
#define VS1053_SCI_AICTRL3                                                     \
  0x0F //!< SCI_AICTRL register 3. Used to access the user's application program
#define VS1053_SCI_WRAM 0x06     //!< RAM write/read
#define VS1053_SCI_WRAMADDR 0x07 //!< Base address for RAM write/read

#define VS1053_PARA_PLAYSPEED 0x1E04 //!< 0,1 = normal speed, 2 = 2x, 3 = 3x etc

#define VS1053_DATABUFFERLEN 32 //!< Length of the data buffer

/*!
 * Driver for the Adafruit VS1053
 */
class Adafruit_VS1053 {
public:
  /*!
   * @brief Software SPI constructor - must specify all pins
   * @param mosi MOSI (Microcontroller Out Serial In) pin
   * @param miso MISO (Microcontroller In Serial Out) pin
   * @param clk Clock pin
   * @param rst Reset pin
   * @param cs SCI Chip Select pin
   * @param dcs SDI Chip Select pin
   * @param dreq Data Request pin
   */
  Adafruit_VS1053(int8_t mosi, int8_t miso, int8_t clk, int8_t rst, int8_t cs,
                  int8_t dcs, int8_t dreq);
  /*!
   * @brief Hardware SPI constructor - assumes hardware SPI pins
   * @param rst Reset pin
   * @param cs SCI Chip Select pin
   * @param dcs SDI Chip Select pin
   * @param dreq Data Request pin
   */
  Adafruit_VS1053(int8_t rst, int8_t cs, int8_t dcs, int8_t dreq);
  /*!
   * @brief Initialize communication and (hard) reset the chip.
   * @return Returns true if a VS1053 is found
   */
  uint8_t begin(void);
  /*!
   * @brief Performs a hard reset of the chip
   */
  void reset(void);
  /*!
   * @brief Attempts a soft reset of the chip
   */
  void softReset(void);
  /*!
   * @brief Reads from the specified register on the chip
   * @param addr Register address to read from
   * @return Retuns the 16-bit data corresponding to the received address
   */
  uint16_t sciRead(uint8_t addr);
  /*!
   * @brief Writes to the specified register on the chip
   * @param addr Register address to write to
   * @param data Data to write
   */
  void sciWrite(uint8_t addr, uint16_t data);
  /*!
   * @brief Generate a sine-wave test signal
   * @param n Defines the sine test to use
   * @param ms Delay (in ms)
   */
  void sineTest(uint8_t n, uint16_t ms);
  /*!
   * @brief Reads the DECODETIME register from the chip
   * @return Returns the decode time as an unsigned 16-bit integer
   */
  uint16_t decodeTime(void);
  /*!
   * @brief Set the output volume for the chip
   * @param left Desired left channel volume
   * @param right Desired right channel volume
   */
  void setVolume(uint8_t left, uint8_t right);
  /*!
   * @brief Prints the contents of the MODE, STATUS, CLOCKF and VOLUME registers
   */
  void dumpRegs(void);

  /*!
   * @brief Decode and play the contents of the supplied buffer
   * @param buffer Buffer to decode and play
   * @param buffsiz Size to decode and play
   */
  void playData(uint8_t *buffer, uint8_t buffsiz);
  /*!
   * @brief Test if ready for more data
   * @return Returns true if it is ready for data
   */
  boolean readyForData(void);
  /*!
   * @brief Apply a code patch
   * @param patch Patch to apply
   * @param patchsize Patch size
   */
  void applyPatch(const uint16_t *patch, uint16_t patchsize);
  /*!
   * @brief Load the specified plug-in
   * @param fn Plug-in to load
   * @return Either returns 0xFFFF if there is an error, or the address of the
   * plugin that was loaded
   */
  uint16_t loadPlugin(char *fn);

  /*!
   * @brief Write to a GPIO pin
   * @param i GPIO pin to write to
   * @param val Value to write
   */
  void GPIO_digitalWrite(uint8_t i, uint8_t val);
  /*!
   * @brief Write to all 8 GPIO pins at once
   * @param i Value to write
   */
  void GPIO_digitalWrite(uint8_t i);
  /*!
   * @brief Read all 8 GPIO pins at once
   * @return Returns a 2 byte value with the reads from the 8 pins
   */
  uint16_t GPIO_digitalRead(void);
  /*!
   * @brief Read a single GPIO pin
   * @param i pin to read
   * @return Returns the state of the specified GPIO pin
   */
  boolean GPIO_digitalRead(uint8_t i);
  /*!
   * @brief Set the Pin Mode (INPUT/OUTPUT) for a GPIO pin.
   * @param i Pin to set the mode for
   * @param dir Mode to set
   */
  void GPIO_pinMode(uint8_t i, uint8_t dir);

  /*!
   * @brief Initialize chip for OGG recording
   * @param plugin Binary file of the plugin to use
   * @return Returns true if the device is ready to record
   */
  boolean prepareRecordOgg(char *plugin);
  /*!
   * @brief Start recording
   * @param mic mic=true for microphone input
   */
  void startRecordOgg(boolean mic);
  /*!
   * @brief Stop the recording
   */
  void stopRecordOgg(void);
  /*!
   * @brief Returns the number of words recorded
   * @return 2-byte unsigned int with the number of words
   */
  uint16_t recordedWordsWaiting(void);
  /*!
   * @brief Reads the next word from the buffer of recorded words
   * @return Returns the 16-bit data corresponding to the received address
   */
  uint16_t recordedReadWord(void);

  uint8_t mp3buffer[VS1053_DATABUFFERLEN]; //!< mp3 buffer that gets sent to the
                                           //!< device

#ifdef ARDUINO_ARCH_SAMD
protected:
  uint32_t _dreq;                  //!< Data request pin
  boolean usingInterrupts = false; //!< True if using interrupts

private:
  Adafruit_SPIDevice *spi_dev_ctrl = NULL; ///< Pointer to SPI dev for control
  Adafruit_SPIDevice *spi_dev_data = NULL; ///< Pointer to SPI dev for data
  int32_t _mosi, _miso, _clk, _reset, _cs, _dcs;
  boolean useHardwareSPI;
#else
protected:
  uint8_t _dreq;                   //!< Data request pin
  boolean usingInterrupts = false; //!< True if using interrupts

private:
  Adafruit_SPIDevice *spi_dev_ctrl = NULL; ///< Pointer to SPI dev for control
  Adafruit_SPIDevice *spi_dev_data = NULL; ///< Pointer to SPI dev for data
  int8_t _mosi, _miso, _clk, _reset, _cs, _dcs;
  boolean useHardwareSPI;
#endif
};

/*!
 * @brief File player for the Adafruit VS1053
 */
class Adafruit_VS1053_FilePlayer : public Adafruit_VS1053 {
public:
  /*!
   * @brief Software SPI constructor. Uses Software SPI, so you must specify all
   * SPI pins
   * @param mosi MOSI (Microcontroller Out Serial In) pin
   * @param miso MISO (Microcontroller In Serial Out) pin
   * @param clk Clock pin
   * @param rst Reset pin
   * @param cs SCI Chip Select pin
   * @param dcs SDI Chip Select pin
   * @param dreq Data Request pin
   * @param cardCS CS pin for the SD card on the SPI bus
   */
  Adafruit_VS1053_FilePlayer(int8_t mosi, int8_t miso, int8_t clk, int8_t rst,
                             int8_t cs, int8_t dcs, int8_t dreq, int8_t cardCS);
  /*!
   * @brief Hardware SPI constructor. Uses Hardware SPI and assumes the default
   * SPI pins
   * @param rst Reset pin
   * @param cs SCI Chip Select pin
   * @param dcs SDI Chip Select pin
   * @param dreq Data Request pin
   * @param cardCS CS pin for the SD card on the SPI bus
   */
  Adafruit_VS1053_FilePlayer(int8_t rst, int8_t cs, int8_t dcs, int8_t dreq,
                             int8_t cardCS);

  /*!
   * @brief Hardware SPI constructor. Uses Hardware SPI and assumes the default
   * SPI pins
   * @param cs SCI Chip Select pin
   * @param dcs SDI Chip Select pin
   * @param dreq Data Request pin
   * @param cardCS CS pin for the SD card on the SPI bus
   */
  Adafruit_VS1053_FilePlayer(int8_t cs, int8_t dcs, int8_t dreq, int8_t cardCS);

  /*!
   * @brief Initialize communication and reset the chip.
   * @return Returns true if a VS1053 is found
   */
  boolean begin(void);
  /*!
   * @brief Specifies the argument to use for interrupt-driven playback
   * @param type interrupt to use. Valid arguments are
   * VS1053_FILEPLAYER_TIMER0_INT and VS1053_FILEPLAYER_PIN_INT
   * @return Returs true/false for success/failure
   */
  boolean useInterrupt(uint8_t type);
  File currentTrack;             //!< File that is currently playing
  volatile boolean playingMusic; //!< Whether or not music is playing
  /*!
   * @brief Feeds the buffer. Reads mp3 file data from the SD card and file and
   * puts it into the buffer that the decoder reads from to play a file
   */
  void feedBuffer(void);
  /*!
   * @brief Checks if the inputted filename is an mp3
   * @param fileName File to check
   * @return Returns true or false
   */
  static boolean isMP3File(const char *fileName);
  /*!
   * @brief Checks for an ID3 tag at the beginning of the file.
   * @param mp3 File to read
   * @return returns the seek position within the file where the mp3 data starts
   */
  unsigned long mp3_ID3Jumper(File mp3);
  /*!
   * @brief Begin playing the specified file from the SD card using
   * interrupt-drive playback.
   * @param *trackname File to play
   * @return Returns true when file starts playing
   */
  boolean startPlayingFile(const char *trackname);
  /*!
   * @brief Play the complete file. This function will not return until the
   * playback is complete
   * @param *trackname File to play
   * @return Returns true when file starts playing
   */
  boolean playFullFile(const char *trackname);
  void stopPlaying(void); //!< Stop playback
  /*!
   * @brief If playback is paused
   * @return Returns true if playback is paused
   */
  boolean paused(void);
  /*!
   * @brief If playback is stopped
   * @return Returns true if playback is stopped
   */
  boolean stopped(void);
  /*!
   * @brief Pause playback
   * @param pause whether or not to pause playback
   */
  void pausePlaying(boolean pause);
  /*!
   * @brief Set state for playback looping
   * @param loopState Sets playback loop state (Note: Only use with
   * startPlayingFile, if used with playFullFile no subsequent code in the
   * sketch executes)
   */
  void playbackLoop(boolean loopState);
  /*!
   * @brief Retrieve playback loop state
   * @return Returns true when looped playback is enabled
   */
  boolean playbackLooped();
  /*!
   * @brief Determine current playback speed
   * @return Returns playback speed, i.e. 1 for 1x, 2 for 2x, 3 for 3x
   */
  uint16_t getPlaySpeed();
  /*!
   * @brief Set playback speed
   * @param speed Set playback speed, i.e. 1 for 1x, 2 for 2x, 3 for 3x
   */
  void setPlaySpeed(uint16_t speed);

private:
  void feedBuffer_noLock(void);

  uint8_t _cardCS;
};

#endif // ADAFRUIT_VS1053_H
