#define GLEW_STATIC 1
#include <GL/glew.h>

#include "GLViewer.h"

#include <QCoreApplication>
#include <QMouseEvent>

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cstdio>
#include <cassert>
#include <string>

using namespace std;

void openOFF (const std::string filename, unsigned int normWeight, Mesh & mesh) {
     mesh.clear ();
     vector<Vertex> & V = mesh.getVertices ();
     vector<Triangle> & T = mesh.getTriangles ();

     ifstream in (filename.c_str ());
     if (!in)
        exit (EXIT_FAILURE);
     string offString;
     unsigned int sizeV, sizeT, tmp;
     in >> offString >> sizeV >> sizeT >> tmp;
     for (unsigned int i = 0; i < sizeV; i++) {
         Vec3Df p;
         in >> p;
         V.push_back (Vertex (p));
     }
     int s;
     for (unsigned int i = 0; i < sizeT; i++) {
         in >> s;
         unsigned int v[3];
         for (unsigned int j = 0; j < 3; j++)
             in >> v[j];
         T.push_back (Triangle (v[0], v[1], v[2]));
     }
     in.close ();

     Vec3Df center;
     float radius;
     Vertex::scaleToUnitBox (V, center, radius);
     mesh.recomputeSmoothVertexNormals (normWeight);
}

void GLViewer::open (const QString & filename) {
    openOFF (string (qPrintable (filename)), 2, mesh);
}

void GLViewer::initLights () {
    GLfloat light_position0[4] = {-50, 50, -10, 0};
    GLfloat light_position1[4] = {42, 374, 161, 0};
    GLfloat light_position2[4] = {473, -351, -259, 0};
    GLfloat light_position3[4] = {-438, 167, -48, 0};

    GLfloat direction1[3] = {-42, -374, -161,};
    GLfloat direction2[3] = {-473, 351, 259};
    GLfloat direction3[3] = {438, -167, 48};

    GLfloat color1[4] = {1.0, 1.0, 1.0, 1};
    GLfloat color2[4] = {0.28, 0.39, 1.0, 1};
    GLfloat color3[4] = {1.0, 0.69, 0.23, 1};

    GLfloat specularColor1[4] = {0.8, 0.8, 0.8, 1};
    GLfloat specularColor2[4] = {0.8, 0.8, 0.8, 1};
    GLfloat specularColor3[4] = {0.8, 0.8, 0.8, 1};

    GLfloat ambient[4] = {0.3f, 0.3f, 0.3f, 0.5f};

    glLightfv (GL_LIGHT0, GL_POSITION, light_position0);

    glLightfv (GL_LIGHT1, GL_POSITION, light_position1);
    glLightfv (GL_LIGHT1, GL_SPOT_DIRECTION, direction1);
    glLightfv (GL_LIGHT1, GL_DIFFUSE, color1);
    glLightfv (GL_LIGHT1, GL_SPECULAR, specularColor1);

    glLightfv (GL_LIGHT2, GL_POSITION, light_position2);
    glLightfv (GL_LIGHT2, GL_SPOT_DIRECTION, direction2);
    glLightfv (GL_LIGHT2, GL_DIFFUSE, color2);
    glLightfv (GL_LIGHT2, GL_SPECULAR, specularColor2);

    glLightfv (GL_LIGHT3, GL_POSITION, light_position3);
    glLightfv (GL_LIGHT3, GL_SPOT_DIRECTION, direction3);
    glLightfv (GL_LIGHT3, GL_DIFFUSE, color3);
    glLightfv (GL_LIGHT3, GL_SPECULAR, specularColor3);

    glLightModelfv (GL_LIGHT_MODEL_AMBIENT, ambient);

    glEnable (GL_LIGHTING);
}

void GLViewer::setSunriseLight () {
    glDisable (GL_LIGHT0);
    glDisable (GL_LIGHT1);
    glDisable (GL_LIGHT2);
    glDisable (GL_LIGHT3);
}

void GLViewer::setSingleSpotLight () {
    glEnable (GL_LIGHT0);
    glDisable (GL_LIGHT1);
    glDisable (GL_LIGHT2);
    glDisable (GL_LIGHT3);
}

void GLViewer::setDefaultMaterial () {
    GLfloat material_color[4] = {1,1,1,1.0f};
    GLfloat material_specular[4] = {0.5,0.5,0.5,1.0};
    GLfloat material_ambient[4] = {0.0,0.0,0.0,1.0};

    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_specular);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_color);
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material_ambient);
    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 128);

    glDisable (GL_COLOR_MATERIAL);
    diffuseRef = 0.8f;
    specRef = 1.5f;
    shininess = 128.0f;
}

void GLViewer::initGLList () {
    glID = glGenLists (1);
    glNewList (glID, GL_COMPILE);
    drawMesh (false);
    glEndList ();
}

