// 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 <string>
#include <sstream>
#include <vector>
using namespace std;

class Renderer {

public:
  int mode;

private:
  double alpha;
  double beta;


public:
  Renderer() : mode(1), alpha(0.0), beta(0.0) {}

public:

  void init() {
    glEnable(GL_DEPTH_TEST);
  }

  void resize(int w, int h) {
    double aspect = float(w)/float(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);
  }

  void display() {
    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    if(mode == 1) { // isometric
      alpha = 45.0;
      beta = 180.0 /  M_PI * atan(1.0/sqrt(2.0));
    }
    if(mode == 2) { // dimetric
      alpha= 180.0 /  M_PI * atan(1.0/sqrt(7.0));
      beta = 180.0 /  M_PI * atan(1.0/(2.0*sqrt(2.0)));
    }
    if(mode == 3) { // trimetric
      alpha= 30.0;
      beta = 35.0;
    }

    if(mode < 4) {
      glRotated( beta, 1.0, 0.0, 0.0);
      glRotated(-alpha, 0.0f, 1.0, 0.0f);
    }

    if(mode == 4) { // isometric
      //alternativ implementation
      gluLookAt(1.0, 1.0, 1.0, // eye
                0.0, 0.0, 0.0, // look at
                0.0, 1.0, 0.0); //up
    }

    if(mode == 5) { // dimetric
      //alternativ implementation
      gluLookAt(1.0, 1.0, sqrt(7.0),
                0.0, 0.0, 0.0,
                0.0, 1.0, 0.0);
    }


    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();
  }

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();
  }

public:
  void getAbsLengthXYZ(double& lx, double& ly, double &lz) {
      double cb =  cos(beta/180.0*M_PI);
      double ca =  cos(alpha/180.0*M_PI);
      double sb =  sin(beta/180.0*M_PI);
      double sa =  sin(alpha/180.0*M_PI);
      lx = sqrt(ca*ca+sb*sb*sa*sa);
      ly = sqrt(cb*cb);
      lz = sqrt(sa*sa+sb*sb*ca*ca);
  }

};

//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;
  switch(key) {
        case '1':
      renderer->mode = 1;
      redraw = true;
      modeStr = string("isometric");
      break;
    case '2':
      renderer->mode = 2;
      redraw = true;
      modeStr = string("dimetric");
      break;
    case '3':
      renderer->mode = 3;
      redraw = true;
      modeStr = string("trimetric");
      break;
    case '4':
      renderer->mode = 4;
      redraw = true;
      modeStr = string("isometric alternative");
      break;
    case '5':
      renderer->mode = 5;
      redraw = true;
      modeStr = string("dimetric alternative");
      break;

    }
    if(redraw) {
      glutDisplay();

      if(renderer->mode < 4) {
        double lx, ly, lz;
        renderer->getAbsLengthXYZ(lx, ly, lz);
		std::stringstream ss;
		ss << "    lx: " <<  lx << " ly: " << ly << " lz: " << lz;
        modeStr += ss.str();
      }
      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 change axonometry class");

  glutDisplayFunc(glutDisplay);
  //glutIdleFunc(glutDisplay);
  glutReshapeFunc(glutResize);
  glutKeyboardFunc(glutKeyboard);

  renderer = new Renderer;
  renderer->init();

  glutMainLoop();
}