/** 
 * 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
 */

#include <Arduino.h>
#include "JQ6500_Serial.h"
#include <SoftwareSerial.h>


void  JQ6500_Serial::play()
{
  this->sendCommand(0x0D);
}

void  JQ6500_Serial::restart()
{
  byte oldVolume = this->getVolume();  
  this->setVolume(0);
  this->next();
  this->pause();
  this->setVolume(oldVolume);
  this->prev();
}

void  JQ6500_Serial::pause()
{
  this->sendCommand(0x0E);
}

void  JQ6500_Serial::next()
{
  this->sendCommand(0x01);
}

void  JQ6500_Serial::prev()
{
  this->sendCommand(0x02);
}

void  JQ6500_Serial::playFileByIndexNumber(unsigned int fileNumber)
{  
  this->sendCommand(0x03, (fileNumber>>8) & 0xFF, fileNumber & (byte)0xFF);
}

void  JQ6500_Serial::nextFolder()
{
  this->sendCommand(0x0F, 0x01);  
}

void  JQ6500_Serial::prevFolder()
{
  this->sendCommand(0x0F, 0x00);
}

void  JQ6500_Serial::playFileNumberInFolderNumber(unsigned int folderNumber, unsigned int fileNumber)
{
  this->sendCommand(0x12, folderNumber & 0xFF, fileNumber & 0xFF);
}

void  JQ6500_Serial::volumeUp()
{
  this->sendCommand(0x04);
}

void  JQ6500_Serial::volumeDn()
{
  this->sendCommand(0x05);
}

void  JQ6500_Serial::setVolume(byte volumeFrom0To30)
{
  this->sendCommand(0x06, volumeFrom0To30);
}

void  JQ6500_Serial::setEqualizer(byte equalizerMode)
{
  this->sendCommand(0x07, equalizerMode);
}

void  JQ6500_Serial::setLoopMode(byte loopMode)
{
  this->sendCommand(0x11, loopMode);
}

void  JQ6500_Serial::setSource(byte source)
{
  this->sendCommand(0x09, source);
}

void  JQ6500_Serial::sleep()
{
  this->sendCommand(0x0A);
}

