/** 
 * Arduino Library for JQ6500 MP3 Module
 * 
 * Copyright (C) 2014 James Sleeman, <http://sparks.gogo.co.nz/jq6500/index.html>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
 * THE SOFTWARE.
 * 
 * @author James Sleeman, http://sparks.gogo.co.nz/
 * @license MIT License
 * @file
 */

// Please note, the Arduino IDE is a bit retarded, if the below define has an
// underscore other than _h, it goes mental.  Wish it wouldn't  mess
// wif ma files!
#ifndef JQ6500Serial_h
#define JQ6500Serial_h

#include <SoftwareSerial.h>

#define MP3_EQ_NORMAL     0
#define MP3_EQ_POP        1
#define MP3_EQ_ROCK       2
#define MP3_EQ_JAZZ       3
#define MP3_EQ_CLASSIC    4
#define MP3_EQ_BASS       5

#define MP3_SRC_SDCARD    1
#define MP3_SRC_BUILTIN   4

// Looping options, ALL, FOLDER, ONE and ONE_STOP are the 
// only ones that appear to do much interesting
//  ALL plays all the tracks in a repeating loop
//  FOLDER plays all the tracks in the same folder in a repeating loop
//  ONE plays the same track repeating
//  ONE_STOP does not loop, plays the track and stops
//  RAM seems to play one track and someties disables the ability to 
//  move to next/previous track, really weird.

#define MP3_LOOP_ALL      0
#define MP3_LOOP_FOLDER   1
#define MP3_LOOP_ONE      2
#define MP3_LOOP_RAM      3
#define MP3_LOOP_ONE_STOP 4
#define MP3_LOOP_NONE     4 

#define MP3_STATUS_STOPPED 0
#define MP3_STATUS_PLAYING 1
#define MP3_STATUS_PAUSED  2

// The response from a status query we get is  for some reason
// a bit... iffy, most of the time it is reliable, but sometimes
// instead of a playing (1) response, we get a paused (2) response
// even though it is playing.  Stopped responses seem reliable.
// So to work around this when getStatus() is called we actually
// request the status this many times and only if one of them is STOPPED
// or they are all in agreement that it is playing or paused then
// we return that status.  If some of them differ, we do another set 
// of tests etc...
#define MP3_STATUS_CHECKS_IN_AGREEMENT 4

#define MP3_DEBUG 0

class JQ6500_Serial : public SoftwareSerial
{
  
  public: 

    /** Create JQ6500 object.
     * 
     * Example, create global instance:
     * 
     *     JQ6500_Serial mp3(8,9);
     * 
     * For a 5v Arduino:
     * -----------------
     *  * TX on JQ6500 connects to D8 on the Arduino
     *  * RX on JQ6500 connects to one end of a 1k resistor,
     *      other end of resistor connects to D9 on the Arduino
     * 
     * For a 3v3 Arduino:
     * -----------------
     *  * TX on JQ6500 connects to D8 on the Arduino
     *  * RX on JQ6500 connects to D9 on the Arduino
     * 
     * Of course, power and ground are also required, VCC on JQ6500 is 5v tolerant (but RX isn't totally, hence the resistor above).
     * 
     * And then you can use in your setup():
     * 
     *     mp3.begin(9600)
     *     mp3.reset();
     *
     * and all the other commands :-)
     */
    
    JQ6500_Serial(short rxPin, short txPin) : SoftwareSerial(rxPin,txPin) { };
    
    /** Start playing the current file.
     */
    
    void play();
    
    /** Restart the current (possibly paused) track from the 
     *  beginning.
     *  
     *  Note that this is not an actual command the JQ6500 knows
     *  what we do is mute, advance to the next track, pause,
     *  unmute, and go back to the previous track (which will
     *  cause it to start playing.
     * 
     *  That said, it appears to work just fine.
     * 
     */
    
    void restart();
    
    /** Pause the current file.  To unpause, use play(),
     *  to unpause and go back to beginning of track use restart()
     */
    
    void pause();
    
    /** Play the next file.
     */
    
    void next();
    
    /** Play the previous file.
     */
    
    void prev();
    
    /** Play the next folder. 
     */
    
    void nextFolder();
    
    /** Play the previous folder. 
     */
    
