// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#include <catch2/catch_test_macros.hpp>
#include "audio_core/hle/decoder.h"
#include "audio_core/lle/lle.h"
#include "common/common_paths.h"
#include "core/core_timing.h"
#include "core/memory.h"

TEST_CASE("DSP LLE Sanity", "[audio_core][lle]") {
    Memory::MemorySystem memory;
    Core::Timing core_timing(1, 100);

    AudioCore::DspLle lle(memory, core_timing, true);
    {
        FileUtil::SetUserPath();
        // dspaudio.cdc can be dumped from Pokemon X & Y, It can be found in the romfs at
        // "rom:/sound/dspaudio.cdc".
        // One could also extract the firmware from the 3DS sound app using a modified version of
        // https://github.com/zoogie/DSP1.
        std::string firm_filepath =
            FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "3ds" DIR_SEP "dspaudio.cdc";

        if (!FileUtil::Exists(firm_filepath)) {
            SKIP("Test requires dspaudio.cdc");
        }

        FileUtil::IOFile firm_file(firm_filepath, "rb");

        std::vector<u8> firm_file_buf(firm_file.GetSize());
        firm_file.ReadArray(firm_file_buf.data(), firm_file_buf.size());
        lle.LoadComponent(firm_file_buf);
    }
    lle.SetSemaphoreHandler([&lle]() {
        u16 slot = lle.RecvData(2);
        u16 side = slot % 2;
        u16 pipe = slot / 2;
        fmt::print("SetSemaphoreHandler slot={}\n", slot);
        if (pipe > 15)
            return;
        if (side != 0)
            return;
        if (pipe == 0) {
            // pipe 0 is for debug. 3DS automatically drains this pipe and discards the
            // data
            lle.PipeRead(static_cast<AudioCore::DspPipe>(pipe),
                         lle.GetPipeReadableSize(static_cast<AudioCore::DspPipe>(pipe)));
        }
    });
    lle.SetRecvDataHandler(0, []() { fmt::print("SetRecvDataHandler 0\n"); });
    lle.SetRecvDataHandler(1, []() { fmt::print("SetRecvDataHandler 1\n"); });
    lle.SetRecvDataHandler(2, []() { fmt::print("SetRecvDataHandler 2\n"); });
    SECTION("Initialise Audio Pipe") {
        std::vector<u8> buffer(4, 0);
        buffer[0] = 0;

        lle.PipeWrite(AudioCore::DspPipe::Audio, buffer);
        lle.SetSemaphore(0x4000);

        // todo: wait for interrupt
        do {
            core_timing.GetTimer(0)->AddTicks(core_timing.GetTimer(0)->GetDowncount());
            core_timing.GetTimer(0)->Advance();
            core_timing.GetTimer(0)->SetNextSlice();
        } while (lle.GetPipeReadableSize(AudioCore::DspPipe::Audio) == 0);

        REQUIRE(lle.GetPipeReadableSize(AudioCore::DspPipe::Audio) >= 32);

        buffer = lle.PipeRead(AudioCore::DspPipe::Audio, 2);
        u16 size;
        memcpy(&size, buffer.data(), sizeof(size));
        // see AudioCore::DspHle::Impl::AudioPipeWriteStructAddresses()
        REQUIRE(size * 2 == 30);
    }
    SECTION("Initialise EncodeAAC - Binary Pipe") {
        std::vector<u8> buffer(32, 0);
        AudioCore::HLE::BinaryMessage& request =
            *reinterpret_cast<AudioCore::HLE::BinaryMessage*>(buffer.data());

        request.header.codec = AudioCore::HLE::DecoderCodec::EncodeAAC;
        request.header.cmd = AudioCore::HLE::DecoderCommand::Init;

        // Values used by the 3DS sound app.
        request.encode_aac_init.unknown1 = 1;
        request.encode_aac_init.sample_rate = static_cast<AudioCore::HLE::DecoderSampleRate>(5);
        request.encode_aac_init.unknown3 = 2;
        request.encode_aac_init.unknown4 = 0;
        request.encode_aac_init.unknown5 = rand();
        request.encode_aac_init.unknown6 = rand();
        lle.PipeWrite(AudioCore::DspPipe::Binary, buffer);
        lle.SetSemaphore(0x4000);

        // todo: wait for interrupt
        do {
            core_timing.GetTimer(0)->AddTicks(core_timing.GetTimer(0)->GetDowncount());
            core_timing.GetTimer(0)->Advance();
            core_timing.GetTimer(0)->SetNextSlice();
        } while (lle.GetPipeReadableSize(AudioCore::DspPipe::Binary) == 0);

        REQUIRE(lle.GetPipeReadableSize(AudioCore::DspPipe::Binary) >= 32);

        buffer = lle.PipeRead(AudioCore::DspPipe::Binary, 32);
        AudioCore::HLE::BinaryMessage& resp =
            *reinterpret_cast<AudioCore::HLE::BinaryMessage*>(buffer.data());
        REQUIRE(resp.header.result == AudioCore::HLE::ResultStatus::Success);
        REQUIRE(resp.data == request.data);
    }
}