// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)

#include <kernel/types/KProcess.h>
#include <audio/downmixer.h>
#include "voice.h"

namespace skyline::service::audio::IAudioRenderer {
    void Voice::SetWaveBufferIndex(u8 index) {
        bufferIndex = index & 3;
        bufferReload = true;
    }

    Voice::Voice(const DeviceState &state) : state(state) {}

    void Voice::ProcessInput(const VoiceIn &input) {
        // Voice no longer in use, reset it
        if (acquired && !input.acquired) {
            bufferReload = true;
            bufferIndex = 0;
            sampleOffset = 0;

            output.playedSamplesCount = 0;
            output.playedWaveBuffersCount = 0;
            output.voiceDropsCount = 0;
        }

        acquired = input.acquired;

        if (!acquired)
            return;

        if (input.firstUpdate) {
            if (input.format != skyline::audio::AudioFormat::Int16 && input.format != skyline::audio::AudioFormat::ADPCM)
                throw exception("Unsupported voice PCM format: {}", input.format);

            format = input.format;
            sampleRate = input.sampleRate;

            if (input.channelCount > (input.format == skyline::audio::AudioFormat::ADPCM ? 1 : 6))
                throw exception("Unsupported voice channel count: {}", input.channelCount);

            channelCount = static_cast<u8>(input.channelCount);

            if (input.format == skyline::audio::AudioFormat::ADPCM) {
                std::vector<std::array<i16, 2>> adpcmCoefficients(input.adpcmCoeffsSize / (sizeof(u16) * 2));
                span(adpcmCoefficients).copy_from(span(input.adpcmCoeffs, input.adpcmCoeffsSize / sizeof(u32)));

                adpcmDecoder = skyline::audio::AdpcmDecoder(std::move(adpcmCoefficients));
            }

            SetWaveBufferIndex(static_cast<u8>(input.baseWaveBufferIndex));
        }

        waveBuffers = input.waveBuffers;
        volume = input.volume;
        playbackState = input.playbackState;
    }

    void Voice::UpdateBuffers() {
        const auto &currentBuffer{waveBuffers.at(bufferIndex)};
        span buffer(currentBuffer.pointer, currentBuffer.size);

        if (currentBuffer.size == 0)
            return;

        switch (format) {
            case skyline::audio::AudioFormat::Int16:
                samples.resize(currentBuffer.size / sizeof(i16));
                span(samples).copy_from(buffer);
                break;
            case skyline::audio::AudioFormat::ADPCM: {
                samples = adpcmDecoder->Decode(buffer);
                break;
            }
            default:
                throw exception("Unsupported PCM format used by Voice: {}", format);
        }

        if (sampleRate != constant::SampleRate)
            samples = resampler.ResampleBuffer(samples, static_cast<double>(sampleRate) / constant::SampleRate, channelCount);

        if (channelCount == 1 && constant::StereoChannelCount != channelCount) {
            auto originalSize{samples.size()};
            samples.resize((originalSize / channelCount) * constant::StereoChannelCount);

            for (auto monoIndex{originalSize - 1}, targetIndex{samples.size()}; monoIndex > 0; monoIndex--) {
                auto sample{samples[monoIndex]};
                for (u8 i{}; i < constant::StereoChannelCount; i++)
                    samples[--targetIndex] = sample;
            }
        }

        if (channelCount == constant::SurroundChannelCount) {
            span(samples).copy_from(span(skyline::audio::DownMix(span(samples).cast<skyline::audio::Surround51Sample>())));
            samples.resize((samples.size() / constant::SurroundChannelCount) * constant::StereoChannelCount);
        }
    }

    std::vector<i16> &Voice::GetBufferData(u32 maxSamples, u32 &outOffset, u32 &outSize) {
        auto &currentBuffer{waveBuffers.at(bufferIndex)};

        if (!acquired || playbackState != skyline::audio::AudioOutState::Started) {
            outSize = 0;
            return samples;
        }

        if (bufferReload) {
            bufferReload = false;
            UpdateBuffers();
        }

        outOffset = sampleOffset;
        outSize = std::min(maxSamples * constant::StereoChannelCount, static_cast<u32>(samples.size() - sampleOffset));

        output.playedSamplesCount += outSize / constant::StereoChannelCount;
        sampleOffset += outSize;

        if (sampleOffset == samples.size()) {
            sampleOffset = 0;

            if (currentBuffer.lastBuffer)
                playbackState = skyline::audio::AudioOutState::Paused;

            if (!currentBuffer.looping)
                SetWaveBufferIndex(static_cast<u8>(bufferIndex + 1));

            output.playedWaveBuffersCount++;
        }

        return samples;
    }
}
