/********************************************************************************
* OpenGL-Framework                                                              *
* Copyright (c) 2013 Daniel Chappuis                                            *
*********************************************************************************
*                                                                               *
* This software is provided 'as-is', without any express or implied warranty.   *
* In no event will the authors be held liable for any damages arising from the  *
* use of this software.                                                         *
*                                                                               *
* Permission is granted to anyone to use this software for any purpose,         *
* including commercial applications, and to alter it and redistribute it        *
* freely, subject to the following restrictions:                                *
*                                                                               *
* 1. The origin of this software must not be misrepresented; you must not claim *
*    that you wrote the original software. If you use this software in a        *
*    product, an acknowledgment in the product documentation would be           *
*    appreciated but is not required.                                           *
*                                                                               *
* 2. Altered source versions must be plainly marked as such, and must not be    *
*    misrepresented as being the original software.                             *
*                                                                               *
* 3. This notice may not be removed or altered from any source distribution.    *
*                                                                               *
********************************************************************************/

// Libraries
#include "Shader.h"
#include <cassert>
#include <fstream>
#include <iostream>

// Namespaces
using namespace openglframework;
using namespace std;

// Constructor
Shader::Shader() : mProgramObjectID(0) {

}

// Constructor with arguments
Shader::Shader(const std::string vertexShaderFilename, const std::string fragmentShaderFilename)
    : mProgramObjectID(0) {

    // Create the shader
    create(vertexShaderFilename, fragmentShaderFilename);
}

// Destructor
Shader::~Shader() {

}

// Create the shader
bool Shader::create(const std::string vertexShaderFilename,
                    const std::string fragmentShaderFilename) {

    // Set the shader filenames
    mFilenameVertexShader = vertexShaderFilename;
    mFilenameFragmentShader = fragmentShaderFilename;

    // Check that the needed OpenGL extensions are available
    bool isExtensionOK = checkOpenGLExtensions();
    if (!isExtensionOK) {
       cerr << "Error : Impossible to use GLSL vertex or fragment shaders on this platform" << endl;
       assert(false);
       return false;
    }

    // Delete the current shader
    destroy();

    assert(!vertexShaderFilename.empty() && !fragmentShaderFilename.empty());

    // ------------------- Load the vertex shader ------------------- //
    GLuint vertexShaderID;
    std::ifstream fileVertexShader;
    fileVertexShader.open(vertexShaderFilename.c_str(), std::ios::binary);

    if (fileVertexShader.is_open()) {

        // Get the size of the file
        fileVertexShader.seekg(0, std::ios::end);
        uint fileSize = (uint) (fileVertexShader.tellg());
        assert(fileSize != 0);

        // Read the file
        fileVertexShader.seekg(std::ios::beg);
        char* bufferVertexShader = new char[fileSize + 1];
        fileVertexShader.read(bufferVertexShader, fileSize);
        fileVertexShader.close();
        bufferVertexShader[fileSize] = '\0';

        // Create the OpenGL vertex shader and compile it
        vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
        assert(vertexShaderID != 0);
        glShaderSource(vertexShaderID, 1, (const char **) (&bufferVertexShader), NULL);
        glCompileShader(vertexShaderID);
        delete[] bufferVertexShader;

        // Get the compilation information
        int compiled;
        glGetShaderiv(vertexShaderID, GL_COMPILE_STATUS, &compiled);

        // If the compilation failed
        if (compiled == 0) {

            // Get the log of the compilation
            int lengthLog;
            glGetShaderiv(vertexShaderID, GL_INFO_LOG_LENGTH, &lengthLog);
            char* str = new char[lengthLog];
            glGetShaderInfoLog(vertexShaderID, lengthLog, NULL, str);

            // Display the log of the compilation
            std::cerr << "Vertex Shader Error : " << str << std::endl;
            delete[] str;
            assert(false);
            return false;
        }
    }
    else {
        std::cerr << "Error : Impossible to open the vertex shader file " <<
                     vertexShaderFilename << std::endl;
        assert(false);
        return false;
    }

    // ------------------- Load the fragment shader ------------------- //
    GLuint fragmentShaderID;
    std::ifstream fileFragmentShader;
    fileFragmentShader.open(fragmentShaderFilename.c_str(), std::ios::binary);
    assert(fileFragmentShader.is_open());

    if (fileFragmentShader.is_open()) {

        // Get the size of the file
        fileFragmentShader.seekg(0, std::ios::end);
        uint fileSize = (uint) (fileFragmentShader.tellg());
        assert(fileSize != 0);

        // Read the file
        fileFragmentShader.seekg(std::ios::beg);
        char* bufferFragmentShader = new char[fileSize + 1];
        fileFragmentShader.read(bufferFragmentShader, fileSize);
        fileFragmentShader.close();
        bufferFragmentShader[fileSize] = '\0';

        // Create the OpenGL fragment shader and compile it
        fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
        assert(fragmentShaderID != 0);
        glShaderSource(fragmentShaderID, 1, (const char **) (&bufferFragmentShader), NULL);
        glCompileShader(fragmentShaderID);
        delete[] bufferFragmentShader;

        // Get the compilation information
        int compiled;
        glGetShaderiv(fragmentShaderID, GL_COMPILE_STATUS, &compiled);

        // If the compilation failed
        if (compiled == 0) {

            // Get the log of the compilation
            int lengthLog;
            glGetShaderiv(fragmentShaderID, GL_INFO_LOG_LENGTH, &lengthLog);
            char* str = new char[lengthLog];
            glGetShaderInfoLog(fragmentShaderID, lengthLog, NULL, str);

            // Display the log of the compilation
            std::cerr << "Fragment Shader Error : " << str << std::endl;
            delete[] str;
            assert(false);
            return false;
        }
    }
    else {
        std::cerr << "Error : Impossible to open the fragment shader file " <<
                     fragmentShaderFilename << std::endl;
        assert(false);
        return false;
    }

    // Create the shader program and attach the shaders
    mProgramObjectID = glCreateProgram();
    assert(mProgramObjectID);
    glAttachShader(mProgramObjectID, vertexShaderID);
    glAttachShader(mProgramObjectID, fragmentShaderID);
    glDeleteShader(vertexShaderID);
    glDeleteShader(fragmentShaderID);

    // Try to link the program
    glLinkProgram(mProgramObjectID);
    int linked;
    glGetProgramiv(mProgramObjectID, GL_LINK_STATUS, &linked);
    if (!linked) {
        int logLength;
        glGetProgramiv(mProgramObjectID, GL_INFO_LOG_LENGTH, &logLength);
        char* strLog = new char[logLength];
        glGetProgramInfoLog(mProgramObjectID, logLength, NULL, strLog);
        cerr << "Linker Error in vertex shader " << vertexShaderFilename <<
                " or in fragment shader " << fragmentShaderFilename << " : " << strLog << endl;
        delete[] strLog;
        destroy();
        assert(false);
    }

    return true;
}