// This code example is created for educational purpose
// by Thorsten Thormaehlen (contact: www.thormae.de).
// It is distributed without any warranty.

#include <GL/glew.h>
#include <GL/freeglut.h> // we use glut here as window manager

#define _USE_MATH_DEFINES
#include <math.h>

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;

class Renderer {

private:
  struct Vertex {
    float position[3];
    float texCoord[2];
    float normal[3];
  };

  struct LightSource {
    float position[3];
    float color[3];
    float constantAttenuation, linearAttenuation, quadraticAttenuation;
    float spotCutoff, spotExponent;
    float spotDirection[3];
    float lookAt[16];
    float proj[16];
    float projLookAt[16];
  };
  
  struct LightSourceUniformLoc {
    int position;
    int color;
    int constantAttenuation, linearAttenuation, quadraticAttenuation;
    int spotCutoff, spotExponent;
    int spotDirection;
  };

  struct ShaderID {
    GLuint progID;
    GLuint vertID;
    GLuint fragID;
  };

public:
  float t;
  int modeVal;
  std::vector<LightSource> lights;
  int activeLight;

private:
  enum {Scene, Debug, numVAOs};
  enum {SceneAll, DebugAll, numVBOs};
  GLuint vaoID[numVAOs];
  GLuint bufID[numVBOs];
  int sceneVertNo;
  GLint vertexLoc;
  GLint texCoordLoc;
  GLint normalLoc;
  GLint projectionLoc;
  GLint modelviewLoc;
  GLint normalMatrixLoc;
  GLint modeLoc;
  float projection[16];  // projection matrix
  float modelview[16];  // modelview matrix
  std::string filename;
  std::vector<LightSourceUniformLoc> lightsLoc;
  std::vector<ShaderID> shaders;

  // for debug visualizations
  GLint vertexDebugLoc;
  GLint texCoordDebugLoc;
  GLint colorDebugLoc;
  GLint projectionDebugLoc;
  GLint modelviewDebugLoc;
  GLint modeDebugLoc;
  GLuint indicesBufID;

public:
  // constructor
  Renderer() : t(0.0), modeVal(1), activeLight(0), sceneVertNo(0), vertexLoc(0), texCoordLoc(0), normalLoc(0),
               projectionLoc(0), modelviewLoc(0), normalMatrixLoc(0), modeLoc(0),
               filename("./teapot.vbo"),
               vertexDebugLoc(0), texCoordDebugLoc(0), colorDebugLoc(0),
               projectionDebugLoc(0), modelviewDebugLoc(0), modeDebugLoc(0),  indicesBufID(0)
  {
     createLights();
  }
  //destructor
  ~Renderer() {
     deleteAll();
  }

public:
  void reload() {
    deleteAll();
    init();
  }

  void init() {
    glEnable(GL_DEPTH_TEST);

    setupShaders();

    // create the Vertex Array Objects (VAO)
    glGenVertexArrays(numVAOs, vaoID);

    // generate the Vertex Buffer Objects (VBO)
    glGenBuffers(numVBOs, bufID);

    // binding the scene VAO
    glBindVertexArray(vaoID[Scene]);

    std::vector <float> data;
    if(!loadVertexData(filename, data)) exit(0);

    sceneVertNo = int(data.size()) / (3+2+3);

    glBindBuffer(GL_ARRAY_BUFFER, bufID[SceneAll]);
    glBufferData(GL_ARRAY_BUFFER, sceneVertNo*sizeof(Vertex),
                 &data[0], GL_STATIC_DRAW);

    int stride = sizeof(Vertex); // stride in bytes
    int offset = 0; // offset in bytes

    // position
    if (vertexLoc != -1) {
      glVertexAttribPointer(vertexLoc, 3, GL_FLOAT, GL_FALSE, stride, (const void*)(intptr_t)offset);
      glEnableVertexAttribArray(vertexLoc);
    }

    // texCoord
    if (texCoordLoc != -1) {
      offset = 3 * sizeof(float);
      glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, stride, (const void*)(intptr_t)offset);
      glEnableVertexAttribArray(texCoordLoc);
    }

