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

#include <array>
#include <chrono>

#include "audio_core/adsp/apps/audio_renderer/audio_renderer.h"
#include "audio_core/audio_core.h"
#include "audio_core/common/common.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::ADSP::AudioRenderer {

AudioRenderer::AudioRenderer(Core::System& system_, Core::Memory::Memory& memory_,
                             Sink::Sink& sink_)
    : system{system_}, memory{memory_}, sink{sink_} {}

AudioRenderer::~AudioRenderer() {
    Stop();
}

void AudioRenderer::Start() {
    CreateSinkStreams();

    mailbox.Initialize(AppMailboxId::AudioRenderer);

    main_thread = std::jthread([this](std::stop_token stop_token) { Main(stop_token); });

    mailbox.Send(Direction::DSP, {Message::InitializeOK, {}});
    if (mailbox.Receive(Direction::Host).msg != Message::InitializeOK) {
        LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
                                 "message response from ADSP!");
        return;
    }
    running = true;
}

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

    mailbox.Send(Direction::DSP, {Message::Shutdown, {}});
    if (mailbox.Receive(Direction::Host).msg != Message::Shutdown) {
        LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
                                 "message response from ADSP!");
    }
    main_thread.request_stop();
    main_thread.join();

    for (auto& stream : streams) {
        if (stream) {
            stream->Stop();
            sink.CloseStream(stream);
            stream = nullptr;
        }
    }
    running = false;
}

void AudioRenderer::Signal() {
    signalled_tick = system.CoreTiming().GetGlobalTimeNs().count();
    Send(Direction::DSP, {Message::Render, {}});
}

void AudioRenderer::Wait() {
    auto received = Receive(Direction::Host);
    if (received.msg != Message::RenderResponse) {
        LOG_ERROR(Service_Audio,
                  "Did not receive the expected render response from the AudioRenderer! Expected "
                  "{}, got {}",
                  Message::RenderResponse, received.msg);
    }
}

void AudioRenderer::Send(Direction dir, MailboxMessage message) {
    mailbox.Send(dir, std::move(message));
}

MailboxMessage AudioRenderer::Receive(Direction dir, bool block) {
    return mailbox.Receive(dir, block);
}

void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
                                     u64 applet_resource_user_id, bool reset) noexcept {
    command_buffers[session_id].buffer = buffer;
    command_buffers[session_id].size = size;
    command_buffers[session_id].time_limit = time_limit;
    command_buffers[session_id].applet_resource_user_id = applet_resource_user_id;
    command_buffers[session_id].reset_buffer = reset;
}

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

void AudioRenderer::ClearRemainCommandCount(s32 session_id) noexcept {
    command_buffers[session_id].remaining_command_count = 0;
}

u64 AudioRenderer::GetRenderingStartTick(s32 session_id) const noexcept {
    return (1000 * command_buffers[session_id].render_time_taken_us) + signalled_tick;
}

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::Main(std::stop_token stop_token) {
    static constexpr char name[]{"AudioRenderer"};
    MicroProfileOnThreadCreate(name);
    Common::SetCurrentThreadName(name);
    Common::SetCurrentThreadPriority(Common::ThreadPriority::High);

    // TODO: Create buffer map/unmap thread + mailbox
    // TODO: Create gMix devices, initialize them here

    if (mailbox.Receive(Direction::DSP).msg != Message::InitializeOK) {
        LOG_ERROR(Service_Audio,
                  "ADSP Audio Renderer -- Failed to receive initialize message from host!");
        return;
    }

    mailbox.Send(Direction::Host, {Message::InitializeOK, {}});

    // 0.12 seconds (2,304,000 / 19,200,000)
    constexpr u64 max_process_time{2'304'000ULL};

    while (!stop_token.stop_requested()) {
        auto received{mailbox.Receive(Direction::DSP)};
        switch (received.msg) {
        case Message::Shutdown:
            mailbox.Send(Direction::Host, {Message::Shutdown, {}});
            return;

        case Message::Render: {
            if (system.IsShuttingDown()) [[unlikely]] {
                std::this_thread::sleep_for(std::chrono::milliseconds(5));
                mailbox.Send(Direction::Host, {Message::RenderResponse, {}});
                continue;
            }
            std::array<bool, MaxRendererSessions> buffers_reset{};
            std::array<u64, MaxRendererSessions> render_times_taken{};
            const auto start_time{system.CoreTiming().GetGlobalTimeUs().count()};

            for (u32 index = 0; index < MaxRendererSessions; index++) {
                auto& command_buffer{command_buffers[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_buffer && !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 ==
                                          command_buffers[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);

                    if (index == 0) {
                        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().GetGlobalTimeUs().count()};

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

            mailbox.Send(Direction::Host, {Message::RenderResponse, {}});
        } break;

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

} // namespace AudioCore::ADSP::AudioRenderer
