// 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_3_3_Core>
#include <QKeyEvent>
#include <QTimer>
#include <QMessageBox>

#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
#include <math.h>

class Renderer : protected QOpenGLFunctions_3_3_Core {

private:
  struct Vertex
  {
    float position[3];
    float color[4];
  };

private:
  enum {Triangle, numVAOs};
  enum {TriangleAll, numVBOs};
  GLuint vaoID[numVAOs];
  GLuint bufID[numVBOs];
  int triangleVertNo;
  GLuint progID;
  GLuint vertID;
  GLuint fragID;
  GLint vertexLoc;
  GLint colorLoc;

public:
  // constructor
  Renderer() : triangleVertNo(0), progID(0), vertID(0),
               fragID(0), vertexLoc(-1), colorLoc(-1) {}
public:
  void init() {
    initializeOpenGLFunctions();
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_DEPTH);

    setupShaders();

    // create a Vertex Array Objects (VAO)
    glGenVertexArrays(numVAOs, vaoID);

    // generate a Vertex Buffer Object (VBO)
    glGenBuffers(numVBOs, bufID);

    // binding the Triangle VAO
    glBindVertexArray(vaoID[Triangle]);

    float triangleVertexData[] = {
       0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
      -0.5f,-0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
       0.5f,-0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,
    };
    triangleVertNo = 3;

    glBindBuffer(GL_ARRAY_BUFFER, bufID[TriangleAll]);
    glBufferData(GL_ARRAY_BUFFER, triangleVertNo*sizeof(Vertex),
                 triangleVertexData, GL_STATIC_DRAW);

    int stride = sizeof(Vertex); // stride in bytes
    int offset = 0; // offset in bytes

    // position
    if (vertexLoc != -1) {
      glVertexAttribPointer(vertexLoc, 3, GL_FLOAT, GL_FALSE, stride, (const void*)(intptr_t)offset);
      glEnableVertexAttribArray(vertexLoc);
    }

    // color
    if (colorLoc != -1) {
      offset = 3 * sizeof(float);
      glVertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, stride, (const void*)(intptr_t)offset);
      glEnableVertexAttribArray(colorLoc);
    }
  }

  void resize(int w, int h) {
    glViewport(0, 0, w, h);
  }

  void display() {
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glUseProgram(progID);

    // bind Triangle VAO
    glBindVertexArray(vaoID[Triangle]);
    // render data
    glDrawArrays(GL_TRIANGLES, 0, triangleVertNo);
  }

  void dispose() {
    glDeleteVertexArrays(numVAOs, vaoID);
    glDeleteBuffers(numVBOs, bufID);
    glDeleteProgram(progID);
    glDeleteShader(vertID);
    glDeleteShader(fragID);
  }

private:
  void setupShaders() {

    // create shader
    vertID = glCreateShader(GL_VERTEX_SHADER);
    fragID = glCreateShader(GL_FRAGMENT_SHADER);

    // load shader source from file
    std::string vs = loadShaderSrc(findFile("first.vert", "qt", 5));
    const char* vss = vs.c_str();

    std::string fs = loadShaderSrc(findFile("first.frag", "qt", 5));
    const char* fss = fs.c_str();

    // specify shader source
    glShaderSource(vertID, 1, &(vss), NULL);
    glShaderSource(fragID, 1, &(fss), NULL);

    // compile the shader
    glCompileShader(vertID);
    glCompileShader(fragID);

    // check for errors
    printShaderInfoLog(vertID);
    printShaderInfoLog(fragID);

    // create program and attach shaders
    progID = glCreateProgram();
    glAttachShader(progID, vertID);
    glAttachShader(progID, fragID);

    // "outColor" is a user-provided OUT variable
    // of the fragment shader.
    // Its output is bound to the first color buffer
    // in the framebuffer
    glBindFragDataLocation(progID, 0, "outputColor");

    // link the program
    glLinkProgram(progID);
    // output error messages
    printProgramInfoLog(progID);

    // "inputPosition" and "inputColor" are user-provided
    // IN variables of the vertex shader.
    // Their locations are stored to be used later with
    // glEnableVertexAttribArray()
    vertexLoc = glGetAttribLocation(progID,"inputPosition");
    colorLoc = glGetAttribLocation(progID, "inputColor");

  }

  void printShaderInfoLog(GLuint obj) {
    int infoLogLength = 0;
    int returnLength  = 0;
    char *infoLog;
    glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infoLogLength);
    if (infoLogLength > 0) {
      infoLog = (char *)malloc(infoLogLength);
      glGetShaderInfoLog(obj, infoLogLength, &returnLength, infoLog);
      printf("%s\n",infoLog);
      free(infoLog);
    }
  }

  void printProgramInfoLog(GLuint obj) {
    int infoLogLength = 0;
    int returnLength  = 0;
    char *infoLog;
    glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infoLogLength);
    if (infoLogLength > 0) {
      infoLog = (char *)malloc(infoLogLength);
      glGetProgramInfoLog(obj, infoLogLength, &returnLength, infoLog);
      printf("%s\n",infoLog);
      free(infoLog);
    }
  }

  std::string loadShaderSrc(const std::string& filename) {
    std::ifstream is(filename);
    if (is.is_open()) {
      std::stringstream buffer;
      buffer << is.rdbuf();
      return buffer.str();
    }
    cerr << "Unable to open file " << filename << endl;
    exit(1);
  }

  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;
  QTimer *timer;

public:
  MyWidget(QWidget *parent = NULL) : QOpenGLWidget(parent) {
    this->setWindowTitle("First Shader Example");
    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();}
};

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
}
