#include "pch.h"
#include "log.h"
#include "shader.h"
#include "asset.h"

std::map<kShader, Shader> ShaderManager::m_shaders;
Shader* ShaderManager::m_current;
bool ShaderManager::ext_framebuffer_fetch = false;

std::string Shader::read(const std::string& path)
{
    Asset a;
    std::string ret;
    if (a.open(path.c_str()))
    {
        struct stat tmp_info;
        if (stat(path.c_str(), &tmp_info) == 0)
            m_deps[path] = tmp_info;

        std::regex reg_include(R"!(#include "([^"]+)")!");
        std::string data((char*)a.read_all(), a.m_len);

        // split path
        std::string name, base, ext;
        std::regex reg_path(R"((.*)[\\/]([^\\/]+)\.(\w+)$)");
        std::smatch m;
        if (std::regex_search(path, m, reg_path))
        {
            base = m[1].str();
            name = m[2].str();
            ext = m[3].str();
        }

        for (const auto& l : split(data, '\n'))
        {
            std::smatch m;
            if (std::regex_search(l, m, reg_include))
            {
                std::string inc = base + "/" + m[1].str();
                if (Asset::exist(inc.c_str()))
                {
                    std::string subdata = read(inc);
                    ret.append(subdata + "\n");
                }
            }
            else
            {
                ret.append(l + "\n");
            }
        }
    }
    return ret;
}

void Shader::parse_error(const char* msg, const char* code)
{
    auto code_lines = split(code, '\n');
    auto error_lines = split(msg, '\n');
    std::smatch m;
    std::regex r(R"(\((\d+)\))");
    for (const auto& line : error_lines)
    {
        LOG("%s", line.c_str());
        if (std::regex_search(line, m, r))
        {
            int ln = std::stoi(m[1].str());
            if (ln < code_lines.size())
            {
                int n = 2;
                int s = std::max(ln - n, 1);
                int e = std::min((int)code_lines.size() - 1, ln + n);
                for (int i = s; i < e; i++)
                {
                    LOG("- line %02d: %s", i, code_lines[i - 1].c_str());
                }
            }
        }
    }
}

bool Shader::load(const std::string& path)
{
    std::string data = read(path);
    if (data.empty()) return false;

    std::map<std::string, std::shared_ptr<std::string>> sections;
    std::shared_ptr<std::string> current_section = nullptr;
    std::regex reg_section(R"!(\[\[(.*)\]\])!");
    for (const auto& l : split(data, '\n'))
    {
        std::smatch m;
        if (std::regex_search(l, m, reg_section))
        {
            std::string section_name = m[1].str();
            if (!sections[section_name])
                sections[section_name] = std::make_shared<std::string>();
            current_section = sections[section_name];
        }
        else
        {
            // create an un-named section
            if (!current_section)
                current_section = sections[""] = std::make_shared<std::string>();
            current_section->append(l + "\n");
        }
    }

    if (sections.find("vertex") != sections.end() && sections.find("fragment") != sections.end())
    {
        *sections["vertex"] = SHADER_VERSION + *sections["vertex"];
        *sections["fragment"] = SHADER_VERSION + *sections["fragment"];
        if (create(sections["vertex"]->c_str(), sections["fragment"]->c_str()))
        {
            m_path = path;
            return true;
        }
    }
    else
    {
        LOG("could not find [[vertex]] and [[fragment]] sections on %s", path.c_str());
    }

    return false;
}

bool Shader::reload()
{
    if (m_path.empty())
        return false;

    bool dirty = false;
    struct stat tmp_info;
    for (auto& d : m_deps)
    {
        if (stat(d.first.c_str(), &tmp_info) != 0)
            continue;
        if (tmp_info.st_mtime > d.second.st_mtime)
        {
            d.second = tmp_info;
            dirty = true;
        }
    }

    if (dirty)
    {
        LOG("reload shader %s", m_path.c_str());
        destroy();
        return load(m_path);
    }
    
    return false;
}

bool Shader::create(const char* vertex, const char* fragment)
{
    GLint status;
    static char infolog[4096];
    int infolen;
    const GLchar* source;
    
    auto vs = glCreateShader(GL_VERTEX_SHADER);
    if (!vs)
    {
        return false;
    }
    source = vertex;
    glShaderSource(vs, 1, &source, nullptr);
    glCompileShader(vs);
    glGetShaderiv(vs, GL_COMPILE_STATUS, &status);
    glGetShaderInfoLog(vs, sizeof(infolog), &infolen, infolog);
    if (infolen > 0)
    {
        LOG("\nVERTEX SHADER: %s", m_path.c_str());
        parse_error(infolog, vertex);
    }
    if (status == 0)
    {
        glDeleteShader(vs);
        return false;
    }
    
    auto fs = glCreateShader(GL_FRAGMENT_SHADER);
    if (!fs)
    {
        glDeleteShader(vs);
        return false;
    }
    source = fragment;
    glShaderSource(fs, 1, &source, nullptr);
    glCompileShader(fs);
    glGetShaderiv(fs, GL_COMPILE_STATUS, &status);
    glGetShaderInfoLog(fs, sizeof(infolog), &infolen, infolog);
    if (infolen > 0)
    {
        LOG("\nFRAGMENT SHADER: %s", m_path.c_str());
        parse_error(infolog, fragment);
    }
    if (status == 0)
    {
        glDeleteShader(vs);
        glDeleteShader(fs);
        return false;
    }
    
    auto ps = glCreateProgram();
    if (!ps)
    {
        glDeleteShader(vs);
        glDeleteShader(fs);
        return false;
    }
    glAttachShader(ps, vs);
    glAttachShader(ps, fs);
    glDeleteShader(vs);
    glDeleteShader(fs);
    
    glLinkProgram(ps);
    if (glGetAttribLocation(ps, "pos") != -1)
        glBindAttribLocation(ps, 0, "pos");
    
    if (glGetAttribLocation(ps, "uvs") != -1)
        glBindAttribLocation(ps, 1, "uvs");
    
    if (glGetAttribLocation(ps, "uvs2") != -1)
        glBindAttribLocation(ps, 2, "uvs2");

    if (glGetAttribLocation(ps, "col") != -1)
        glBindAttribLocation(ps, 3, "col");

    if (glGetAttribLocation(ps, "nor") != -1)
        glBindAttribLocation(ps, 3, "nor");

    glLinkProgram(ps);
    glGetProgramiv(ps, GL_LINK_STATUS, &status);
    glGetProgramInfoLog(ps, sizeof(infolog), &infolen, infolog);
    if (infolen > 0)
        LOG("LINK SHADER: %s\n%s", m_path.c_str(), infolog);
    if (status == 0)
    {
        glDeleteProgram(ps);
        return false;
    }

    // Parse shader uniforms
    {
        GLint count;
        GLint size; // size of the variable
        GLenum type; // type of the variable (float, vec3 or mat4, etc)
        const GLsizei bufSize = 16; // maximum name length
        GLchar name[bufSize]; // variable name in GLSL
        GLsizei length; // name length
        glGetProgramiv(ps, GL_ACTIVE_UNIFORMS, &count);
        for (int i = 0; i < count; i++)
        {
            glGetActiveUniform(ps, (GLuint)i, bufSize, &length, &size, &type, name);
            m_umap[(kShaderUniform)const_hash(name)] = glGetUniformLocation(ps, name);
            //printf("Uniform #%d Type: %u Name: %s Loc: %d\n", i, type, name, glGetUniformLocation(ps, name));
        }
    }
    
    prog = ps;
    
    return true;
}

void Shader::destroy()
{
    if (prog)
    {
        glUseProgram(0);
        glDeleteProgram(prog);
        prog = 0;
    }
    m_umap.clear();
}

void Shader::use()
{
    glUseProgram(prog);
}
void Shader::u_vec4(kShaderUniform id, const glm::vec4& v)
{
    if (m_umap.count(id) == 0) 
        LOG("UNIFORM vec4 %d NOT FOUND in shader %d", (int)id, (int)name)
    else glUniform4fv(m_umap[id], 1, glm::value_ptr(v));
}
void Shader::u_vec3(kShaderUniform id, const glm::vec3& v)
{
    if (m_umap.count(id) == 0)
        LOG("UNIFORM vec3 %d NOT FOUND in shader %d", (int)id, (int)name)
    else glUniform3fv(m_umap[id], 1, glm::value_ptr(v));
}

void Shader::u_vec2(kShaderUniform id, const glm::vec2& v)
{
    if (m_umap.count(id) == 0) 
        LOG("UNIFORM vec2 %d NOT FOUND in shader %d", (int)id, (int)name)
    else glUniform2fv(m_umap[id], 1, glm::value_ptr(v));
}

void Shader::u_mat4(kShaderUniform id, const glm::mat4& m)
{
    if (m_umap.count(id) == 0) 
        LOG("UNIFORM mat4 %d NOT FOUND in shader %d", (int)id, (int)name)
    else glUniformMatrix4fv(m_umap[id], 1, GL_FALSE, glm::value_ptr(m));
}
void Shader::u_int(kShaderUniform id, int i)
{
    if (m_umap.count(id) == 0)
        LOG("UNIFORM int %d NOT FOUND in shader %d", (int)id, (int)name)
    else
        glUniform1i(m_umap[id], i);
}
void Shader::u_float(kShaderUniform id, float f)
{
    if (m_umap.count(id) == 0) 
        LOG("UNIFORM float %d NOT FOUND in shader %d", (int)id, (int)name)
    else glUniform1f(m_umap[id], f);
}
GLint Shader::GetAttribLocation(const char* name)
{
    return glGetAttribLocation(prog, name);
}

bool ShaderManager::load(kShader id, const std::string& path)
{
    m_shaders[id].name = id;
    return m_shaders[id].load(path);
}

bool ShaderManager::reload()
{
    bool success = false;
    for (auto& s : m_shaders)
        success |= s.second.reload();
    return success;
}

bool ShaderManager::create(kShader id, const char* vertex, const char* fragment)
{
    m_shaders[id].name = id;
    return m_shaders[id].create(vertex, fragment);
}

void ShaderManager::use(kShader id)
{
    m_current = &m_shaders[id];
    m_current->use();
}

void ShaderManager::use(const char* name)
{
    m_current = &m_shaders[(kShader)const_hash(name)];
    m_current->use();
}

void ShaderManager::u_vec4(kShaderUniform id, const glm::vec4& v)
{
    m_current->u_vec4(id, v);
}

void ShaderManager::u_vec3(kShaderUniform id, const glm::vec3& v)
{
    m_current->u_vec3(id, v);
}

void ShaderManager::u_vec2(kShaderUniform id, const glm::vec2& v)
{
    m_current->u_vec2(id, v);
}

void ShaderManager::u_mat4(kShaderUniform id, const glm::mat4& m)
{
    m_current->u_mat4(id, m);
}

void ShaderManager::u_int(kShaderUniform id, int i)
{
    m_current->u_int(id, i);
}

Shader* ShaderManager::get(kShader id)
{
    auto it = m_shaders.find(id);
    return (it == m_shaders.end()) ? nullptr : &it->second;
}

void ShaderManager::u_float(kShaderUniform id, float f)
{
    m_current->u_float(id, f);
}

void ShaderManager::invalidate()
{
    m_shaders.clear();
}

bool check_uniform_uniqueness()
{
    std::vector<uint16_t> v = {
        const_hash("mvp"),
        const_hash("tex"),
        const_hash("tex_alpha"),
        const_hash("tex_fg"),
        const_hash("tex_bg"),
        const_hash("tex_mix"),
        const_hash("tex_mix_alpha"),
        const_hash("tex_mask"),
        const_hash("tex_dual"),
        const_hash("tex_stroke"),
        const_hash("tex_pattern"),
        const_hash("pattern_offset"),
        const_hash("pattern_alpha"),
        const_hash("mix_alpha"),
        const_hash("opacity"),
        const_hash("wet"),
        const_hash("lock"),
        const_hash("col"),
        const_hash("tof"),
        const_hash("tsz"),
        const_hash("alpha"),
        const_hash("mask"),
        const_hash("resolution"),
        const_hash("highlight"),
        const_hash("blend_mode"),
        const_hash("dual_blend_mode"),
        const_hash("noise"),
        const_hash("dir"),
        const_hash("use_dual"),
        const_hash("use_pattern"),
        const_hash("light_dir"),
        const_hash("mode"),
        const_hash("ambient"),
        const_hash("pattern_invert"),
        const_hash("pattern_scale"),
        const_hash("pattern_bright"),
        const_hash("pattern_contr"),
        const_hash("pattern_depth"),
        const_hash("patt_blend_mode"),
        const_hash("colorize"),
        const_hash("dual_alpha"),
        const_hash("use_fragcoord"),
    };
    std::sort(v.begin(), v.end());
    int last = 0;
    for (auto o : v)
    {
        if (o == last)
            return false;
        last = o;
    }
    return true;
}
