// This code example is created for educational purpose
// by Thorsten Thormaehlen (contact: www.thormae.de).
// It is distributed without any warranty.

#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:
  int mode;
  double alpha;

public:
  Renderer() : mode(1), alpha(-45.0) {}

public:
  void display() {
    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    float m[16]; // identity
    glGetFloatv(GL_MODELVIEW_MATRIX, m);
    float angle = (M_PI / 180.0f) * float(alpha);
    if(mode == 1) { // cavalier
      m[2*4+0] = -cos(angle);
      m[2*4+1] = sin(angle);
    }
    if(mode == 2) { // cabinet
      m[2*4+0] = -cos(angle)/2.0f;
      m[2*4+1] = sin(angle)/2.0f;
    }
    glMultMatrixf(m);

    glColor3f(0.0f, 0.0f, 1.0f);
    drawCoordinateAxisZ();
    glColor3f(0.0f, 1.0f, 0.0f);
    drawCoordinateAxisY();
    glColor3f(1.0f, 0.0f, 0.0f);
    drawCoordinateAxisX();

    drawUnitCube();
  }

  void init() {
    glEnable(GL_DEPTH_TEST);
  }

  void resize(int w, int h) {
    double aspect = double(w)/double(h);
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-2.0*aspect, 2.0*aspect, -2.0, 2.0, -5.0, 5.0);
  }


private:
  void drawCoordinateAxisZ() {
    glLineWidth(2);
    glBegin(GL_LINES);
    glVertex3f(0.0f, 0.0f, 0.0f); // z-axis
    glVertex3f(0.0f, 0.0f, 2.0f);
    glEnd();
    glLineWidth(1);

    // z-axis tip
    glBegin(GL_TRIANGLES);
    glVertex3f( 0.0f, 0.0f, 2.0f);
    glVertex3f(-0.05f, 0.05f, 1.9f);
    glVertex3f( 0.05f, 0.05f, 1.9f);
    glVertex3f( 0.0f,  0.0f, 2.0f);
    glVertex3f( 0.05f, -0.05f, 1.9f);
    glVertex3f(-0.05f, -0.05f, 1.9f);
    glVertex3f( 0.0f,  0.0f, 2.0f);
    glVertex3f( 0.05f,  0.05f, 1.9f);
    glVertex3f( 0.05f, -0.05f, 1.9f);
    glVertex3f( 0.0f,  0.0f, 2.0f);
    glVertex3f(-0.05f, -0.05f, 1.9f);
    glVertex3f(-0.05f,  0.05f, 1.9f);
    glEnd();
    glBegin(GL_POLYGON);
    glVertex3f( 0.05f, -0.05f, 1.9f);
    glVertex3f( 0.05f,  0.05f, 1.9f);
    glVertex3f(-0.05f,  0.05f, 1.9f);
    glVertex3f(-0.05f, -0.05f, 1.9f);
    glEnd();
  }

  void drawCoordinateAxisX() {
      glPushMatrix();
      glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
      drawCoordinateAxisZ();
      glPopMatrix();
  }

  void drawCoordinateAxisY() {
      glPushMatrix();
      glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
      drawCoordinateAxisZ();
      glPopMatrix();
  }

  void drawUnitCube() {
    glColor3f(0.0f,0.0f,0.0f);
    glBegin(GL_LINE_LOOP);
    glVertex3f(0.0f, 1.0f, 1.0f);
    glVertex3f(0.0f,0.0f, 1.0f);
    glVertex3f( 1.0f,0.0f, 1.0f);
    glVertex3f( 1.0f, 1.0f, 1.0f);
    glEnd();
    glBegin(GL_LINE_LOOP);
    glVertex3f( 1.0f, 1.0f,0.0f);
    glVertex3f( 1.0f,0.0f,0.0f);
    glVertex3f(0.0f,0.0f,0.0f);
    glVertex3f(0.0f, 1.0f,0.0f);
    glEnd();
    glBegin(GL_LINE_LOOP);
    glVertex3f( 1.0f, 1.0f, 1.0f);
    glVertex3f( 1.0f,0.0f, 1.0f);
    glVertex3f( 1.0f,0.0f,0.0f);
    glVertex3f( 1.0f, 1.0f,0.0f);
    glEnd();
    glBegin(GL_LINE_LOOP);
    glVertex3f(0.0f, 1.0f,0.0f);
    glVertex3f(0.0f,0.0f,0.0f);
    glVertex3f(0.0f,0.0f, 1.0f);
    glVertex3f(0.0f, 1.0f, 1.0f);
    glEnd();
    glBegin(GL_LINE_LOOP);
    glVertex3f( 1.0f, 1.0f,0.0f);
    glVertex3f(0.0f, 1.0f,0.0f);
    glVertex3f(0.0f, 1.0f, 1.0f);
    glVertex3f( 1.0f, 1.0f, 1.0f);
    glEnd();
    glBegin(GL_LINE_LOOP);
    glVertex3f( 1.0f, 0.0f, 1.0f);
    glVertex3f(0.0f, 0.0f, 1.0f);
    glVertex3f(0.0f, 0.0f,0.0f);
    glVertex3f( 1.0f, 0.0f,0.0f);
    glEnd();
  }
};

//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 glutKeyboard(unsigned char key, int x, int y) {
  bool redraw = false;
  std::string modeStr;
  std::stringstream ss;
  switch(key) {
    case '1':
      renderer->mode = 1;
      redraw = true;
      modeStr = string("cavalier");
      break;
    case '2':
      renderer->mode = 2;
      redraw = true;
      modeStr = string("cabinet");
      break;
    case '3':
      renderer->alpha += 2.0f;
      if(renderer->alpha > 360.0f) renderer->alpha = 0.0f;
      redraw = true;
	  ss << "alpha: " <<  renderer->alpha;
      modeStr = ss.str();
      break;
    case '0':
      renderer->alpha = -45.0f;
      redraw = true;
	  ss << "alpha: " <<  renderer->alpha;
      modeStr = ss.str();
      break;
    }
    if(redraw) {
      glutDisplay();
	    cout << modeStr << endl;
      glutSetWindowTitle(modeStr.c_str());
    }
}


int main(int argc, char **argv) 
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
  glutInitWindowPosition(100,100);
  glutInitWindowSize(320, 320);

  glutCreateWindow("Use 1, 2, and 3 keys to rotate (ALT+key rotates in other direction)");

  glutDisplayFunc(glutDisplay);
  //glutIdleFunc(glutDisplay);
  glutReshapeFunc(glutResize);
  glutKeyboardFunc(glutKeyboard);

  renderer = new Renderer;
  renderer->init();

  glutMainLoop();
}