/*
 * ShaderProgram.cpp
 *
 *  Created on: 31 janv. 2020
 *      Author: simon
 */

#include "render/shaders/ShaderProgram.h"

#include <fstream>
#include <iostream>

#include <glm/gtc/type_ptr.hpp>

#ifdef __ANDROID__
#include <android/log.h>
#endif

ShaderProgram::ShaderProgram() :
    programID(0), vertexShaderID(0), fragmentShaderID(0) {
}

ShaderProgram::~ShaderProgram() {
    cleanUp();
}

void ShaderProgram::start() const {
    glUseProgram(programID);
}

void ShaderProgram::stop() const {
    glUseProgram(0);
}

int ShaderProgram::getUniformLocation(const std::string& uniformName) const {
    const int location = glGetUniformLocation(programID, uniformName.c_str());
    if (location == -1) {
        std::cout << "Warning ! Uniform variable " << uniformName << " not found !\n";
    }
    return location;
}

void ShaderProgram::loadFloat(const int location, const float value) const {
    glUniform1f(location, value);
}

void ShaderProgram::loadInt(const int& location, const int& value) const {
    glUniform1i(location, value);
}

void ShaderProgram::loadVector(const int& location,
    const glm::vec2& vector) const {
    glUniform2f(location, vector.x, vector.y);
}

void ShaderProgram::loadVector(const int& location,
    const glm::vec3& vector) const {
    glUniform3f(location, vector.x, vector.y, vector.z);
}

void ShaderProgram::loadVector(const int& location,
    const glm::vec4& vector) const {
    glUniform4f(location, vector.x, vector.y, vector.z, vector.w);
}

void ShaderProgram::loadBoolean(const int& location, const bool& value) const {
    glUniform1i(location, value);
}

void ShaderProgram::cleanUp() const {
    stop();
    glDetachShader(programID, vertexShaderID);
    glDetachShader(programID, fragmentShaderID);
    glDeleteShader(vertexShaderID);
    glDeleteShader(fragmentShaderID);
    glDeleteProgram(programID);
}

void ShaderProgram::loadProgramFile(const std::string& vertexFile,
    const std::string& fragmentFile) {
    vertexShaderID = loadShaderFromFile(vertexFile, GL_VERTEX_SHADER);
    fragmentShaderID = loadShaderFromFile(fragmentFile, GL_FRAGMENT_SHADER);
    programID = glCreateProgram();
    glAttachShader(programID, vertexShaderID);
    glAttachShader(programID, fragmentShaderID);
    glLinkProgram(programID);
    glValidateProgram(programID);
    getAllUniformLocation();
}

void ShaderProgram::loadProgram(const std::string& vertexSource,
    const std::string& fragmentSource) {
    vertexShaderID = loadShader(vertexSource, GL_VERTEX_SHADER);
    fragmentShaderID = loadShader(fragmentSource, GL_FRAGMENT_SHADER);
    programID = glCreateProgram();
    glAttachShader(programID, vertexShaderID);
    glAttachShader(programID, fragmentShaderID);
    glLinkProgram(programID);
    glValidateProgram(programID);
    getAllUniformLocation();
}

int ShaderProgram::loadShader(const std::string& source, GLenum type) {
    unsigned int shaderID = glCreateShader(type);

    const char* c_str = source.c_str();
    int* null = 0;
    glShaderSource(shaderID, 1, &c_str, null); // @suppress("Function cannot be resolved")
    glCompileShader(shaderID);
    GLint compilesuccessful;
    glGetShaderiv(shaderID, GL_COMPILE_STATUS, &compilesuccessful);
    if (compilesuccessful == false) {
        std::cout << "Could not compile shader !\n";
        GLsizei size;
        glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &size);
        char error[size];
        glGetShaderInfoLog(shaderID, size, &size, error);
        std::cout << error << std::endl;
#ifdef __ANDROID__
        __android_log_print(ANDROID_LOG_ERROR, "TRACKERS", "Could not compile shader !\n %s", error);
#endif
    }
    return shaderID;
}

int ShaderProgram::loadShaderFromFile(const std::string& file, GLenum type) {
    std::string shaderSource = "";
    std::ifstream fileStream(file);
    if (fileStream.is_open()) {
        std::string line;
        while (getline(fileStream, line)) {
            shaderSource += line + "\n";
        }
        fileStream.close();
    }
    unsigned int shaderID = glCreateShader(type);

    const char* c_str = shaderSource.c_str();
    int* null = 0;
    glShaderSource(shaderID, 1, &c_str, null); // @suppress("Function cannot be resolved")
    glCompileShader(shaderID);
    GLint compilesuccessful;
    glGetShaderiv(shaderID, GL_COMPILE_STATUS, &compilesuccessful);
    if (compilesuccessful == false) {
        std::cout << "Could not compile shader !\n";
        GLsizei size;
        glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &size);
        char error[size];
        glGetShaderInfoLog(shaderID, size, &size, error);
        std::cout << error << std::endl;
    }
    return shaderID;
}

void ShaderProgram::loadMatrix(const int& location, const glm::mat4& matrix) {
    glUniformMatrix4fv(location, 1, false, glm::value_ptr(matrix));
}
