
#include <cassert>
#include <iostream>

using namespace std;

#include <GL/glut.h>
#include <GL/gl.h>


#include <zpr.h>
#include <graphmisc.h>
#include <commandline.h>
#include <gobj.h>
#include <pointsdisplay.h>
#include <random.h>
#include <halfspaceD3.h>
#include <print.h>
#include <aclock.h>


#include <d4tri.h>
#include <d4tess.h>
#include <d4tessdraw.h> 
#include <d4minboundary.h>
#include <d4meshpointreader.h>

#include <d3func.h>
#include <d3sphere.h>

#define SHOW(x) #x << '=' << (x)

typedef point4<double> pt4;
typedef point3<double> pt3;


d4tess *mesh;
d4tessdraw * meshdraw;
d3func * p3dfunc = new d3sphere(point3<double>(),0.6);

gobjContainer xGraphics;

Message error("error.txt",true);

random11<double> fr;




class trianglefacedraw : public gobj
{
  pt3 A;
  pt3 B;
  pt3 C;

public:

  bool on;

  trianglefacedraw(bool _on=false)
    :on(_on) {}

  void draw() const
  {
    if (on==false)
      return;

    d3halfspace<double> h(A,B,C); 

    glColor3ub(218,165,32);
    glBegin(GL_TRIANGLES);
    glNormal3f(h.pn.x,h.pn.y,h.pn.z);
    glVertex3f(A.x,A.y,A.z);
    glNormal3f(h.pn.x,h.pn.y,h.pn.z);
    glVertex3f(B.x,B.y,B.z);
    glNormal3f(h.pn.x,h.pn.y,h.pn.z);
    glVertex3f(C.x,C.y,C.z);
    glEnd();
  }

  void set(pt3 _A, pt3 _B, pt3 _C)
  {
    A = _A;
    B = _B;
    C = _C;
    on=true;
  }
};

trianglefacedraw* tfd; 


void init01()
{
  mesh->pt.push_back( pt4(0.0,1.0,0.0,0.0) );
  mesh->pt.push_back( pt4(0.0,-1.0,0.0,0.0) );
  mesh->pt.push_back( pt4(1.0,0.0,0.0,0.0) );
  mesh->pt.push_back( pt4(-0.2,0.0,-0.7,0.0) );
  mesh->pt.push_back( pt4(-0.5,0.1,0.1,0.0) );
  mesh->pt.push_back( pt4(0.0,0.0,1.0,0.0) );

  mesh->viadd(1,6,2,5,0,2,0,4);
  mesh->viadd(1,5,2,4,0,3,0,1);
  mesh->viadd(4,2,1,3,4,0,0,2);
  mesh->viadd(3,2,1,6,1,0,0,3);

  for (uint i=1; i<mesh->pt.size(); ++i)
  {
    pt4 & x(mesh->pt[i]);
    pt3 y(x.x,x.y,x.z);
    x[3] = p3dfunc->eval(y);
  }

  assert( mesh->debugcheck() );
}

void init02()
{
  mesh->pt.push_back( pt4(0.0,1.0,0.0,0.0) );
  mesh->pt.push_back( pt4(0.0,-1.0,0.0,0.0) );
  mesh->pt.push_back( pt4(1.0,0.0,0.0,0.0) );
  mesh->pt.push_back( pt4(-0.2,0.0,-0.7,0.0) );
  mesh->pt.push_back( pt4(-0.5,0.1,0.7,0.0) );

  mesh->viadd(4,3,5,1, 0,0,0,2);
  mesh->viadd(5,3,4,2, 0,0,0,1);

  for (uint i=1; i<mesh->pt.size(); ++i)
  {
    pt4 & x(mesh->pt[i]);
    pt3 y(x.x,x.y,x.z);
    x[3] = p3dfunc->eval(y);
  }

  assert( mesh->debugcheck() );
}

void init03()
{
  mesh->pt.push_back( pt4(0.0,1.0,0.0,0.0) );
  mesh->pt.push_back( pt4(0.0,-1.0,0.0,0.0) );
  mesh->pt.push_back( pt4(1.0,0.0,0.0,0.0) );
  mesh->pt.push_back( pt4(-0.2,0.0,-0.7,0.0) );

  for (uint i=1; i<mesh->pt.size(); ++i)
  {
    pt4 & x(mesh->pt[i]);
    pt3 y(x.x,x.y,x.z);
    x[3] = p3dfunc->eval(y);
  }

  mesh->initialize();
}