    // normal
    if (normalLoc != -1) {
      offset = (3 + 2) * sizeof(float);
      glVertexAttribPointer(normalLoc, 3, GL_FLOAT, GL_FALSE, stride, (const void*)(intptr_t)offset);
      glEnableVertexAttribArray(normalLoc);
    }

    // the following is only to display debug information
    // binding the debug VAO
    glBindVertexArray(vaoID[Debug]);

    // In order to display the light's debug information (position and orientiation),
    // the idea to render a cube [-1.0,1.0][-1.0,1.0][-1.0,1.0].
    // This cube is multiplied for drawing with the inverse "projection*modelview matrix"
    // resulting in a "pyramid" (this is the inverse process of transforming
    // camera to clipping coordinates).

    float debugData[] = {
      -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, // bottom
      -1.0f, -1.0f,  1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
       1.0f, -1.0f,  1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
       1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
      -1.0f,  1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, //top
       1.0f,  1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f,
       1.0f,  1.0f,  1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f,
      -1.0f,  1.0f,  1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f,
       1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, // right
       1.0f, -1.0f,  1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f,
       1.0f,  1.0f,  1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f,
       1.0f,  1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f,
      -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, // left
      -1.0f,  1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f,
      -1.0f,  1.0f,  1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f,
      -1.0f, -1.0f,  1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f,
      -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, // near
       1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f,
       1.0f,  1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f,
      -1.0f,  1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f,
      -1.0f, -1.0f,  1.0f,  0.0f,  0.0f, 1.0f, 1.0f, 1.0f, // far
      -1.0f,  1.0f,  1.0f,  0.0f,  1.0f, 1.0f, 1.0f, 1.0f,
       1.0f,  1.0f,  1.0f,  1.0f,  1.0f, 1.0f, 1.0f, 1.0f,
       1.0f, -1.0f,  1.0f,  1.0f,  0.0f, 1.0f, 1.0f, 1.0f
    };

    glBindBuffer(GL_ARRAY_BUFFER, bufID[DebugAll]);
    glBufferData(GL_ARRAY_BUFFER, 24*sizeof(Vertex),
                 &debugData[0], GL_STATIC_DRAW);

    // position
    offset = 0;
    glVertexAttribPointer(vertexDebugLoc, 3, GL_FLOAT, GL_FALSE, stride, (const void*)(intptr_t)offset);
    glEnableVertexAttribArray(vertexDebugLoc);

    // texCoord
    offset = 3*sizeof(float);
    glVertexAttribPointer(texCoordDebugLoc, 2, GL_FLOAT, GL_FALSE, stride, (const void*)(intptr_t)offset);
    glEnableVertexAttribArray(texCoordDebugLoc);

    // color
    offset = (3+2)*sizeof(float);
    glVertexAttribPointer(colorDebugLoc, 3, GL_FLOAT, GL_FALSE, stride, (const void*)(intptr_t)offset);
    glEnableVertexAttribArray(colorDebugLoc);

    std::vector<int> indicesData;
    for(int i=0; i < 6; i++) {
      indicesData.push_back(0 + 4*i); 
      indicesData.push_back(1 + 4*i);
      indicesData.push_back(2 + 4*i);
      indicesData.push_back(0 + 4*i);
      indicesData.push_back(2 + 4*i);
      indicesData.push_back(3 + 4*i);
    }

