// 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 <QMessageBox>

#include <math.h>
#ifndef M_PI
#define M_PI 3.1415926535897932385f
#endif

#include <sstream>
#include <fstream>
#include <vector>
using namespace std;

class Renderer : protected QOpenGLFunctions_2_0 {

public:
  Renderer() : rot1(0.0),  rot2(0.0),  rot3(0.0) {}

public:
  void display() {
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // view scene from the side
    glTranslatef(0.0f,0.0f,-3.0f);
    glRotatef( -45.0f, 0.0f, 0.0f, 1.0f);
    glRotatef( -45.0f, 0.0f, 1.0f, 0.0f);
    glRotatef( 135.0f, 1.0f, 0.0f, 0.0f);

    // rotate around z
    glRotatef(rot1, 0.0f, 0.0f, 1.0f);
    glColor3f(0.0f, 0.0f, 1.0f);
    drawCoordinateAxisZ();

    // rotate around local y
    glRotatef(rot2, 0.0f, 1.0f, 0.0f);
    glColor3f(0.0f, 1.0f, 0.0f);
    drawCoordinateAxisY();

    // rotate around local x
    glRotatef(rot3, 1.0f, 0.0f, 0.0f);
    glColor3f(1.0f, 0.0f, 0.0f);
    drawCoordinateAxisX();

    // draw the plane in the local coordinate system
    drawToyPlane();
  }

  void init() {
    initializeOpenGLFunctions();
    // read vertex data from file
    ifstream input(findFile("ToyPlaneData.txt", "qt", 5));
    if(!input) {
      QMessageBox msgBox;
      msgBox.setText("Can not find vertex data file \"ToyPlaneData.txt\"");
      msgBox.exec();
    } else {
      int vertSize;
      double vertData;
      if(input >> vertSize) {
        if(vertSize > 0) {
          toyPlaneData.resize(vertSize);
          int i = 0;
          while(input >> vertData && i < vertSize) {
            // store it in the vector.
            toyPlaneData[i] = vertData;
            i++;
          }
          if(i != vertSize || vertSize % 3) toyPlaneData.resize(0);
        }
      }
      input.close();
    }

    glEnable(GL_DEPTH_TEST);
  }
  void resize(int w, int h) {
    // ignore this for now
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspectiveR (45.0, (float)w/(float)h, 0.1, 10.0);
  }
  void dispose() {}

public:
  float rot1;
  float rot2;
  float rot3;
  std::vector <double> toyPlaneData;

private:
  void drawCoordinateAxisZ() {
    glBegin(GL_LINE_LOOP); // circle in x-y plane
    for(int a=0; a<360; a+=10) {
      float angle = M_PI / 180.0f * a;
      glVertex3f(cos(angle), sin(angle), 0.0);
    }
    glEnd();

    glBegin(GL_LINES);
    glVertex3f(0.9f, 0.0f, 0.0f); // x-axis
    glVertex3f(1.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 0.9f, 0.0f); // y-axis
    glVertex3f(0.0f, 1.0f, 0.0f);
    glVertex3f(0.0f, 0.0f,-1.0f); // z-axis
    glVertex3f(0.0f, 0.0f, 1.0f);
    glEnd();

    glBegin(GL_TRIANGLES); // z-axis tip
    glVertex3f(0.0f,-0.1f, 0.9f);
    glVertex3f(0.0f, 0.0f, 1.0f);
    glVertex3f(0.0f, 0.1f, 0.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 drawToyPlane() {
    glColor3f(0.5f, 0.5f, 0.5f);
    glBegin(GL_TRIANGLES);
    for(unsigned i=0; i < toyPlaneData.size(); i+=3) {
      glVertex3d(toyPlaneData[i],toyPlaneData[i+1], toyPlaneData[i+2]);
    }
    glEnd();
  }

  // implemention of gluPerspective to avoid GLU header
  void gluPerspectiveR(float fovyInDegrees, float aspectRatio, float znear, float zfar) {
    float ymax = znear * tanf(fovyInDegrees * M_PI / 360.0);
    float xmax = ymax * aspectRatio;
    glFrustum(-xmax, xmax, -ymax, ymax, znear, zfar);
  }

  bool fileExists(const std::string& filename)
  {
    ifstream myfile(filename.c_str());
    if (!myfile.is_open()) {
      return false;
    }
    myfile.close();
    return true;
  }

  std::string findFile(const std::string& filename, const std::string& subdir, int depth)
  {
    int counter = 0;
    std::string path("");

    while (counter < depth) {
      if (fileExists(path + filename)) return path + filename;
      if (fileExists(path + "/" + subdir + "/" + filename)) return path + "/" + subdir + "/" + filename;
      path += "../";
      counter++;
    }
    return filename;
  }
};

class MyWidget : public QOpenGLWidget {

private:
  Renderer *renderer;

public:
  MyWidget(QWidget *parent = NULL) : QOpenGLWidget(parent) {
    this->setWindowTitle("Use 1, 2, and 3 keys to rotate (ALT+key rotates in other direction)");
    this->resize(320, 320);
    renderer = new Renderer();
  }

  ~MyWidget() {
    makeCurrent();
    renderer->dispose();
    doneCurrent();
    delete renderer;
  }

protected:
  void initializeGL() { renderer->init(); }
  void resizeGL(int w, int h){ renderer->resize(w, h); }
  void paintGL() { renderer->display(); }
  void keyPressEvent(QKeyEvent* event){
    bool redraw = false;
    float offset = 2.5f;
    if(event->modifiers() & Qt::AltModifier) offset = -offset;

    switch(event->key()) {
    case '1':
      renderer->rot1 += offset;
      redraw = true;
      break;
    case '2':
      renderer->rot2 += offset;
      redraw = true;
      break;
    case '3':
      renderer->rot3 += offset;
      redraw = true;
      break;
    case '0':
      renderer->rot1 = 0.0f;
      renderer->rot2 = 0.0f;
      renderer->rot3 = 0.0f;
      redraw = true;
      break;
    }
    if(redraw) {
      this->setWindowTitle(QString("Yaw %1, Pitch %2, Roll %3").arg(renderer->rot1).arg(renderer->rot2).arg(renderer->rot3));
      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
}