void setface(uintc face)
{
  d4tri t(mesh->cpsimplex());
  uint a,b,c;
  t.getanticlockwiseface(a,b,c,face);
  pt3 A(mesh->pt[t.pi[a]]);
  pt3 B(mesh->pt[t.pi[b]]);
  pt3 C(mesh->pt[t.pi[c]]);

  tfd->set(A,B,C);
}

void timmingexperiment02()
{
  cout << "Reading Points from a File" << endl;
  cout << "Expecting the file to have 3 or 4 columns of numbers." << endl;
  cout << endl;
  cout << "  Enter the filename: ";
  string fname;

  //fname = "dataset2D08.txt";
  //cout << fname << endl;
  cin >> fname;

  bool res;
  d4meshpointreader mr(res,*mesh,fname);

  if (res==false)
    return;

  cout << "Finished reading file: " << fname << endl << endl;
  
  cout << "Starting insertion of points into the mesh" << endl;

  aclock c;
  c.measure();
  mr.eval();
  c.measure();

  cout << "Finished insertion of points into the mesh" << endl << endl;
  cout << "Time without graphics: ";
  cout << c.diff_s() << "s" << endl;

}

void timmingexperiment03()
{
  cout << "Creating a 3D Data Set" << endl;
  cout << "Writing Points to a File" << endl;
  cout << "  Enter the filename: ";
  string fname;
  cin >> fname;
  cout << "  Enter the number of points: ";
  uint n;
  cin >> n;

  ofstream targ(fname.c_str());

  for (uint i=0; i<n; ++i)
    targ << -1.0+2.0*fr() << " " << -1.0+2.0*fr() << " " << -1.0+2.0*fr() << endl;

  cout << "Finished writing file: " << fname << endl << endl;
}



