// 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 <QTimer>
#include <QMessageBox>
#include <fstream>
#include <iostream>
#include <string>
#include <fstream>
using namespace std;

#include <GL/glu.h>

class Renderer : protected QOpenGLFunctions_2_0 {

public:
  float t;
  int wrapS;
  int wrapT;
  int magFilter;
  int minFilter;
  int selectedTexID;
private:
  GLuint texID0;
  GLuint texID1;

public:
  // constructor
  Renderer() : t(-90.0f),
               wrapS(GL_REPEAT), wrapT(GL_REPEAT),
               magFilter(GL_LINEAR), minFilter(GL_LINEAR_MIPMAP_LINEAR),
               selectedTexID(0),
               texID0(0), texID1(0)
  {}

public:
  void init() {
    initializeOpenGLFunctions();
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_DEPTH);
    std::string fileName0(findFile("checker_texture_512.ppm", "qt", 5));
    texID0 = loadTexture(fileName0);
    std::string fileName1(findFile("checker_texture_32.ppm", "qt", 5));
    texID1 = loadTexture(fileName1);
  }

  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.7f, 0.7f, 0.7f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    // set camera
    gluLookAt(8.0, -2.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
    //gluLookAt(0.0, 0.0, 10.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0);
    // draw scene
    glRotatef(t, 0.0f, 0.0f, 1.0f);
    drawTexturedPlane();
  }

  void dispose() {
    if(texID0 !=0) glDeleteTextures( 1, &texID0);
    if(texID1 !=0) glDeleteTextures( 1, &texID1);
  }

private:
  // returns a valid textureID on success, otherwise 0
  GLuint loadTexture(std::string &filename) {

    unsigned width;
    unsigned height;
    int level = 0;
    int border = 0;
    std::vector<unsigned char> imgData;

    // load image data
    if(!loadPPMImageFlipped(filename, width, height, imgData)) return 0;

    // data is aligned in byte order
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    //request textureID
    GLuint textureID;
    glGenTextures(1, &textureID);

    // bind texture
    glBindTexture(GL_TEXTURE_2D, textureID);


    // parameters that define how to warp the texture
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    GLfloat borderColor[4] = {1.0f, 1.0f, 0.0f, 1.0f};
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

    // parameters that define how to filter the texture
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);


    // texture colors should replace the original color values
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); //GL_MODULATE

    // specify the 2D texture map
    glTexImage2D (GL_TEXTURE_2D, level, GL_RGB, width, height, border, GL_RGB, GL_UNSIGNED_BYTE, &imgData[0]);

    // return unique texture identifier
    return textureID;
  }

  void setTextureParameters() { 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
  }

  void drawTexturedPlane() {
    glEnable(GL_TEXTURE_2D);

    if(selectedTexID == 0) {
      glBindTexture(GL_TEXTURE_2D, texID0);
    }else{
      glBindTexture(GL_TEXTURE_2D, texID1);
    }

    setTextureParameters();

    glColor3f(1.0f,0.0f,0.0f);
    glBegin(GL_POLYGON);
    glTexCoord2f(5.00f,  5.00f); glVertex3f(-10.0f, 10.0f, 0.0f);
    glTexCoord2f(5.00f, -4.00f); glVertex3f(-10.0f,-10.0f, 0.0f);
    glTexCoord2f(-4.00f, -4.00f); glVertex3f( 10.0f,-10.0f, 0.0f);
    glTexCoord2f(-4.00f,  5.00f); glVertex3f( 10.0f, 10.0f, 0.0f);
    glEnd();
    glDisable(GL_TEXTURE_2D);
  }

  bool loadPPMImageFlipped(std::string &filename, unsigned &width, unsigned &height, std::vector<unsigned char> &imgData) {

    ifstream input(filename.c_str(), ifstream::in | ifstream::binary);
    if(!input) { // cast istream to bool to see if something went wrong
      QMessageBox msgBox;
      msgBox.setText(QString("Can not find texture data file ")+QString(filename.c_str()));
      msgBox.exec();
      return false;
    }
    input.unsetf(std::ios_base::skipws);

    string line;
    input >> line >> std::ws;
    if (line != "P6") {
      QMessageBox msgBox;
      msgBox.setText("File is not PPM P6 raw format");
      msgBox.exec();
      return false;
    }

    width = 0;
    height = 0;
    unsigned depth = 0;
    unsigned readItems = 0;
    unsigned char lastCharBeforeBinary;

    while (readItems < 3) {
      input >> std::ws;
      if(input.peek() != '#') {
        if (readItems == 0) input >> width;
        if (readItems == 1) input >> height;
        if (readItems == 2) input >> depth >> lastCharBeforeBinary;
        readItems++;
      }else{ // skip comments
        std::getline(input, line);
      }
    }

    if(depth >= 256) {
      QMessageBox msgBox;
      msgBox.setText("Only 8-bit PPM format is supported");
      msgBox.exec();
      return false;
    }

    unsigned byteCount = width * height * 3;
    imgData.resize(byteCount);
    input.read((char*)&imgData[0], byteCount*sizeof(unsigned char));

    // vertically flip the image because the image origin
    // in OpenGL is the lower-left corner
    unsigned char tmpData;
    for(unsigned y=0; y < height / 2; y++) {
      int sourceIndex = y * width * 3;
      int targetIndex = (height-1-y) * width *3;
      for(unsigned x=0; x < width*3; x++) {
          tmpData = imgData[targetIndex];
          imgData[targetIndex] = imgData[sourceIndex];
          imgData[sourceIndex] = tmpData;
          sourceIndex++;
          targetIndex++;
      }
    }

    return true;
  }

  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("Use the 1 to 5 keys to change the texture parameters");
    this->resize(320, 320);
    renderer = new Renderer();
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    timer->start(30);
  }

  ~MyWidget() {
    makeCurrent();
    renderer->dispose();
    doneCurrent();
    delete renderer;
    delete timer;
  }

