#pragma once
#include <chrono>
#include <Eigen/Eigen>
#include <filesystem>

#include "renderer_buffer.h"
#include "core/pipeline/pipeline.h"

class renderer_texture_array;
class sdf_text_pipeline;
class segment_pipeline;
class texture_pipeline;
class rounded_rect_pipeline;
class rect_pipeline;
class renderer_window;
class renderer_texture;

enum class renderer_api {
    dx11,
    opengl,
    vulkan,
    metal,
};

enum class device_memory_type {
    gpu, // 显存
    shared, // 共享内存
    cpu, // 内存
};

enum class texture_format {
    // 未压缩的颜色格式

    // 单通道 8 位
    R8_UNORM,
    R8_SNORM,
    R8_UINT,
    R8_SINT,

    // 双通道 8 位
    RG8_UNORM,
    RG8_SNORM,
    RG8_UINT,
    RG8_SINT,

    // 三通道 8 位
    RGB8_UNORM,
    RGB8_SNORM,
    RGB8_UINT,
    RGB8_SINT,

    // 四通道 8 位
    RGBA8_UNORM,
    RGBA8_SNORM,
    RGBA8_UINT,
    RGBA8_SINT,

    // SRGB 格式
    SRGB8,
    SRGB8_ALPHA8,

    // 16 位浮点格式
    R16_FLOAT,
    RG16_FLOAT,
    RGB16_FLOAT,
    RGBA16_FLOAT,

    // 32 位浮点格式
    R32_FLOAT,
    RG32_FLOAT,
    RGB32_FLOAT,
    RGBA32_FLOAT,

    // 整数格式
    R16_UINT,
    R16_SINT,
    RG16_UINT,
    RG16_SINT,
    RGBA16_UINT,
    RGBA16_SINT,
    R32_UINT,
    R32_SINT,
    RG32_UINT,
    RG32_SINT,
    RGBA32_UINT,
    RGBA32_SINT,

    // 深度/模板格式
    D16_UNORM,
    D24_UNORM_S8_UINT,
    D32_FLOAT,
    D32_FLOAT_S8X24_UINT,

    // 压缩格式（以 DXGI 格式为例）
    BC1_UNORM,       // DXT1
    BC1_UNORM_SRGB,
    BC2_UNORM,       // DXT3
    BC2_UNORM_SRGB,
    BC3_UNORM,       // DXT5
    BC3_UNORM_SRGB,
    BC4_UNORM,
    BC4_SNORM,
    BC5_UNORM,
    BC5_SNORM,
    BC6H_UF16,
    BC6H_SF16,
    BC7_UNORM,
    BC7_UNORM_SRGB,
};

inline int get_format_channel_count(texture_format format) {
    switch (format) {
    case texture_format::R8_UNORM:
    case texture_format::R8_SNORM:
    case texture_format::R8_UINT:
    case texture_format::R8_SINT:
    case texture_format::R16_FLOAT:
    case texture_format::R16_UINT:
    case texture_format::R16_SINT:
    case texture_format::R32_FLOAT:
    case texture_format::R32_UINT:
    case texture_format::R32_SINT:
        return 1;
    case texture_format::RG8_UNORM:
    case texture_format::RG8_SNORM:
    case texture_format::RG8_UINT:
    case texture_format::RG8_SINT:
    case texture_format::RG16_FLOAT:
    case texture_format::RG16_UINT:
    case texture_format::RG16_SINT:
    case texture_format::RG32_FLOAT:
    case texture_format::RG32_UINT:
    case texture_format::RG32_SINT:
        return 2;
    case texture_format::RGB8_UNORM:
    case texture_format::RGB8_SNORM:
    case texture_format::RGB8_UINT:
    case texture_format::RGB8_SINT:
    case texture_format::RGB16_FLOAT:
    case texture_format::RGB32_FLOAT:
        return 3;
    case texture_format::RGBA8_UNORM:
    case texture_format::RGBA8_SNORM:
    case texture_format::RGBA8_UINT:
    case texture_format::RGBA8_SINT:
    case texture_format::RGBA16_FLOAT:
    case texture_format::RGBA32_FLOAT:
    case texture_format::RGBA32_UINT:
    case texture_format::RGBA32_SINT:
        return 4;
    default:
        return 0;
    }
}

