#include "Shader.h"
#include <fstream>
#include <sstream>
#include <iostream>
#include "systems/Logger.h"

namespace OX
{

    Shader::Shader(const std::string &vertexPath, const std::string &fragmentPath)
    {
        LoadFromFiles(vertexPath, fragmentPath);
    }

    bool Shader::LoadFromFiles(const std::string &vertexPath, const std::string &fragmentPath)
    {
        m_vertexPath = vertexPath;
        m_fragmentPath = fragmentPath;

        std::string vertexSrc = ReadFile(vertexPath);
        std::string fragmentSrc = ReadFile(fragmentPath);

        try {
            m_vertTimestamp = std::filesystem::last_write_time(vertexPath);
            m_fragTimestamp = std::filesystem::last_write_time(fragmentPath);
        } catch (...) {
            std::cerr << "[Shader] Warning: Could not get file timestamps.\n";
        }

        return LoadFromSource(vertexSrc, fragmentSrc);
    }


    bool Shader::LoadFromSource(const std::string &vertexSrc, const std::string &fragmentSrc)
    {
        GLuint vertexShader = CompileShader(GL_VERTEX_SHADER, vertexSrc);
        GLuint fragmentShader = CompileShader(GL_FRAGMENT_SHADER, fragmentSrc);

        if (!vertexShader || !fragmentShader) return false;

        GLuint program = glCreateProgram();
        glAttachShader(program, vertexShader);
        glAttachShader(program, fragmentShader);
        glLinkProgram(program);

        GLint success;
        glGetProgramiv(program, GL_LINK_STATUS, &success);
        if (!success) {
            char info[512];
            glGetProgramInfoLog(program, 512, nullptr, info);
            Logger::LogError("'%s' Linking failed: '%s'", m_name.c_str(), info);
            glDeleteProgram(program);
            return false;
        }

        if (m_programID)
            glDeleteProgram(m_programID);

        m_programID = program;
        m_uniformCache.clear();

        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);
        Logger::LogDebug("Shader '%s' Loaded", m_name.c_str());
        return true;
    }

    void Shader::Reload()
    {
        OX_PROFILE_FUNCTION();
        LoadFromFiles(m_vertexPath, m_fragmentPath);
    }

    void Shader::Use() const
    {
        glUseProgram(m_programID);
    }

    GLuint Shader::CompileShader(GLenum type, const std::string &source)
    {
        OX_PROFILE_FUNCTION();
        GLuint shader = glCreateShader(type);
        const char *src = source.c_str();
        glShaderSource(shader, 1, &src, nullptr);
        glCompileShader(shader);

        GLint success;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (!success) {
            char info[512];
            glGetShaderInfoLog(shader, 512, nullptr, info);


            Logger::LogDebug("Failed to Compile Shader: '%s'", m_name.c_str());

            return 0;
        }

        return shader;
    }

    void Shader::CheckHotReload()
    {
        OX_PROFILE_FUNCTION();

        if (m_vertexPath.empty() || m_fragmentPath.empty()) {
            return;
        }

        try {
           OX_PROFILE_LABEL("Get Write Time");
            auto vertTime = std::filesystem::last_write_time(m_vertexPath);
            auto fragTime = std::filesystem::last_write_time(m_fragmentPath);

            if (vertTime != m_vertTimestamp || fragTime != m_fragTimestamp) {
                std::cout << "[Shader] Reloading shader: " << m_vertexPath << " / " << m_fragmentPath << "\n";
                Reload();
            }
        } catch (const std::exception &e) {
            Logger::LogError("Failed to Reload Shader '%s', %s", m_name.c_str(), e.what());

        }
    }


    std::string Shader::ReadFile(const std::string &path)
    {
        if (!std::filesystem::exists(path)) {
            std::ofstream file(path, std::ios::app);
        }
        std::ifstream file(path);
        std::stringstream ss;
        ss << file.rdbuf();
        return ss.str();
    }

    GLint Shader::GetUniformLocation(const std::string &name)
    {
        if (m_uniformCache.contains(name))
            return m_uniformCache[name];

        GLint location = glGetUniformLocation(m_programID, name.c_str());
        m_uniformCache[name] = location;
        return location;
    }

    void Shader::SetInt(const std::string &name, int value)
    {
        glUniform1i(GetUniformLocation(name), value);
    }

    void Shader::SetFloat(const std::string &name, float value)
    {
        glUniform1f(GetUniformLocation(name), value);
    }

    void Shader::SetVec2(const std::string &name, const glm::vec2 &value)
    {
        glUniform2fv(GetUniformLocation(name), 1, &value[0]);
    }

    void Shader::SetVec3(const std::string &name, const glm::vec3 &value)
    {
        glUniform3fv(GetUniformLocation(name), 1, &value[0]);
    }

    void Shader::SetVec4(const std::string &name, const glm::vec4 &value)
    {
        glUniform4fv(GetUniformLocation(name), 1, &value[0]);
    }

    void Shader::SetMat4(const std::string &name, const glm::mat4 &value)
    {
        glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, &value[0][0]);
    }


    void Shader::SetBool(const std::string &name, bool value)
    {
        glUniform1i(GetUniformLocation(name), (int) value);
    }
}
