#include <iostream>
#include <fstream>
#include <vector>
using namespace std;

#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>
#include <GL/glu.h>

#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
#endif


//==============================================================================
class Vertex {
//==============================================================================
public:
  double x, y, z;

  Vertex( double x=0, double y=0, double z=0 ) {
    setCoordinates( x, y, z );
  }
  void setCoordinates( double xval, double yval, double zval ) {
    x = xval; y = yval; z = zval;
  }
};


//==============================================================================
class Triangle
//==============================================================================
{
public:
  int v0, v1, v2;

  Triangle( int v0=-1, int v1=-1, int v2=-1 ) : v0(v0), v1(v1), v2(v2) {}
};



//==============================================================================
class Lamp
//==============================================================================
{
public:

  enum {
    HEAD = 0,
    NECK = 1,
    BODY = 2,
    BASE = 3
  };

  vector<Vertex> headVert;
  vector<Triangle> headTri;
  vector<Vertex> neckVert;
  vector<Triangle> neckTri;
  vector<Vertex> bodyVert;
  vector<Triangle> bodyTri;
  vector<Vertex> baseVert;
  vector<Triangle> baseTri;
  
  vector<Vertex> nullVert;
  vector<Triangle> nullTri;

  Lamp( ) {
    int i;
    float x, y;
    int nFaces;
    int v, t;

      // create head
      nFaces = 16;
      headVert.resize(3*nFaces+1);
      headTri.resize(5*nFaces);
      headVert[0] = Vertex(1.0f, 0.0f,  2.0f);
      t = 0;
      v = 1;
      for (i = 0; i < nFaces; i++) {
          x = (float) cos(2.0*M_PI*i/nFaces);
          y = (float) sin(2.0*M_PI*i/nFaces);
          headVert[v++] = Vertex(x+1.0, y,  2.0f);
          headVert[v++] = Vertex(x+1.0, y, -1.0f);
          headVert[v++] = Vertex(3*x+1.0, 3*y, -4.0f);
          if (i > 0) {
              headTri[t++] = Triangle(0, 3*i-2, 3*i+1);
              headTri[t++] = Triangle(3*i-2, 3*i-1, 3*i+1);
              headTri[t++] = Triangle(3*i-1, 3*i+2, 3*i+1);
              headTri[t++] = Triangle(3*i-1, 3*i, 3*i+2);
              headTri[t++] = Triangle(3*i, 3*i+3, 3*i+2);
          }
      }
      i = nFaces;
      headTri[t++] = Triangle(0, 3*i-2, 1);
      headTri[t++] = Triangle(3*i-2, 3*i-1, 1);
      headTri[t++] = Triangle(3*i-1, 2, 1);
      headTri[t++] = Triangle(3*i-1, 3*i, 2);
      headTri[t++] = Triangle(3*i, 3, 2);

      // create neck
      neckVert.resize(8+8+12);
      neckTri.resize(12+12+4);
      v = 0;
      t = 0;
      box(v,t, neckVert, neckTri, 1.0f, 11.0f, -0.15f, 0.15f, 0.2f, 0.5f);
      box(v,t, neckVert, neckTri, 1.0f, 11.0f, -0.15f, 0.15f, -0.5f, -0.2f);
      neckVert[v++] = Vertex(0.0f, -0.16f, 0.0f);
      neckVert[v++] = Vertex(1.0f, -0.16f, -0.5f);
      neckVert[v++] = Vertex(1.0f, -0.16f, 0.5f);
      neckTri[t++] = Triangle(v-3, v-2, v-1);
      neckVert[v++] = Vertex(0.0f, 0.16f, 0.0f);
      neckVert[v++] = Vertex(1.0f, 0.16f, 0.5f);
      neckVert[v++] = Vertex(1.0f, 0.16f, -0.5f);
      neckTri[t++] = Triangle(v-3, v-2, v-1);
      neckVert[v++] = Vertex(12.0f, -0.16f, 0.0f);
      neckVert[v++] = Vertex(11.0f, -0.16f, 0.5f);
      neckVert[v++] = Vertex(11.0f, -0.16f, -0.5f);
      neckTri[t++] = Triangle(v-3, v-2, v-1);
      neckVert[v++] = Vertex(12.0f, 0.16f, 0.0f);
      neckVert[v++] = Vertex(11.0f, 0.16f, -0.5f);
      neckVert[v++] = Vertex(11.0f, 0.16f, 0.5f);
      neckTri[t++] = Triangle(v-3, v-2, v-1);

      // create body
      bodyVert.resize(8+8+12);
      bodyTri.resize(12+12+4);
      v = 0;
      t = 0;
      box(v,t,bodyVert, bodyTri, 1.0f, 11.0f, -0.15f, 0.15f, 0.2f, 0.5f);
      box(v,t,bodyVert, bodyTri, 1.0f, 11.0f, -0.15f, 0.15f, -0.5f, -0.2f);
      bodyVert[v++] = Vertex(0.0f, -0.16f, 0.0f);
      bodyVert[v++] = Vertex(1.0f, -0.16f, -0.5f);
      bodyVert[v++] = Vertex(1.0f, -0.16f, 0.5f);
      bodyTri[t++] = Triangle(v-3, v-2, v-1);
      bodyVert[v++] = Vertex(0.0f, 0.16f, 0.0f);
      bodyVert[v++] = Vertex(1.0f, 0.16f, 0.5f);
      bodyVert[v++] = Vertex(1.0f, 0.16f, -0.5f);
      bodyTri[t++] = Triangle(v-3, v-2, v-1);
      bodyVert[v++] = Vertex(12.0f, -0.16f, 0.0f);
      bodyVert[v++] = Vertex(11.0f, -0.16f, 0.5f);
      bodyVert[v++] = Vertex(11.0f, -0.16f, -0.5f);
      bodyTri[t++] = Triangle(v-3, v-2, v-1);
      bodyVert[v++] = Vertex(12.0f, 0.16f, 0.0f);
      bodyVert[v++] = Vertex(11.0f, 0.16f, -0.5f);
      bodyVert[v++] = Vertex(11.0f, 0.16f, 0.5f);
      bodyTri[t++] = Triangle(v-3, v-2, v-1);

      //create base
      baseVert.resize(2*nFaces+2+6);
      baseTri.resize(4*nFaces+2);
      int q = 2*nFaces+1;
      baseVert[0] = Vertex(0.0f, 0.0f,  1.0f);
      baseVert[q] = Vertex(0.0f, 0.0f,  0.0f);
      t = 0;
      v = 1;
      for (i = 0; i < nFaces; i++) {
          x = (float) cos(2.0*M_PI*i/nFaces);
          y = (float) sin(2.0*M_PI*i/nFaces);
          baseVert[v++] = Vertex(4*x, 4*y, 0.75f);
          baseVert[v++] = Vertex(4*x, 4*y, 0.0f);
          if (i > 0) {
              baseTri[t++] = Triangle(0, 2*i-1, 2*i+1);
              baseTri[t++] = Triangle(2*i-1, 2*i, 2*i+2);
              baseTri[t++] = Triangle(2*i-1, 2*i+2, 2*i+1);
              baseTri[t++] = Triangle(2*i, q, 2*i+2);
          }
      }
      i = nFaces;
      v++;
      baseTri[t++] = Triangle(0, 2*i-1, 1);
      baseTri[t++] = Triangle(2*i-1, 2*i, 2);
      baseTri[t++] = Triangle(2*i-1, 2, 1);
      baseTri[t++] = Triangle(2*i, q, 2);

      baseVert[v++] = Vertex(-1.0f, -0.16f, 0.75f);
      baseVert[v++] = Vertex(1.0f, -0.16f, 0.75f);
      baseVert[v++] = Vertex(0.0f, -0.16f, 2.5f);
      baseTri[t++] = Triangle(v-3, v-2, v-1);

      baseVert[v++] = Vertex(1.0f, 0.16f, 0.75f);
      baseVert[v++] = Vertex(-1.0f, 0.16f, 0.75f);
      baseVert[v++] = Vertex(0.0f, 0.16f, 2.5f);
      baseTri[t++] = Triangle(v-3, v-2, v-1);
  }

  void box(int& v, int& t, vector<Vertex>& vList, vector<Triangle>& tList,
      float xmin, float xmax,
      float ymin, float ymax,
      float zmin, float zmax) {
      // 8 verts and 12 tris
      int b = v;
      vList[v++] = Vertex(xmin, ymin, zmin);
      vList[v++] = Vertex(xmax, ymin, zmin);
      vList[v++] = Vertex(xmin, ymax, zmin);
      vList[v++] = Vertex(xmax, ymax, zmin);
      vList[v++] = Vertex(xmin, ymin, zmax);
      vList[v++] = Vertex(xmax, ymin, zmax);
      vList[v++] = Vertex(xmin, ymax, zmax);
      vList[v++] = Vertex(xmax, ymax, zmax);
      tList[t++] = Triangle(b+0, b+2, b+3);
      tList[t++] = Triangle(b+0, b+3, b+1);
      tList[t++] = Triangle(b+0, b+6, b+2);
      tList[t++] = Triangle(b+0, b+4, b+6);
      tList[t++] = Triangle(b+0, b+1, b+5);
      tList[t++] = Triangle(b+0, b+5, b+4);
      tList[t++] = Triangle(b+7, b+3, b+2);
      tList[t++] = Triangle(b+7, b+2, b+6);
      tList[t++] = Triangle(b+7, b+5, b+1);
      tList[t++] = Triangle(b+7, b+1, b+3);
      tList[t++] = Triangle(b+7, b+6, b+4);
      tList[t++] = Triangle(b+7, b+4, b+5);
  }

   int getElements() {
      return 4;
  }

   vector<Vertex>& getVertices(int element) {
      if (element == HEAD) return headVert;
      if (element == NECK) return neckVert;
      if (element == BODY) return bodyVert;
      if (element == BASE) return baseVert;
      return nullVert;
      
  }

   vector<Triangle> getTriangles(int element) {
      if (element == HEAD) return headTri;
      if (element == NECK) return neckTri;
      if (element == BODY) return bodyTri;
      if (element == BASE) return baseTri;
      return nullTri;
  }

   void Draw(int element) {
      vector<Vertex>& vert = getVertices(element);
      vector<Triangle>& tri = getTriangles(element);
      int v;

      for (int i = 0; i < (int)tri.size(); i++) {
          glBegin(GL_POLYGON);
          v = tri[i].v0;
          glVertex3d(vert[v].x, vert[v].y, vert[v].z);
          v = tri[i].v1;
          glVertex3d(vert[v].x, vert[v].y, vert[v].z);
          v = tri[i].v2;
          glVertex3d(vert[v].x, vert[v].y, vert[v].z);
          glEnd();
      }
  }
};