inline int get_format_bits_pre_channel(texture_format format) {
    switch (format) {
        case texture_format::R8_UNORM:
        case texture_format::R8_SNORM:
        case texture_format::R8_UINT:
        case texture_format::R8_SINT:
        case texture_format::RG8_UNORM:
        case texture_format::RG8_SNORM:
        case texture_format::RG8_UINT:
        case texture_format::RG8_SINT:
        case texture_format::RGB8_UNORM:
        case texture_format::RGB8_SNORM:
        case texture_format::RGB8_UINT:
        case texture_format::RGB8_SINT:
        case texture_format::RGBA8_UNORM:
        case texture_format::RGBA8_SNORM:
        case texture_format::RGBA8_UINT:
        case texture_format::RGBA8_SINT:
        case texture_format::SRGB8:
        case texture_format::SRGB8_ALPHA8:
        case texture_format::BC1_UNORM:
        case texture_format::BC1_UNORM_SRGB:
        case texture_format::BC2_UNORM:
        case texture_format::BC2_UNORM_SRGB:
        case texture_format::BC3_UNORM:
        case texture_format::BC3_UNORM_SRGB:
        case texture_format::BC4_UNORM:
        case texture_format::BC4_SNORM:
        case texture_format::BC5_UNORM:
        case texture_format::BC5_SNORM:
        case texture_format::BC6H_UF16:
        case texture_format::BC6H_SF16:
        case texture_format::BC7_UNORM:
        case texture_format::BC7_UNORM_SRGB:
            return 8;
        case texture_format::R16_FLOAT:
        case texture_format::R16_UINT:
        case texture_format::R16_SINT:
        case texture_format::RG16_FLOAT:
        case texture_format::RG16_UINT:
        case texture_format::RG16_SINT:
        case texture_format::RGB16_FLOAT:
        case texture_format::RGBA16_FLOAT:
        case texture_format::D16_UNORM:
            return 16;
        case texture_format::R32_FLOAT:
        case texture_format::R32_UINT:
        case texture_format::R32_SINT:
        case texture_format::RG32_FLOAT:
        case texture_format::RG32_UINT:
        case texture_format::RG32_SINT:
        case texture_format::RGB32_FLOAT:
        case texture_format::RGBA32_FLOAT:
            return 32;
        case texture_format::D24_UNORM_S8_UINT:
        case texture_format::D32_FLOAT:
        case texture_format::D32_FLOAT_S8X24_UINT:
            return 24;
        default:
            return 0;
    }
}

class renderer {
    friend class window_manager;
public:
    virtual ~renderer() = default;

    virtual bool init() = 0;
    virtual void destroy() = 0;
    virtual void tick();
    virtual bool render() = 0;

    template<typename T>
    renderer_texture* create_texture(const Eigen::Vector2<T>& in_size, texture_format in_format, device_memory_type in_memory_type) {
        return create_texture(in_size.x(), in_size.y(), in_format, in_memory_type);
    }
    virtual renderer_texture* create_texture(uint32_t in_size_width, uint32_t in_size_height, texture_format in_format, device_memory_type in_memory_type) = 0;
    virtual void destroy_texture(renderer_texture* texture);
    template<typename T>
    renderer_texture_array* create_texture_array(const Eigen::Vector2<T>& in_size, const int in_count, const texture_format in_format) {
        return create_texture_array(in_size.x(), in_size.y(), in_count, in_format);
    }
    virtual renderer_texture_array* create_texture_array(uint32_t in_size_width, uint32_t in_size_height, uint32_t in_count, texture_format in_format, device_memory_type in_memory_type) = 0;
    virtual void destroy_texture_array(renderer_texture_array* texture_array);

    renderer_texture* load_image(const std::string& file_path, texture_format in_format);

    /**
     * 读取着色器代码
     * @param shader_name 着色器文件名称, 不需要路径和后缀, 会使用aorii::get_shader_path获取路径, 在不同的图形API下会自动添加后缀
     * @return 着色器代码
     */
    virtual std::vector<int8_t> load_shader(const std::string& shader_name) = 0;

    renderer_buffer* create_vertex_buffer(const int32_t in_size = 4) {
        return create_buffer(buffer_type::vertex, sizeof(aorii_vertex), in_size);
    }
    renderer_buffer* create_index_buffer(const int32_t in_size = 2) {
        return create_buffer(buffer_type::index, sizeof(aorii_triangle), in_size);
    }
    virtual renderer_buffer* create_buffer(buffer_type in_buffer_type, int32_t in_element_byte, int32_t in_element_count) = 0;
    virtual void destroy_buffer(renderer_buffer* buffer) { delete buffer; }

    virtual rect_pipeline* get_rect_pipeline() = 0;
    virtual rounded_rect_pipeline* get_rounded_rect_pipeline() = 0;
    virtual texture_pipeline* get_texture_pipeline() = 0;
    virtual segment_pipeline* get_segment_pipeline() = 0;
    virtual sdf_text_pipeline* get_text_pipeline() = 0;

    virtual Eigen::Matrix4f make_projection_matrix(const Eigen::Vector2i& size) = 0;
private:
    virtual renderer_window* create_window() = 0;
    virtual void destroy_window(renderer_window* window);
};

namespace aorii {
    renderer* get_renderer_raw();

    template<typename T>
    static T* get_renderer() {
        return static_cast<T*>(get_renderer_raw());
    }

    bool create_renderer(renderer_api api);
    void destroy_renderer();
    void update();

    const std::chrono::duration<double>& get_delta_time();
    std::chrono::duration<double> get_total_time();

    inline std::filesystem::path s_shader_relative_path = "resource/shaders";
    inline void set_shader_relative_path(const std::filesystem::path& path) { s_shader_relative_path = path; }
    inline std::filesystem::path get_shader_path(const std::string& shader_name) { return std::filesystem::current_path() / s_shader_relative_path / shader_name; }
}
