// ---------------------------------------------------------------------------
// AUTHOR/LICENSE:
//  The following code was written by Antoine Beauchamp. For other authors, see AUTHORS file.
//  The code & updates for the library can be found at https://github.com/end2endzone/AnyRtttl
//  MIT License: http://www.opensource.org/licenses/mit-license.php
// ---------------------------------------------------------------------------
#include "Arduino.h"
#include "anyrtttl.h"
#include "binrtttl.h"

/*********************************************************
 * RTTTL Library data
 *********************************************************/

namespace anyrtttl
{

const uint16_t notes[] = { NOTE_SILENT,
NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,
NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5,
NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6,
NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7
};

#define isdigit(n) (n >= '0' && n <= '9')
typedef uint16_t TONE_DURATION;
static const byte NOTES_PER_OCTAVE = 12;

const char * buffer = "";
ReadCharFuncPtr readCharFunc = &readChar;
int bufferIndex = -32760;
byte default_dur = 4;
byte default_oct = 5;
RTTTL_BPM bpm = 63;
RTTTL_DURATION wholenote;
byte pin = -1;
unsigned long delayToNextNote = 0; //milliseconds before playing the next note
bool playing = false;
TONE_DURATION duration;
byte noteOffset;
RTTTL_OCTAVE_VALUE scale;
int tmpNumber;

const char * readNumber(const char * iBuffer, int & oValue, ReadCharFuncPtr iReadCharFunc)
{
  oValue = 0;
  while(isdigit(iReadCharFunc(iBuffer)))
  {
    oValue = (oValue * 10) + (readCharFunc(buffer) - '0');
    iBuffer++;
  }
  return iBuffer;
}

void serialPrint(const char * iBuffer, ReadCharFuncPtr iReadCharFunc)
{
  char c = readCharFunc(iBuffer);
  while(c) {
    Serial.print(c);
    iBuffer++;
    c = readCharFunc(iBuffer);
  }
}

/****************************************************************************
 * Custom functions
 ****************************************************************************/

ToneFuncPtr _tone = &tone;
NoToneFuncPtr _noTone = &noTone;
DelayFuncPtr _delay = &delay;
MillisFuncPtr _millis = &millis;

void setToneFunction(ToneFuncPtr iFunc) {
  _tone = iFunc;
}

void setNoToneFunction(NoToneFuncPtr iFunc) {
  _noTone = iFunc;
}

void setDelayFunction(DelayFuncPtr iFunc) {
  _delay = iFunc;
}

void setMillisFunction(MillisFuncPtr iFunc) {
  _millis = iFunc;
}

char readChar(const char * iBuffer) {
  return *iBuffer;
}

char readChar_P(const char * iBuffer) {
  return pgm_read_byte_near(iBuffer);
}



/****************************************************************************
 * Blocking API
 ****************************************************************************/
namespace blocking
{

void play(byte iPin, const char * iBuffer, ReadCharFuncPtr iReadCharFunc) {
  // Absolutely no error checking in here

  default_dur = 4;
  default_oct = 6;
  bpm = 63;
  buffer = iBuffer;
  readCharFunc = iReadCharFunc;
  
  #ifdef ANY_RTTTL_DEBUG
  Serial.print("playing: ");
  serialPrint(buffer, readCharFunc);
  Serial.println();
  #endif

  // format: d=N,o=N,b=NNN:
  // find the start (skip name, etc)

  while(readCharFunc(buffer) != ':') buffer++; // ignore name
  buffer++;                        // skip ':'

  // get default duration
  if(readCharFunc(buffer) == 'd')
  {
    buffer++; buffer++;           // skip "d="
    buffer = readNumber(buffer, tmpNumber, readCharFunc);
    if(tmpNumber > 0)
      default_dur = tmpNumber;
    buffer++;                      // skip comma
  }

  #ifdef ANY_RTTTL_INFO
  Serial.print("ddur: "); Serial.println(default_dur, 10);
  #endif

  // get default octave
  if(readCharFunc(buffer) == 'o')
  {
    buffer++; buffer++;           // skip "o="
    buffer = readNumber(buffer, tmpNumber, readCharFunc);
    if(tmpNumber >= 3 && tmpNumber <= 7)
      default_oct = tmpNumber;
    buffer++;                      // skip comma
  }

  #ifdef ANY_RTTTL_INFO
  Serial.print("doct: "); Serial.println(default_oct, 10);
  #endif

  // get BPM
  if(readCharFunc(buffer) == 'b')
  {
    buffer++; buffer++;         // skip "b="
    buffer = readNumber(buffer, tmpNumber, readCharFunc);
    bpm = tmpNumber;
    buffer++;                    // skip colon
  }

  #ifdef ANY_RTTTL_INFO
  Serial.print("bpm: "); Serial.println(bpm, 10);
  #endif

  // BPM usually expresses the number of quarter notes per minute
  wholenote = (60 * 1000L / bpm) * 4;  // this is the time for whole noteOffset (in milliseconds)

  #ifdef ANY_RTTTL_INFO
  Serial.print("wn: "); Serial.println(wholenote, 10);
  #endif

  // now begin note loop
  while(readCharFunc(buffer))
  {
    // first, get note duration, if available
    buffer = readNumber(buffer, tmpNumber, readCharFunc);
    
    if(tmpNumber)
      duration = wholenote / tmpNumber;
    else
      duration = wholenote / default_dur;  // we will need to check if we are a dotted noteOffset after

    // now get the note
    noteOffset = getNoteOffsetFromLetter(readCharFunc(buffer));
    buffer++;

    // now, get optional '#' sharp
    if(readCharFunc(buffer) == '#')
    {
      noteOffset++;
      buffer++;
    }

    // now, get optional '.' dotted note
    if(readCharFunc(buffer) == '.')
    {
      duration += duration/2;
      buffer++;
    }
  
    // now, get scale
    if(isdigit(readCharFunc(buffer)))
    {
      scale = readCharFunc(buffer) - '0';
      buffer++;
    }
    else
    {
      scale = default_oct;
    }

    if(readCharFunc(buffer) == ',')
      buffer++;       // skip comma for next note (or we may be at the end)

    // now play the note
    if(noteOffset)
    {
      uint16_t frequency = notes[(scale - 4) * NOTES_PER_OCTAVE + noteOffset];

      #ifdef ANY_RTTTL_INFO
      Serial.print("Playing: ");
      Serial.print(scale, 10); Serial.print(' ');
      Serial.print(noteOffset, 10); Serial.print(" (");
      Serial.print(frequency, 10);
      Serial.print(") ");
      Serial.println(duration, 10);
      #endif

      _tone(iPin, frequency, duration);
      _delay(duration+1);
      _noTone(iPin);
    }
    else
    {
      #ifdef ANY_RTTTL_INFO
      Serial.print("Pausing: ");
      Serial.println(duration, 10);
      #endif
      _delay(duration);
    }
  }
}

void play(byte iPin, const char * iBuffer)              { play(iPin, iBuffer, &readChar); }

void play(byte iPin, const __FlashStringHelper* str)    { play(iPin, (const char *)str, &readChar_P); }
void playProgMem(byte iPin, const char * iBuffer)       { play(iPin, iBuffer, &readChar_P); }
void play_P(byte iPin, const char * iBuffer)            { play(iPin, iBuffer, &readChar_P); }
void play_P(byte iPin, const __FlashStringHelper* str)  { play(iPin, (const char *)str, &readChar_P); }

void play16Bits(int iPin, const unsigned char * iBuffer, int iNumNotes) {
  // Absolutely no error checking in here

  RTTTL_DEFAULT_VALUE_SECTION * defaultSection = (RTTTL_DEFAULT_VALUE_SECTION *)iBuffer;
  RTTTL_NOTE * notesBuffer = (RTTTL_NOTE *)iBuffer;

  bpm = defaultSection->bpm;

  #ifdef ANY_RTTTL_DEBUG
  Serial.print("numNotes=");
  Serial.println(iNumNotes);
  // format: d=N,o=N,b=NNN:
  Serial.print("d=");
  Serial.print(getNoteDurationFromIndex(defaultSection->durationIdx));
  Serial.print(",o=");
  Serial.print(getNoteOctaveFromIndex(defaultSection->octaveIdx));
  Serial.print(",b=");
  Serial.println(bpm);
  #endif
  
  // BPM usually expresses the number of quarter notes per minute
  wholenote = (60 * 1000L / bpm) * 4;  // this is the time for whole noteOffset (in milliseconds)

  // now begin note loop
  for(int i=0; i<iNumNotes; i++) {
    const RTTTL_NOTE & n = notesBuffer[i+1]; //offset by 16 bits for RTTTL_DEFAULT_VALUE_SECTION

    // first, get note duration, if available
    duration = wholenote / getNoteDurationFromIndex(n.durationIdx);

    // now get the note
    //noteOffset = noteOffsets[n.noteIdx];
    noteOffset = getNoteOffsetFromLetterIndex(n.noteIdx);

    // now, get optional '#' sharp
    if(n.pound)
    {
      noteOffset++;
    }

    // now, get optional '.' dotted note
    if(n.dotted)
    {
      duration += duration/2;
    }

    // now, get scale
    scale = getNoteOctaveFromIndex(n.octaveIdx);

    if(noteOffset)
    {
      #ifdef ANY_RTTTL_DEBUG
      Serial.print(getNoteDurationFromIndex(n.durationIdx));
      static const char noteCharacterValues[] =   {'c','d','e','f','g','a','b','p'};
      Serial.print(noteCharacterValues[n.noteIdx]);
      Serial.print( (n.pound ? "#" : "") );
      Serial.print( (n.dotted ? "." : "") );
      Serial.println(getNoteOctaveFromIndex(n.octaveIdx));
      #endif
      
      uint16_t frequency = notes[(scale - 4) * NOTES_PER_OCTAVE + noteOffset];

      _tone(iPin, frequency, duration);
      _delay(duration+1);
      _noTone(iPin);
    }
    else
    {
      #ifdef ANY_RTTTL_DEBUG
      Serial.print(getNoteDurationFromIndex(n.durationIdx));
      static const char noteCharacterValues[] =   {'c','d','e','f','g','a','b','p'};
      Serial.print(noteCharacterValues[n.noteIdx]);
      Serial.print( (n.pound ? "#" : "") );
      Serial.print( (n.dotted ? "." : "") );
      Serial.println();
      #endif

      _delay(duration);
    }
  }
}

void play10Bits(int iPin, int iNumNotes, BitReadFuncPtr iFuncPtr) {
  // Absolutely no error checking in here

  //read default section
  RTTTL_DEFAULT_VALUE_SECTION defaultSection;
  defaultSection.raw = iFuncPtr(16);

  bpm = defaultSection.bpm;

  #ifdef ANY_RTTTL_DEBUG
  Serial.print("numNotes=");
  Serial.println(iNumNotes);
  // format: d=N,o=N,b=NNN:
  Serial.print("d=");
  Serial.print(getNoteDurationFromIndex(defaultSection.durationIdx));
  Serial.print(",o=");
  Serial.print(getNoteOctaveFromIndex(defaultSection.octaveIdx));
  Serial.print(",b=");
  Serial.println(bpm);
  #endif
  
  // BPM usually expresses the number of quarter notes per minute
  wholenote = (60 * 1000L / bpm) * 4;  // this is the time for whole noteOffset (in milliseconds)

  // now begin note loop
  for(int i=0; i<iNumNotes; i++) {
    RTTTL_NOTE n;
    n.raw = iFuncPtr(10);

    // first, get note duration, if available
    duration = wholenote / getNoteDurationFromIndex(n.durationIdx);

    // now get the note
    noteOffset = getNoteOffsetFromLetterIndex(n.noteIdx);

    // now, get optional '#' sharp
    if(n.pound)
    {
      noteOffset++;
    }

    // now, get optional '.' dotted note
    if(n.dotted)
    {
      duration += duration/2;
    }

    // now, get scale
    scale = getNoteOctaveFromIndex(n.octaveIdx);

    if(noteOffset)
    {
      #ifdef ANY_RTTTL_DEBUG
      Serial.print(getNoteDurationFromIndex(n.durationIdx));
      static const char noteCharacterValues[] =   {'c','d','e','f','g','a','b','p'};
      Serial.print(noteCharacterValues[n.noteIdx]);
      Serial.print( (n.pound ? "#" : "") );
      Serial.print( (n.dotted ? "." : "") );
      Serial.println(getNoteOctaveFromIndex(n.octaveIdx));
      #endif
      
      uint16_t frequency = notes[(scale - 4) * 12 + noteOffset];
      _tone(iPin, frequency, duration);
      _delay(duration+1);
      _noTone(iPin);
    }
    else
    {
      #ifdef ANY_RTTTL_DEBUG
      Serial.print(getNoteDurationFromIndex(n.durationIdx));
      static const char noteCharacterValues[] =   {'c','d','e','f','g','a','b','p'};
      Serial.print(noteCharacterValues[n.noteIdx]);
      Serial.print( (n.pound ? "#" : "") );
      Serial.print( (n.dotted ? "." : "") );
      Serial.println();
      #endif

      _delay(duration);
    }
  }
}



}; //blocking namespace


/****************************************************************************
 * Non-blocking API
 ****************************************************************************/
namespace nonblocking
{


//pre-declaration
void nextnote();

void begin(byte iPin, const char * iBuffer, ReadCharFuncPtr iReadCharFunc)
{
  //init values
  pin = iPin;
  buffer = iBuffer;
  bufferIndex = 0;
  default_dur = 4;
  default_oct = 6;
  bpm=63;
  playing = true;
  delayToNextNote = 0;
  readCharFunc = iReadCharFunc;
  
  #ifdef ANY_RTTTL_DEBUG
  Serial.print("playing: ");
  serialPrint(buffer, readCharFunc);
  Serial.println();
  #endif

  //stop current note
  noTone(pin);

  // format: d=N,o=N,b=NNN:
  // find the start (skip name, etc)

  //read buffer until first note
  while(readCharFunc(buffer) != ':') buffer++;     // ignore name
  buffer++;                           // skip ':'

  // get default duration
  if(readCharFunc(buffer) == 'd')
  {
    buffer++; buffer++;               // skip "d="
    buffer = readNumber(buffer, tmpNumber, readCharFunc);
    if(tmpNumber > 0)
      default_dur = tmpNumber;
    buffer++;                         // skip comma
  }

  #ifdef ANY_RTTTL_INFO
  Serial.print("ddur: "); Serial.println(default_dur, 10);
  #endif
  
  // get default octave
  if(readCharFunc(buffer) == 'o')
  {
    buffer++; buffer++;               // skip "o="
    buffer = readNumber(buffer, tmpNumber, readCharFunc);
    if(tmpNumber >= 3 && tmpNumber <= 7)
      default_oct = tmpNumber;
    buffer++;                         // skip comma
  }

  #ifdef ANY_RTTTL_INFO
  Serial.print("doct: "); Serial.println(default_oct, 10);
  #endif
  
  // get BPM
  if(readCharFunc(buffer) == 'b')
  {
    buffer++; buffer++;              // skip "b="
    buffer = readNumber(buffer, tmpNumber, readCharFunc);
    bpm = tmpNumber;
    buffer++;                   // skip colon
  }

  #ifdef ANY_RTTTL_INFO
  Serial.print("bpm: "); Serial.println(bpm, 10);
  #endif

  // BPM usually expresses the number of quarter notes per minute
  wholenote = (60 * 1000L / bpm) * 4;  // this is the time for whole noteOffset (in milliseconds)

  #ifdef ANY_RTTTL_INFO
  Serial.print("wn: "); Serial.println(wholenote, 10);
  #endif
}

void begin(byte iPin, const char * iBuffer)             { begin(iPin, iBuffer, &readChar); }

void begin(byte iPin, const __FlashStringHelper* str)   { begin(iPin, (const char *)str, &readChar_P); }
void beginProgMem(byte iPin, const char * iBuffer)      { begin(iPin, iBuffer, &readChar_P); }
void begin_P(byte iPin, const char * iBuffer)           { begin(iPin, iBuffer, &readChar_P); }
void begin_P(byte iPin, const __FlashStringHelper* str) { begin(iPin, (const char *)str, &readChar_P); }

void nextnote()
{
  //stop current note
  _noTone(pin);

  // first, get note duration, if available
  buffer = readNumber(buffer, tmpNumber, readCharFunc);
  
  if(tmpNumber)
    duration = wholenote / tmpNumber;
  else
    duration = wholenote / default_dur;  // we will need to check if we are a dotted noteOffset after

  // now get the note
  noteOffset = getNoteOffsetFromLetter(readCharFunc(buffer));
  buffer++;

  // now, get optional '#' sharp
  if(readCharFunc(buffer) == '#')
  {
    noteOffset++;
    buffer++;
  }

  // now, get optional '.' dotted note
  if(readCharFunc(buffer) == '.')
  {
    duration += duration/2;
    buffer++;
  }

  // now, get scale
  if(isdigit(readCharFunc(buffer)))
  {
    scale = readCharFunc(buffer) - '0';
    buffer++;
  }
  else
  {
    scale = default_oct;
  }

  if(readCharFunc(buffer) == ',')
    buffer++;       // skip comma for next note (or we may be at the end)

  // now play the note
  if(noteOffset)
  {
    #ifdef ANY_RTTTL_INFO
    Serial.print("Playing: ");
    Serial.print(scale, 10); Serial.print(' ');
    Serial.print(noteOffset, 10); Serial.print(" (");
    Serial.print(notes[(scale - 4) * NOTES_PER_OCTAVE + noteOffset], 10);
    Serial.print(") ");
    Serial.println(duration, 10);
    #endif
    
    uint16_t frequency = notes[(scale - 4) * NOTES_PER_OCTAVE + noteOffset];
    _tone(pin, frequency, duration);
    
    delayToNextNote = _millis() + (duration+1);
  }
  else
  {
    #ifdef ANY_RTTTL_INFO
    Serial.print("Pausing: ");
    Serial.println(duration, 10);
    #endif
    
    delayToNextNote = _millis() + (duration);
  }
}

void play()
{
  //if done playing the song, return
  if (!playing)
  {
    #ifdef ANY_RTTTL_DEBUG
    Serial.println("done playing...");
    #endif
    
    return;
  }
  
  //are we still playing a note ?
  unsigned long m = _millis();
  if (m < delayToNextNote)
  {
    #ifdef ANY_RTTTL_DEBUG
    Serial.println("still playing a note...");
    #endif
    
    //wait until the note is completed
    return;
  }

  //ready to play the next note
  if (readCharFunc(buffer) == '\0')
  {
    //no more notes. Reached the end of the last note

    #ifdef ANY_RTTTL_DEBUG
    Serial.println("end of note...");
    #endif
    
    playing = false;

    //stop current note (if any)
    _noTone(pin);

    return; //end of the song
  }
  else
  {
    //more notes to play...

    #ifdef ANY_RTTTL_DEBUG
    Serial.println("next note...");
    #endif
    
    nextnote();
  }
}

void stop()
{
  if (playing)
  {
    //increase song buffer until the end
    while (readCharFunc(buffer) != '\0')
    {
      buffer++;
    }
  }

  playing = false;

  //stop current note (if any)
  _noTone(pin);
}

bool done()
{
  return !playing;
}

bool isPlaying()
{
  return playing;
}

}; //nonblocking namespace

}; //anyrtttl namespace
