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

#include "audio_core/renderer/behavior/behavior_info.h"
#include "audio_core/renderer/memory/memory_pool_info.h"
#include "audio_core/renderer/performance/performance_manager.h"
#include "common/common_funcs.h"

namespace AudioCore::AudioRenderer {

void PerformanceManager::CreateImpl(const size_t version) {
    switch (version) {
    case 1:
        impl = std::make_unique<
            PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
                                   PerformanceEntryVersion1, PerformanceDetailVersion1>>();
        break;
    case 2:
        impl = std::make_unique<
            PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
                                   PerformanceEntryVersion2, PerformanceDetailVersion2>>();
        break;
    default:
        LOG_WARNING(Service_Audio, "Invalid PerformanceMetricsDataFormat {}, creating version 1",
                    static_cast<u32>(version));
        impl = std::make_unique<
            PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
                                   PerformanceEntryVersion1, PerformanceDetailVersion1>>();
        break;
    }
}

void PerformanceManager::Initialize(std::span<u8> workbuffer, const u64 workbuffer_size,
                                    const AudioRendererParameterInternal& params,
                                    const BehaviorInfo& behavior,
                                    const MemoryPoolInfo& memory_pool) {
    CreateImpl(behavior.GetPerformanceMetricsDataFormat());
    impl->Initialize(workbuffer, workbuffer_size, params, behavior, memory_pool);
}

bool PerformanceManager::IsInitialized() const {
    if (impl) {
        return impl->IsInitialized();
    }
    return false;
}

u32 PerformanceManager::CopyHistories(u8* out_buffer, u64 out_size) {
    if (impl) {
        return impl->CopyHistories(out_buffer, out_size);
    }
    return 0;
}

bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
                                      const PerformanceSysDetailType sys_detail_type,
                                      const s32 node_id) {
    if (impl) {
        return impl->GetNextEntry(addresses, unk, sys_detail_type, node_id);
    }
    return false;
}

bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses,
                                      const PerformanceEntryType entry_type, const s32 node_id) {
    if (impl) {
        return impl->GetNextEntry(addresses, entry_type, node_id);
    }
    return false;
}

bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses,
                                      const PerformanceDetailType detail_type,
                                      const PerformanceEntryType entry_type, const s32 node_id) {
    if (impl) {
        return impl->GetNextEntry(addresses, detail_type, entry_type, node_id);
    }
    return false;
}

void PerformanceManager::TapFrame(const bool dsp_behind, const u32 voices_dropped,
                                  const u64 rendering_start_tick) {
    if (impl) {
        impl->TapFrame(dsp_behind, voices_dropped, rendering_start_tick);
    }
}

bool PerformanceManager::IsDetailTarget(const u32 target_node_id) const {
    if (impl) {
        return impl->IsDetailTarget(target_node_id);
    }
    return false;
}

void PerformanceManager::SetDetailTarget(const u32 target_node_id) {
    if (impl) {
        impl->SetDetailTarget(target_node_id);
    }
}

template <>
void PerformanceManagerImpl<
    PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
    PerformanceDetailVersion1>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size,
                                           const AudioRendererParameterInternal& params,
                                           const BehaviorInfo& behavior,
                                           const MemoryPoolInfo& memory_pool) {
    workbuffer = workbuffer_;
    entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1;
    max_detail_count = MaxDetailEntries;
    frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params);
    const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)};
    max_frames = frame_count - 1;
    translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size);

    // The first frame is the "current" frame we're writing to.
    auto buffer_offset{workbuffer.data()};
    frame_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset);
    buffer_offset += sizeof(PerformanceFrameHeaderVersion1);
    entry_buffer = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset), entries_per_frame};
    buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1);
    detail_buffer = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset), max_detail_count};

    // After the current, is a ringbuffer of history frames, the current frame will be copied here
    // before a new frame is written.
    frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size);

    // If there's room for any history frames.
    if (frame_count >= 2) {
        buffer_offset = frame_history.data();
        frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset);
        buffer_offset += sizeof(PerformanceFrameHeaderVersion1);
        frame_history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset),
                                 entries_per_frame};
        buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1);
        frame_history_details = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset),
                                 max_detail_count};
    } else {
        frame_history_header = {};
        frame_history_entries = {};
        frame_history_details = {};
    }

    target_node_id = 0;
    version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat());
    entry_count = 0;
    detail_count = 0;
    frame_header->entry_count = 0;
    frame_header->detail_count = 0;
    output_frame_index = 0;
    last_output_frame_index = 0;
    is_initialized = true;
}