    void prevFolder();
    
    /** Play a specific file based on it's (FAT table) index number.  Note that the index number
     *  has nothing to do with the file name (except if you uploaded/copied them to the media in
     *  order of file name).
     * 
     *  To sort your SD Card FAT table, search for a FAT sorting utility for your operating system 
     *  of choice.
     */
    
    void playFileByIndexNumber(unsigned int fileNumber);        
    
    /** Play a specific file in a specific folder based on the name of those folder and file.
     * 
     * Only applies to SD Card.
     * 
     * To use this function, folders must be named from 00 to 99, and the files in those folders
     * must be named from 000.mp3 to 999.mp3
     * 
     * So to play the file on the SD Card "/03/006.mp3" use mp3.playFileNumberInFolderNumber(3, 6);
     * 
     */
    
    void playFileNumberInFolderNumber(unsigned int folderNumber, unsigned int fileNumber);
    
    /** Increase the volume by 1 (volume ranges 0 to 30). */
    
    void volumeUp();
    
    /** Decrease the volume by 1 (volume ranges 0 to 30). */
    
    void volumeDn();
    
    /** Set the volume to a specific level (0 to 30). 
     * 
     * @param volumeFrom0To30 Level of volume to set from 0 to 30
     */
    
    void setVolume(byte volumeFrom0To30);
    
    /** Set the equalizer to one of 6 preset modes.
     * 
     * @param equalizerMode One of the following, 
     * 
     *  *  MP3_EQ_NORMAL
     *  *  MP3_EQ_POP        
     *  *  MP3_EQ_ROCK       
     *  *  MP3_EQ_JAZZ       
     *  *  MP3_EQ_CLASSIC    
     *  *  MP3_EQ_BASS       
     * 
     */
    
    void setEqualizer(byte equalizerMode); // EQ_NORMAL to EQ_BASS
    
    /** Set the looping mode.
     * 
     * @param loopMode One of the following,
     * 
     *  *  MP3_LOOP_ALL       - Loop through all files.
     *  *  MP3_LOOP_FOLDER    - Loop through all files in the same folder (SD Card only)
     *  *  MP3_LOOP_ONE       - Loop one file.
     *  *  MP3_LOOP_RAM       - Loop one file (uncertain how it is different to the previous!)
     *  *  MP3_LOOP_NONE      - No loop, just play one file and then stop. (aka MP3_LOOP_ONE_STOP)
     */
    
    void setLoopMode(byte loopMode);
    
    /** Set the source to read mp3 data from.
     * 
     *  @param source One of the following,
     * 
     *   * MP3_SRC_BUILTIN    - Files read from the on-board flash memory
     *   * MP3_SRC_SDCARD     - Files read from the SD Card (JQ6500-28P only)
     */
    
    void setSource(byte source);        // SRC_BUILTIN or SRC_SDCARD       
    
    /** Put the device to sleep.
     * 
     *  Not recommanded if you are using SD Card as for some reason
     *  it appears to cause the SD Card to not be recognised again
     *  until the device is totally powered off and on again :-/
     * 
     */
    
    void sleep();
    
    /** Reset the device (softly).
     *       
     * It may be necessary in practice to actually power-cycle the device
     * as sometimes it can get a bit confused, especially if changing
     * SD Cards on-the-fly which really doesn't work too well.
     * 
     * So if designing a PCB/circuit including JQ6500 modules it might be 
     * worth while to include such ability (ie, power the device through 
     * a MOSFET which you can turn on/off at will).
     * 
     */
    
    void reset();
    
    // Status querying commands
    /** Get the status from the device.
     * 
     * CAUTION!  This is somewhat unreliable for the following reasons...
     * 
     *  1. When playing from the on board memory (MP3_SRC_BUILTIN), STOPPED sems
     *     to never be returned, only PLAYING and PAUSED
     *  2. Sometimes PAUSED is returned when it is PLAYING, to try and catch this
     *     getStatus() actually queries the module several times to ensure that
     *     it is really sure about what it tells us.
     *
     * @return One of MP3_STATUS_PAUSED, MP3_STATUS_PLAYING and MP3_STATUS_STOPPED
     */
    
    byte getStatus();
    
    /** Get the current volume level.
     * 
     * @return Value between 0 and 30
     */
    
