#include "ShaderProgram.h"
#include <fstream>
#include <cstring>

ShaderProgram::ShaderProgram()
{
}

ShaderProgram::~ShaderProgram()
{
    if(vertexShader != 0)
    {
        glDeleteShader(vertexShader);
    }
    if(fragmentShader != 0)
    {
        glDeleteShader(fragmentShader);
    }
    if(program != 0)
    {
        glDeleteProgram(program);
    }
}

bool ShaderProgram::isValid()
{
    return valid;
}

GLuint ShaderProgram::getProgram()
{
    return program;
}

void ShaderProgram::compile(const GLchar* vertexPath, const GLchar* fragmentPath)
{
    if(valid)
    {
        return;
    }
    GLchar* vertexData = readFile(vertexPath);
    if(vertexData == nullptr)
    {
        cout << "cannot read " << vertexPath << endl;
        return;
    }
    GLchar* fragmentData = readFile(fragmentPath);
    if(fragmentData == nullptr)
    {
        cout << "cannot read " << fragmentPath << endl;
        delete[] vertexData;
        return;
    }
    
    compile(vertexPath, vertexData, fragmentPath, fragmentData);
    delete[] vertexData;
    delete[] fragmentData;
}

GLchar* ShaderProgram::readFile(const GLchar* name)
{
    ifstream in;
    in.open(name);
    if(!in.fail())
    {
        int size = 128;
        int index = 0;
        GLchar* content = new GLchar[size];
        
        while(true)
        {
            GLchar c = in.get();
            if(in.eof())
            {
                break;
            }
            if(index >= size - 1)
            {
                GLchar* newContent = new GLchar[size * 2];
                memcpy(newContent, content, size);
                size *= 2;
                delete[] content;
                content = newContent;
            }
            content[index] = c;
            index++;
        }
        
        content[index] = '\0';
        index++;
        
        in.close();
        return content;
    }
    return nullptr;
}

bool ShaderProgram::checkShaderErrors(const GLchar* name, GLuint shader)
{
    bool returnValue = false;
    
    cout << "compiling " << name << " shader ..." << endl;
    GLenum error = glGetError();
    if(error)
    {
        cout << "error: " << glGetError() << endl;
        returnValue = true;
    }
    else
    {
        cout << "no error occured ..." << endl;
    }
    
    GLint compiled[1];
    glGetShaderiv(shader, GL_COMPILE_STATUS, compiled);
    if(compiled[0])
    {
        cout << name << " shader successfully compiled" << endl;
    }
    else
    {
        cout << name << "compiling of " << name << " failed:" << endl;
        GLchar buffer[512];
        GLsizei bufferSize = 512;
        GLsizei charsUsed = 0;
        glGetShaderInfoLog(shader, bufferSize, &charsUsed, buffer);
        // glGetProgramInfoLog should be null terminated ...
        buffer[bufferSize - 1] = '\0';
        cout << buffer << endl;
        returnValue = true;
    }
    return returnValue;
}

void ShaderProgram::compile(const GLchar* vertexPath, const GLchar* vertexData, const GLchar* fragmentPath, const GLchar* fragmentData)
{
    vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexData, nullptr);
    glCompileShader(vertexShader);

    if(checkShaderErrors(vertexPath, vertexShader))
    {
        return;
    }    
    
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentData, nullptr);
    glCompileShader(fragmentShader);

    if(checkShaderErrors(fragmentPath, fragmentShader))
    {
        return;
    }    

    program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);
    
    cout << "linking shaders to program ..." << endl;

    GLenum error = glGetError();
    if(error)
    {
        cout << "error: " << glGetError() << endl;
        return;
    }
    else
    {
        cout << "no error occured ..." << endl;
    }

    GLint compiled[1];
    glGetProgramiv(program, GL_LINK_STATUS, compiled);
    if(compiled[0])
    {
        cout << "shaders successfully linked" << endl;
    }
    else
    {
        cout << "linking of shaders failed:" << endl;
        GLchar buffer[512];
        GLsizei bufferSize = 512;
        GLsizei charsUsed = 0;
        glGetProgramInfoLog(program, bufferSize, &charsUsed, buffer);
        // glGetProgramInfoLog should be null terminated ...
        buffer[bufferSize - 1] = '\0';
        cout << buffer << endl;
        return;
    }
    valid = true;
}