#include "sources/audiosourcestereoproxy.h"

#include "util/logger.h"
#include "util/sample.h"

namespace mixxx {

namespace {

const Logger kLogger("AudioSourceStereoProxy");

constexpr audio::ChannelCount kChannelCount = audio::ChannelCount::stereo();

audio::SignalInfo proxySignalInfo(
        const audio::SignalInfo& signalInfo) {
    DEBUG_ASSERT(signalInfo.isValid());
    return audio::SignalInfo(
            kChannelCount,
            signalInfo.getSampleRate());
}

} // anonymous namespace

AudioSourceStereoProxy::AudioSourceStereoProxy(
        AudioSourcePointer pAudioSource,
        SINT maxReadableFrames)
        : AudioSourceProxy(
                std::move(pAudioSource),
                proxySignalInfo(pAudioSource->getSignalInfo())),
          m_tempSampleBuffer(
                  (m_pAudioSource->getSignalInfo().getChannelCount() != kChannelCount) ?
                  m_pAudioSource->getSignalInfo().frames2samples(maxReadableFrames) :
                  0),
          m_tempWritableSlice(m_tempSampleBuffer) {
}

AudioSourceStereoProxy::AudioSourceStereoProxy(
        AudioSourcePointer pAudioSource,
        SampleBuffer::WritableSlice tempWritableSlice)
        : AudioSourceProxy(
                std::move(pAudioSource),
                proxySignalInfo(pAudioSource->getSignalInfo())),
          m_tempWritableSlice(std::move(tempWritableSlice)) {
}

namespace {

inline bool isDisjunct(
        const SampleBuffer::WritableSlice& slice1,
        const SampleBuffer::WritableSlice& slice2) {
    if (slice1.data() == slice2.data()) {
        return false;
    }
    if ((slice1.length() == 0) || (slice2.length() == 0)) {
        return true;
    }
    if (slice1.data() < slice2.data()) {
        return slice1.data(slice1.length()) <= slice2.data();
    } else {
        return slice2.data(slice2.length()) <= slice1.data();
    }
}

} // namespace

ReadableSampleFrames AudioSourceStereoProxy::readSampleFramesClamped(
        const WritableSampleFrames& sampleFrames) {
    if (m_pAudioSource->getSignalInfo().getChannelCount() == kChannelCount) {
        return readSampleFramesClampedOn(*m_pAudioSource, sampleFrames);
    }

    // Check location and capacity of temporary buffer
    VERIFY_OR_DEBUG_ASSERT(isDisjunct(
            m_tempWritableSlice,
            SampleBuffer::WritableSlice(sampleFrames.writableSlice()))) {
        kLogger.warning()
                << "Overlap between output and temporary sample buffer detected";
        return ReadableSampleFrames();
    }
    {
        const SINT numberOfSamplesToRead =
                m_pAudioSource->getSignalInfo().frames2samples(
                        sampleFrames.frameLength());
        VERIFY_OR_DEBUG_ASSERT(m_tempWritableSlice.length() >= numberOfSamplesToRead) {
            kLogger.warning()
                    << "Insufficient temporary sample buffer capacity"
                    << m_tempWritableSlice.length()
                    << "<"
                    << numberOfSamplesToRead
                    << "for reading frames"
                    << sampleFrames.frameIndexRange();
            return ReadableSampleFrames();
        }
    }

    const auto readableSampleFrames =
            readSampleFramesClampedOn(
                    *m_pAudioSource,
                    WritableSampleFrames(
                            sampleFrames.frameIndexRange(),
                            m_tempWritableSlice));
    if (readableSampleFrames.frameIndexRange().empty()) {
        return readableSampleFrames;
    }
    DEBUG_ASSERT(
            readableSampleFrames.frameIndexRange().isSubrangeOf(sampleFrames.frameIndexRange()));
    DEBUG_ASSERT(
            readableSampleFrames.frameIndexRange().start() >=
            sampleFrames.frameIndexRange().start());
    const SINT frameOffset =
            readableSampleFrames.frameIndexRange().start() -
            sampleFrames.frameIndexRange().start();
    SampleBuffer::WritableSlice writableSlice(
            sampleFrames.writableData(getSignalInfo().frames2samples(frameOffset)),
            getSignalInfo().frames2samples(readableSampleFrames.frameLength()));
    if (m_pAudioSource->getSignalInfo().getChannelCount() == 1) {
        SampleUtil::copyMonoToDualMono(
                writableSlice.data(),
                readableSampleFrames.readableData(),
                readableSampleFrames.frameLength());
    } else {
        SampleUtil::copyMultiToStereo(
                writableSlice.data(),
                readableSampleFrames.readableData(),
                readableSampleFrames.frameLength(),
                m_pAudioSource->getSignalInfo().getChannelCount());
    }
    return ReadableSampleFrames(
            readableSampleFrames.frameIndexRange(),
            SampleBuffer::ReadableSlice(
                    writableSlice.data(),
                    writableSlice.length()));
}

} // namespace mixxx