template <>
bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
                            PerformanceEntryVersion1, PerformanceDetailVersion1>::IsInitialized()
    const {
    return is_initialized;
}

template <>
u32 PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
                           PerformanceEntryVersion1,
                           PerformanceDetailVersion1>::CopyHistories(u8* out_buffer, u64 out_size) {
    if (out_buffer == nullptr || out_size == 0 || !is_initialized) {
        return 0;
    }

    // Are there any new frames waiting to be output?
    if (last_output_frame_index == output_frame_index) {
        return 0;
    }

    PerformanceFrameHeaderVersion1* out_header{nullptr};
    u32 out_history_size{0};

    while (last_output_frame_index != output_frame_index) {
        PerformanceFrameHeaderVersion1* history_header{nullptr};
        std::span<PerformanceEntryVersion1> history_entries{};
        std::span<PerformanceDetailVersion1> history_details{};

        if (max_frames > 0) {
            auto frame_offset{&frame_history[last_output_frame_index * frame_size]};
            history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(frame_offset);
            frame_offset += sizeof(PerformanceFrameHeaderVersion1);
            history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(frame_offset),
                               history_header->entry_count};
            frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion1);
            history_details = {reinterpret_cast<PerformanceDetailVersion1*>(frame_offset),
                               history_header->detail_count};
        } else {
            // Original code does not break here, but will crash when trying to dereference the
            // header in the next if, so let's just skip this frame and continue...
            // Hopefully this will not happen.
            LOG_WARNING(Service_Audio,
                        "max_frames should not be 0! Skipping frame to avoid a crash");
            last_output_frame_index++;
            continue;
        }

        if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion1) +
                           history_header->detail_count * sizeof(PerformanceDetailVersion1) +
                           2 * sizeof(PerformanceFrameHeaderVersion1)) {
            break;
        }

        u32 out_offset{sizeof(PerformanceFrameHeaderVersion1)};
        auto out_entries{std::span<PerformanceEntryVersion1>(
            reinterpret_cast<PerformanceEntryVersion1*>(out_buffer + out_offset),
            history_header->entry_count)};
        u32 out_entry_count{0};
        u32 total_processing_time{0};
        for (auto& history_entry : history_entries) {
            if (history_entry.processed_time > 0 || history_entry.start_time > 0) {
                out_entries[out_entry_count++] = history_entry;
                total_processing_time += history_entry.processed_time;
            }
        }

        out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion1));
        auto out_details{std::span<PerformanceDetailVersion1>(
            reinterpret_cast<PerformanceDetailVersion1*>(out_buffer + out_offset),
            history_header->detail_count)};
        u32 out_detail_count{0};
        for (auto& history_detail : history_details) {
            if (history_detail.processed_time > 0 || history_detail.start_time > 0) {
                out_details[out_detail_count++] = history_detail;
            }
        }

        out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion1));
        out_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(out_buffer);
        out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F');
        out_header->entry_count = out_entry_count;
        out_header->detail_count = out_detail_count;
        out_header->next_offset = out_offset;
        out_header->total_processing_time = total_processing_time;
        out_header->frame_index = history_header->frame_index;

        out_history_size += out_offset;

        out_buffer += out_offset;
        out_size -= out_offset;
        last_output_frame_index = (last_output_frame_index + 1) % max_frames;
    }

    // We're out of frames to output, so if there's enough left in the output buffer for another
    // header, and we output at least 1 frame, set the next header to null.
    if (out_size > sizeof(PerformanceFrameHeaderVersion1) && out_header != nullptr) {
        std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion1));
    }

    return out_history_size;
}

