// AssetManager.cpp
#include "AssetManager.h"
#include "utils.h"
#include <GL/glew.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include <future>
#include <iostream>

// Static definitions
std::unordered_map<uint64_t, AssetInfo> AssetManager::s_Assets;
std::unordered_map<std::string, uint64_t> AssetManager::s_PathToUAID;
uint64_t AssetManager::s_NextUAID = 1;

void AssetManager::Init() {}

uint64_t AssetManager::GenerateUAID()
{

    return s_NextUAID++;
}

void AssetManager::LoadAssetAsync(const std::string &path, AssetType type)
{
    {

        auto itPath = s_PathToUAID.find(path);
        if (itPath != s_PathToUAID.end() && s_Assets[itPath->second].loaded)
        {
            Logger::LogVerbose("[AssetManager] Skipping already-loaded image: %s (UAID: %llu)", path.c_str(), itPath->second);
            return;
        }
    }

    uint64_t uaid = GenerateUAID();
    {
        AssetInfo info;
        info.uaid = uaid;
        info.path = path;
        info.filename = GetFilenameFromPath(path);
        info.filetype = ::GetFileExtension(path);
        info.type = type;

        s_Assets[uaid] = info;
        s_PathToUAID[path] = uaid;
    }

    if (type == AssetType::Image)
    {
        Logger::LogVerbose("[AssetManager] Scheduling load: %s (UAID: %llu)", path.c_str(), uaid);
        AssetManager::LoadImageInternal(path, uaid);
    }
    else
    {
        Logger::LogWarning("[AssetManager] Unknown Asset Type: '%d'", static_cast<int>(type));
    }
}

void AssetManager::LoadImageInternal(const std::string &path, uint64_t uaid)
{
    {
        auto it = s_Assets.find(uaid);
        if (it != s_Assets.end() && it->second.loaded)
            return;

        auto itPath = s_PathToUAID.find(path);
        if (itPath != s_PathToUAID.end() && s_Assets[itPath->second].loaded)
            return;
    }

    Logger::LogVerbose("[AssetManager] Begin loading image: %s (UAID: %llu)", path.c_str(), uaid);
    stbi_set_flip_vertically_on_load(false);

    int w, h, channels;
    stbi_uc *pixels = stbi_load(path.c_str(), &w, &h, &channels, STBI_rgb_alpha);
    if (!pixels)
    {
        Logger::LogError("[AssetManager] Failed to load image: %s", path.c_str());
        return;
    }

    GLuint texID;
    glGenTextures(1, &texID);
    glBindTexture(GL_TEXTURE_2D, texID);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    glBindTexture(GL_TEXTURE_2D, 0);
    stbi_image_free(pixels);

    if (texID == 0)
    {
        Logger::LogError("[AssetManager] Failed to Load asset.");
        return;
    }

    auto &asset = s_Assets[uaid];
    asset.size = {static_cast<float>(w), static_cast<float>(h)};
    asset.textureID = texID;
    asset.loaded = true;
    asset.channels = channels;
    asset.format = "GL_RGBA";
    asset.lastModified = GetFileLastWrite(path);
    asset.hash = GetFileHash(path);

    Logger::LogVerbose("[AssetManager] Finished loading image: %s (%dx%d) (UAID: %llu, Texture ID: %u)", path.c_str(), w, h, uaid, texID);
}

const AssetInfo *AssetManager::GetAssetByID(uint64_t uaid)
{

    auto it = s_Assets.find(uaid);
    return it != s_Assets.end() ? &it->second : nullptr;
}

const AssetInfo *AssetManager::GetAssetByPath(const std::string &path)
{

    auto it = s_PathToUAID.find(path);
    return it != s_PathToUAID.end() ? &s_Assets[it->second] : nullptr;
}

const std::unordered_map<uint64_t, AssetInfo> &AssetManager::GetAllAssets()
{
    return s_Assets;
}

void AssetManager::UnloadAsset(uint64_t uaid)
{

    auto it = s_Assets.find(uaid);
    if (it != s_Assets.end())
    {
        if (it->second.textureID)
            glDeleteTextures(1, &it->second.textureID);
        s_PathToUAID.erase(it->second.path);
        s_Assets.erase(it);
    }
}

void AssetManager::ClearAllAssets()
{

    for (auto &kv : s_Assets)
        if (kv.second.textureID)
            glDeleteTextures(1, &kv.second.textureID);
    s_Assets.clear();
    s_PathToUAID.clear();
}

void AssetManager::Save(YAML::Emitter &out)
{
    out << YAML::Key << "Assets" << YAML::BeginSeq;
    for (const auto &kv : s_Assets)
    {
        const auto &a = kv.second;
        out << YAML::BeginMap
            << YAML::Key << "uaid" << YAML::Value << a.uaid
            << YAML::Key << "path" << YAML::Value << a.path
            << YAML::Key << "filename" << YAML::Value << a.filename
            << YAML::Key << "filetype" << YAML::Value << a.filetype
            << YAML::Key << "type" << YAML::Value << static_cast<int>(a.type)
            << YAML::Key << "size" << YAML::Value << YAML::Flow << YAML::BeginSeq << a.size.x << a.size.y << YAML::EndSeq
            << YAML::Key << "hash" << YAML::Value << a.hash
            << YAML::Key << "channels" << YAML::Value << a.channels
            << YAML::Key << "format" << YAML::Value << a.format
            << YAML::Key << "lastModified" << YAML::Value << a.lastModified
            << YAML::EndMap;
    }
    out << YAML::EndSeq;
}

void AssetManager::Load(const YAML::Node &node)
{
    if (!node)
        return;

    for (const auto &item : node)
    {
        uint64_t uaid = item["uaid"].as<uint64_t>();
        std::string path = item["path"].as<std::string>();
        AssetType type = static_cast<AssetType>(item["type"].as<int>());
        float sx = item["size"][0].as<float>();
        float sy = item["size"][1].as<float>();

        AssetInfo asset;
        asset.uaid = uaid;
        asset.path = path;
        asset.filename = item["filename"] ? item["filename"].as<std::string>() : GetFilenameFromPath(path);
        asset.filetype = item["filetype"] ? item["filetype"].as<std::string>() : ::GetFileExtension(path);
        asset.type = type;
        asset.size = {sx, sy};
        asset.hash = item["hash"] ? item["hash"].as<std::string>() : "";
        asset.channels = item["channels"] ? item["channels"].as<int>() : 4;
        asset.format = item["format"] ? item["format"].as<std::string>() : "GL_RGBA";
        asset.lastModified = item["lastModified"] ? item["lastModified"].as<std::time_t>() : 0;
        asset.loaded = false;
        asset.textureID = 0;

        s_Assets[uaid] = asset;
        s_PathToUAID[path] = uaid;

        if (uaid >= s_NextUAID)
            s_NextUAID = uaid + 1;

        if (type == AssetType::Image)
        {
            Logger::LogVerbose("[AssetManager] Reloading from file: %s (UAID: %llu)", path.c_str(), uaid);
            LoadImageInternal(path, uaid);
        }
    }

    
}
