/**
 * 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;

  GLchar const* filesVertex[] = { vertexShaderSource };
  GLchar const* filesFragment[] = { fragmentShaderSource };

  // Load the vertex shader first
  shaderVertex = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(shaderVertex, 1, filesVertex, 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, filesFragment, 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) {
  shaderUseMatrix(shader, shader->uniView, &camera->view);
  shaderUseMatrix(shader, shader->uniProj, &camera->projection);
}

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 shaderUseMatrix(shader_t *shader,shaderuniform_t uniform,matrix_t *matrix){
  glUniformMatrix4fv(uniform, 1, GL_FALSE, matrix->internalMatrix[0]);
}

void shaderUsePosition(shader_t *shader,
  float x, float y, float z,
  float pitch, float yaw, float roll
) {
  matrix_t matrix;

  matrixIdentity(&matrix);
  matrixTranslate(&matrix, x, y, z);

  // Rotation (YZX order)
  matrixRotate(&matrix, yaw, 0, 1, 0);
  matrixRotate(&matrix, roll, 0, 0, 1);
  matrixRotate(&matrix, pitch, 1, 0, 0);
  shaderUseMatrix(shader, shader->uniModl, &matrix);
}

void shaderUsePositionAndScale(shader_t *shader,
  float x, float y, float z,
  float pitch, float yaw, float roll,
  float scaleX, float scaleY, float scaleZ
) {
  matrix_t matrix;

  matrixIdentity(&matrix);
  matrixTranslate(&matrix, x, y, z);

  // Rotation (YZX order)
  matrixRotate(&matrix, yaw, 0, 1, 0);
  matrixRotate(&matrix, roll, 0, 0, 1);
  matrixRotate(&matrix, pitch, 1, 0, 0);

  matrixScale(&matrix, scaleX, scaleY, scaleZ);

  shaderUseMatrix(shader, shader->uniModl, &matrix);
}