#include "AlsaInterface.h"
#include "FileLogger.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(const AudioDevicePtr& 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(const AudioDevicePtr& device)
{
    if (snd_pcm_hw_params_set_access(mHandle, mHardwareParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
        MLOG_ERROR("Error setting device access.");
        return;
    }
}

void AlsaInterface::SetSampleFormat(const AudioDevicePtr& 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;
    }
}

void AlsaInterface::SetSampleRate(const AudioDevicePtr& 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;
    }
    if (rate != exact_rate) {
        MLOG_ERROR("The rate is not supported by your hardware.");
    }
}

void AlsaInterface::SetPeriod(const AudioDevicePtr& 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;
    }
}

void AlsaInterface::SetBufferSize(const AudioDevicePtr& 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;
    }
}

void AlsaInterface::SetChannelNumber(const AudioDevicePtr& device)
{
    /* Set number of channels */
    if (snd_pcm_hw_params_set_channels(mHandle, mHardwareParams, device->GetNumChannels()) < 0)
    {
        MLOG_ERROR("Error setting channels");
        return;
    }
}

void AlsaInterface::Play(const AudioDevicePtr& device)
{
    MLOG_INFO("Playing audio");
    AudioSynth synth;
    const unsigned duration = 100;
    double freq = 440;
    auto data = synth.GetSineWave(freq, duration)->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 >>>>>>>>>>>>>>>");
        }
    }
}