    byte getVolume();
    
    /** Get the equalizer mode.
     * 
     * @return One of the following, 
     * 
     *  *  MP3_EQ_NORMAL
     *  *  MP3_EQ_POP        
     *  *  MP3_EQ_ROCK       
     *  *  MP3_EQ_JAZZ       
     *  *  MP3_EQ_CLASSIC    
     *  *  MP3_EQ_BASS      
     */
    
    byte getEqualizer();
    
    /** Get loop mode.
     * 
     * @return One of the following,
     * 
     *  *  MP3_LOOP_ALL       - Loop through all files.
     *  *  MP3_LOOP_FOLDER    - Loop through all files in the same folder (SD Card only)
     *  *  MP3_LOOP_ONE       - Loop one file.
     *  *  MP3_LOOP_RAM       - Loop one file (uncertain how it is different to the previous!)
     *  *  MP3_LOOP_NONE      - No loop, just play one file and then stop. (aka MP3_LOOP_ONE_STOP)
     */
    
    byte getLoopMode();
    
    /** Count the number of files on the specified media.
     * 
     * @param source One of MP3_SRC_BUILTIN and MP3_SRC_SDCARD
     * @return Number of files present on that media.
     * 
     */
    
    unsigned int   countFiles(byte source);    
    
    /** Count the number of folders on the specified media.
     * 
     *  Note that only SD Card can have folders.
     * 
     * @param source One of MP3_SRC_BUILTIN and MP3_SRC_SDCARD
     * @return Number of folders present on that media.
     */
    
    unsigned int   countFolders(byte source);    
    
    /** For the currently playing (or paused, or file that would be played 
     *  next if stopped) file, return the file's (FAT table) index number.
     * 
     *  This number can be used with playFileByIndexNumber();
     * 
     *  @param source One of MP3_SRC_BUILTIN and MP3_SRC_SDCARD
     *  @return Number of file.
     */
    
    unsigned int   currentFileIndexNumber(byte source);
 
    /** For the currently playing or paused file, return the 
     *  current position in seconds.
     * 
     * @return Number of seconds into the file currently played.
     * 
     */
    unsigned int   currentFilePositionInSeconds();
    
    /** For the currently playing or paused file, return the 
     *  total length of the file in seconds.
     * 
     * @return Length of audio file in seconds.
     */
    
    unsigned int   currentFileLengthInSeconds();
    
    /** Get the name of the "current" file on the SD Card.
     *  
     * The current file is the one that is playing, paused, or if stopped then
     * could be next to play or last played, uncertain.
     * 
     * It would be best to only consult this when playing or paused
     * and you know that the SD Card is the active source.
     * 
     * Unfortunately there is no way to query the device to find out
     * which media is the active source (at least not that I know of).
     * 
     */
    
    void           currentFileName(char *buffer, unsigned int bufferLength);    
        
    
  protected:
    
    /** Send a command to the JQ6500 module, 
     * @param command        Byte value of to send as from the datasheet.
     * @param arg1           First (if any) argument byte
     * @param arg2           Second (if any) argument byte
     * @param responseBuffer Buffer to store a single line of response, if NULL, no response is read.
     * @param buffLength     Length of response buffer including NULL terminator.
     */
    
    void sendCommand(byte command, byte arg1, byte arg2, char *responseBuffer, unsigned int bufferLength);

    // Just some different versions of that for ease of use
    void sendCommand(byte command);    
    void sendCommand(byte command, byte arg1);    
    void sendCommand(byte command, byte arg1, byte arg2);
    
    /** Send a command to the JQ6500 module, and get a response.
     * 
     * For the query commands, the JQ6500 generally sends an integer response
     * (over the UART as 4 hexadecimal digits).
     * 
     * @param command        Byte value of to send as from the datasheet.
     * @return Response from module.
     */
    
    unsigned int sendCommandWithUnsignedIntResponse(byte command);

    // This seems not that useful since there only seems to be a version 1 anway :/
    unsigned int getVersion();
    
    size_t readBytesUntilAndIncluding(char terminator, char *buffer, size_t length, byte maxOneLineOnly = 0);
    
    int    waitUntilAvailable(unsigned long maxWaitTime = 1000);
    
};

#endif