/**
 * Copyright (c) 2021 Dominic Msters
 * 
 * This software is released under the MIT License.
 * https://opensource.org/licenses/MIT
 */

#include "shader.h"

void shaderInit(shader_t *shader,
  char *vertexShaderSource, char* fragmentShaderSource
) {
  int isSuccess, maxLength;
  char *error;
  GLuint shaderVertex, shaderFragment, shaderProgram;
  
  // Load the vertex shader first
  shaderVertex = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(shaderVertex, 1, &vertexShaderSource, 0);
  glCompileShader(shaderVertex);

  // Validate
  glGetShaderiv(shaderVertex, GL_COMPILE_STATUS, &isSuccess);
  if(!isSuccess) {
    glGetShaderiv(shaderVertex, GL_INFO_LOG_LENGTH, &maxLength);
    error = malloc(maxLength);
    glGetShaderInfoLog(shaderVertex, maxLength, &maxLength, error);
    printf("Failed to compile vertex shader %s\n", error);
    free(error);
    return;
  }

  // Now load the Frag shader
  shaderFragment = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(shaderFragment, 1, &fragmentShaderSource, 0);
  glCompileShader(shaderFragment);
  glGetShaderiv(shaderFragment, GL_COMPILE_STATUS, &isSuccess);
  if(!isSuccess) {
    glGetShaderiv(shaderFragment, GL_INFO_LOG_LENGTH, &maxLength);
    error = malloc(maxLength);
    glGetShaderInfoLog(shaderFragment, maxLength, &maxLength, error);
    printf("Failed to compile fragment shader %s\n", error);
    free(error);
    glDeleteShader(shaderVertex);
    return;
  }

  // Now create the shader program.
  shaderProgram = glCreateProgram();
  glAttachShader(shaderProgram, shaderVertex);
  glAttachShader(shaderProgram, shaderFragment);

  //Bind, Verify & Use the shader program
  glLinkProgram(shaderProgram);
  glGetProgramiv(shaderProgram, GL_LINK_STATUS, &isSuccess);
  if(!isSuccess) {
    glGetProgramiv(shaderProgram, GL_INFO_LOG_LENGTH, &maxLength);
    error = malloc(maxLength);
    glGetProgramInfoLog(shaderProgram, maxLength, &maxLength, error);
    printf("Failed to load shader program %s\n", error);
    free(error);
    glDeleteShader(shaderVertex);
    glDeleteShader(shaderFragment);
    return;
  }

  // Everything is okay, let's create the encapsulated shader.
  shader->shaderVertex = shaderVertex;
  shader->shaderFrag = shaderFragment;
  shader->shaderProgram = shaderProgram;

  shader->uniProj = glGetUniformLocation(shader->shaderProgram,SHADER_UNI_PROJ);
  shader->uniView = glGetUniformLocation(shader->shaderProgram,SHADER_UNI_VIEW);
  shader->uniText = glGetUniformLocation(shader->shaderProgram,SHADER_UNI_TEXT);
  shader->uniModl = glGetUniformLocation(shader->shaderProgram,SHADER_UNI_MODL);

  // Bind the shader
  shaderUse(shader);

  // Reset position
  shaderUsePosition(shader, 0, 0, 0, 0, 0, 0);
}

void shaderDispose(shader_t *shader) {
  glDeleteProgram(shader->shaderProgram);
  glDeleteShader(shader->shaderVertex);
  glDeleteShader(shader->shaderFrag);
}

void shaderUse(shader_t *shader) {
  glUseProgram(shader->shaderProgram);
}

void shaderUseCamera(shader_t *shader, camera_t *camera) {
  glUniformMatrix4fv(shader->uniView, 1, GL_FALSE, camera->view[0]);
  glUniformMatrix4fv(shader->uniProj, 1, GL_FALSE, camera->projection[0]);
}

void shaderUseTexture(shader_t *shader, texture_t *texture) {
  // OpenGL requires us to set the active texure.
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, texture->id);
  glUniform1i(shader->uniText, 0);
}

void shaderUsePosition(shader_t *shader,
  float x, float y, float z,
  float pitch, float yaw, float roll
) {
  mat4 MATRIX_POSITION;
  vec3 axis;

  // Identify mat.
  glm_mat4_identity(MATRIX_POSITION);

  //Position
  axis[0] = x, axis[1] = y, axis[2] = z;
  glm_translate_make(MATRIX_POSITION, axis);

  // Rotation (YZX order)
  axis[0] = 0, axis[1] = 1, axis[2] = 0;
  glm_rotate(MATRIX_POSITION, yaw, axis);
  axis[1] = 0, axis[2] = 1;
  glm_rotate(MATRIX_POSITION, roll, axis);
  axis[0] = 1, axis[2] = 0;
  glm_rotate(MATRIX_POSITION, pitch, axis);

  //Send to the shader.
  glUniformMatrix4fv(shader->uniModl, 1, GL_FALSE, MATRIX_POSITION[0]);
}

void shaderUsePositionAndScale(shader_t *shader,
  float x, float y, float z,
  float pitch, float yaw, float roll,
  float scaleX, float scaleY, float scaleZ
) {
  mat4 MATRIX_POSITION;
  vec3 axis;

  // Identify mat.
  glm_mat4_identity(MATRIX_POSITION);

  //Position
  axis[0] = x, axis[1] = y, axis[2] = z;
  glm_translate_make(MATRIX_POSITION, axis);

  // Rotation (YZX order)
  axis[0] = 0, axis[1] = 1, axis[2] = 0;
  glm_rotate(MATRIX_POSITION, yaw, axis);
  axis[1] = 0, axis[2] = 1;
  glm_rotate(MATRIX_POSITION, roll, axis);
  axis[0] = 1, axis[2] = 0;
  glm_rotate(MATRIX_POSITION, pitch, axis);

  // Scale
  axis[0] = scaleX, axis[1] = scaleY, axis[2] = scaleZ;
  glm_scale(MATRIX_POSITION, axis);

  glUniformMatrix4fv(shader->uniModl, 1, GL_FALSE, MATRIX_POSITION[0]);
}