Lamp mom;
int width, height;
int mode = 0;

void Draw()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();
  if( mode == 0 )
  {
    gluLookAt(0, 0,-60,  0,0,0,   0,1,0);   // world-to-camera transform
    glRotated(-90, 1, 0, 0);                
    glColor3d(0,0,1);
    mom.Draw(Lamp::BASE);
    mom.Draw(Lamp::BODY);
    mom.Draw(Lamp::NECK);
    mom.Draw(Lamp::HEAD);
  }
  else if( mode == 1 )
  {
    glTranslated(0.0, 0.0, -60.0);          // world-to-view transform
    glColor3d(0,0,1);
    glRotated(-90, 1, 0, 0);                // base-to-world transform
    mom.Draw(Lamp::BASE);
    glTranslated(0,0,2.5);                  // body-to-base transform
    mom.Draw(Lamp::BODY);
    glTranslated(12,0,0);                   // neck-to-body transform
    mom.Draw(Lamp::NECK);
    glTranslated(12,0,0);                   // head-to-neck transform
    mom.Draw(Lamp::HEAD);

  }
  else
  {
    glTranslated(0.0, -12.0, -60.0);        // world-to-view transform
    glColor3d(0,0,1);
    glRotated(-90, 1, 0, 0);                // base-to-world transform
    mom.Draw(Lamp::BASE);
    glTranslated(0,0,2.5);                  // body-to-base transform
    glRotated(-30, 0, 1, 0);                // rotate body at base pivot
    mom.Draw(Lamp::BODY);
    glTranslated(12,0,0);                   // neck-to-body transform
    glRotated(-115, 0, 1, 0);               // rotate neck at body pivot
    mom.Draw(Lamp::NECK);
    glTranslated(12,0,0);                   // head-to-neck transform
    glRotated(180, 1, 0, 0);                // rotate head at neck pivot
    mom.Draw(Lamp::HEAD);
  }
  glFlush();
  glutSwapBuffers();
}

void Init(int w, int h ) {
    width = w; height = h;
    glViewport(0, 0, w, h);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);						// BackGround Color 
    glEnable(GL_DEPTH_TEST);									// Enables Depth Testing 
    glDepthFunc(GL_LEQUAL);										// The Type Of Depth Test To Do 

    glMatrixMode(GL_PROJECTION);								// Select The Projection Matrix
    glLoadIdentity();											// Reset The Projection Matrix
    gluPerspective(30.0, ((double) w)/((double) h), 0.1, 100.0);

    glMatrixMode(GL_MODELVIEW);									// Select The Modelview Matrix
    glLoadIdentity();											// Reset The Modelview Matrix
}

void onKeyPress(unsigned char key, int x, int y) 
{
  if( key == ' ' )
    mode++;
    glutPostRedisplay();
}

int main( int argc, char *argv[] ) {
  int w = 256; int h = 256;
    
  // glut initialization functions:
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
  glutInitWindowSize(w,h);
  glutInitWindowPosition(100,100);
  glutCreateWindow("Luxo");

  Init(w, h);

  glutDisplayFunc(Draw);       
  glutKeyboardFunc(onKeyPress);
  glutReshapeFunc(Init);
  glutMainLoop();

  return 0;
}
