#include "SDL_log.h"
#include <SDL2/SDL.h>
#include <frostbite2D/graphics/shader.h>
#include <frostbite2D/resource/asset.h>
#include <glad/glad.h>
#include <sstream>

namespace frostbite2D {

Shader::Shader(const std::string& name, uint32_t programID)
    : name_(name), programID_(programID) {}

Shader::~Shader() {
  if (programID_ != 0) {
    glDeleteProgram(programID_);
    programID_ = 0;
  }
  uniformLocations_.clear();
}

void Shader::use() {
  glUseProgram(programID_);
}

void Shader::unuse() {
  glUseProgram(0);
}

bool Shader::compile(uint32_t type, const std::string& source) {
  uint32_t shaderID = glCreateShader(type);
  const char* src = source.c_str();
  glShaderSource(shaderID, 1, &src, nullptr);
  glCompileShader(shaderID);
  
  int success;
  glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success);
  if (!success) {
    int length;
    glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &length);
    std::vector<char> log(length);
    glGetShaderInfoLog(shaderID, length, &length, log.data());
    
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Shader compilation failed: %s", log.data());
    glDeleteShader(shaderID);
    return false;
  }
  
  return true;
}

bool Shader::link(uint32_t vertexID, uint32_t fragmentID) {
  programID_ = glCreateProgram();
  glAttachShader(programID_, vertexID);
  glAttachShader(programID_, fragmentID);
  glLinkProgram(programID_);
  
  int success;
  glGetProgramiv(programID_, GL_LINK_STATUS, &success);
  if (!success) {
    int length;
    glGetProgramiv(programID_, GL_INFO_LOG_LENGTH, &length);
    std::vector<char> log(length);
    glGetProgramInfoLog(programID_, length, &length, log.data());
    
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Shader linking failed: %s", log.data());
    glDeleteProgram(programID_);
    programID_ = 0;
    return false;
  }
  
  glDeleteShader(vertexID);
  glDeleteShader(fragmentID);
  
  return true;
}

int Shader::getUniformLocation(const std::string& name) {
  auto it = uniformLocations_.find(name);
  if (it != uniformLocations_.end()) {
    return it->second;
  }
  
  int location = glGetUniformLocation(programID_, name.c_str());
  uniformLocations_[name] = location;
  return location;
}

void Shader::setInt(const std::string& name, int value) {
  int location = getUniformLocation(name);
  if (location >= 0) {
    glUniform1i(location, value);
  }
}

void Shader::setFloat(const std::string& name, float value) {
  int location = getUniformLocation(name);
  if (location >= 0) {
    glUniform1f(location, value);
  }
}

void Shader::setVec2(const std::string& name, const Vec2& value) {
  int location = getUniformLocation(name);
  if (location >= 0) {
    glUniform2f(location, value.x, value.y);
  }
}

void Shader::setVec3(const std::string& name, const glm::vec3& value) {
  int location = getUniformLocation(name);
  if (location >= 0) {
    glUniform3f(location, value.x, value.y, value.z);
  }
}

void Shader::setVec4(const std::string& name, const glm::vec4& value) {
  int location = getUniformLocation(name);
  if (location >= 0) {
    glUniform4f(location, value.x, value.y, value.z, value.w);
  }
}

void Shader::setMat3(const std::string& name, const glm::mat3& value) {
  int location = getUniformLocation(name);
  if (location >= 0) {
    glUniformMatrix3fv(location, 1, GL_FALSE, &value[0][0]);
  }
}

void Shader::setMat4(const std::string& name, const glm::mat4& value) {
  int location = getUniformLocation(name);
  if (location >= 0) {
    glUniformMatrix4fv(location, 1, GL_FALSE, &value[0][0]);
  }
}

void Shader::setTexture(const std::string& name, int slot) {
  int location = getUniformLocation(name);
  if (location >= 0) {
    glUniform1i(location, slot);
  }
}

}