template <>
bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
                            PerformanceEntryVersion1, PerformanceDetailVersion1>::
    GetNextEntry([[maybe_unused]] PerformanceEntryAddresses& addresses, [[maybe_unused]] u32** unk,
                 [[maybe_unused]] PerformanceSysDetailType sys_detail_type,
                 [[maybe_unused]] s32 node_id) {
    return false;
}

template <>
bool PerformanceManagerImpl<
    PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
    PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses,
                                             const PerformanceEntryType entry_type,
                                             const s32 node_id) {
    if (!is_initialized) {
        return false;
    }

    addresses.translated_address = translated_buffer;
    addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
                                          offsetof(PerformanceFrameHeaderVersion1, entry_count);

    auto entry{&entry_buffer[entry_count++]};
    addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
                                        offsetof(PerformanceEntryVersion1, start_time);
    addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
                                            offsetof(PerformanceEntryVersion1, processed_time);

    std::memset(entry, 0, sizeof(PerformanceEntryVersion1));
    entry->node_id = node_id;
    entry->entry_type = entry_type;
    return true;
}

template <>
bool PerformanceManagerImpl<
    PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
    PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses,
                                             const PerformanceDetailType detail_type,
                                             const PerformanceEntryType entry_type,
                                             const s32 node_id) {
    if (!is_initialized || detail_count > MaxDetailEntries) {
        return false;
    }

    auto detail{&detail_buffer[detail_count++]};

    addresses.translated_address = translated_buffer;
    addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
                                          offsetof(PerformanceFrameHeaderVersion1, detail_count);
    addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
                                        offsetof(PerformanceDetailVersion1, start_time);
    addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
                                            offsetof(PerformanceDetailVersion1, processed_time);

    std::memset(detail, 0, sizeof(PerformanceDetailVersion1));
    detail->node_id = node_id;
    detail->entry_type = entry_type;
    detail->detail_type = detail_type;
    return true;
}

template <>
void PerformanceManagerImpl<
    PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
    PerformanceDetailVersion1>::TapFrame([[maybe_unused]] bool dsp_behind,
                                         [[maybe_unused]] u32 voices_dropped,
                                         [[maybe_unused]] u64 rendering_start_tick) {
    if (!is_initialized) {
        return;
    }

    if (max_frames > 0) {
        if (!frame_history.empty() && !workbuffer.empty()) {
            auto history_frame = reinterpret_cast<PerformanceFrameHeaderVersion1*>(
                &frame_history[output_frame_index * frame_size]);
            std::memcpy(history_frame, workbuffer.data(), frame_size);
            history_frame->frame_index = history_frame_index++;
        }
        output_frame_index = (output_frame_index + 1) % max_frames;
    }

    entry_count = 0;
    detail_count = 0;
    frame_header->entry_count = 0;
    frame_header->detail_count = 0;
}

template <>
bool PerformanceManagerImpl<
    PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
    PerformanceDetailVersion1>::IsDetailTarget(const u32 target_node_id_) const {
    return target_node_id == target_node_id_;
}

template <>
void PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
                            PerformanceEntryVersion1,
                            PerformanceDetailVersion1>::SetDetailTarget(const u32 target_node_id_) {
    target_node_id = target_node_id_;
}

template <>
void PerformanceManagerImpl<
    PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
    PerformanceDetailVersion2>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size,
                                           const AudioRendererParameterInternal& params,
                                           const BehaviorInfo& behavior,
                                           const MemoryPoolInfo& memory_pool) {
    workbuffer = workbuffer_;
    entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1;
    max_detail_count = MaxDetailEntries;
    frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params);
    const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)};
    max_frames = frame_count - 1;
    translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size);

    // The first frame is the "current" frame we're writing to.
    auto buffer_offset{workbuffer.data()};
    frame_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset);
    buffer_offset += sizeof(PerformanceFrameHeaderVersion2);
    entry_buffer = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset), entries_per_frame};
    buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2);
    detail_buffer = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset), max_detail_count};

    // After the current, is a ringbuffer of history frames, the current frame will be copied here
    // before a new frame is written.
    frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size);

    // If there's room for any history frames.
    if (frame_count >= 2) {
        buffer_offset = frame_history.data();
        frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset);
        buffer_offset += sizeof(PerformanceFrameHeaderVersion2);
        frame_history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset),
                                 entries_per_frame};
        buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2);
        frame_history_details = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset),
                                 max_detail_count};
    } else {
        frame_history_header = {};
        frame_history_entries = {};
        frame_history_details = {};
    }

    target_node_id = 0;
    version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat());
    entry_count = 0;
    detail_count = 0;
    frame_header->entry_count = 0;
    frame_header->detail_count = 0;
    output_frame_index = 0;
    last_output_frame_index = 0;
    is_initialized = true;
}