protected:
  void initializeGL() { renderer->init(); }
  void resizeGL(int w, int h){ renderer->resize(w, h); }
  void paintGL() {
    float offset = 0.25f;
    renderer->t += offset;
    renderer->display();
  }
  void keyPressEvent(QKeyEvent* event){
    bool redraw = false;

    QString mode;


    switch(event->key()) {
    case '1':
      if(renderer->wrapS == GL_REPEAT) {
        renderer->wrapS = GL_CLAMP;
        mode = "wrapS = GL_CLAMP";
      }
      else if(renderer->wrapS == GL_CLAMP) {
        renderer->wrapS = GL_MIRRORED_REPEAT;
        mode = "wrapS = GL_MIRRORED_REPEAT";
      }
      else if(renderer->wrapS == GL_MIRRORED_REPEAT) {
        renderer->wrapS = GL_CLAMP_TO_BORDER;
        mode = "wrapS = GL_CLAMP_TO_BORDER";
      }
      else if(renderer->wrapS == GL_CLAMP_TO_BORDER) {
        renderer->wrapS = GL_REPEAT;
        mode = "wrapS = GL_REPEAT";
      }

      redraw = true;
      break;
    case '2':
      if(renderer->wrapT == GL_REPEAT) {
        renderer->wrapT = GL_CLAMP;
        mode = "wrapT = GL_CLAMP";
      }
      else if(renderer->wrapT == GL_CLAMP) {
        renderer->wrapT =  GL_MIRRORED_REPEAT;
        mode = "wrapT =  GL_MIRRORED_REPEAT";
      }
      else if(renderer->wrapT == GL_MIRRORED_REPEAT) {
        renderer->wrapT = GL_CLAMP_TO_BORDER;
        mode = "wrapT = GL_CLAMP_TO_BORDER";
      }
      else if(renderer->wrapT == GL_CLAMP_TO_BORDER) {
        renderer->wrapT = GL_REPEAT;
        mode = "wrapT = GL_REPEAT";
      }
      redraw = true;
      break;
    case '3':
      if(renderer->minFilter == GL_NEAREST) {
        renderer->minFilter = GL_LINEAR;
        mode = "minFilter = GL_LINEAR";
      }
      else if(renderer->minFilter == GL_LINEAR) {
        renderer->minFilter = GL_NEAREST_MIPMAP_NEAREST;
        mode = "minFilter = GL_NEAREST_MIPMAP_NEAREST";
      }
      else if(renderer->minFilter == GL_NEAREST_MIPMAP_NEAREST) {
        renderer->minFilter = GL_LINEAR_MIPMAP_NEAREST;
        mode = "minFilter = GL_LINEAR_MIPMAP_NEAREST";
      }
      else if(renderer->minFilter == GL_LINEAR_MIPMAP_NEAREST) {
        renderer->minFilter = GL_NEAREST_MIPMAP_LINEAR;
        mode = "minFilter = GL_NEAREST_MIPMAP_LINEAR";
      }
      else if(renderer->minFilter == GL_NEAREST_MIPMAP_LINEAR) {
        renderer->minFilter = GL_LINEAR_MIPMAP_LINEAR;
        mode = "minFilter = GL_LINEAR_MIPMAP_LINEAR";
      }
      else if(renderer->minFilter == GL_LINEAR_MIPMAP_LINEAR) {
        renderer->minFilter = GL_NEAREST;
        mode = "minFilter = GL_NEAREST";
      }
      redraw = true;
      break;
    case '4':
      if(renderer->magFilter == GL_NEAREST) {
        renderer->magFilter = GL_LINEAR;
        mode = "magFilter = GL_LINEAR";
      }
      else if(renderer->magFilter == GL_LINEAR) {
        renderer->magFilter = GL_NEAREST;
        mode = "magFilter = GL_NEAREST";
      }
      redraw = true;
      break;
    case '5':
      if(renderer->selectedTexID == 0) {
        renderer->selectedTexID = 1;
        mode = "texture resolution 32x32 pixels";
      } else {
        renderer->selectedTexID = 0;
        mode = "texture resolution 512x512 pixels";
      }
      redraw = true;
      break;
    }
    if(redraw) {
      this->setWindowTitle(mode);
      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
}