    // generating index VBO
    glGenBuffers(1, &indicesBufID);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesBufID);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesData.size()*sizeof(int), &indicesData[0], GL_STATIC_DRAW);

    glBindVertexArray(0);

  }


  void resize(int w, int h) {
    glViewport(0, 0, w, h);

    // this function replaces gluPerspective
    mat4Perspective(projection, 30.0f, (float)w/(float)h, 0.5f, 40.0f);
    // mat4Print(projection);
  }

  void display() {
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // camera orbits in the z=2 plane
    // and looks at the origin
    // mat4LookAt replaces gluLookAt
    double rad = M_PI / 180.0f * t;
    mat4LookAt(modelview,
               2.0f*float(cos(rad)), 2.0f*float(sin(rad)), 2.0f, // eye
               0.0f, 0.0f, 0.0f, // look at
               0.0f, 0.0f, 1.0f); // up

    float modelviewInv[16], normalmatrix[16];
    mat4Invert(modelview, modelviewInv);
    mat4Transpose(modelviewInv, normalmatrix);
    updateLights();

    glUseProgram(shaders[0].progID);
    // load the current projection and modelview matrix into the
    // corresponding UNIFORM variables of the shader
    glUniformMatrix4fv(projectionLoc, 1, false, projection);
    glUniformMatrix4fv(modelviewLoc, 1, false, modelview);
    if(normalMatrixLoc != -1) glUniformMatrix4fv(normalMatrixLoc, 1, false, normalmatrix);
    if(modeLoc != -1) glUniform1i(modeLoc, modeVal);

    for(unsigned i=0; i < lightsLoc.size(); i++) {
      glUniform3fv(lightsLoc[i].position, 1, lights[i].position);
      glUniform3fv(lightsLoc[i].color, 1, lights[i].color);
      glUniform1f(lightsLoc[i].constantAttenuation , lights[i].constantAttenuation);
      glUniform1f(lightsLoc[i].linearAttenuation, lights[i].linearAttenuation);
      glUniform1f(lightsLoc[i].quadraticAttenuation, lights[i].quadraticAttenuation);
      glUniform1f(lightsLoc[i].spotCutoff, lights[i].spotCutoff);
      glUniform1f(lightsLoc[i].spotExponent , lights[i].spotExponent );
      glUniform3fv(lightsLoc[i].spotDirection, 1, lights[i].spotDirection);
    }

    // bind scene VAO
    glBindVertexArray(vaoID[Scene]);
    // render data
    glDrawArrays(GL_TRIANGLES, 0, sceneVertNo);

    drawLightsDebug();
  }

