// 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 <string>
#include <sstream>
#include <vector>
using namespace std;

class Renderer {

public:
  float t;
  int mode;
  unsigned ayimutSegs;
  unsigned polarSegs;
private:
  GLuint vertBufID, indicesBufID;
  int vertSize;

public:
  // constructor
  Renderer() : t(0.0), mode(1), ayimutSegs(10), polarSegs(10),
               vertBufID(0), indicesBufID(0), vertSize(0)  {}
  //destructor
  ~Renderer() {
    if(vertBufID !=0) glDeleteBuffers( 1, &vertBufID);
    if(indicesBufID !=0) glDeleteBuffers( 1, &indicesBufID);
  }

public:
  void init() {
    glEnable(GL_DEPTH_TEST);
    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 * float(M_PI) / float(ayimutSegs);
    float polarStep = float(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 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() {
    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);

  }
};

//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();
  glutReportErrors();
}

static void timer(int v) 
{
  float offset = 0.25f;
  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;
  switch(key) {
    case '1':
      if(renderer->mode == 1) renderer->mode = 0;
      else renderer->mode = 1;
      redraw = true;
      break;
    case '2':
      if(renderer->ayimutSegs < 1000) {
        renderer->ayimutSegs *= 10;
        renderer->polarSegs *= 10;
      }else{
        renderer->ayimutSegs = 10;
        renderer->polarSegs = 10;
      }
      renderer->recreateVBOs();
      redraw = true;
      break;
  }
  if(redraw) {
    glutDisplay();
  }
}


int main(int argc, char **argv) 
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
  glutInitWindowPosition(100,100);
  glutInitWindowSize(320, 320);

  glutCreateWindow("Press 1 to change color, 2 to increase vertex count");
  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();
}