#include "AlsaInterface.h"

#include "FileLogger.h"
#include "AudioDevice.h"

#include "AudioSynth.h"

#include <vector>

AlsaInterface::AlsaInterface()
    :mHandle(),
     mHardwareParams(),
     mPeriodSize(8192)
{

}

AlsaInterface::~AlsaInterface()
{

}

std::unique_ptr<AlsaInterface> AlsaInterface::Create()
{
    return std::make_unique<AlsaInterface>();
}

void AlsaInterface::openDevice(AudioDevice* device)
{
    MLOG_INFO("Opening Device");
    snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
    if (snd_pcm_open(&mHandle, device->getName().c_str(), stream, 0) < 0)
    {
        MLOG_ERROR("Error opening PCM device: " + device->getName());
        return;
    }

    snd_pcm_hw_params_alloca(&mHardwareParams);
    if (snd_pcm_hw_params_any(mHandle, mHardwareParams) < 0)
    {
        MLOG_ERROR("Can not configure this PCM device.");
        return;
    }

    setAccessType(device);
    setSampleFormat(device);
    setSampleRate(device);
    setPeriod(device);
    setBufferSize(device);
    setChannelNumber(device);

    /* Apply HW parameter settings to */
    /* PCM device and prepare device  */
    if (snd_pcm_hw_params(mHandle, mHardwareParams) < 0)
    {
        MLOG_ERROR("Error setting HW params.");
        return;
    }
}

void AlsaInterface::setAccessType(AudioDevice* device)
{
    if (snd_pcm_hw_params_set_access(mHandle, mHardwareParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
    {
        MLOG_ERROR("Error setting device access.");
        return;
    }
}

bool AlsaInterface::setSampleFormat(AudioDevice* device)
{
    /* Set sample format */
    if (snd_pcm_hw_params_set_format(mHandle, mHardwareParams, SND_PCM_FORMAT_S16_LE) < 0)
    {
        MLOG_ERROR("Error setting format. ");
        return false;
    }
    return true;
}

bool AlsaInterface::setSampleRate(AudioDevice* device)
{
    unsigned rate = device->getSampleRate();
    unsigned exact_rate = rate;
    if (snd_pcm_hw_params_set_rate_near(mHandle, mHardwareParams, &exact_rate, 0) < 0)
    {
        MLOG_ERROR("Error setting rate. ");
        return false;
    }

    if (rate != exact_rate)
    {
        MLOG_ERROR("The rate is not supported by your hardware.");
        return false;
    }
    return true;
}

bool AlsaInterface::setPeriod(AudioDevice* device)
{
    /* Set number of periods. Periods used to be called fragments. */
    if (snd_pcm_hw_params_set_periods(mHandle, mHardwareParams, device->getPeriod(), 0) < 0)
    {
        MLOG_ERROR("Error setting periods. ");
        return false;
    }
    return true;
}

bool AlsaInterface::setBufferSize(AudioDevice* device)
{
    int periods = static_cast<int>(device->getPeriod());

    /* Set buffer size (in frames). The resulting latency is given by */
    /* latency = periodsize * periods / (rate * bytes_per_frame)     */
    if (snd_pcm_hw_params_set_buffer_size(mHandle, mHardwareParams, (mPeriodSize * periods)>>2) < 0)
    {
        MLOG_ERROR("Error setting buffersize. ");
        return false;
    }
    return true;
}

bool AlsaInterface::setChannelNumber(AudioDevice* device)
{
    /* Set number of channels */
    if (snd_pcm_hw_params_set_channels(mHandle, mHardwareParams, device->getNumChannels()) < 0)
    {
        MLOG_ERROR("Error setting channels");
        return false;
    }
    return true;
}

void AlsaInterface::play(AudioDevice* device, AudioSample* sample, unsigned duration)
{
    if (!device->getIsOpen())
    {
        openDevice(device);
    }

    if (!device->getIsOpen())
    {
        return;
    }

    MLOG_INFO("Playing audio");
    const auto data = sample->getChannelData(0);

    int numFrames = mPeriodSize >> 2;
    for(int count = 0; count < duration; count++)
    {
        const auto offset= count*4*numFrames;
        unsigned char frame[4] = {data[offset], data[offset+1], data[offset+2], data[offset+3]};
        while ((snd_pcm_writei(mHandle, frame, numFrames)) < 0)
        {
            snd_pcm_prepare(mHandle);
            MLOG_ERROR("<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>");
        }
    }
}
