/******************************************************************************\
 * Copyright (c) 2004-2020
 *
 * Author(s):
 *  Simon Tomlinson
 *
 ******************************************************************************
 *
 * 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 2 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, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
\******************************************************************************/

#include "sound.h"
#include "androiddebug.cpp"

/* Implementation *************************************************************/

CSound::CSound ( void           (*fpNewProcessCallback) ( CVector<short>& psData, void* arg ),
                 void*          arg,
                 const int      iCtrlMIDIChannel,
                 const bool     ,
                 const QString& ) :
    CSoundBase ( "OpenSL", true, fpNewProcessCallback, arg, iCtrlMIDIChannel )

{
     pSound = this;
#ifdef ANDROIDDEBUG
  qInstallMessageHandler(myMessageHandler);
#endif
}

void CSound::setupCommonStreamParams(oboe::AudioStreamBuilder *builder)
{
    // We request EXCLUSIVE mode since this will give us the lowest possible
    // latency. If EXCLUSIVE mode isn't available the builder will fall back to SHARED mode
    builder->setCallback(this)
            ->setFormat(oboe::AudioFormat::Float)
            ->setSharingMode(oboe::SharingMode::Shared)
            ->setChannelCount(oboe::ChannelCount::Mono)
           // ->setSampleRate(48000)
           // ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Medium)
            ->setPerformanceMode(oboe::PerformanceMode::None);
    return;
}

void CSound::openStreams()
{
    // Create callback
    mCallback = this;

    //Setup output stream
     oboe::AudioStreamBuilder inBuilder, outBuilder;
     outBuilder.setDirection(oboe::Direction::Output);
     setupCommonStreamParams(&outBuilder);
     oboe::Result result = outBuilder.openManagedStream(mPlayStream);
     if (result != oboe::Result::OK) {
         return;
     }
     mPlayStream->setBufferSizeInFrames(pSound->iOpenSLBufferSizeStereo);

    warnIfNotLowLatency(mPlayStream, "PlayStream");
    printStreamDetails(mPlayStream);

   //Setup input stream
    inBuilder.setDirection(oboe::Direction::Input);
    setupCommonStreamParams(&inBuilder);
    result = inBuilder.openManagedStream(mRecordingStream);
    if (result != oboe::Result::OK) {
         closeStream(mPlayStream);
         return;
    }
    mRecordingStream->setBufferSizeInFrames(pSound->iOpenSLBufferSizeStereo);

     warnIfNotLowLatency(mRecordingStream, "RecordStream");
     printStreamDetails(mRecordingStream);
}

void CSound::printStreamDetails(oboe::ManagedStream &stream)
{

    QString sDirection = (stream->getDirection()==oboe::Direction::Input?"Input":"Output");
    QString sFramesPerBurst = QString::number(stream->getFramesPerBurst());
    QString sBufferSizeInFrames =  QString::number(stream->getBufferSizeInFrames());
    QString sBytesPerFrame =  QString::number(stream->getBytesPerFrame());
    QString sBytesPerSample =  QString::number(stream->getBytesPerSample());
    QString sBufferCapacityInFrames =  QString::number(stream->getBufferCapacityInFrames());
    QString sPerformanceMode = (stream->getPerformanceMode()==oboe::PerformanceMode::LowLatency?"LowLatency":"NotLowLatency");
    QString sSharingMode = (stream->getSharingMode() == oboe::SharingMode::Exclusive?"Exclusive":"Shared");
    QString sDeviceID =  QString::number(stream->getDeviceId());
    QString sSampleRate =  QString::number(stream->getSampleRate());
    QString sAudioFormat = (stream->getFormat()==oboe::AudioFormat::I16?"I16":"Float");

    QString sFramesPerCallback =  QString::number(stream->getFramesPerCallback());
    //QString sSampleRateConversionQuality = (stream.getSampleRateConversionQuality()==oboe::SampleRateConversionQuality::

    qInfo() << "Stream details: [sDirection: " << sDirection <<
               ", FramesPerBurst: " << sFramesPerBurst <<
               ", BufferSizeInFrames: " << sBufferSizeInFrames <<
               ", BytesPerFrame: " << sBytesPerFrame <<
               ", BytesPerSample: " << sBytesPerSample <<
               ", BufferCapacityInFrames: " << sBufferCapacityInFrames <<
               ", PerformanceMode: " << sPerformanceMode <<
               ", SharingMode: " << sSharingMode <<
               ", DeviceID: " << sDeviceID <<
               ", SampleRate: " << sSampleRate <<
               ", AudioFormat: " << sAudioFormat <<
               ", FramesPerCallback: " << sFramesPerCallback << "]";

}

void CSound::warnIfNotLowLatency(oboe::ManagedStream &stream, QString streamName) {
    if (stream->getPerformanceMode() != oboe::PerformanceMode::LowLatency) {
        QString latencyMode = (stream->getPerformanceMode()==oboe::PerformanceMode::None ? "None" : "Power Saving");
       // throw CGenErr ( tr ( "Stream is NOT low latency."
                         //    "Check your requested format, sample rate and channel count." ) );
    }
}

void CSound::closeStream(oboe::ManagedStream &stream)
{
    if (stream) {
        oboe::Result requestStopRes = stream->requestStop();
        oboe::Result result = stream->close();
        if (result != oboe::Result::OK) {
            throw CGenErr ( tr ( "Error closing stream: $s",
                                 oboe::convertToText(result) ) );
        }
        stream.reset();
    }
}