void keyboard(unsigned char key, int x, int y)
{
  switch (key)
  {
    case 27:
      exit(0);
      break;

    case 'a':
      mesh->vs.anticlockwise();
      break;

    case 'A':
      mesh->vs.clockwise();
      break;

    case 'j':
      mesh->vs.left();
      break;

    case 'k':
      mesh->vs.right();
      break;
   
    case 'm':
      mesh->vs.down();
      break;

//  aim:    d f      Move left, right and down.
//           c
    case 'd':
      mesh->tetmoveleft();
      break;

    case 'f':
      mesh->tetmoveright();
      break;

    case 'c':
      mesh->tetmovedown();
      break;

    case 'b':
      mesh->boundaryorient();
      break;

    case 'w':
      mesh->surfaceleft();
      break;
    case 'e':
      mesh->surfaceright();
      break;

    case 's':
      mesh->surfacedown();
      break;



    case 'T':
      {
        cout << "  1:  Read points from file." << endl;

        cout << "  2:  Create a new data set by writing 3D random data points to a file." << endl;
/*
        cout << "  3:  Create a new data set by writing 4D random data points to a file." << endl;

*/
        cout << "  q:  quit" << endl;
        cout << "      Enter a choice  ";
        char ch;
        cin >> ch;
        switch (ch)
        {
          case '1':
            timmingexperiment02();
            break;
          case '2':
            timmingexperiment03();
            meshdraw->meshupdate();
            break;
        }
      }
      break;


    case 'r':
      {
        random11<double> r;

        static gobjQuadric q3;
        q3.radius=0.005;

        xGraphics.push_back( new gobjglColor3f(1.0,0.0,0.0) );
                
        uint k= mesh->pt.size();

        pt3 x(-1.0+2.0*r(),-1.0+2.0*r(),-1.0+2.0*r());
        mesh->pt.push_back(pt4(x.x,x.y,x.z,p3dfunc->eval(x)));
        xGraphics.push_back( new gobjMySphereDraw( x, &q3 ) );

        mesh->addpoint(k);
/*
        uint face;
        if (mesh->searchinsidemesh(face,k))
          mesh->addpoint(k);
        else
          setface(face);
*/

        meshdraw->meshupdate();
      }
      break;


















/*


    case 'r':
      {
        random11<double> r;
        bool found,status;
        uint face;

        static gobjQuadric q3;
        q3.radius=0.005;

        xGraphics.push_back( new gobjglColor3f(1.0,0.0,0.0) );
                
        uint k;

        // Only add points in existing mesh.

        for (found=false; !found; )
        {
          k = mesh->pt.size();

          pt3 x(-1.0+2.0*r(),-1.0+2.0*r(),-1.0+2.0*r());
          mesh->pt.push_back(pt4(x.x,x.y,x.z,0.0));
          xGraphics.push_back( new gobjMySphereDraw( x, &q3 ) );

//          mesh->pt.push_back(pt4(-1.0+2.0*r(),-1.0+2.0*r(),-1.0+2.0*r(),0.0));
          found = mesh->search(status,face,k);
        }

        mesh->addpoint(k);
        meshdraw->meshupdate();
      }
      break;


*/

    // Writing out state to file.
    case '1':
      {
        Message e1("e1.txt",true); 
        e1() << *mesh << endl;
      }
      break;
  
    // Writing out state to file.
    case '2':
      {
        Message e2("e2.txt",true); 
        e2() << *mesh << endl;
      }
      break;      
    




    case 'R':
      {
        static gobjQuadric q2;
        q2.radius=0.00625;

        uint k;
        uint face;
        bool found;

        random11<double> r;
        for (uint i=0; i<100; ++i)
        {
          pt3 x(-1.0+2.0*r(),-1.0+2.0*r(),-1.0+2.0*r());

          k = mesh->pt.size();
          mesh->pt.push_back(pt4(x.x,x.y,x.z,0.0));

          // addpoint also uses the search function, 
          //   so its called before addpoint.
          //   Else a search occures where the very point is already
          //   in the mesh.
          found = mesh->searchinsidemesh(face,k);

          mesh->addpoint(k);
          meshdraw->meshupdate();

           
          if (found)
            xGraphics.push_back( new gobjglColor3f(0.0,sqrt(r()),0.0) );
          else
            continue;
            //xGraphics.push_back( new gobjglColor3f(sqrt(r()),0.0,0.0) );

          xGraphics.push_back( new gobjMySphereDraw( x, &q2 ) );
        }

      }
      break;

    case 'i':
      {
        static gobjQuadric q;
        q.radius=0.0125;

        cout << "Input a point" << endl;
        pt3 x;
        cin >> x.x >> x.y >> x.z;

        uint k = mesh->pt.size();
        mesh->pt.push_back(pt4(x.x,x.y,x.z,0.0));

        random11<double> r;
        uint face;
        bool found = mesh->searchinsidemesh(face,k);

        if (found)
        {
          xGraphics.push_back( new gobjglColor3f(0.0,sqrt(r()),0.0) );
          xGraphics.push_back( new gobjMySphereDraw( x, &q ) );
          tfd->on=false;
        }
        else
        {
          setface(face);

/*

          d4tri t(mesh->cptri());
          uint a,b,c;
          t.getanticlockwiseface(a,b,c,face);
          pt3 A(mesh->pt[t.pi[a]]);
          pt3 B(mesh->pt[t.pi[b]]);
          pt3 C(mesh->pt[t.pi[c]]);

          tfd->set(A,B,C);
*/

          xGraphics.push_back( new gobjglColor3f(sqrt(r()),0.0,0.0) );
          xGraphics.push_back( new gobjMySphereDraw( x, &q ) );
        }

      }
      break;


    case 'I':
      {
        static gobjQuadric q3;
        q3.radius=0.0125;

        cout << "Input a new mesh point" << endl;
        pt3 x;
        cin >> x.x >> x.y >> x.z;
//        x.x=.2;
//        x.y=.3;
//        x.z=.4;

        uint k = mesh->pt.size();
        mesh->pt.push_back(pt4(x.x,x.y,x.z,0.0));

        mesh->addpoint(k);

meshdraw->meshupdate();

        // Draw a green point if its inside the existing mesh,
        // a red point if outside the mesh.

/*
        uint face;
        bool found = mesh->search(face,k);

        random11<double> r;

        if (found)
          xGraphics.push_back( new gobjglColor3f(0.0,sqrt(r()),0.0) );
        else
          xGraphics.push_back( new gobjglColor3f(sqrt(r()),0.0,0.0) );

        xGraphics.push_back( new gobjMySphereDraw( x, &q3 ) );
*/
      }
      break;





      case 'p':
        cout << endl;
        cout << *mesh << endl;
        break;
/*
      case 'd':
        cout << "d: debug check" << endl;
        mesh->debugcheck();
        break;
*/

/*
    case 'r':
    {
      random11<double> r;
      double x = r()*10.0;
      double y = r()*8.0;  
      double z = r()*8.0;  
      cout << SHOW(x) << " " << SHOW(y) << " ";
      cout << SHOW(z) << endl;
      mesh->eval(x,y,z);
      glutPostRedisplay();
    }
      break;
*/

    case 'z':
      mesh->tet2to3(); 
      meshdraw->meshupdate();
      break;

    case 'Z':
      mesh->tet2to3Inverse();
      meshdraw->meshupdate();
      break;

    case 't':
      {
        uint steps;
        bool res = mesh->rotateaboutaxis(steps);
        cout << SHOW(res) << " ";
        cout << SHOW(steps) << endl;
      }
      break;

  }


  glutPostRedisplay();
}



