#pragma once
#include "renderer_api/renderer_api.h"

#include "../libs/glm/glm/glm.hpp"

#include <cstdint>
#include <map>
#include <string>
#include <sys/stat.h>

uint16_t constexpr shader_const_hash(const char* input)
{
    return *input ?
        static_cast<uint16_t>(*input) + 33 * shader_const_hash(input + 1) :
        5381;
}

bool check_uniform_uniqueness();

enum class kShaderUniform : uint16_t
{
    MVP = shader_const_hash("mvp"),
    Tex = shader_const_hash("tex"),
    TexFG = shader_const_hash("tex_fg"),
    TexBG = shader_const_hash("tex_bg"),
    TexMix = shader_const_hash("tex_mix"),
    TexMixA = shader_const_hash("tex_mix_alpha"),
    TexMask = shader_const_hash("tex_mask"),
    TexDual = shader_const_hash("tex_dual"),
    TexStroke = shader_const_hash("tex_stroke"),
    TexPattern = shader_const_hash("tex_pattern"),
    PatternOffset = shader_const_hash("pattern_offset"),
    PatternAlpha = shader_const_hash("pattern_alpha"),
    MixAlpha = shader_const_hash("mix_alpha"),
    Opacity = shader_const_hash("opacity"),
    Wet = shader_const_hash("wet"),
    Lock = shader_const_hash("lock"),
    Col = shader_const_hash("col"),
    Tof = shader_const_hash("tof"),
    Tsz = shader_const_hash("tsz"),
    Alpha = shader_const_hash("alpha"),
    Mask  = shader_const_hash("mask"),
    Resolution = shader_const_hash("resolution"),
    Highlight = shader_const_hash("highlight"),
    BlendMode = shader_const_hash("blend_mode"),
    DualBlendMode = shader_const_hash("dual_blend_mode"),
    Noise = shader_const_hash("noise"),
    Direction = shader_const_hash("dir"),
    UseDual = shader_const_hash("use_dual"),
    UsePattern = shader_const_hash("use_pattern"),
    LightDir = shader_const_hash("light_dir"),
    Mode = shader_const_hash("mode"),
    Ambient = shader_const_hash("ambient"),
    PatternInvert = shader_const_hash("pattern_invert"),
    PatternScale = shader_const_hash("pattern_scale"),
    PatternBright = shader_const_hash("pattern_bright"),
    PatternContrast = shader_const_hash("pattern_contr"),
    PatternDepth = shader_const_hash("pattern_depth"),
    PatternBlendMode = shader_const_hash("patt_blend_mode"),
    Colorize = shader_const_hash("colorize"),
    DualAlpha = shader_const_hash("dual_alpha"),
    UseFragcoord = shader_const_hash("use_fragcoord"),
    DrawOutline = shader_const_hash("draw_outline"),
};

enum class kShader : uint16_t
{
    Color       = shader_const_hash("color"),
    ColorQuad   = shader_const_hash("color-quad"),
    ColorTri    = shader_const_hash("color-tri"),
    ColorHue    = shader_const_hash("color-hue"),
    Texture     = shader_const_hash("texture"),
    TextureMask = shader_const_hash("texture-mask"),
    TextureColorize = shader_const_hash("texture-colorize"),
    TextureAlpha= shader_const_hash("texture-alpha"),
    TextureBlend= shader_const_hash("texture-blend"),
    CompErase   = shader_const_hash("comp-erase"),
    CompDraw    = shader_const_hash("comp-draw"),
    UVs         = shader_const_hash("uvs"),
    UVs_2       = shader_const_hash("uvs2"),
    Font        = shader_const_hash("font"),
    Atlas       = shader_const_hash("atlas"),
    Stroke      = shader_const_hash("stroke"),
    StrokePad   = shader_const_hash("stroke-pad"),
    StrokeDilate= shader_const_hash("stroke-dilate"),
    StrokePreview = shader_const_hash("stroke-preview"),
    Checkerboard= shader_const_hash("checkerboard"),
    Equirect    = shader_const_hash("equirect"),
    BrushStroke = shader_const_hash("brush-stroke"),
    VertexColor = shader_const_hash("vertex-color"),
    Lambert     = shader_const_hash("lambert"),
    LambertLightmap = shader_const_hash("lambert-lightmap"),
    BakeUV      = shader_const_hash("bakeuv"),
};

class Shader
{
    std::map<std::string, struct stat> m_deps;
    std::string m_path;
    std::map<kShaderUniform, unsigned int> m_umap;
    unsigned int prog;
    std::string read(const std::string& path);
public:
    kShader name;
    void parse_error(const std::string& msg, const std::string& code);
    bool load(const std::string& path);
    bool reload();
    bool create(const std::string& vertex, const std::string& fragment);
    void destroy();
    void use();
    void u_vec4(kShaderUniform id, const glm::vec4& v);
    void u_vec3(kShaderUniform id, const glm::vec3& v);
    void u_vec2(kShaderUniform id, const glm::vec2& v);
    void u_mat4(kShaderUniform id, const glm::mat4& m);
    void u_int(kShaderUniform id, int i);
    void u_int(const char* uniform_name, int i);
    void u_float(kShaderUniform id, float f);
    int GetAttribLocation(const char* attribute_name);
};

class ShaderManager
{
    static std::map<kShader, Shader> m_shaders;
    static Shader* m_current;
    static pp::renderer::RenderDeviceFeatures m_render_device_features;
public:
    static bool ext_framebuffer_fetch;
    static bool ext_float32;
    static bool ext_float32_linear;
    static bool ext_float16;
    static bool ext_map_aligned;
    static bool load(kShader id, const std::string& path);
    static bool reload();
    static bool create(kShader id, const std::string& vertex, const std::string& fragment);
    static void use(kShader id);
    static void use(const char* name);
    static Shader* get(kShader id);
    static void u_vec4(kShaderUniform id, const glm::vec4& v);
    static void u_vec3(kShaderUniform id, const glm::vec3& v);
    static void u_vec2(kShaderUniform id, const glm::vec2& v);
    static void u_mat4(kShaderUniform id, const glm::mat4& m);
    static void u_int(kShaderUniform id, int i);
    static void u_int(const char* name, int i);
    static void u_float(kShaderUniform id, float f);
    static void set_render_device_features(pp::renderer::RenderDeviceFeatures features) noexcept;
    static pp::renderer::RenderDeviceFeatures render_device_features() noexcept;
    static void invalidate();
};