void GLViewer::init () {
  glewInit();
  if (glewGetExtension ("GL_ARB_vertex_shader")        != GL_TRUE ||
      glewGetExtension ("GL_ARB_shader_objects")       != GL_TRUE ||
      glewGetExtension ("GL_ARB_shading_language_100") != GL_TRUE) {
      cerr << "Driver does not support OpenGL Shading Language" << endl;
      exit (EXIT_FAILURE);
  }
  if (glewGetExtension ("GL_ARB_vertex_buffer_object") != GL_TRUE) {
      cerr << "Driver does not support Vertex Buffer Objects";
      exit (EXIT_FAILURE);
  }
  setBackgroundColor (Qt::white);
  setAutoBufferSwap (true);
  glCullFace (GL_BACK);
  glEnable (GL_CULL_FACE);
  glDepthFunc (GL_LESS);
  glEnable (GL_DEPTH_TEST);
  initLights ();
  setSingleSpotLight ();
  setDefaultMaterial ();
  mode = Phong;
  wireframe = false;
  initGLList ();

  try {
      phongShader = new PhongShader;
      phongShader->bind ();
      setShaderValues ();
  } catch (ShaderException e) {
      cerr << e.getMessage () << endl;
      exit (EXIT_FAILURE);
  }
}


QString GLViewer::helpString() const {
  QString text("<h2>qMini: Minimal OpenGL Viewer</h2>");
  text+= "--------------------------------------";
  text += "Author : Tamy Boubekeur (http://www.telecom-paristech.fr/~boubek)";
  text += "--------------------------------------";
  text += "Keyboard commands";
  text += "--------------------------------------";
  text += " ?: Print help";
  text += " w: Toggle wireframe Mode";
  text += " s: Toggle Phong/Flat shading Mode";
  text += " f: Toggle full screen mode";
  text += " D/d: Increase/Decrease diffuse reflection";
  text += " S/s: Increase/Decrease specular reflection";
  text += " +/-: Increase/Decrease shininess";
  text += " q, <esc>: Quit";
  text += "--------------------------------------";
  return text;
}



// -----------------------------------------------
// Drawing functions
// -----------------------------------------------


inline void glVertexVec3Df (const Vec3Df & v) {
    glVertex3f (v[0], v[1], v[2]);
}

inline void glNormalVec3Df (const Vec3Df & n) {
    glNormal3f (n[0], n[1], n[2]);
}

inline void glDrawPoint (const Vec3Df & pos, const Vec3Df & normal) {
    glNormalVec3Df (normal);
    glVertexVec3Df (pos);
}

inline void glDrawPoint (const Vertex & v) {
    glDrawPoint (v.getPos (), v.getNormal ());
}

void GLViewer::setShaderValues () {
    phongShader->setDiffuseRef (diffuseRef);
    phongShader->setSpecRef (specRef);
    phongShader->setShininess (shininess);
}

void GLViewer::drawMesh (bool flat) {
    const vector<Vertex> & V = mesh.getVertices ();
    const vector<Triangle> & T = mesh.getTriangles ();
    glBegin (GL_TRIANGLES);
    for (unsigned int i = 0; i < T.size (); i++) {
        const Triangle & t = T[i];
        if (flat) {
            Vec3Df normal = Vec3Df::crossProduct (V[t.getVertex (1)].getPos ()
                                                  - V[t.getVertex (0)].getPos (),
                                                  V[t.getVertex (2)].getPos ()
                                                  - V[t.getVertex (0)].getPos ());
            normal.normalize ();
            glNormalVec3Df (normal);
        }
        for (unsigned int j = 0; j < 3; j++)
            if (!flat) {
                glNormalVec3Df (V[t.getVertex (j)].getNormal ());
                glVertexVec3Df (V[t.getVertex (j)].getPos ());
            } else
                glVertexVec3Df (V[t.getVertex (j)].getPos ());
    }
    glEnd ();
}

void GLViewer::drawSolidModel () {
    glEnable (GL_LIGHTING);
    glEnable (GL_COLOR_MATERIAL);
    glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
    glPolygonOffset (1.0, 1.0);
    glEnable (GL_POLYGON_OFFSET_FILL);
    glShadeModel (GL_FLAT);
    phongShader->bind ();
    setShaderValues();
    setDefaultMaterial();
    glColor3f (1.0, 1.0, 1.0);
    drawMesh (true);
    glPolygonMode (GL_FRONT, GL_LINE);
    glPolygonMode (GL_BACK, GL_FILL);
    glColor3f (0.0, 0.0, 0.0);
    drawMesh (true);
    glDisable (GL_POLYGON_OFFSET_FILL);
    glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
    glDisable (GL_COLOR_MATERIAL);
    glDisable (GL_LIGHTING);
    glShadeModel (GL_SMOOTH);
}

void GLViewer::drawPhongModel () {
    phongShader->bind ();
    setShaderValues ();
    glColor3f (1.0, 1.0, 1.0);
    glCallList (glID);
}

void GLViewer::draw () {
    if (mode == Solid)
        drawSolidModel ();
    else if (mode == Phong)
        drawPhongModel ();
}

void GLViewer::keyPressEvent (QKeyEvent * e) {
    if (e->text () == "s") {
        if (mode == Solid)
            mode = Phong;
        else
            mode = Solid;
        updateGL ();
    } else if (e->text () == "w") {
        if (wireframe) {
            wireframe = false;
            glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
        } else {
            wireframe = true;
            glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
        }
        updateGL ();
    }  else
        QGLViewer::keyPressEvent (e);
}


