//
// Created by sebastian on 30.07.26.
//

#include "AssetLoader.h"

#include <dr_wav.h>
#include <iostream>

#include "models/ModelImporter.h"
#include "models/ModelUploader.h"
#include "sounds/SoundUploader.h"
#include "spdlog/spdlog.h"
#include "textures/TextureImporter.h"
#include "textures/TextureUploader.h"

void engine::AssetLoader::scheduleAsset(const AssetRequest &request) {
    {
        std::lock_guard<std::mutex> lock(pendingMutex);
        pendingQueue.push(request);
        ++total;
    }
    pendingCondition.notify_one();
    std::visit([]<typename T>(const T& req) -> void {
           using Type = std::decay_t<T>;
           if constexpr (std::is_same_v<Type, TextureRequest>) {
               spdlog::debug("Scheduled Texture Request: {} -> {}", req.name, req.path);
           } else if constexpr (std::is_same_v<Type, ModelRequest>) {
               spdlog::debug("Scheduled Model Request: {} -> {}", req.name, req.path);
           } else if constexpr (std::is_same_v<Type, SoundRequest>) {
               spdlog::debug("Scheduled Sound Request: {} -> {}", req.name, req.path);
           }
       }, request);
}

void engine::AssetLoader::start() {
    if (!running) {
        running = true;
        loadingThread = std::thread(&AssetLoader::loadingThreadFunc, this);
    }
}

void engine::AssetLoader::stop() {
    running = false;
    pendingCondition.notify_all();
    if (loadingThread.joinable()) {
        loadingThread.join();
    }
}



std::vector<engine::LoadedAsset> engine::AssetLoader::processUploadQueue(int maxPerFrame) {
    std::vector<LoadedAsset> result;
    auto uploadQueue = determineUploadQueue(maxPerFrame);
    std::cout << "Upload Queue: " << uploadQueue.size() << std::endl;
    for (const auto& asset : uploadQueue) {
        std::visit([&result]<typename T0>(T0& rawData) -> void {
            using T = std::decay_t<T0>;
            if constexpr (std::is_same_v<T, RawTextureData>) {
                Texture2D texture = TextureUploader::upload(rawData);
                result.emplace_back(LoadedTexture{rawData.name, std::make_shared<Texture2D>(std::move(texture))});
            } else if constexpr (std::is_same_v<T, RawModelBundle>) {
                std::cout << "Uploading model " << rawData.model.name << std::endl;
                auto modelAssets = ModelUploader::upload(rawData, rawData.model.splitPolicy);
                std::cout << "Moddel Assets" << modelAssets.size() << std::endl;
                result.insert(result.end(), modelAssets.begin(), modelAssets.end());
                std::cout << "Result size" << result.size() << std::endl;
            } else if constexpr (std::is_same_v<T, RawSoundData>) {
                auto soundAsset = SoundUploader::upload(rawData);
                result.emplace_back(LoadedSound{rawData.name, std::make_shared<audio::SoundBuffer>(std::move(soundAsset))});
            } else {
                static_assert(
            always_false<T>,
            "Unhandled asset type in AssetLoader::processUploadQueue"
        );
            }
        }, asset);
        ++loaded;
    }

    std::cout << "Loaded Assets: " << result.size() << std::endl;

    return result;
}

engine::LoadingProgress engine::AssetLoader::getProgress() const {
    return {total.load(), loaded.load()};
}

void engine::AssetLoader::loadingThreadFunc() {
    while (running) {
        AssetRequest request;
        {
            std::unique_lock lock(pendingMutex);
            pendingCondition.wait(lock, [this] {
                return !pendingQueue.empty() || !running;
            });

            if (!running && pendingQueue.empty()) {
                return;
            }

            request = pendingQueue.front();
            pendingQueue.pop();
        }

        IntermediateAsset result = std::visit([]<typename T0>(T0& req) -> IntermediateAsset {
                    using T = std::decay_t<T0>;
                    if constexpr (std::is_same_v<T, TextureRequest>) {
                        return processTextureRequest(req);
                    } else if constexpr (std::is_same_v<T, ModelRequest>) {
                        return processModelRequest(req);
                    } else if constexpr (std::is_same_v<T, SoundRequest>) {
                        return processSoundRequest(req);
                    } else {
                        static_assert(always_false<T>, "Unhandled asset type in AssetLoader::loadingThreadFunc");
                    }
                }, request);

        {
            std::lock_guard lock(readyMutex);
            readyQueue.push(std::move(result));
        }
    }
}

engine::RawTextureData engine::AssetLoader::processTextureRequest(const TextureRequest &request) {
    spdlog::debug("Processing Texture Request: {} -> {}", request.name, request.path);
    RawTextureData textureData = TextureImporter::import(request.path, request.flipY);
    textureData.name = request.name;
    return textureData;
}

engine::RawModelBundle engine::AssetLoader::processModelRequest(const ModelRequest &request) {
    spdlog::debug("Processing Model Request: {} -> {}", request.name, request.path);
    RawModelBundle modelData = ModelImporter::importFromFile(request.path, request.splitPolicy);
    modelData.model.name = request.name;
    return modelData;
}

engine::RawSoundData engine::AssetLoader::processSoundRequest(const SoundRequest &request) {
    RawSoundData soundData;
    unsigned int channels, sampleRate;
    drwav_uint64 frameCount;

    drwav_int16* pcmData = drwav_open_file_and_read_pcm_frames_s16(
        request.path.c_str(), &channels, &sampleRate, &frameCount, nullptr
    );

    if (!pcmData) {
        throw std::runtime_error("Failed to load sound file: " + request.path);
    }

    soundData.name = request.name;
    soundData.channels = channels;
    soundData.sampleRate = sampleRate;
    soundData.bitsPerSample = 16;
    size_t dataSize = frameCount * channels * sizeof(drwav_int16);
    soundData.pcmData.resize(dataSize);
    std::memcpy(
        soundData.pcmData.data(),
        pcmData,
        dataSize
    );
    drwav_free(pcmData, nullptr);

    return soundData;
}

std::vector<engine::IntermediateAsset> engine::AssetLoader::determineUploadQueue(int maxPerFrame) {
    std::vector<IntermediateAsset> result;
    std::lock_guard lock(readyMutex);
    for (int i = 0; i < maxPerFrame && !readyQueue.empty(); ++i) {
        result.push_back(std::move(readyQueue.front()));
        readyQueue.pop();
    }

    return result;
}
