#include "soundio/sounddevice.h"

#include "soundio/soundmanagerconfig.h"
#include "soundio/soundmanagerutil.h"
#include "soundmanagerconfig.h"
#include "util/debug.h"
#include "util/defs.h"
#include "util/sample.h"

SoundDevice::SoundDevice(UserSettingsPointer config, SoundManager* sm)
        : m_pConfig(config),
          m_pSoundManager(sm),
          m_strDisplayName("Unknown Soundcard"),
          m_numOutputChannels(mixxx::audio::ChannelCount::stereo()),
          m_numInputChannels(mixxx::audio::ChannelCount::stereo()),
          m_sampleRate(SoundManagerConfig::kMixxxDefaultSampleRate),
          m_hostAPI("Unknown API"),
          m_configFramesPerBuffer(0) {
}

mixxx::audio::ChannelCount SoundDevice::getNumInputChannels() const {
    return m_numInputChannels;
}

mixxx::audio::ChannelCount SoundDevice::getNumOutputChannels() const {
    return m_numOutputChannels;
}

void SoundDevice::setSampleRate(mixxx::audio::SampleRate sampleRate) {
    m_sampleRate = sampleRate.isValid() ? sampleRate : SoundManagerConfig::kMixxxDefaultSampleRate;
}

void SoundDevice::setConfigFramesPerBuffer(unsigned int framesPerBuffer) {
    if (framesPerBuffer > kMaxEngineFrames) {
        // framesPerBuffer * 2 because a frame will generally end up
        // being 2 samples and MAX_BUFFER_LEN is a number of samples
        // this isn't checked elsewhere, so...
        reportFatalErrorAndQuit("framesPerBuffer too big in "
                                "SoundDevice::setFramesPerBuffer(uint)");
    }
    m_configFramesPerBuffer = framesPerBuffer;
}

SoundDeviceStatus SoundDevice::addOutput(const AudioOutputBuffer& out) {
    // Check if the output channels are already used
    foreach (AudioOutputBuffer myOut, m_audioOutputs) {
        if (out.channelsClash(myOut)) {
            return SoundDeviceStatus::ErrorDuplicateOutputChannel;
        }
    }
    if (out.getChannelGroup().getChannelBase()
            + out.getChannelGroup().getChannelCount() > getNumOutputChannels()) {
        return SoundDeviceStatus::ErrorExcessiveOutputChannel;
    }
    m_audioOutputs.append(out);
    return SoundDeviceStatus::Ok;
}

void SoundDevice::clearOutputs() {
    m_audioOutputs.clear();
}

SoundDeviceStatus SoundDevice::addInput(const AudioInputBuffer& in) {
    // DON'T check if the input channels are already used, there's no reason
    // we can't send the same inputted samples to different places in mixxx.
    // -- bkgood 20101108
    if (in.getChannelGroup().getChannelBase()
            + in.getChannelGroup().getChannelCount() > getNumInputChannels()) {
        return SoundDeviceStatus::ErrorExcessiveInputChannel;
    }
    m_audioInputs.append(in);
    return SoundDeviceStatus::Ok;
}

void SoundDevice::clearInputs() {
    m_audioInputs.clear();
}

bool SoundDevice::operator==(const SoundDevice &other) const {
    return m_deviceId == other.getDeviceId();
}

void SoundDevice::composeOutputBuffer(CSAMPLE* outputBuffer,
                                      const SINT framesToCompose,
                                      const SINT framesReadOffset,
                                      const int iFrameSize) {
    //qDebug() << "SoundDevice::composeOutputBuffer()"
    //         << device->getInternalName()
    //         << framesToCompose << iFrameSize;

    // Interlace Audio data onto portaudio buffer.  We iterate through the
    // source list to find out what goes in the buffer data is interlaced in
    // the order of the list

    if (iFrameSize == 2 && m_audioOutputs.size() == 1 &&
            m_audioOutputs.at(0).getChannelGroup().getChannelCount() == 2) {
        // Special case for one stereo device only
        const AudioOutputBuffer& out = m_audioOutputs.at(0);
        const CSAMPLE* pAudioOutputBuffer = out.getBuffer(); // Always Stereo
        pAudioOutputBuffer = &pAudioOutputBuffer[framesReadOffset*2];
        SampleUtil::copyClampBuffer(outputBuffer, pAudioOutputBuffer,
               framesToCompose * 2);
    } else {
        // Reset sample for each open channel
        SampleUtil::clear(outputBuffer, framesToCompose * iFrameSize);

        for (QList<AudioOutputBuffer>::iterator i = m_audioOutputs.begin(),
                     e = m_audioOutputs.end(); i != e; ++i) {
            AudioOutputBuffer& out = *i;

            const ChannelGroup outChans = out.getChannelGroup();
            const int iChannelCount = outChans.getChannelCount();
            const int iChannelBase = outChans.getChannelBase();

            const CSAMPLE* pAudioOutputBuffer = out.getBuffer();
            // advanced to offset; pAudioOutputBuffer is always stereo
            pAudioOutputBuffer = &pAudioOutputBuffer[framesReadOffset*2];
            if (iChannelCount == 1) {
                // All AudioOutputs are stereo as of Mixxx 1.12.0. If we have a mono
                // output then we need to downsample.
                for (SINT iFrameNo = 0; iFrameNo < framesToCompose; ++iFrameNo) {
                    // iFrameBase is the "base sample" in a frame (ie. the first
                    // sample in a frame)
                    const SINT iFrameBase = iFrameNo * iFrameSize;
                    outputBuffer[iFrameBase + iChannelBase] = SampleUtil::clampSample(
                            (pAudioOutputBuffer[iFrameNo * 2] +
                                    pAudioOutputBuffer[iFrameNo * 2 + 1]) / 2.0f);
                }
            } else {
                for (SINT iFrameNo = 0; iFrameNo < framesToCompose; ++iFrameNo) {
                    // iFrameBase is the "base sample" in a frame (ie. the first
                    // sample in a frame)
                    const SINT iFrameBase = iFrameNo * iFrameSize;
                    const SINT iLocalFrameBase = iFrameNo * iChannelCount;

                    // this will make sure a sample from each channel is copied
                    for (int iChannel = 0; iChannel < iChannelCount; ++iChannel) {
                        outputBuffer[iFrameBase + iChannelBase + iChannel] =
                                SampleUtil::clampSample(
                                        pAudioOutputBuffer[iLocalFrameBase + iChannel]);

                        // Input audio pass-through (useful for debugging)
                        //if (in)
                        //    output[iFrameBase + src.channelBase + iChannel] =
                        //    in[iFrameBase + src.channelBase + iChannel];
                    }
                }
            }
        }
    }
}

