/* display teapot with vertex and fragment shaders */
/* sets up elapsed time parameter for use by shaders */

#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>


const float nearVal     = 1.0f;
const float farVal      = 300.0f;
const float lightPos[3] = {3.0f, 3.0f, 3.0f};

int width  = 512;
int height = 512;

GLint       program = 0;
GLint       timeParam;

/* shader reader */
/* creates null terminated string from file */

char* readShaderSource(const char* shaderFile)
{
    struct stat statBuf;
    FILE* fp = fopen(shaderFile, "r");
    char* buf;

    stat(shaderFile, &statBuf);
    buf = (char*) malloc(statBuf.st_size + 1 * sizeof(char));
    fread(buf, 1, statBuf.st_size, fp);
    buf[statBuf.st_size] = '\0';
    fclose(fp);
    return buf;
\}

/* error printing function */

static void checkError(GLint status, const char *msg)
{
    if (!status)
    {
    printf("%s\n", msg);
    exit(EXIT_FAILURE);
    }
}

/* standard OpenGL initialization */

static void init()
{
    const float teapotColor[]     = {0.3f, 0.5f, 0.4f, 1.0f};
    const float teapotSpecular[]  = {0.8f, 0.8f, 0.8f, 1.0f};
    const float teapotShininess[] = {80.0f};

    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, teapotColor);
    glMaterialfv(GL_FRONT, GL_SPECULAR, teapotSpecular);
    glMaterialfv(GL_FRONT, GL_SHININESS, teapotShininess);

    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslate(0.0f, 0.0f, 10.0f);

    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_DEPTH_TEST);
}

/* GLSL initialization */

static void initShader(const GLchar* vShaderFile, const GLchar* fShaderFile)
{
    GLint vShader = 0;
    GLint fShader = 0;
    GLint status = 0;

    /* read shader files */

    GLchar* vSource = readShaderSource(vShaderFile);
    GLchar* fSource = readShaderSource(fShaderFile);

    /* create program and shader objects */

    vShader = glCreateShaderObject(GL_VERTEX_SHADER);
    fShader = glCreateShaderObject(GL_FRAGMENT_SHADER);
    program = glCreateProgramObject();

    /* attach shaders to the program object */

    glAttachObject(program, vShader);
    glAttachObject(program, fShader);

    /* read shaders */

    glShaderSource(vShader, 1, &vShaderFile, NULL);
    checkError(status, "Failed to read vertex shader");
    glShaderSource(fShader, 1, &fShaderFile, NULL);
    checkError(status, "Failed to read vertex shader");

    /* compile shaders */

    glCompileShader(vShader);
    glCompileShader(fShader);

    /* error check */

    glGetObjectParameteriv(vShader, GL_OBJECT_COMPILE_STATUS, &status);
    checkError(status, "Failed to compile the vertex shader.");

    glGetObjectParameteriv(fShader, GL_OBJECT_COMPILE_STATUS, &status);
    checkError(status, "Failed to compile the fragment shader.");

    /* link */

    glLinkProgram(program);

    glGetObjectParameteriv(program, GL_OBJECT_LINK_STATUS, &status);
    checkError(status, "Failed to link the shader program object.");

    /* use program object */

    glUseProgramObject(program);

    /* set up uniform parameter */

    timeParam = glGetUniformLocation(program, "time");
}

static void draw()
{

    /* send elapsed time to shaders */

    glUniform1f(timeParam, glutGet(GLUT_ELAPSED_TIME));

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glTranslatef(0.0f, 0.0f, -10.0f);
    glutSolidTeapot(2.0);
    glutSwapBuffers();
}

static void reshape(int w, int h)
\{
    width  = w;
    height = h;


    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, (double)width / (double)height, nearVal, farVal);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glViewport(0, 0, width, height);

    glutPostRedisplay();
}

static void keyboard(unsigned char key, int x, int y)
{
    switch (key) {
    case 27:
    case 'Q':
    case 'q':
        exit(EXIT_SUCCESS);
        break;
    default:
        break;
    }
}


int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(width, height);
    glutCreateWindow("Simple GLSL example");
    glutDisplayFunc(draw);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);

    init();
    initShader("vPhong.glsl","fPassThrough.glsl");

    glutMainLoop();
}

/* the following code is for the vertex and fragment shaders */
/* shader code is assumed to be in separate files */


// Vphong.glsl
// modified Phong vertex shader

uniform float time;

void main()
{
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

    vec4 eyePosition = gl_ModelViewMatrix * gl_Vertex;
    vec4 eyeLightPos = gl_LightSource[0].position;

    vec3 eyeNormalVec = normalize(gl_NormalMatrix * gl_Normal);
    vec3 eyeLightVec = normalize(eyeLightPos.xyz - eyePosition.xyz);
    vec3 eyeViewVec = -normalize(eyePosition.xyz);
    vec3 eyeHalfVec = normalize(eyeLightVec + eyeViewVec);

    float Kd = max(dot(eyeLightVec, eyeNormalVec), 0.0);
    float Ks = pow(dot(eyeNormalVec, eyeHalfVec),
    gl_FrontMaterial.shininess);
    float Ka = 1.0;

    gl_FrontColor = Kd * gl_FrontLightProduct[0].diffuse +
                Ks * gl_FrontLightProduct[0].specular +
            gl_FrontLightModelProduct.sceneColor;
}


// fPassThrough.glsl
// Pass through fragment shader.

void main()
{
    gl_FragColor = gl_Color;
}
