/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
#include "Shaders.h"

#include <iostream>
#include <fstream>

// Setting floats
void ShaderProgram::setUniform(unsigned int uProgId, const char* name, float* fValues)
{
    int iLoc = glGetUniformLocation(uProgId, name);
    glUniform1fv(iLoc, 1, fValues);
}

void ShaderProgram::setUniform(unsigned int uProgId, const char* name, const float fValue)
{
    int iLoc = glGetUniformLocation(uProgId, name);
    glUniform1fv(iLoc, 1, &fValue);
}

// Setting vectors
void ShaderProgram::setUniform(unsigned int uProgId, const char* name, glm::vec2* vVectors)
{
    int iLoc = glGetUniformLocation(uProgId, name);
    glUniform2fv(iLoc, 1, (GLfloat*)vVectors);
}

void ShaderProgram::setUniform(unsigned int uProgId, const char* name, const glm::vec2 vVector)
{
    int iLoc = glGetUniformLocation(uProgId, name);
    glUniform2fv(iLoc, 1, (GLfloat*)&vVector);
}

void ShaderProgram::setUniform(unsigned int uProgId, const char* name, glm::vec3* vVectors)
{
    int iLoc = glGetUniformLocation(uProgId, name);
    glUniform3fv(iLoc, 1, (GLfloat*)vVectors);
}

void ShaderProgram::setUniform(unsigned int uProgId, const char* name, const glm::vec3 vVector)
{
    int iLoc = glGetUniformLocation(uProgId, name);
    glUniform3fv(iLoc, 1, (GLfloat*)&vVector);
}

void ShaderProgram::setUniform(unsigned int uProgId, const char* name, glm::vec4* vVectors)
{
    int iLoc = glGetUniformLocation(uProgId, name);
    glUniform4fv(iLoc, 1, (GLfloat*)vVectors);
}

void ShaderProgram::setUniform(unsigned int uProgId, const char* name, const glm::vec4 vVector)
{
    int iLoc = glGetUniformLocation(uProgId, name);
    glUniform4fv(iLoc, 1, (GLfloat*)&vVector);
}

// Setting 3x3 matrices
void ShaderProgram::setUniform(unsigned int uProgId, const char* name, glm::mat3* mMatrices)
{
    GLuint iLoc = glGetUniformLocation(uProgId, name);
    glUniformMatrix3fv(iLoc, 1, false, (GLfloat*)mMatrices);
}

void ShaderProgram::setUniform(unsigned int uProgId, const char* name, const glm::mat3 mMatrix)
{
    GLuint iLoc = glGetUniformLocation(uProgId, name);
    glUniformMatrix3fv(iLoc, 1, false, (GLfloat*)&mMatrix);
}

// Setting 4x4 matrices
void ShaderProgram::setUniform(unsigned int uProgId, const char* name, glm::mat4* mMatrices)
{
    GLuint iLoc = glGetUniformLocation(uProgId, name);
    glUniformMatrix4fv(iLoc, 1, false, (GLfloat*)mMatrices);
}

void ShaderProgram::setUniform(unsigned int uProgId, const char* name, const glm::mat4 mMatrix)
{
    GLuint iLoc = glGetUniformLocation(uProgId, name);
    glUniformMatrix4fv(iLoc, 1, false, (GLfloat*)&mMatrix);
}

// Setting integers
void ShaderProgram::setUniform(unsigned int uProgId, const char* name, int* iValues)
{
    int iLoc = glGetUniformLocation(uProgId, name);
    glUniform1iv(iLoc, 1, iValues);
}

void ShaderProgram::setUniform(unsigned int uProgId, const char* name, const int iValue)
{
    int iLoc = glGetUniformLocation(uProgId, name);
    glUniform1i(iLoc, iValue);
}

unsigned int ShaderProgram::createProgram(const std::string& vShader, const std::string& fShader)
{
	unsigned int programId = glCreateProgram();
	if (!loadShader(programId, vShader, GL_VERTEX_SHADER))
		return 0;
	if (!loadShader(programId, fShader, GL_FRAGMENT_SHADER))
		return 0;

	return programId;
}

unsigned int ShaderProgram::createProgram(const char* pvShader, const char* pfShader)
{
    unsigned int programId = glCreateProgram();
    if (!loadShader(programId, pvShader, GL_VERTEX_SHADER))
        return 0;
    if (!loadShader(programId, pfShader, GL_FRAGMENT_SHADER))
        return 0;

    return programId;
}

void ShaderProgram::deleteProgram(unsigned int programId)
{
	glDeleteShader(programId);
}

void ShaderProgram::useProgram(unsigned int programId)
{
	glUseProgram(programId);
}

bool ShaderProgram::loadShader(unsigned int programId, const std::string& shader, int type)
{
	unsigned int shaderId = glCreateShader(type);

	std::string shaderCode;
	if (!readShaderFile(shader, shaderCode))
	{
		std::cout << "compileShader : readShaderFile failed." << std::endl;
		return false;
	}

	if (!compileShader(shaderCode.c_str(), shaderId))
	{
		std::cout << "compileShader : compileShader failed." << std::endl;
		return false;
	}

	if (!linkProgram(programId, shaderId))
	{
		std::cout << "compileShader : linkProgram failed." << std::endl;
		return false;
	}

	deleteShader(shaderId);
	return true;
}

bool ShaderProgram::loadShader(unsigned int programId, const char* pShader, int type)
{
    unsigned int shaderId = glCreateShader(type);

    if (!compileShader(pShader, shaderId))
    {
        std::cout << "compileShader : compileShader failed." << std::endl;
        return false;
    }

    if (!linkProgram(programId, shaderId))
    {
        std::cout << "compileShader : linkProgram failed." << std::endl;
        return false;
    }

    deleteShader(shaderId);
    return true;
}

bool ShaderProgram::compileShader(const char* pShader, unsigned int shaderId)
{
	glShaderSource(shaderId, 1, &pShader, NULL);
	glCompileShader(shaderId);
	int iStatus = 0;
	glGetShaderiv(shaderId, GL_COMPILE_STATUS, &iStatus);
	if (GL_FALSE == iStatus)
	{
		int iLogLength;
		char sInfoLog[1024] = {0};
		glGetShaderInfoLog(shaderId, 1024, &iLogLength, sInfoLog);
		std::cout << sInfoLog << std::endl;
		return false;
	}
	return true;
}

bool ShaderProgram::linkProgram(unsigned int programId, unsigned int shaderId)
{
	int iStatus = 0;
	glAttachShader(programId, shaderId);
	glLinkProgram(programId);
	glGetProgramiv(programId, GL_LINK_STATUS, &iStatus);
	if (GL_FALSE == iStatus)
	{
		int iLogLength;
		char sInfoLog[1024] = {0};
		glGetShaderInfoLog(shaderId, 1024, &iLogLength, sInfoLog);
		std::cout << sInfoLog << std::endl;
		return false;
	}
	return true;
}

void ShaderProgram::deleteShader(unsigned int shaderId)
{
	glDeleteShader(shaderId);
}

bool ShaderProgram::readShaderFile(const std::string& fileName, std::string& value)
{
	std::ifstream shaderStream(fileName, std::ios::in);
	if (!shaderStream.is_open())
	{
		return false;
	}

	std::string Line = "";
	while(getline(shaderStream, Line))
		value += "\n" + Line;
	shaderStream.close();

	return true;
}
