// This code example is created for educational purpose
// by Thorsten Thormaehlen (contact: www.thormae.de).
// It is distributed without any warranty.

#include <QApplication>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_0>
#include <QKeyEvent>
#include <QTimer>
#include <QMessageBox>

#include <iostream>
using namespace std;
#include <math.h>

#include <GL/glu.h>

class Renderer : protected QOpenGLFunctions_3_0 {

private:
  struct Vertex
  {
    float position[3];
    float color[4];
  };

public:
  float t;
private:
  enum {Pyramid, Cube, numVAOs};
  enum {PyramidAll, CubePos, CubeCol, CubeIndices, numVBOs};
  GLuint vaoID[numVAOs];
  GLuint bufID[numVBOs];
  int pyramidVertNo;
  int cubeIndicesNo;

public:
  // constructor
  Renderer() : t(0.0), pyramidVertNo(0), cubeIndicesNo(0) {}

public:
  void init() {
    initializeOpenGLFunctions();
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_DEPTH);

    // now we create 2 Vertex Array Objects (VAO):
    // one for a pyramid and one for a cube.
    // The pyramid uses one interleaved VBO
    // which is later drawn with "glDrawArrays",
    // while the cube uses 3 separate VBOs
    // which are later drawn with "glDrawElements"

    // create the Vertex Array Objects
    glGenVertexArrays(numVAOs, vaoID);

    // generating Vertex Buffer Objects (VBO)
    glGenBuffers(numVBOs, bufID);

    // specifying the pyramid VAO
    glBindVertexArray(vaoID[Pyramid]);

    float pyramidVertexData[] = {
      0.0f, 0.0f, 2.0f, 1.0f, 0.0f, 0.0f, 0.0f,
      -0.5f,-0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
      0.5f,-0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
      0.0f, 0.0f, 2.0f, 0.0f, 1.0f, 0.0f, 0.0f,
      0.5f,-0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
      0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
      0.0f, 0.0f, 2.0f, 0.0f, 0.0f, 1.0f, 0.0f,
      0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
      -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
      0.0f, 0.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f,
      -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
      -0.5f,-0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
    };
    pyramidVertNo = 12;

    glBindBuffer(GL_ARRAY_BUFFER, bufID[PyramidAll]);
    glBufferData(GL_ARRAY_BUFFER, pyramidVertNo*sizeof(Vertex),
                 pyramidVertexData, GL_STATIC_DRAW);

    int stride = sizeof(Vertex); // stride in bytes
    int offset = 0; // offset in bytes

    // position
    glVertexPointer(3, GL_FLOAT, stride, (const void*)(intptr_t)offset);
    glEnableClientState(GL_VERTEX_ARRAY);

    // color
    offset = 3 * sizeof(float);
    glColorPointer(4, GL_FLOAT, stride, (const void*)(intptr_t)offset);
    glEnableClientState(GL_COLOR_ARRAY);

    // specifying the cube VAO
    glBindVertexArray(vaoID[Cube]);

    float cubePosData[] = {
      -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
    };

    float cubeColorData[] = {
      1.0f, 1.0f, 0.0f,
      1.0f, 0.0f, 0.0f,
      0.0f, 1.0f, 0.0f,
      0.0f, 0.0f, 1.0f,
      1.0f, 1.0f, 0.0f,
      1.0f, 0.0f, 0.0f,
      0.0f, 1.0f, 0.0f,
      0.0f, 0.0f, 1.0f,
    };

    int cubeIndicesData[] =
    { 0, 1, 2, 3,
      0, 4, 5, 1,
      4, 7, 6, 5,
      2, 6, 7, 3,
      1, 5, 6, 2,
      3, 7, 4, 0
    };

    cubeIndicesNo = 24;

    // position
    glBindBuffer(GL_ARRAY_BUFFER, bufID[CubePos]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubePosData), cubePosData, GL_STATIC_DRAW);
    glVertexPointer(3, GL_FLOAT, 0, NULL);
    glEnableClientState(GL_VERTEX_ARRAY);

    // color
    glBindBuffer(GL_ARRAY_BUFFER, bufID[CubeCol]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeColorData), cubeColorData, GL_STATIC_DRAW);
    glColorPointer(3, GL_FLOAT, 0, NULL);
    glEnableClientState(GL_COLOR_ARRAY);

    // indices
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufID[CubeIndices]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cubeIndicesData), cubeIndicesData, GL_STATIC_DRAW);

  }


  void resize(int w, int h) {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective (60.0, (float)w/(float)h, 0.1, 50.0);
  }

  void display() {
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    // set camera
    gluLookAt(3.0, -1.0, 4.5, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0);

    // draw scene
    glRotatef(t, 0.0f, 0.0f, 1.0f);
    // bind cube VAO
    glBindVertexArray(vaoID[Cube]);
    // render data
    glDrawElements(GL_QUADS, cubeIndicesNo, GL_UNSIGNED_INT, NULL);

    glTranslatef(0.0f, 0.0f, 1.0f);
    // bind pyramid VAO
    glBindVertexArray(vaoID[Pyramid]);
    // render data
    glDrawArrays(GL_TRIANGLES, 0, pyramidVertNo);
  }

  void dispose() {
    glDeleteVertexArrays(numVAOs, vaoID);
    glDeleteBuffers(numVBOs, bufID);
  }
};


class MyWidget : public QOpenGLWidget {

private:
  Renderer *renderer;
  QTimer *timer;

public:
  MyWidget(QWidget *parent = NULL) : QOpenGLWidget(parent) {
    this->setWindowTitle("Vertex Array Object (VAO) Demo");
    this->resize(320, 320);
    renderer = new Renderer();
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    timer->start(30);
  }

  ~MyWidget() {
    makeCurrent();
    renderer->dispose();
    doneCurrent();
    delete renderer;
    delete timer;
  }

protected:
  void initializeGL() { renderer->init(); }
  void resizeGL(int w, int h){ renderer->resize(w, h); }
  void paintGL() {
      float offset = 1.0f;
      renderer->t += offset;
      renderer->display();
  }
};

int main (int argc, char* argv[]) {
    // create a QApplication object that handles initialization,
    // finalization, and the main event loop
    QApplication appl(argc, argv);
    MyWidget widget;  // create a widget
    widget.show(); //show the widget and its children
    return appl.exec(); // execute the application
}
