/*
  AudioGeneratorAAC
  Audio output generator using the Helix AAC decoder
  
  Copyright (C) 2017  Earle F. Philhower, III

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#pragma GCC optimize ("O3")

#include "AudioGeneratorAAC.h"

AudioGeneratorAAC::AudioGeneratorAAC()
{
  preallocateSpace = NULL;
  preallocateSize = 0;

  running = false;
  file = NULL;
  output = NULL;

  buff = (uint8_t*)malloc(buffLen);
  outSample = (int16_t*)malloc(1024 * 2 * sizeof(uint16_t));
  if (!buff || !outSample) {
    audioLogger->printf_P(PSTR("ERROR: Out of memory in AAC\n"));
    Serial.flush();
  }

  hAACDecoder = AACInitDecoder();
  if (!hAACDecoder) {
    audioLogger->printf_P(PSTR("Out of memory error! hAACDecoder==NULL\n"));
    Serial.flush();
  }

  buffValid = 0;
  lastFrameEnd = 0;
  validSamples = 0;
  curSample = 0;
  lastRate = 0;
  lastChannels = 0;
}

AudioGeneratorAAC::AudioGeneratorAAC(void *preallocateData, int preallocateSz)
{
  preallocateSpace = preallocateData;
  preallocateSize = preallocateSz;

  running = false;
  file = NULL;
  output = NULL;

  uint8_t *p = (uint8_t*)preallocateSpace;
  buff = (uint8_t*) p;
  p += (buffLen + 7) & ~7;
  outSample = (int16_t*) p;
  p += (1024 * 2 * sizeof(int16_t) + 7) & ~7;
  int used = p - (uint8_t*)preallocateSpace;
  int availSpace = preallocateSize - used;
  if (availSpace < 0 ) {
    audioLogger->printf_P(PSTR("ERROR: Out of memory in AAC\n"));
  }

  hAACDecoder = AACInitDecoderPre(p, availSpace);
  if (!hAACDecoder) {
    audioLogger->printf_P(PSTR("Out of memory error! hAACDecoder==NULL\n"));
    Serial.flush();
  }
  buffValid = 0;
  lastFrameEnd = 0;
  validSamples = 0;
  curSample = 0;
  lastRate = 0;
  lastChannels = 0;
}



AudioGeneratorAAC::~AudioGeneratorAAC()
{
  if (!preallocateSpace) {
    AACFreeDecoder(hAACDecoder);
    free(buff);
    free(outSample);
  }
}

bool AudioGeneratorAAC::stop()
{
  running = false;
  output->stop();
  return file->close();
}

bool AudioGeneratorAAC::isRunning()
{
  return running;
}

bool AudioGeneratorAAC::FillBufferWithValidFrame()
{
  buff[0] = 0; // Destroy any existing sync word @ 0
  int nextSync;
  do {
    nextSync = AACFindSyncWord(buff + lastFrameEnd, buffValid - lastFrameEnd);
    if (nextSync >= 0) nextSync += lastFrameEnd;
    lastFrameEnd = 0;
    if (nextSync == -1) {
      if (buffValid && buff[buffValid-1]==0xff) { // Could be 1st half of syncword, preserve it...
        buff[0] = 0xff;
        buffValid = file->read(buff+1, buffLen-1);
        if (buffValid==0) return false; // No data available, EOF
      } else { // Try a whole new buffer
        buffValid = file->read(buff, buffLen-1);
        if (buffValid==0) return false; // No data available, EOF
      }
    }
  } while (nextSync == -1);

  // Move the frame to start at offset 0 in the buffer
  buffValid -= nextSync; // Throw out prior to nextSync
  memmove(buff, buff+nextSync, buffValid);

  // We have a sync word at 0 now, try and fill remainder of buffer
  buffValid += file->read(buff + buffValid, buffLen - buffValid);

  return true;
}

bool AudioGeneratorAAC::loop()
{
  if (!running) goto done; // Nothing to do here!

  // If we've got data, try and pump it out...
  while (validSamples) {
    if (lastChannels == 1) {
       lastSample[0] = outSample[curSample];
       lastSample[1] = outSample[curSample];
    } else {
      lastSample[0] = outSample[curSample*2];
      lastSample[1] = outSample[curSample*2 + 1];
    }
    if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
    validSamples--;
    curSample++;
  }

  // No samples available, need to decode a new frame
  if (FillBufferWithValidFrame()) {
    // buff[0] start of frame, decode it...
    unsigned char *inBuff = reinterpret_cast<unsigned char *>(buff);
    int bytesLeft = buffValid;
    int ret = AACDecode(hAACDecoder, &inBuff, &bytesLeft, outSample);
    if (ret) {
      // Error, skip the frame...
      char buff[48];
      sprintf_P(buff, PSTR("AAC decode error %d"), ret);
      cb.st(ret, buff);
    } else {
      lastFrameEnd = buffValid - bytesLeft;
      AACFrameInfo fi;
      AACGetLastFrameInfo(hAACDecoder, &fi);
      if ((int)fi.sampRateOut != (int)lastRate) {
        output->SetRate(fi.sampRateOut);
        lastRate = fi.sampRateOut;
      }
      if (fi.nChans != lastChannels) {
        output->SetChannels(fi.nChans);
        lastChannels = fi.nChans;
      }
      curSample = 0;
      validSamples = fi.outputSamps / lastChannels;
    }
  } else {
    running = false; // No more data, we're done here...
  }

done:
  file->loop();
  output->loop();

  return running;
}

bool AudioGeneratorAAC::begin(AudioFileSource *source, AudioOutput *output)
{
  if (!source) return false;
  file = source;
  if (!output) return false;
  this->output = output;
  if (!file->isOpen()) return false; // Error

  output->begin();
  
  // AAC always comes out at 16 bits
  output->SetBitsPerSample(16);
 

  memset(buff, 0, buffLen);
  memset(outSample, 0, 1024*2*sizeof(int16_t));

 
  running = true;
  
  return true;
}