void display()
{ 
  glMatrix temp;
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  //axes(0.5);
    //f3().draw();

  xGraphics.draw();

  meshdraw->draw();

  glerrordisplay();

  {
    glPushMatrix();

    glPushAttrib(GL_CURRENT_BIT);
    glPushAttrib(GL_LIGHTING_BIT);
 
    glColor3f(1.0,1.0,0.0);
    glEnable(GL_LIGHTING);
  
    //glutWireSphere(0.6,10,10);

    glPopAttrib();
    glPopAttrib();

    glPopMatrix();
  }
  glerrordisplay();
  
  glutSwapBuffers();
}


void help()
{
  cout << "Command Line Options" << endl;
  cout << "For boolean values true and 1, false and 0 are equivalent" << endl;
  cout << "cp=true        - current pointer" << endl;
  cout << "winding=true   - display tetrahedrons triangle windings" << endl;
  cout << "base=true      - display each triangles base" << endl;
  cout << "surface=true   - draw the surface" << endl;
  cout << "grid=true      - draw the tetrahedrons as blue grid" << endl;
  cout << "points=true    - label each point with its integer index" << endl;
  cout << "tets=1         - label each tetrahedron with its integer index" << endl;


  cout << endl;
  cout << "Keyboard Commands" << endl;
  cout << "'a' 'A' - rotate anticlockwise or clockwise the base triangle on the current tetrahedron" << endl;
  cout << "'j' 'k'  - move left, right and down on current tetrahedron surface" << endl;
  cout << "  'm'" << endl;
  cout << "'d' 'f'  - move left, right and down through the mesh(tetmove)" << endl;
  cout << "  'c'        relative to the current tetredrons base" << endl;
  cout << "'b' - orient the current tetrahedron to the boundary if possible" << endl;
  cout << "'w' 'e'  First orient the cp to the boundary with 'b'.  Then move" << endl;
  cout << "  's'      on the surface left, right, and down respectively." << endl;
  cout << "z and Z   2->3 and 3->2" << endl;

  cout << endl;
  cout << "Minimizers" << endl;
  cout << "  Explore mesh balancing." << endl;
  cout << "greedy=true    - very bad mesh generated." << endl;
  cout << "greedy2=true   - very experimental, do I know what I am doing?" << endl;

}


int main(int argc, char** argv )
{
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  unsigned int const wx=800;
  unsigned int const wy=800;

  glutInitWindowSize(wx,wy);

  glutCreateWindow("");

  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);

  OpenGLinitialisation();

  glEnable(GL_DEPTH_TEST);

  //glDisable(GL_CULL_FACE);
  glEnable(GL_CULL_FACE);

  glEnable(GL_NORMALIZE);

  xGraphics.set();

  error.setMessageGlobal();


  commandline cmd(argc,argv);
  
  bool greedy(false);
  cmd.mapvar(greedy,"greedy");
  bool greedy2(false);
  cmd.mapvar(greedy2,"greedy2");

  tfd = new trianglefacedraw();
  xGraphics.push_back(tfd);

  


  unsigned int N(1000);
  cmd.mapvar(N,"N");

  mesh = new d4tess(N);

  init03();

  meshdraw = new d4tessdraw(*mesh);


  cmd.mapvar(meshdraw->graphicsDeffered.v[0]->isdrawn,"points");
  cmd.mapvar(meshdraw->graphicsDeffered.v[1]->isdrawn,"tets");
  cmd.mapvar(meshdraw->graphicsDeffered.v[2]->isdrawn,"winding");
  cmd.mapvar(meshdraw->graphicsDeffered.v[3]->isdrawn,"grid",true);
  cmd.mapvar(meshdraw->graphicsDeffered.v[4]->isdrawn,"base");
  cmd.mapvar(meshdraw->graphicsDeffered.v[5]->isdrawn,"surface");


  cmd.mapvar(meshdraw->graphicsImmediate.v[0]->isdrawn,"cp",true);


  meshdraw->meshupdate();



//    *mesh,enablegrid,enablewinding,enablepoints,enablebase,enabletets);

  cout << SHOW(greedy) << endl;

  if (greedy)
    mesh->minimizerSet( new d4mingreedy(*mesh) );
    //mesh->minimizerSet( new d4minboundary(*mesh) );

  if (greedy2)
    mesh->minimizerSet( new d4mingreedy2(*mesh) );


  zprInit();

  help();
  glutMainLoop();

  return 0;
}



