#include "shader_program.hpp"

namespace VE
{
  shader_program::shader_program()
  {

  }

  shader_program::~shader_program()
  {
    glDeleteProgram(m_id);

    for (auto s : m_shaders)
      glDeleteShader(s);

    m_shaders.clear();
  }

  bool shader_program::add_shader(GLuint type, const std::filesystem::path& p)
  {
    auto [err_v, shader] = create(type, p);
    if (err_v) return false;

    m_shaders.push_back(shader);
    return true;
  }

  std::pair<bool, GLuint> shader_program::create(GLuint type, const std::filesystem::path& p)
  { 
    GLuint shader_id = glCreateShader(type);

    auto [err, src] = get_shader_content(p);
    if (err)
    {
      glDeleteShader(shader_id);
      return std::pair{ true, shader_id };
    }

    const char* code =src.c_str();
    glShaderSource(shader_id, 1, &code, NULL);
    glCompileShader(shader_id);

    if (errors(shader_id, type))
    {
      glDeleteShader(shader_id);
      return std::pair{ true, shader_id };
    }

    return std::pair{ false, shader_id };
  }

  void shader_program::link()
  {
    m_id = glCreateProgram();
    for (auto s : m_shaders)
      glAttachShader(m_id, s);
    glLinkProgram(m_id);
  }

  void shader_program::use()
  {
    glUseProgram(m_id);
  }

  GLuint shader_program::get_id() const noexcept
  {
    return m_id;
  }

  void shader_program::set(std::string location, glm::vec2 value)
  {
    glUniform2fv(glGetUniformLocation(m_id, location.c_str()), 1, glm::value_ptr(value));
  }

  void shader_program::set(std::string location, glm::vec3 value)
  {
    glUniform3fv(glGetUniformLocation(m_id, location.c_str()), 1, glm::value_ptr(value));
  }

  void shader_program::set(std::string location, glm::vec4 value)
  {
    glUniform4fv(glGetUniformLocation(m_id, location.c_str()), 1, glm::value_ptr(value));
  }

  void shader_program::set(std::string location, glm::mat3 value)
  {
    glUniformMatrix3fv(glGetUniformLocation(m_id, location.c_str()), 1, GL_FALSE, glm::value_ptr(value));
  }

  void shader_program::set(std::string location, glm::mat4 value)
  {
    glUniformMatrix4fv(glGetUniformLocation(m_id, location.c_str()), 1, GL_FALSE, glm::value_ptr(value));
  }

  void shader_program::set(std::string location, float value)
  {
    glUniform1f(glGetUniformLocation(m_id, location.c_str()), value);
  }

  void shader_program::set(std::string location, int value)
  {
    glUniform1i(glGetUniformLocation(m_id, location.c_str()), value);
  }

  bool shader_program::errors(unsigned int shader, GLuint type)
  {
    GLint compale;
    char info[1024];
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compale);

    if (compale == GL_FALSE)
    {
      glGetShaderInfoLog(shader, 1024, NULL, info);
      hack::error()("shader compale error:", type_to_name(type), info);
      return true;
    }
    hack::log()("shader compale is good:", type_to_name(type));
    return false;
  }
}

