#pragma once

#include <string>
#include <unordered_map>
#include <memory>
#include <future>
#include <yaml-cpp/yaml.h>
#include "../types/vec2.h"
#include <GL/glew.h>
#include <string_view>
#include "miniaudio.h"

using UAssetID = uint64_t;

class Object;

#define ASSET_TEXTURE "ASSET_TEXTURE"
#define ASSET_AUDIO "ASSET_AUDIO"
#define ASSET_FONT "ASSET_FONT"
#define ASSET_SHADER "ASSET_SHADER"
#define ASSET_SCRIPT "ASSET_SCRIPT"
#define ASSET_SCENE "ASSET_SCENE"
#define ASSET_VIDEO "ASSET_VIDEO"
#define ASSET_PREFAB "ASSET_PREFAB"
#define ASSET_UNKNOWN "ASSET_UNKNOWN"

#define ASSET_TYPE_LIST \
    X(Image)            \
    X(Audio)            \
    X(Font)             \
    X(Prefab)           \
    X(Shader)           \
    X(Script)           \
    X(Scene)            \
    X(Video)            \
    X(Unknown)

enum class AssetType
{
#define X(name) name,
    ASSET_TYPE_LIST
#undef X
};

inline const char *AssetTypeToString(AssetType type)
{
    switch (type)
    {
#define X(name)           \
    case AssetType::name: \
        return #name;
        ASSET_TYPE_LIST
#undef X
    default:
        return "Unknown";
    }
}

inline AssetType StringToAssetType(const std::string_view str)
{
    static const std::unordered_map<std::string_view, AssetType> map = {
#define X(name) {#name, AssetType::name},
        ASSET_TYPE_LIST
#undef X
    };

    auto it = map.find(str);
    return it != map.end() ? it->second : AssetType::Unknown;
}

struct AssetInfo
{
    uint64_t uaid = 0;
    std::string path;
    std::string filename;
    std::string filetype;
    AssetType type = AssetType::Image;
    std::string hash;
    std::time_t lastModified = 0;
    bool loaded = false;

    virtual ~AssetInfo() = default;

    virtual void Save(YAML::Emitter &out) const = 0;
    virtual void Load(const YAML::Node &node) = 0;
};

struct ImageAssetInfo : public AssetInfo
{
    core::types::Vec2 size = {0, 0};
    int channels = 4;
    std::string format = "GL_RGBA";
    GLuint textureID = 0;

    ImageAssetInfo() { type = AssetType::Image; }

    void Save(YAML::Emitter &out) const override;
    void Load(const YAML::Node &node) override;
};

struct AudioAssetInfo : public AssetInfo
{
    ma_sound *sound = nullptr;

    AudioAssetInfo() { type = AssetType::Audio; }

    void Save(YAML::Emitter &out) const override;
    void Load(const YAML::Node &node) override;
};

struct FontAssetInfo : public AssetInfo
{
    GLuint atlasTextureID = 0;
    int atlasWidth = 0;
    int atlasHeight = 0;

    FontAssetInfo();

    void Save(YAML::Emitter &out) const override;
    void Load(const YAML::Node &node) override;
};

struct PrefabAssetInfo : public AssetInfo
{
    YAML::Node prefabYAML;
    std::shared_ptr<Object> prefabRoot = nullptr;

    std::string prefabName = "Unnamed Prefab";

    PrefabAssetInfo();
    ~PrefabAssetInfo() override
    {
        prefabRoot.reset();
    }
    std::shared_ptr<Object> Instantiate() const;

    void Save(YAML::Emitter &out) const override;
    void Load(const YAML::Node &node) override;

    void ReloadFromYAML();
};

const char *MiniaudioResultToString(ma_result result);

class AssetManager
{
public:
    static void Init();
    static void LoadAssetAsync(const std::string &path, AssetType type);
    static const AssetInfo *GetAssetByID(uint64_t uaid);
    static const AssetInfo *GetAssetByPath(const std::string &path);
    static const std::unordered_map<uint64_t, std::shared_ptr<AssetInfo>> &GetAllAssets();
    static void UnloadAsset(uint64_t uaid);
    static void ClearAllAssets();
    static void Save(YAML::Emitter &out);
    static void Load(const YAML::Node &node);

    static uint64_t GenerateUAID();

    static AssetType AssetTypeFromExtension(std::string ext);

private:
    static std::unordered_map<uint64_t, std::shared_ptr<AssetInfo>> s_Assets;
    static std::unordered_map<std::string, uint64_t> s_PathToUAID;
    static uint64_t s_NextUAID;

    static void LoadImageInternal(const std::string &path, uint64_t uaid);
    static void LoadAudioInternal(const std::string &path, uint64_t uaid);
    static void LoadPrefabInternal(const std::string &path, uint64_t uaid);
    static void LoadFontInternal(const std::string &path, uint64_t uaid);
};