private:

  void updateLights() {
    for(unsigned i = 0; i < 3; i++) {
      float null[] = {0.0f,0.0f,0.0f};
      float nullTrans[3], res[3];
      mat4MultVec3(modelview, null, nullTrans);
      res[0] = nullTrans[0] - lights[i].position[0];
      res[1] = nullTrans[1] - lights[i].position[1];
      res[2] = nullTrans[2] - lights[i].position[2];
      vec3Normalize(res);
      lights[i].spotDirection[0] = res[0];
      lights[i].spotDirection[1] = res[1];
      lights[i].spotDirection[2] = res[2];
      mat4LookAt(lights[i].lookAt,
                 lights[i].position[0], lights[i].position[1], lights[i].position[2], // eye
                 nullTrans[0],nullTrans[1],nullTrans[2], // look at
                 0.0f, 1.0f, 0.0f); // up
      mat4Perspective(lights[i].proj, 30.0f, 9.0f/9.0f, 0.001f, 0.3f);
      mat4Multiply(lights[i].proj, lights[i].lookAt, lights[i].projLookAt);
    }
  }


  void createLights() {
    lights.resize(3);

    for(unsigned i = 0; i < lights.size(); i++) {
      lights[i].position[0] = 0.0f;
      lights[i].position[1] = 0.0f;
      lights[i].position[2] = -1.0f;
      lights[i].color[0] = 1.0f;
      lights[i].color[1] = 1.0f;
      lights[i].color[2] = 1.0f;
      lights[i].constantAttenuation = 1.0f;
      lights[i].linearAttenuation = 0.0f;
      lights[i].quadraticAttenuation = 0.0f;
      lights[i].spotCutoff = 0.0f;
      lights[i].spotExponent = 60.0f;
    }

    lights[0].position[0] = 0.8f;
    lights[0].position[1] = -0.2f;
    lights[0].position[2] = -2.1f;

    lights[1].position[0] = -1.1f;
    lights[1].position[1] = -0.4f;
    lights[1].position[2] = -3.0f;

    lights[2].position[0] = -0.3f;
    lights[2].position[1] = -0.6f;
    lights[2].position[2] = -2.2f;
  }

  void drawLightsDebug() {

    // In order to display the light's debug information (position and orientiation),
    // the idea to render a cube [-1.0,1.0][-1.0,1.0][-1.0,1.0].
    // This cube is multiplied for drawing with the inverse "projection*modelview matrix"
    // resulting in a "pyramid" (this is the inverse process of transforming
    // camera to clipping coordinates).

    // bind debug VAO
    glBindVertexArray(vaoID[Debug]);

    glUseProgram(shaders[1].progID);
    glUniformMatrix4fv(projectionDebugLoc, 1, false, projection);

    for(unsigned i = 0; i < lights.size(); i++) {
      float lightInv[16];
      mat4Invert(lights[i].projLookAt, lightInv);
      glUniformMatrix4fv(modelviewDebugLoc, 1, false, lightInv);
      if(modeDebugLoc != -1) glUniform1i(modeDebugLoc, modeVal);

      // render data
      glDrawElements(GL_TRIANGLES, 6*6, GL_UNSIGNED_INT, NULL);
    }

    glUseProgram(0);
  }



  void setupShaders() {

    // blinn phong shader
    ShaderID s = createShader(string("./BlinnPhong.vert"),string("./BlinnPhong.frag"));

    // retrieve the location of the IN variables of the vertex shaders
    vertexLoc = glGetAttribLocation(s.progID,"inputPosition");
    texCoordLoc = glGetAttribLocation(s.progID,"inputTexCoord");
    normalLoc = glGetAttribLocation(s.progID, "inputNormal");

    // retrieve the location of the UNIFORM variables of the shaders
    projectionLoc = glGetUniformLocation(s.progID, "projection");
    modelviewLoc = glGetUniformLocation(s.progID, "modelview");
    normalMatrixLoc = glGetUniformLocation(s.progID, "normalMat");
    modeLoc = glGetUniformLocation(s.progID, "mode");

    lightsLoc.resize(lights.size());

    for(unsigned i=0; i < lightsLoc.size(); i++) {
      stringstream ss;
      ss << i;
      string b = ss.str();
      string a("lights[");
      lightsLoc[i].position = glGetUniformLocation(s.progID, (a+b+string("].position")).c_str() );
      lightsLoc[i].color = glGetUniformLocation(s.progID, (a+b+string("].color")).c_str());
      lightsLoc[i].constantAttenuation = glGetUniformLocation(s.progID, (a+b+string("].constantAttenuation")).c_str());
      lightsLoc[i].linearAttenuation = glGetUniformLocation(s.progID, (a+b+string("].linearAttenuation")).c_str());
      lightsLoc[i].quadraticAttenuation = glGetUniformLocation(s.progID, (a+b+string("].quadraticAttenuation")).c_str());
      lightsLoc[i].spotCutoff = glGetUniformLocation(s.progID, (a+b+string("].spotCutoff")).c_str());
      lightsLoc[i].spotExponent = glGetUniformLocation(s.progID, (a+b+string("].spotExponent")).c_str());
      lightsLoc[i].spotDirection = glGetUniformLocation(s.progID, (a+b+string("].spotDirection")).c_str());
    }
    shaders.push_back(s);

    // debug information shader
    ShaderID d = createShader(string("./debug.vert"),string("./debug.frag"));

    // retrieve the location of the IN variables of the vertex shaders
    vertexDebugLoc = glGetAttribLocation(d.progID,"inputPosition");
    texCoordDebugLoc = glGetAttribLocation(d.progID,"inputTexCoord");
    colorDebugLoc = glGetAttribLocation(d.progID, "inputColor");

    // retrieve the location of the UNIFORM variables of the shaders
    projectionDebugLoc = glGetUniformLocation(d.progID, "projection");
    modelviewDebugLoc = glGetUniformLocation(d.progID, "modelview");
    modeDebugLoc = glGetUniformLocation(d.progID, "mode");

    shaders.push_back(d);
  }

  ShaderID createShader(string vertSrc, string fragSrc) {

    ShaderID s;

    // create shader
    s.vertID = glCreateShader(GL_VERTEX_SHADER);
    s.fragID = glCreateShader(GL_FRAGMENT_SHADER);

    // load shader source from file
    GLint vlen, flen;
    const char* vs = loadShaderSrc(vertSrc.c_str(), vlen);
    const char* fs = loadShaderSrc(fragSrc.c_str(), flen);

    // specify shader source
    glShaderSource(s.vertID, 1, &vs, &vlen);
    glShaderSource(s.fragID, 1, &fs, &flen);

    free((char*)vs);
    free((char*)fs);

    // compile the shader
    glCompileShader(s.vertID);
    glCompileShader(s.fragID);

    // check for errors
    printShaderInfoLog(s.vertID);
    printShaderInfoLog(s.fragID);

    // create program and attach shaders
    s.progID = glCreateProgram();
    glAttachShader(s.progID, s.vertID);
    glAttachShader(s.progID, s.fragID);

    // "outColor" is a user-provided OUT variable
    // of the fragment shader.
    // Its output is bound to the first color buffer
    // in the framebuffer
    glBindFragDataLocation(s.progID, 0, "outputColor");

    // link the program
    glLinkProgram(s.progID);
    // output error messages
    printProgramInfoLog(s.progID);

    return s;
  }


  void deleteAll() {
    glDeleteVertexArrays(numVAOs, vaoID);
    glDeleteBuffers(numVBOs, bufID);
    if(indicesBufID !=0) glDeleteBuffers( 1, &indicesBufID);
    for(unsigned i=0; i < shaders.size(); i++) {
      glDeleteProgram(shaders[i].progID);
      glDeleteShader(shaders[i].vertID);
      glDeleteShader(shaders[i].fragID);
    }
  }

  void printShaderInfoLog(GLuint obj) {
    int infoLogLength = 0;
    int returnLength  = 0;
    char *infoLog;
    glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infoLogLength);
    if (infoLogLength > 0) {
      infoLog = (char *)malloc(infoLogLength);
      glGetShaderInfoLog(obj, infoLogLength, &returnLength, infoLog);
      printf("%s\n",infoLog);
      free(infoLog);
    }
  }

  void printProgramInfoLog(GLuint obj) {
    int infoLogLength = 0;
    int returnLength  = 0;
    char *infoLog;
    glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infoLogLength);
    if (infoLogLength > 0) {
      infoLog = (char *)malloc(infoLogLength);
      glGetProgramInfoLog(obj, infoLogLength, &returnLength, infoLog);
      printf("%s\n",infoLog);
      free(infoLog);
    }
  }

  // Loads text file into char* fname.
  // Caller needs to free the allocated memory
  const char* loadShaderSrc(const char *fname, GLint &fSize) {
    ifstream::pos_type size;
    char * memblock;
    string text;

    ifstream file (fname, ios::in|ios::binary|ios::ate);
    if (file.is_open()) {
      size = file.tellg();
      fSize = (GLuint) size;
      memblock = new char [size];
      file.seekg (0, ios::beg);
      file.read (memblock, size);
      file.close();
      cout << "file " << fname << " loaded" << endl;
      text.assign(memblock);
    } else {
      cout << "Unable to open file " << fname << endl;
      exit(1);
    }
    return memblock;
  }

  // the following functions are some matrix and vector helpers,
  // which work for this demo but in general it is recommended
  // to use more advanced matrix libraries,
  // e.g. OpenGL Mathematics (GLM)
  float vec3Dot( float *a, float *b) {
    return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
  }
  
  void vec3Add( float *a, float *b, float *res) {
     res[0] = a[0]+b[0]; res[1] = a[1]+b[1]; res[2] = a[2]+b[2];
  }

  void vec3Cross( float *a, float *b, float *res) {
    res[0] = a[1] * b[2]  -  b[1] * a[2];
    res[1] = a[2] * b[0]  -  b[2] * a[0];
    res[2] = a[0] * b[1]  -  b[0] * a[1];
  }

  void vec3Normalize(float *a) {
    float mag = sqrt(a[0] * a[0]  +  a[1] * a[1]  +  a[2] * a[2]);
    a[0] /= mag; a[1] /= mag; a[2] /= mag;
  }

  void mat4Identity( float *a) {
    for (int i = 0; i < 16; ++i) a[i] = 0.0f;
    for (int i = 0; i < 4; ++i) a[i + i * 4] = 1.0f;
  }

  void mat4Scale(float *a, float sx, float sy, float sz) {
    for (int i = 0; i < 16; ++i) a[i] = 0.0f;
    a[0 + 0 * 4] = sx;
    a[1 + 1 * 4] = sy;
    a[2 + 2 * 4] = sz;
    a[3 + 3 * 4] = 1.0f;
  }


  void mat4Multiply(float *a, float *b, float *res) {
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        res[j*4 + i] = 0.0f;
        for (int k = 0; k < 4; ++k) {
          res[j*4 + i] += a[k*4 + i] * b[j*4 + k];
        }
      }
    }
  }

  void mat4MultVec3(float *mat, float *vec, float *res) {
    for (int i = 0; i < 3; ++i) {
      res[i] = 0.0f;
      for (int k = 0; k < 3; ++k) {
        res[i] += mat[i + k*4] * vec[k];
      }
      res[i] += mat[i + 3*4] * 1.0f;
    }
  }

  void mat4Perspective(float *a, float fov, float aspect, float zNear, float zFar) {
    float f = 1.0f / float(tan (fov/2.0f * (M_PI / 180.0f)));
    mat4Identity(a);
    a[0] = f / aspect;
    a[1 * 4 + 1] = f;
    a[2 * 4 + 2] = (zFar + zNear)  / (zNear - zFar);
    a[3 * 4 + 2] = (2.0f * zFar * zNear) / (zNear - zFar);
    a[2 * 4 + 3] = -1.0f;
    a[3 * 4 + 3] = 0.0f;
  }

  void mat4LookAt(float *viewMatrix,
                  float eyeX, float eyeY, float eyeZ,
                  float centerX, float centerY, float centerZ,
                  float upX, float upY, float upZ) {

    float dir[3], right[3], up[3], eye[3];
    up[0]=upX; up[1]=upY; up[2]=upZ;
    eye[0]=eyeX; eye[1]=eyeY; eye[2]=eyeZ;

    dir[0]=centerX-eyeX; dir[1]=centerY-eyeY; dir[2]=centerZ-eyeZ;
    vec3Normalize(dir);
    vec3Cross(dir,up,right);
    vec3Normalize(right);
    vec3Cross(right,dir,up);
    vec3Normalize(up);
    // first row
    viewMatrix[0]  = right[0];
    viewMatrix[4]  = right[1];
    viewMatrix[8]  = right[2];
    viewMatrix[12] = -vec3Dot(right, eye);
    // second row
    viewMatrix[1]  = up[0];
    viewMatrix[5]  = up[1];
    viewMatrix[9]  = up[2];
    viewMatrix[13] = -vec3Dot(up, eye);
    // third row
    viewMatrix[2]  = -dir[0];
    viewMatrix[6]  = -dir[1];
    viewMatrix[10] = -dir[2];
    viewMatrix[14] =  vec3Dot(dir, eye);
    // forth row
    viewMatrix[3]  = 0.0f;
    viewMatrix[7]  = 0.0f;
    viewMatrix[11] = 0.0f;
    viewMatrix[15] = 1.0f;
  }

  void mat4Print(float* a) {
    // opengl uses column major order
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        cout << a[j * 4 + i] << " ";
      }
      cout << endl;
    }
  }

  void mat4Transpose(float* a, float *transposed) {
    int t = 0;
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        transposed[t++] = a[j * 4 + i];
      }
    }
  }

  bool mat4Invert(float* m, float *inverse) {
    float inv[16];
    inv[0] = m[5]*m[10]*m[15]-m[5]*m[11]*m[14]-m[9]*m[6]*m[15]+
             m[9]*m[7]*m[14]+m[13]*m[6]*m[11]-m[13]*m[7]*m[10];
    inv[4] = -m[4]*m[10]*m[15]+m[4]*m[11]*m[14]+m[8]*m[6]*m[15]-
             m[8]*m[7]*m[14]-m[12]*m[6]*m[11]+m[12]*m[7]*m[10];
    inv[8] = m[4]*m[9]*m[15]-m[4]*m[11]*m[13]-m[8]*m[5]*m[15]+
             m[8]*m[7]*m[13]+m[12]*m[5]*m[11]-m[12]*m[7]*m[9];
    inv[12]= -m[4]*m[9]*m[14]+m[4]*m[10]*m[13]+m[8]*m[5]*m[14]-
             m[8]*m[6]*m[13]-m[12]*m[5]*m[10]+m[12]*m[6]*m[9];
    inv[1] = -m[1]*m[10]*m[15]+m[1]*m[11]*m[14]+m[9]*m[2]*m[15]-
             m[9]*m[3]*m[14]-m[13]*m[2]*m[11]+m[13]*m[3]*m[10];
    inv[5] = m[0]*m[10]*m[15]-m[0]*m[11]*m[14]-m[8]*m[2]*m[15]+
             m[8]*m[3]*m[14]+m[12]*m[2]*m[11]-m[12]*m[3]*m[10];
    inv[9] = -m[0]*m[9]*m[15]+m[0]*m[11]*m[13]+m[8]*m[1]*m[15]-
             m[8]*m[3]*m[13]-m[12]*m[1]*m[11]+m[12]*m[3]*m[9];
    inv[13]= m[0]*m[9]*m[14]-m[0]*m[10]*m[13]-m[8]*m[1]*m[14]+
             m[8]*m[2]*m[13]+m[12]*m[1]*m[10]-m[12]*m[2]*m[9];
    inv[2] = m[1]*m[6]*m[15]-m[1]*m[7]*m[14]-m[5]*m[2]*m[15]+
             m[5]*m[3]*m[14]+m[13]*m[2]*m[7]-m[13]*m[3]*m[6];
    inv[6] = -m[0]*m[6]*m[15]+m[0]*m[7]*m[14]+m[4]*m[2]*m[15]-
             m[4]*m[3]*m[14]-m[12]*m[2]*m[7]+m[12]*m[3]*m[6];
    inv[10]= m[0]*m[5]*m[15]-m[0]*m[7]*m[13]-m[4]*m[1]*m[15]+
             m[4]*m[3]*m[13]+m[12]*m[1]*m[7]-m[12]*m[3]*m[5];
    inv[14]= -m[0]*m[5]*m[14]+m[0]*m[6]*m[13]+m[4]*m[1]*m[14]-
             m[4]*m[2]*m[13]-m[12]*m[1]*m[6]+m[12]*m[2]*m[5];
    inv[3] = -m[1]*m[6]*m[11]+m[1]*m[7]*m[10]+m[5]*m[2]*m[11]-
             m[5]*m[3]*m[10]-m[9]*m[2]*m[7]+m[9]*m[3]*m[6];
    inv[7] = m[0]*m[6]*m[11]-m[0]*m[7]*m[10]-m[4]*m[2]*m[11]+
             m[4]*m[3]*m[10]+m[8]*m[2]*m[7]-m[8]*m[3]*m[6];
    inv[11]= -m[0]*m[5]*m[11]+m[0]*m[7]*m[9]+m[4]*m[1]*m[11]-
             m[4]*m[3]*m[9]-m[8]*m[1]*m[7]+m[8]*m[3]*m[5];
    inv[15]= m[0]*m[5]*m[10]-m[0]*m[6]*m[9]-m[4]*m[1]*m[10]+
             m[4]*m[2]*m[9]+m[8]*m[1]*m[6]-m[8]*m[2]*m[5];

    float det = m[0]*inv[0]+m[1]*inv[4]+m[2]*inv[8]+m[3]*inv[12];
    if (det == 0) return false;
    det = 1.0f / det;
    for (int i = 0; i < 16; i++) inverse[i] = inv[i] * det;
    return true;
  }

  bool loadVertexData(std::string &filename, std::vector<float> &data) {
    // read vertex data from file
    ifstream input(filename.c_str());
    if(!input) {
      cerr << "Can not find vertex data file " << filename << endl;
      return false;
    } else {
      int numFloats;
      float vertData;
      if(input >> numFloats) {
        if(numFloats > 0) {
          data.resize(numFloats);
          int i = 0;
          while(input >> vertData && i < numFloats) {
            // store it in the vector.
            data[i] = vertData;
            i++;
          }
          if(i != numFloats || numFloats % (3+2+3)) data.resize(0);
        }
      }
      input.close();
     
    }
     return true;
  }
};