void SoundDevice::composeInputBuffer(const CSAMPLE* inputBuffer,
                                     const SINT framesToPush,
                                     const SINT framesWriteOffset,
                                     const int iFrameSize) {
    //qDebug() << "SoundManager::pushBuffer"
    //         << framesToPush << framesWriteOffset << iFrameSize;
    // This function is called a *lot* and is a big source of CPU usage.
    // It needs to be very fast.

    // If the framesize is only 2, then we only have one pair of input channels
    //  That means we don't have to do any deinterlacing, and we can pass
    //  the audio on to its intended destination.
    if (iFrameSize == 1 && m_audioInputs.size() == 1 &&
            m_audioInputs.at(0).getChannelGroup().getChannelCount() == 1) {
        // One mono device only
        const AudioInputBuffer& in = m_audioInputs.at(0);
        CSAMPLE* pInputBuffer = in.getBuffer(); // Always Stereo
        pInputBuffer = &pInputBuffer[framesWriteOffset * 2];
        for (SINT iFrameNo = 0; iFrameNo < framesToPush; ++iFrameNo) {
            pInputBuffer[iFrameNo * 2] =
                    inputBuffer[iFrameNo];
            pInputBuffer[iFrameNo * 2 + 1] =
                    inputBuffer[iFrameNo];
        }
    } else if (iFrameSize == 2 && m_audioInputs.size() == 1 &&
            m_audioInputs.at(0).getChannelGroup().getChannelCount() == 2) {
        // One stereo device only
        const AudioInputBuffer& in = m_audioInputs.at(0);
        CSAMPLE* pInputBuffer = in.getBuffer(); // Always Stereo
        pInputBuffer = &pInputBuffer[framesWriteOffset * 2];
        SampleUtil::copy(pInputBuffer, inputBuffer, framesToPush * 2);
    } else {
        // Non Stereo input (iFrameSize != 2)
        // Do crazy deinterleaving of the audio into the correct m_inputBuffers.

        for (auto i = m_audioInputs.constBegin(), e = m_audioInputs.constEnd(); i != e; ++i) {
            const AudioInputBuffer& in = *i;
            ChannelGroup chanGroup = in.getChannelGroup();
            int iChannelCount = chanGroup.getChannelCount();
            int iChannelBase = chanGroup.getChannelBase();
            CSAMPLE* pInputBuffer = in.getBuffer();
            pInputBuffer = &pInputBuffer[framesWriteOffset * 2];

            for (SINT iFrameNo = 0; iFrameNo < framesToPush; ++iFrameNo) {
                // iFrameBase is the "base sample" in a frame (ie. the first
                // sample in a frame)
                SINT iFrameBase = iFrameNo * iFrameSize;
                SINT iLocalFrameBase = iFrameNo * 2;

                if (iChannelCount == 1) {
                    pInputBuffer[iLocalFrameBase] =
                            inputBuffer[iFrameBase + iChannelBase];
                    pInputBuffer[iLocalFrameBase + 1] =
                            inputBuffer[iFrameBase + iChannelBase];
                } else if (iChannelCount > 1) {
                    pInputBuffer[iLocalFrameBase] =
                            inputBuffer[iFrameBase + iChannelBase];
                    pInputBuffer[iLocalFrameBase + 1] =
                            inputBuffer[iFrameBase + iChannelBase + 1];
                }
            }
        }
    }
}

void SoundDevice::clearInputBuffer(const SINT framesToPush,
                                   const SINT framesWriteOffset) {
    for (auto i = m_audioInputs.constBegin(), e = m_audioInputs.constEnd(); i != e; ++i) {
        const AudioInputBuffer& in = *i;
        CSAMPLE* pInputBuffer = in.getBuffer();  // Always stereo
        SampleUtil::clear(&pInputBuffer[framesWriteOffset * 2], framesToPush * 2);
    }
}
