#include <shader.h>
#include <textfile.h>
#include <time.h>

using namespace std;

unsigned int AVPHandle = 0;
bool AVPLoaded = false;
unsigned int AFPHandle = 0;
bool AFPLoaded = false;

GLhandleARB AVSHandle = 0;
GLhandleARB AFSHandle = 0;
GLhandleARB ShaderHandle = 0;


void loadShader(const std::string& fn) {
    LOGN("Loading shader from file: " << fn << " - ");

    // find file extension
    unsigned int pos = fn.find_last_of('.');
    if (pos == string::npos)
        ERR("no extension found in file: " + fn);
    string ext = fn.substr(++pos);

    if (ext == "avp") {
        LOG("ARB_vertex_program");
        loadVertexProgram(fn);
    } else if (ext == "afp") {
        LOG("ARB_fragment_program");
        loadFragmentProgram(fn);
    } else if (ext == "avs") {
        LOG("ARB_vertex_shader");
        loadVertexShader(fn);
    } else if (ext == "afs") {
        LOG("ARB_fragment_shader");
        loadFragmentShader(fn);
    } else {
        ERR("Unsupported file extension: ." + ext);
    }

}

void loadVertexProgram(const std::string fn) {
    // check for ARB_vertex_program extension support
    if (!GLEW_ARB_vertex_program) {
        ERR("system doesn't support ARB_vertex_program extension!");
    }

    char* pgmstr = readTextFile(fn);
    unsigned int program;
    // bind shader
    glGenProgramsARB(1, &program);
    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, program);
    // load shader
    glProgramStringARB(GL_VERTEX_PROGRAM_ARB,
                       GL_PROGRAM_FORMAT_ASCII_ARB,
                       strlen(pgmstr),
                       pgmstr);
    // check for errors
    if (glGetError() == GL_INVALID_OPERATION) {
        int errorpos;
        const char* errorstr;

        glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errorpos);
        errorstr = (const char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB);

        ERR("error while loading ARB_vertex_program from " << fn << " at pos " << errorpos << endl << errorstr);
    }
    
    // save handle
    AVPHandle = program;
    AVPLoaded = true;
    
    // free text memory
    delete[] pgmstr;
}

void loadFragmentProgram(const std::string fn) {
    // check for ARB_fragment_program extension support
    if (!GLEW_ARB_fragment_program) {
        ERR("system doesn't support ARB_fragment_program extension!");
    }

    char* pgmstr = readTextFile(fn);
    unsigned int program;
    // bind shader
    glGenProgramsARB(1, &program);
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, program);
    // load shader
    glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB,
                       GL_PROGRAM_FORMAT_ASCII_ARB,
                       strlen(pgmstr),
                       pgmstr);
    // check for errors
    if (glGetError() == GL_INVALID_OPERATION) {
        int errorpos;
        const char* errorstr;

        glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errorpos);
        errorstr = (const char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB);

        ERR("error while loading ARB_vertex_program from " << fn << " at pos " << errorpos << endl << errorstr);
    }
    
    // save handle
    AFPHandle = program;
    AFPLoaded = true;
    
    // free text memory
    delete[] pgmstr;
}

void loadVertexShader(const std::string fn) {
    // check for ARB_vertex_shader extension support
    if (!GLEW_ARB_vertex_shader) {
        ERR("system doesn't support ARB_vertex_shader extension!");
    }

    // create handle
    AVSHandle = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
    // read program
    char* pgmstr = readTextFile(fn);
    const GLcharARB* pgms = pgmstr;

    // generate shader source
    glShaderSourceARB(AVSHandle, 1, &pgms, NULL);
    // compile shader
    glCompileShaderARB(AVSHandle);

    
    // free text memory
    delete[] pgmstr;
}

void loadFragmentShader(const std::string fn) {
    // check for ARB_fragment_shader extension support
    if (!GLEW_ARB_fragment_shader) {
        ERR("system doesn't support ARB_fragment_shader extension!");
    }
    
    // create handle
    AFSHandle = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
    // read program
    char* pgmstr = readTextFile(fn);
    const GLcharARB* pgms = pgmstr;

    // generate shader source
    glShaderSourceARB(AFSHandle, 1, &pgms, NULL);
    // compile shader
    glCompileShaderARB(AFSHandle);

    // free text memory
    delete[] pgmstr;
}

void linkShaders() {
    // genarate shader program handle
    if (AVSHandle || AFSHandle)
        ShaderHandle = glCreateProgramObjectARB();
    // attach vertex shader
    if (AVSHandle)
        glAttachObjectARB(ShaderHandle, AVSHandle);
    // attach fragment shader
    if (AFSHandle)
        glAttachObjectARB(ShaderHandle, AFSHandle);
    // link program
    glLinkProgramARB(ShaderHandle);

    // print info logs
    printInfoLog(AVSHandle);
    printInfoLog(AFSHandle);
    printInfoLog(ShaderHandle);
}

void printInfoLog(GLhandleARB obj) {
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

    glGetObjectParameterivARB(obj, GL_OBJECT_INFO_LOG_LENGTH_ARB,
            &infologLength);

    if (infologLength > 0) {
        infoLog = (char *)malloc(infologLength);
        glGetInfoLogARB(obj, infologLength, &charsWritten, infoLog);
        LOG("Infolog for " << obj);
        printf("%s\n",infoLog);
        free(infoLog);
    }
}

void bindShaders() {
    // bind and enable ARB_vertex_shader?
    if (AVPLoaded) {
        glBindProgramARB(GL_VERTEX_PROGRAM_ARB, AVPHandle);
        glEnable(GL_VERTEX_PROGRAM_ARB);
    }
    // bind and enable ARB_fragment_shader?
    if (AFPLoaded) {
        glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, AFPHandle);
        glEnable(GL_FRAGMENT_PROGRAM_ARB);
    }

    // use GLSL?
    if (ShaderHandle) 
        glUseProgramObjectARB(ShaderHandle);

}

void sendTime() {
    // determine locate of uniform variable timeElapsed
    GLint loc = glGetUniformLocationARB(ShaderHandle, "timeElapsed");
    // some very basic error handling
    GLenum err = glGetError();
    if (err != GL_NO_ERROR)
        LOG("loc: " << loc << " err " << err << " " << gluErrorString(err));
    // calculate a float value between 0.0f and 1.0f 
    GLfloat sec = (GLfloat)((clock() % CLOCKS_PER_SEC))/CLOCKS_PER_SEC;
    // pass it to the shade
    glUniform1fARB(loc, sec);
}