template <>
bool PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
                            PerformanceEntryVersion2, PerformanceDetailVersion2>::IsInitialized()
    const {
    return is_initialized;
}

template <>
u32 PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
                           PerformanceEntryVersion2,
                           PerformanceDetailVersion2>::CopyHistories(u8* out_buffer, u64 out_size) {
    if (out_buffer == nullptr || out_size == 0 || !is_initialized) {
        return 0;
    }

    // Are there any new frames waiting to be output?
    if (last_output_frame_index == output_frame_index) {
        return 0;
    }

    PerformanceFrameHeaderVersion2* out_header{nullptr};
    u32 out_history_size{0};

    while (last_output_frame_index != output_frame_index) {
        PerformanceFrameHeaderVersion2* history_header{nullptr};
        std::span<PerformanceEntryVersion2> history_entries{};
        std::span<PerformanceDetailVersion2> history_details{};

        if (max_frames > 0) {
            auto frame_offset{&frame_history[last_output_frame_index * frame_size]};
            history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(frame_offset);
            frame_offset += sizeof(PerformanceFrameHeaderVersion2);
            history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(frame_offset),
                               history_header->entry_count};
            frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion2);
            history_details = {reinterpret_cast<PerformanceDetailVersion2*>(frame_offset),
                               history_header->detail_count};
        } else {
            // Original code does not break here, but will crash when trying to dereference the
            // header in the next if, so let's just skip this frame and continue...
            // Hopefully this will not happen.
            LOG_WARNING(Service_Audio,
                        "max_frames should not be 0! Skipping frame to avoid a crash");
            last_output_frame_index++;
            continue;
        }

        if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion2) +
                           history_header->detail_count * sizeof(PerformanceDetailVersion2) +
                           2 * sizeof(PerformanceFrameHeaderVersion2)) {
            break;
        }

        u32 out_offset{sizeof(PerformanceFrameHeaderVersion2)};
        auto out_entries{std::span<PerformanceEntryVersion2>(
            reinterpret_cast<PerformanceEntryVersion2*>(out_buffer + out_offset),
            history_header->entry_count)};
        u32 out_entry_count{0};
        u32 total_processing_time{0};
        for (auto& history_entry : history_entries) {
            if (history_entry.processed_time > 0 || history_entry.start_time > 0) {
                out_entries[out_entry_count++] = history_entry;
                total_processing_time += history_entry.processed_time;
            }
        }

        out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion2));
        auto out_details{std::span<PerformanceDetailVersion2>(
            reinterpret_cast<PerformanceDetailVersion2*>(out_buffer + out_offset),
            history_header->detail_count)};
        u32 out_detail_count{0};
        for (auto& history_detail : history_details) {
            if (history_detail.processed_time > 0 || history_detail.start_time > 0) {
                out_details[out_detail_count++] = history_detail;
            }
        }

        out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion2));
        out_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(out_buffer);
        out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F');
        out_header->entry_count = out_entry_count;
        out_header->detail_count = out_detail_count;
        out_header->next_offset = out_offset;
        out_header->total_processing_time = total_processing_time;
        out_header->voices_dropped = history_header->voices_dropped;
        out_header->start_time = history_header->start_time;
        out_header->frame_index = history_header->frame_index;
        out_header->render_time_exceeded = history_header->render_time_exceeded;

        out_history_size += out_offset;

        out_buffer += out_offset;
        out_size -= out_offset;
        last_output_frame_index = (last_output_frame_index + 1) % max_frames;
    }

    // We're out of frames to output, so if there's enough left in the output buffer for another
    // header, and we output at least 1 frame, set the next header to null.
    if (out_size > sizeof(PerformanceFrameHeaderVersion2) && out_header != nullptr) {
        std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion2));
    }

    return out_history_size;
}

