// 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_2_0>
#include <QKeyEvent>
#include <QTimer>
#include <QMessageBox>

#include <iostream>
using namespace std;

#define _USE_MATH_DEFINES
#include <math.h>

#include <gl/glu.h>

class Renderer : protected QOpenGLFunctions_2_0 {

public:
  float t;
  int mode;
  unsigned ayimutSegs;
  unsigned polarSegs;
  int frameCount;
  int currentTime;
  int previousTime;
private:
  GLuint vertBufID, indicesBufID;
  int vertSize;

public:
  // constructor
  Renderer() : t(0.0), mode(1), ayimutSegs(8), polarSegs(8),
               vertBufID(0), indicesBufID(0), vertSize(0)  {}

public:
  void init() {
    initializeOpenGLFunctions();
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_DEPTH);
    recreateVBOs();
  }

  void recreateVBOs() {

    if(vertBufID !=0) glDeleteBuffers( 1, &vertBufID);
    if(indicesBufID !=0) glDeleteBuffers( 1, &indicesBufID);

    // generating VBO input data
    std::vector<float> vertexData;
    std::vector<int> indicesData;
    int totalSegs = ayimutSegs * polarSegs;
    float ayimutStep = 2.0f * M_PI / float(ayimutSegs);
    float polarStep = M_PI / float(polarSegs);
    float r = 1.0;
    vertSize = 0;
    for(unsigned m=0; m < ayimutSegs; m++) {
      for(unsigned n=0; n < polarSegs; n++) {
        float phi = ayimutStep*m;
        float theta = polarStep * n;

        // random radius
        int rval = rand();
        r = 1.0f - 0.3f * (fabs(float(rval))/float(RAND_MAX));

        // create xyz from spherical coordinates
        float x = r * sin(theta) * cos(phi);
        float y = r * sin(theta) * sin(phi);
        float z = r * cos(theta);

        vertexData.push_back(x);
        vertexData.push_back(y);
        vertexData.push_back(z);

        // create vertex indices
        indicesData.push_back(vertSize % totalSegs);
        indicesData.push_back((vertSize+ayimutSegs) % totalSegs);
        indicesData.push_back((vertSize+ayimutSegs + 1) % totalSegs);
        indicesData.push_back((vertSize+1) % totalSegs);

        vertSize++;
      }
    }

    // generating vertex VBO
    glGenBuffers(1, &vertBufID);
    glBindBuffer(GL_ARRAY_BUFFER, vertBufID);
    glBufferData(GL_ARRAY_BUFFER, vertexData.size()*sizeof(float), &vertexData[0], GL_STATIC_DRAW);

    // 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);
  }

  void updateRandomVBOVertex() {
    float ayimutStep = 2.0f * float(M_PI) / float(ayimutSegs);
    float polarStep = float(M_PI) / float(polarSegs);
    float r = 1.0;

    // randomly select point
    int mval = rand();
    int m = int( (ayimutSegs-1) *  (fabs(float(mval))/float(RAND_MAX)));
    int nval = rand();
    int n = int( (polarSegs-1) *  (fabs(float(nval))/float(RAND_MAX)));

    float phi = ayimutStep*m;
    float theta = polarStep * n;

    // random radius
    int rval = rand();
    r = 1.0f - 0.3f * (fabs(float(rval))/float(RAND_MAX));

    // create xyz from spherical coordinates
    float x = r * sin(theta) * cos(phi);
    float y = r * sin(theta) * sin(phi);
    float z = r * cos(theta);

    int vertPos = (m*polarSegs + n) * 3;

    float newVertexData[3];
    newVertexData[0] = x;
    newVertexData[1] = y;
    newVertexData[2] = z;

    // update vertex in VBO
    glBindBuffer(GL_ARRAY_BUFFER, vertBufID);
    glBufferSubData(GL_ARRAY_BUFFER, vertPos*sizeof(float), 3*sizeof(float), newVertexData);

  }


  void resize(int w, int h) {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective (30.0, (float)w/(float)h, 0.1, 50.0);
  }

  void display() {
    // update up to 100 random vertices
    for(unsigned i = 0; i < 100 && i  < (ayimutSegs/2); i++) {
      updateRandomVBOVertex();
    }
    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.5, -1.0, 3.5, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
    // draw scene
    glRotatef(t, 0.0f, 0.0f, 1.0f);

    // activating VBO
    glBindBuffer(GL_ARRAY_BUFFER, vertBufID);
    int stride = 0; // if stride is 0, the vertices are understood to be tightly packed
    glVertexPointer(3, GL_FLOAT, stride, NULL);
    glEnableClientState(GL_VERTEX_ARRAY);

    if(mode == 0) {
      glColor3f(1.0f,1.0f,1.0f);
    }else{
      glColorPointer(3, GL_FLOAT, stride, NULL);
      glEnableClientState(GL_COLOR_ARRAY);
    }

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesBufID);
    glDrawElements(GL_QUADS, vertSize*4, GL_UNSIGNED_INT, NULL);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);

  }

  void dispose() {
    if(vertBufID !=0) glDeleteBuffers( 1, &vertBufID);
    if(indicesBufID !=0) glDeleteBuffers( 1, &indicesBufID);
  }
};


class MyWidget : public QOpenGLWidget {

private:
  Renderer *renderer;
  QTimer *timer;

public:
  MyWidget(QWidget *parent = NULL) : QOpenGLWidget(parent) {
    this->setWindowTitle("Press 1 to change color, 2 to increase vertex count");
    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();
  }
  void keyPressEvent(QKeyEvent* event){
    bool redraw = false;
    switch(event->key()) {
    case '1':
      if(renderer->mode == 1) renderer->mode = 0;
      else renderer->mode = 1;
      redraw = true;
      break;
    case '2':
      if(renderer->ayimutSegs < 512) {
        renderer->ayimutSegs *= 4;
        renderer->polarSegs *= 4;
      }else{
        renderer->ayimutSegs = 8;
        renderer->polarSegs = 8;
      }
      makeCurrent();
      renderer->recreateVBOs();
      doneCurrent();
      redraw = true;
      break;
    }

    if(redraw) {
      this->update();
    }
  }
};

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
}
