#include "shader.h"

Shader::Shader() {
  id = 0;
  uniform_projection = 0;
  uniform_model = 0;
}

Shader::~Shader() {
  clear_shader(this);
}

bool create_shader(Shader *shader, const char *vertex_shader_path, const char *fragment_shader_path) {
  string vertex_shader_code = read_entire_file(vertex_shader_path);
  string fragment_shader_code = read_entire_file(fragment_shader_path);
  

  shader->id = glCreateProgram();

  if (!shader) {
    printf("Error creating shader shader!\n");
    return false;
  }

  {
    bool success = add_shader(shader, vertex_shader_code.c_str(), VERTEX);

    if (!success) {
      printf("Error adding vertex shader");
      return false;
    }
  }

  {
    bool success = add_shader(shader, fragment_shader_code.c_str(), FRAGMENT);

    if (!success) {
      printf("Error adding fragment shader");
      return false;
    }
  }

  s32 result = 0;
  s8 errors[1024] = {0};

  glLinkProgram(shader->id);
  glGetProgramiv(shader->id, GL_LINK_STATUS, &result);

  if (!result) {
    glGetProgramInfoLog(shader->id, sizeof(errors), NULL, errors);
    printf("Error linking shader: '%s'\n", errors);
    return false;
  }

  glValidateProgram(shader->id);
  glGetProgramiv(shader->id, GL_VALIDATE_STATUS, &result);

  if (!result) {
    glGetProgramInfoLog(shader->id, sizeof(errors), NULL, errors);
    printf("Error validating shader: '%s'\n", errors);
    return false;
  }

  shader->uniform_projection = glGetUniformLocation(shader->id, "projection");
  shader->uniform_model = glGetUniformLocation(shader->id, "model");
  shader->uniform_view = glGetUniformLocation(shader->id, "view");
  shader->uniform_eye_position = glGetUniformLocation(shader->id, "eye_position");

  shader->uniform_ambient_color = glGetUniformLocation(shader->id, "sun.color");
  shader->uniform_ambient_intensity = glGetUniformLocation(shader->id, "sun.ambient_intensity");
  shader->uniform_diffuse_direction = glGetUniformLocation(shader->id, "sun.direction");
  shader->uniform_diffuse_intensity = glGetUniformLocation(shader->id, "sun.diffuse_intensity");

  shader->uniform_shininess = glGetUniformLocation(shader->id, "material.shininess");
  shader->uniform_specular_intensity = glGetUniformLocation(shader->id, "material.specular_intensity");

  return true;
}

bool add_shader(Shader *shader, const char *shader_code, Shader_Type shader_type) {
  u32 _shader = glCreateShader(shader_type);

  const char *code[1];
  code[0] = shader_code;

  int code_length[1];
  code_length[0] = strlen(shader_code);

  glShaderSource(_shader, 1, code, code_length);
  glCompileShader(_shader);

  int result = 0;
  char errors[1024] = {0};

  glGetShaderiv(_shader, GL_COMPILE_STATUS, &result);
  if (!result) {
    glGetShaderInfoLog(_shader, sizeof(errors), NULL, errors);
    printf("Error compiling the %d shader->id: '%s'\n", shader_type, errors);
    return false;
  }

  glAttachShader(shader->id, _shader);

  return true;
}

void clear_shader(Shader *shader) {
  if (shader->id != 0) {
    glDeleteProgram(shader->id);
    shader->id = 0;
  }

  shader->uniform_model = 0;
  shader->uniform_projection = 0;
}