//this is a static pointer to a Renderer used in the glut callback functions
static Renderer *renderer;

//glut static callbacks start
static void glutResize(int w, int h) 
{
  renderer->resize(w,h);
}

static void glutDisplay() 
{
  renderer->display();
  glutSwapBuffers();
}

static void timer(int v) 
{
  float offset = 1.0f;
  renderer->t += offset;
  glutDisplay();
  glutTimerFunc(unsigned(20), timer, ++v);
}

static void glutKeyboard(unsigned char key, int x, int y) {
  bool redraw = false;
  std::string modeStr;
  std::stringstream ss;
  int l = renderer->activeLight;
  switch(key) {
    case '1':
      renderer->modeVal = 1;
      redraw = true;
      break;
    case '2':
      renderer->modeVal = 2;
      redraw = true;
      break;
    case '3':
      renderer->modeVal = 3;
      redraw = true;
      break;
    case '4':
      renderer->modeVal = 4;
      redraw = true;
      break;
    case '5':
      renderer->modeVal = 5;
      redraw = true;
      break;
    case '+':
      renderer->activeLight++;
      if(renderer->activeLight >= (int)renderer->lights.size()) renderer->activeLight = 0;
      redraw = true;
      break;
     case '-':
      renderer->activeLight--;
      if(renderer->activeLight < 0) renderer->activeLight = int(renderer->lights.size())-1;
      redraw = true;
      break;
    case 'd':
    case 'D':
      renderer->lights[l].position[0] += 0.1f;
      redraw = true;
      break;
    case 'a':
    case 'A':
      renderer->lights[l].position[0] -= 0.1f;
      redraw = true;
      break;
    case 's':
    case 'S':
      renderer->lights[l].position[1] -= 0.1f;
      redraw = true;
      break;
    case 'w':
    case 'W':
      renderer->lights[l].position[1] += 0.1f;
      redraw = true;
      break;
    case ' ':
      renderer->lights[l].position[2] -= 0.1f;
      redraw = true;
      break;
    case 'c':
    case 'C':
      renderer->lights[l].position[2] += 0.1f;
      redraw = true;
      break;
    case 'm':
    case 'M':
      renderer->lights[l].linearAttenuation += 0.01f;
      redraw = true;
      break;
    case 'n':
    case 'N':
      renderer->lights[l].linearAttenuation -= 0.01f;
      if(renderer->lights[l].linearAttenuation <= 0.0f) renderer->lights[0].linearAttenuation = 0.01f;
      redraw = true;
      break;
    case 'k':
    case 'K':
      renderer->lights[l].spotCutoff += 0.001f;
      if(renderer->lights[l].spotCutoff  > 1.0f) renderer->lights[l].spotCutoff = 1.0f;
      redraw = true;
      break;
    case 'j':
    case 'J':
      renderer->lights[l].spotCutoff -= 0.001f;
      if(renderer->lights[l].spotCutoff  < 0.0f) renderer->lights[l].spotCutoff = 0.0f;
      redraw = true;
      break;
    case 'o':
    case 'O':
      renderer->lights[l].spotExponent += 1.0f;
      redraw = true;
      break;
    case 'i':
    case 'I':
      renderer->lights[l].spotExponent -= 1.0f;
      if(renderer->lights[l].spotExponent < 0.0) renderer->lights[0].spotExponent = 0.0f;
      redraw = true;
      break;
    }
    if(redraw) {
      glutDisplay();
      l = renderer->activeLight;
      ss << "Light " << l << 
        ": lightPos=" << renderer->lights[l].position[0] <<
        "," << renderer->lights[l].position[1] <<
        "," << renderer->lights[l].position[2] <<
        " attenuation=" << renderer->lights[l].linearAttenuation <<
        " cutoff=" << renderer->lights[l].spotCutoff <<
        " exponent=" << renderer->lights[l].spotExponent;
      cout << ss.str() << endl;
      glutSetWindowTitle(ss.str().c_str());
    }
}


int main(int argc, char **argv) 
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
  glutInitWindowPosition(100,100);
  glutInitWindowSize(640, 480);

  glutCreateWindow("Shader Many Modifiable Lights");
  GLenum err = glewInit();
  if (GLEW_OK != err) {
    fprintf(stderr, "Glew error: %s\n", glewGetErrorString(err));
  }
  glutDisplayFunc(glutDisplay);
  //glutIdleFunc(glutDisplay);
  glutReshapeFunc(glutResize);
  glutKeyboardFunc(glutKeyboard);

  renderer = new Renderer;
  renderer->init();

  glutTimerFunc(unsigned(20), timer, 0);

  glutMainLoop();
}