template <>
bool PerformanceManagerImpl<
    PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
    PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
                                             const PerformanceSysDetailType sys_detail_type,
                                             const s32 node_id) {
    if (!is_initialized || detail_count > MaxDetailEntries) {
        return false;
    }

    auto detail{&detail_buffer[detail_count++]};

    addresses.translated_address = translated_buffer;
    addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
                                          offsetof(PerformanceFrameHeaderVersion2, detail_count);
    addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
                                        offsetof(PerformanceDetailVersion2, start_time);
    addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
                                            offsetof(PerformanceDetailVersion2, processed_time);

    std::memset(detail, 0, sizeof(PerformanceDetailVersion2));
    detail->node_id = node_id;
    detail->detail_type = static_cast<PerformanceDetailType>(sys_detail_type);

    if (unk) {
        *unk = &detail->unk_10;
    }
    return true;
}

template <>
bool PerformanceManagerImpl<
    PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
    PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses,
                                             const PerformanceEntryType entry_type,
                                             const s32 node_id) {
    if (!is_initialized) {
        return false;
    }

    auto entry{&entry_buffer[entry_count++]};

    addresses.translated_address = translated_buffer;
    addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
                                          offsetof(PerformanceFrameHeaderVersion2, entry_count);
    addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
                                        offsetof(PerformanceEntryVersion2, start_time);
    addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
                                            offsetof(PerformanceEntryVersion2, processed_time);

    std::memset(entry, 0, sizeof(PerformanceEntryVersion2));
    entry->node_id = node_id;
    entry->entry_type = entry_type;
    return true;
}

template <>
bool PerformanceManagerImpl<
    PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
    PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses,
                                             const PerformanceDetailType detail_type,
                                             const PerformanceEntryType entry_type,
                                             const s32 node_id) {
    if (!is_initialized || detail_count > MaxDetailEntries) {
        return false;
    }

    auto detail{&detail_buffer[detail_count++]};

    addresses.translated_address = translated_buffer;
    addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
                                          offsetof(PerformanceFrameHeaderVersion2, detail_count);
    addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
                                        offsetof(PerformanceDetailVersion2, start_time);
    addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
                                            offsetof(PerformanceDetailVersion2, processed_time);

    std::memset(detail, 0, sizeof(PerformanceDetailVersion2));
    detail->node_id = node_id;
    detail->entry_type = entry_type;
    detail->detail_type = detail_type;
    return true;
}

template <>
void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
                            PerformanceEntryVersion2,
                            PerformanceDetailVersion2>::TapFrame(const bool dsp_behind,
                                                                 const u32 voices_dropped,
                                                                 const u64 rendering_start_tick) {
    if (!is_initialized) {
        return;
    }

    if (max_frames > 0) {
        if (!frame_history.empty() && !workbuffer.empty()) {
            auto history_frame{reinterpret_cast<PerformanceFrameHeaderVersion2*>(
                &frame_history[output_frame_index * frame_size])};
            std::memcpy(history_frame, workbuffer.data(), frame_size);
            history_frame->render_time_exceeded = dsp_behind;
            history_frame->voices_dropped = voices_dropped;
            history_frame->start_time = rendering_start_tick;
            history_frame->frame_index = history_frame_index++;
        }
        output_frame_index = (output_frame_index + 1) % max_frames;
    }

    entry_count = 0;
    detail_count = 0;
    frame_header->entry_count = 0;
    frame_header->detail_count = 0;
}

template <>
bool PerformanceManagerImpl<
    PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
    PerformanceDetailVersion2>::IsDetailTarget(const u32 target_node_id_) const {
    return target_node_id == target_node_id_;
}

template <>
void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
                            PerformanceEntryVersion2,
                            PerformanceDetailVersion2>::SetDetailTarget(const u32 target_node_id_) {
    target_node_id = target_node_id_;
}

} // namespace AudioCore::AudioRenderer