void  JQ6500_Serial::reset()
{
  this->sendCommand(0x0C);
  delay(500); // We need some time for the reset to happen
}


    byte  JQ6500_Serial::getStatus()    
    {
      byte statTotal = 0;
      byte stat       = 0;
      do
      {
        statTotal = 0;
        for(byte x = 0; x < MP3_STATUS_CHECKS_IN_AGREEMENT; x++)
        {
          stat = this->sendCommandWithUnsignedIntResponse(0x42);      
          if(stat == 0) return 0; // STOP is fairly reliable
          statTotal += stat;
        }

      } while (statTotal != 1 * MP3_STATUS_CHECKS_IN_AGREEMENT && statTotal != 2 * MP3_STATUS_CHECKS_IN_AGREEMENT);
      
      return statTotal / MP3_STATUS_CHECKS_IN_AGREEMENT;      
    }
    
    byte  JQ6500_Serial::getVolume()    { return this->sendCommandWithUnsignedIntResponse(0x43); }
    byte  JQ6500_Serial::getEqualizer() { return this->sendCommandWithUnsignedIntResponse(0x44); }
    byte  JQ6500_Serial::getLoopMode()  { return this->sendCommandWithUnsignedIntResponse(0x45); }
    unsigned int  JQ6500_Serial::getVersion()   { return this->sendCommandWithUnsignedIntResponse(0x46); }
    
    unsigned int  JQ6500_Serial::countFiles(byte source)   
    {
      if(source == MP3_SRC_SDCARD)
      {
        return this->sendCommandWithUnsignedIntResponse(0x47); 
      }
      else if (source == MP3_SRC_BUILTIN)
      {
        return this->sendCommandWithUnsignedIntResponse(0x49);
      }     
      
      return 0;
    }
    
    unsigned int  JQ6500_Serial::countFolders(byte source) 
    {
      if(source == MP3_SRC_SDCARD)
      {
        return this->sendCommandWithUnsignedIntResponse(0x53); 
      }
      
      return 0;
    }
    
    unsigned int  JQ6500_Serial::currentFileIndexNumber(byte source)
    {
      if(source == MP3_SRC_SDCARD)
      {
        return this->sendCommandWithUnsignedIntResponse(0x4B); 
      }
      else if (source == MP3_SRC_BUILTIN)
      {
        return this->sendCommandWithUnsignedIntResponse(0x4D)+1; // CRAZY!
      }     
      
      return 0;
    }
    
    unsigned int  JQ6500_Serial::currentFilePositionInSeconds() { return this->sendCommandWithUnsignedIntResponse(0x50); }
    unsigned int  JQ6500_Serial::currentFileLengthInSeconds()   { return this->sendCommandWithUnsignedIntResponse(0x51); }
    
    void          JQ6500_Serial::currentFileName(char *buffer, unsigned int bufferLength) 
    {
      this->sendCommand(0x52, 0, 0, buffer, bufferLength);
    }
    
    // Used for the status commands, they mostly return an 8 to 16 bit integer 
    // and take no arguments
    unsigned int JQ6500_Serial::sendCommandWithUnsignedIntResponse(byte command)
    {      
      char buffer[5];
      this->sendCommand(command, 0, 0, buffer, sizeof(buffer));
      return (unsigned int) strtoul(buffer, NULL, 16);
    }
    
    void  JQ6500_Serial::sendCommand(byte command)
    {
      this->sendCommand(command, 0, 0, 0, 0);
    }
    
    void  JQ6500_Serial::sendCommand(byte command, byte arg1)
    {
       this->sendCommand(command, arg1, 0, 0, 0);
    }
    
    void  JQ6500_Serial::sendCommand(byte command, byte arg1, byte arg2)
    {
       this->sendCommand(command, arg1, arg2, 0, 0);
    }    
    
    void  JQ6500_Serial::sendCommand(byte command, byte arg1, byte arg2, char *responseBuffer, unsigned int bufferLength)
    {
      
      
      // Command structure
      // [7E][number bytes following including command and terminator][command byte][?arg1][?arg2][EF]
      
      // Most commands do not have arguments
      byte args = 0;
      
      // These ones do
      switch(command)
      {        
        case 0x03: args = 2; break;
        case 0x06: args = 1; break;
        case 0x07: args = 1; break;        
        case 0x09: args = 1; break;
        case 0x0F: args = 1; break;
        case 0x11: args = 1; break;
        case 0x12: args = 2; break;
      }
      
#if MP3_DEBUG
      char buf[4];       
      Serial.println();
      Serial.print("7E ");      
      itoa(2+args, buf, 16); Serial.print(buf); Serial.print(" "); memset(buf, 0, sizeof(buf));
      itoa(command, buf, 16); Serial.print(buf); Serial.print(" "); memset(buf, 0, sizeof(buf));
      if(args>=1) itoa(arg1, buf, 16); Serial.print(buf); Serial.print(" "); memset(buf, 0, sizeof(buf));
      if(args>=2) itoa(arg2, buf, 16); Serial.print(buf); Serial.print(" "); memset(buf, 0, sizeof(buf));
      Serial.print("EF");      
#endif
      
      // The device appears to send some sort of status information (namely "STOP" when it stops playing)
      // just discard this right before we send the command
      while(this->waitUntilAvailable(10)) this->read();
      
      this->write((byte)0x7E);
      this->write(2+args);
      this->write(command);
      if(args>=1) this->write(arg1);
      if(args==2) this->write(arg2);
      this->write((byte)0xEF);
      
      
      unsigned int i = 0;
      char         j = 0;
      if(responseBuffer && bufferLength) 
      {
        memset(responseBuffer, 0, bufferLength);
      }
      
      // Allow some time for the device to process what we did and 
      // respond, up to 1 second, but typically only a few ms.
      this->waitUntilAvailable(1000);

      
#if MP3_DEBUG
      Serial.print(" ==> [");
#endif
      
      while(this->waitUntilAvailable(150))
      {
        j = (char)this->read();
        
#if MP3_DEBUG
        Serial.print(j);
#endif
        if(responseBuffer && (i<(bufferLength-1)))
        {
           responseBuffer[i++] = j;
        }
      }
      
#if MP3_DEBUG      
      Serial.print("]");
      Serial.println();
#endif
      
    }
    
    
// as readBytes with terminator character
// terminates if length characters have been read, timeout, or if the terminator character  detected
// returns the number of characters placed in the buffer (0 means no valid data found)

size_t JQ6500_Serial::readBytesUntilAndIncluding(char terminator, char *buffer, size_t length, byte maxOneLineOnly)
{
    if (length < 1) return 0;
  size_t index = 0;
  while (index < length) {
    int c = timedRead();
    if (c < 0) break;    
    *buffer++ = (char)c;
    index++;
    if(c == terminator) break;
    if(maxOneLineOnly && ( c == '\n') ) break;
  }
  return index; // return number of characters, not including null terminator
}


// Waits until data becomes available, or a timeout occurs
int JQ6500_Serial::waitUntilAvailable(unsigned long maxWaitTime)
{
  unsigned long startTime;
  int c = 0;
  startTime = millis();
  do {
    c = this->available();
    if (c) break;
  } while(millis() - startTime < maxWaitTime);
  
  return c;
}