void CSound::closeStreams()
{
    // clean up
    closeStream(mRecordingStream);
    closeStream(mPlayStream);
}

void CSound::Start()
{
    openStreams();

    // call base class
    CSoundBase::Start();

    // finally start the streams so the callback begins, start with inputstream first.
    mRecordingStream->requestStart();
    mPlayStream->requestStart();

}

void CSound::Stop()
{
    closeStreams();

    // call base class
    CSoundBase::Stop();
}

int CSound::Init ( const int iNewPrefMonoBufferSize )
{
    // store buffer size
    iOpenSLBufferSizeMono = 512 ;
            //iNewPrefMonoBufferSize;

    // init base class
    CSoundBase::Init ( iOpenSLBufferSizeMono );

    // set internal buffer size value and calculate stereo buffer size
    iOpenSLBufferSizeStereo = 2 * iOpenSLBufferSizeMono;

    // create memory for intermediate audio buffer
    vecsTmpAudioSndCrdStereo.Init ( iOpenSLBufferSizeStereo );

// TEST
#if ( SYSTEM_SAMPLE_RATE_HZ != 48000 )
# error "Only a system sample rate of 48 kHz is supported by this module"
#endif
// audio interface number of channels is 1 and the sample rate
// is 16 kHz -> just copy samples and perform no filtering as a
// first simple solution
// 48 kHz / 16 kHz = factor 3 (note that the buffer size mono might
// be divisible by three, therefore we will get a lot of drop outs)
iModifiedInBufSize = iOpenSLBufferSizeMono / 3;
vecsTmpAudioInSndCrd.Init ( iModifiedInBufSize );

    return iOpenSLBufferSizeMono;
}

// This is the main callback method for when an audio stream is ready to publish data to an output stream
// or has received data on an input stream.  As per manual much be very careful not to do anything in this back that
// can cause delays such as sleeping, file processing, allocate memory, etc
oboe::DataCallbackResult CSound::onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames)
{
     // only process if we are running
     if ( ! pSound->bRun )
     {
         return oboe::DataCallbackResult::Continue;
     }

     // Need to modify the size of the buffer based on the numFrames requested in this callback.
     // Buffer size can change regularly by android devices
    int& iBufferSizeMono = pSound->iOpenSLBufferSizeMono;

    // perform the processing for input and output
//    QMutexLocker locker ( &pSound->Mutex );
 //   locker.mutex();

    //This can be called from both input and output at different times
    if (oboeStream == pSound->mPlayStream.get() && audioData)
    {
        float *floatData = static_cast<float *>(audioData);

        // Zero out the incoming container array
        memset(audioData, 0, sizeof(float) * numFrames * oboeStream->getChannelCount());

        // Only copy data if we have data to copy, otherwise fill with silence
        if (!pSound->vecsTmpAudioSndCrdStereo.empty())
        {
            for (int frmNum = 0; frmNum < numFrames; ++frmNum)
            {
                for (int channelNum = 0; channelNum < oboeStream->getChannelCount(); channelNum++)
                {
                    // copy sample received from server into output buffer


                    // convert to 32 bit
                    const int32_t iCurSam = static_cast<int32_t> (
                        pSound->vecsTmpAudioSndCrdStereo [frmNum * oboeStream->getChannelCount() + channelNum] );
                    floatData[frmNum * oboeStream->getChannelCount() + channelNum] = (float) iCurSam/ _MAXSHORT;
                }
            }
        }
        else
        {
            // prime output stream buffer with silence
            memset(static_cast<float*>(audioData) + numFrames * oboeStream->getChannelCount(), 0,
                       (numFrames) * oboeStream->getBytesPerFrame());
        }
    }
    else if (oboeStream == pSound->mRecordingStream.get() && audioData)
    {
        // First things first, we need to discard the input queue a little for 500ms or so
        if (pSound->mCountCallbacksToDrain > 0)
        {
            // discard the input buffer
            int32_t numBytes = numFrames * oboeStream->getBytesPerFrame();
            memset(audioData, 0 /* value */, numBytes);
            pSound->mCountCallbacksToDrain--;
        }

        // We're good to start recording now
        // Take the data from the recording device ouput buffer and move
        // it to the vector ready to send up to the server

        float *floatData = static_cast<float *>(audioData);

        // Copy recording data to internal vector
        for (int frmNum = 0; frmNum < numFrames; ++frmNum)
        {
            for (int channelNum = 0; channelNum < oboeStream->getChannelCount(); channelNum++)
            {
               pSound->vecsTmpAudioSndCrdStereo [frmNum * oboeStream->getChannelCount() + channelNum] =
                       (short) floatData[frmNum * oboeStream->getChannelCount() + channelNum] * _MAXSHORT;
            }
        }

        // Tell parent class that we've put some data ready to send to the server
        pSound->ProcessCallback ( pSound->vecsTmpAudioSndCrdStereo  );
    }
  //  locker.unlock();
    return oboe::DataCallbackResult::Continue;
}

//TODO better handling of stream closing errors
void CSound::onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result result)
{
    qDebug() << "CSound::onErrorAfterClose";
}

//TODO better handling of stream closing errors
void CSound::onErrorBeforeClose(oboe::AudioStream *oboeStream, oboe::Result result)
{
     qDebug() << "CSound::onErrorBeforeClose";
}