// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <array>
#include <chrono>

#include "audio_core/audio_core.h"
#include "audio_core/common/common.h"
#include "audio_core/renderer/adsp/audio_renderer.h"
#include "audio_core/sink/sink.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/core_timing.h"

MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));

namespace AudioCore::AudioRenderer::ADSP {

void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) {
    adsp_messages.enqueue(message_);
    adsp_event.Set();
}

RenderMessage AudioRenderer_Mailbox::HostWaitMessage() {
    host_event.Wait();
    RenderMessage msg{RenderMessage::Invalid};
    if (!host_messages.try_dequeue(msg)) {
        LOG_ERROR(Service_Audio, "Failed to dequeue host message!");
    }
    return msg;
}

void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) {
    host_messages.enqueue(message_);
    host_event.Set();
}

RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
    adsp_event.Wait();
    RenderMessage msg{RenderMessage::Invalid};
    if (!adsp_messages.try_dequeue(msg)) {
        LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!");
    }
    return msg;
}

CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const u32 session_id) {
    return command_buffers[session_id];
}

void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) {
    command_buffers[session_id] = buffer;
}

u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const {
    return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken;
}

u64 AudioRenderer_Mailbox::GetSignalledTick() const {
    return signalled_tick;
}

void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) {
    signalled_tick = tick;
}

void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) {
    command_buffers[session_id].remaining_command_count = 0;
}

u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const {
    return command_buffers[session_id].remaining_command_count;
}

void AudioRenderer_Mailbox::ClearCommandBuffers() {
    command_buffers[0].buffer = 0;
    command_buffers[0].size = 0;
    command_buffers[0].reset_buffers = false;
    command_buffers[1].buffer = 0;
    command_buffers[1].size = 0;
    command_buffers[1].reset_buffers = false;
}

AudioRenderer::AudioRenderer(Core::System& system_)
    : system{system_}, sink{system.AudioCore().GetOutputSink()} {
    CreateSinkStreams();
}

AudioRenderer::~AudioRenderer() {
    Stop();
    for (auto& stream : streams) {
        if (stream) {
            sink.CloseStream(stream);
        }
        stream = nullptr;
    }
}

void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
    if (running) {
        return;
    }

    mailbox = mailbox_;
    thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(stop_token); });
    running = true;
}

void AudioRenderer::Stop() {
    if (!running) {
        return;
    }

    for (auto& stream : streams) {
        stream->Stop();
    }
    thread.join();
    running = false;
}

void AudioRenderer::CreateSinkStreams() {
    u32 channels{sink.GetDeviceChannels()};
    for (u32 i = 0; i < MaxRendererSessions; i++) {
        std::string name{fmt::format("ADSP_RenderStream-{}", i)};
        streams[i] =
            sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
        streams[i]->SetRingSize(4);
    }
}

void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
    static constexpr char name[]{"AudioRenderer"};
    MicroProfileOnThreadCreate(name);
    Common::SetCurrentThreadName(name);
    Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
    if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
        LOG_ERROR(Service_Audio,
                  "ADSP Audio Renderer -- Failed to receive initialize message from host!");
        return;
    }

    mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);

    // 0.12 seconds (2304000 / 19200000)
    constexpr u64 max_process_time{2'304'000ULL};

    while (!stop_token.stop_requested()) {
        auto message{mailbox->ADSPWaitMessage()};
        switch (message) {
        case RenderMessage::AudioRenderer_Shutdown:
            mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown);
            return;

        case RenderMessage::AudioRenderer_Render: {
            if (system.IsShuttingDown()) [[unlikely]] {
                std::this_thread::sleep_for(std::chrono::milliseconds(5));
                mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
                continue;
            }
            std::array<bool, MaxRendererSessions> buffers_reset{};
            std::array<u64, MaxRendererSessions> render_times_taken{};
            const auto start_time{system.CoreTiming().GetClockTicks()};

            for (u32 index = 0; index < 2; index++) {
                auto& command_buffer{mailbox->GetCommandBuffer(index)};
                auto& command_list_processor{command_list_processors[index]};

                // Check this buffer is valid, as it may not be used.
                if (command_buffer.buffer != 0) {
                    // If there are no remaining commands (from the previous list),
                    // this is a new command list, initialize it.
                    if (command_buffer.remaining_command_count == 0) {
                        command_list_processor.Initialize(system, command_buffer.buffer,
                                                          command_buffer.size, streams[index]);
                    }

                    if (command_buffer.reset_buffers && !buffers_reset[index]) {
                        streams[index]->ClearQueue();
                        buffers_reset[index] = true;
                    }

                    u64 max_time{max_process_time};
                    if (index == 1 && command_buffer.applet_resource_user_id ==
                                          mailbox->GetCommandBuffer(0).applet_resource_user_id) {
                        max_time = max_process_time - render_times_taken[0];
                        if (render_times_taken[0] > max_process_time) {
                            max_time = 0;
                        }
                    }

                    max_time = std::min(command_buffer.time_limit, max_time);
                    command_list_processor.SetProcessTimeMax(max_time);

                    streams[index]->WaitFreeSpace(stop_token);

                    // Process the command list
                    {
                        MICROPROFILE_SCOPE(Audio_Renderer);
                        render_times_taken[index] =
                            command_list_processor.Process(index) - start_time;
                    }

                    const auto end_time{system.CoreTiming().GetClockTicks()};

                    command_buffer.remaining_command_count =
                        command_list_processor.GetRemainingCommandCount();
                    command_buffer.render_time_taken = end_time - start_time;
                }
            }

            mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
        } break;

        default:
            LOG_WARNING(Service_Audio,
                        "ADSP AudioRenderer received an invalid message, msg={:02X}!",
                        static_cast<u32>(message));
            break;
        }
    }
}

} // namespace AudioCore::AudioRenderer::ADSP
