#include <cassert>
#include <fstream>
#include <iostream>
using namespace std;

#include <angles.h>
#include <commandline.h>
#include <cylinder.h>
#include <disk.h>
#include <diskinttest.h>
#include <gobj.h>
#include <graphmisc.h>
#include <line.h>
#include <menusystem.h>
#include <point.h>
#include <planepointsurface.h>
#include <pointsurface.h>
#include <zpr.h>

cylinder * diskinttest::C1ptr;
cylinder * diskinttest::C2ptr;
disk * diskinttest::D1ptr;
disk * diskinttest::D2ptr;
plane * diskinttest::P1ptr;
gobjMyCircleDraw * diskinttest::D1cd;
gobjMyCircleDraw * diskinttest::D2cd;
gobjContainer * diskinttest::diskintersection;
gobjgluCylinder * diskinttest::C1cd;
gobjgluCylinder * diskinttest::C2cd;
gobjContainer * diskinttest::cylinderintersection;
float diskinttest::transparency = 0.6;
bool * diskinttest::help = 0;
bool diskinttest::cylinderscollide = false;



void diskinttest::display01()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  gobj::global->draw();

  glerrordisplay();
  
  glutSwapBuffers();
}

void diskinttest::keyboard01(unsigned char key, int x, int y)
{
  static double delta = 0.1;
  static bool mode=true;

  switch (key)
  {
    case 27:
      exit(0);
      break;
    case 'j': 
      if (mode)
        D1ptr->pos.x += delta; 
      else
        D1ptr->nml.x += delta;
    break;
    case 'J': 
      if (mode)
        D1ptr->pos.x -= delta; 
      else
        D1ptr->nml.x -= delta;
    break;

    case 'k': 
      if (mode)
        D1ptr->pos.y += delta; 
      else
        D1ptr->nml.y += delta;
      break;
    case 'K': 
      if (mode)
        D1ptr->pos.y -= delta; 
      else
        D1ptr->nml.y -= delta; 

      break;

    case 'l': 
      if (mode)
        D1ptr->pos.z += delta; 
      else
        D1ptr->nml.z += delta; 
      break;
    case 'L': 
      if (mode)
        D1ptr->pos.z -= delta; 
      else
        D1ptr->nml.z -= delta; 
      break;
    
    case '+': delta *= 10.0; if (delta==0.0) delta=0.1; break;
    case '-': delta /= 10.0; break;

    case 'm': mode = !mode; break;

    case 'p':
      cout << (stringc)*D1ptr << endl;
      break;
  }

  update01();
  glutPostRedisplay();
}

void diskinttest::test01(int & argc, char** argv)
{
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(800,600);
  glutCreateWindow("");
  glutDisplayFunc(display01);
  glutKeyboardFunc(keyboard01);

  OpenGLinitialisation();

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glEnable(GL_NORMALIZE);

  zpr zz;

  xGraphics.set();

  commandline cmd(argc,argv);

  string in;

  cmd.mapvar(in,"in");
  if ( in.empty() )
  {
    cout << "error: in=filename expected" << endl;
    return;
  }

  ifstream filein(in.c_str());
  assert(filein.good()==true);
  if (filein.good()==false)
    return;

  point3<double> nml;
  point3<double> pos;
  double radius;
  double d;

  filein >> nml.x;
  filein >> nml.y;
  filein >> nml.z;
  filein >> pos.x;
  filein >> pos.y;
  filein >> pos.z;
  filein >> radius;

  disk D1(nml,pos,radius);
  D1ptr = & D1;
  

  cout << "disk:  " << (stringc)D1 << endl;

  filein >> nml.x;
  filein >> nml.y;
  filein >> nml.z;
  filein >> d;

  plane P1(nml,d);
  P1ptr = &P1;

  cout << "plane:  " << (stringc)P1 << endl;

  planepointsurface f1(P1);
  pointsurface<> ps1(6000);
  ps1.pre.push(new gobjglColor3ub(184,134,11) );
  ps1.addsurface2D(f1);
  gobjpush(&ps1);

  gobjpush(new gobjglColor3ub(255,255,0));
  gobjMyCircle * cir = new gobjMyCircle();
  gobjMyCircleDraw * cird = 
    new gobjMyCircleDraw(D1.radius,D1.pos,D1.nml,*cir);
  gobjpush(cir);
  gobjpush(cird);

  D1cd = cird;

  diskintersection = new gobjContainer(true);
  gobjpush(diskintersection);

  update01();

  menusystem * menu = 
    new menusystem(0,0,true,point2<GLint>(60,30),20);
  gobjpush(menu);

  menu->addfont12("Disk and Plane Intersection");
  menu->addnewline();
  menu->addnewline();
  menu->addfont10("'j' 'J' x position/normal"); 
  menu->addnewline();
  menu->addfont10("'k' 'K' y position/normal"); 
  menu->addnewline();
  menu->addfont10("'l' 'L' z position/normal"); 
  menu->addnewline();
  menu->addfont10("'m'  Toggle position/normal mode");
  menu->addnewline();
  menu->addfont10("'p'  Print the disk and plane to terminal.");
  menu->addnewline();
  menu->addnewline();
  menu->addfont10("ESC      Quit");

  zz.update();
  glutMainLoop();
}


void diskinttest::update01()
{
  assert(D1ptr!=0);
  assert(P1ptr!=0);

  D1ptr->nml.normalize();
  D1cd->center = D1ptr->pos;
  D1cd->axis = D1ptr->nml;

  point3<double> p0;
  point3<double> p1;

  diskintersection->nuke();
  if (D1ptr->intersects(p0,p1,*P1ptr))
  {
    diskintersection->push(new gobjglPushAttrib(GL_LIGHTING));
    diskintersection->push(new gobjglPushAttrib(GL_CURRENT_BIT));
    diskintersection->push(new gobjglDisable(GL_LIGHTING));
    diskintersection->push(new gobjglColor3ub(255,0,0));

    diskintersection->push(new gobjglBegin(GL_LINES));
    diskintersection->push(new gobjglVertex3d(p0));
    diskintersection->push(new gobjglVertex3d(p1));
    diskintersection->push(new gobjglEnd());

    diskintersection->push(new gobjglPopAttrib());
    diskintersection->push(new gobjglPopAttrib());
  }


}

void diskinttest::keyboard02(unsigned char key, int x, int y)
{
  static double delta = 0.1;
  static bool mode=true;

  switch (key)
  {
    case 27:
      exit(0);
      break;

    case 'j': 
      if (mode)
        D2ptr->pos.x += delta; 
      else
        D2ptr->nml.x += delta;
    break;
    case 'J': 
      if (mode)
        D2ptr->pos.x -= delta; 
      else
        D2ptr->nml.x -= delta;
    break;

    case 'k': 
      if (mode)
        D2ptr->pos.y += delta; 
      else
        D2ptr->nml.y += delta;
      break;
    case 'K': 
      if (mode)
        D2ptr->pos.y -= delta; 
      else
        D2ptr->nml.y -= delta;
      break;

    case 'l': 
      if (mode)
        D2ptr->pos.z += delta; 
      else
        D2ptr->nml.z += delta;
      break;
    case 'L': 
      if (mode)
        D2ptr->pos.z -= delta; 
      else
        D2ptr->nml.z -= delta;
      break;
    
    case '+': delta *= 10.0; if (delta==0.0) delta=0.1; break;
    case '-': delta /= 10.0; break;

    case 'm': mode = !mode; break;

    case 'p':
      cout << (stringc)*D1ptr << endl;
      cout << (stringc)*D2ptr << endl;
      break;

    case 'x': D2ptr->radius += delta; break;
    case 'X': D2ptr->radius -= delta; break;

    case 'h': if (help!=0) *help = !*help; break;
      
  
  }
  update02();
  glutPostRedisplay();
}


void diskinttest::test02(int & argc, char** argv)
{
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(800,600);
  glutCreateWindow("");
  glutDisplayFunc(display01);
  glutKeyboardFunc(keyboard02);

  OpenGLinitialisation();

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glEnable(GL_NORMALIZE);

  zpr zz;

  xGraphics.set();

  commandline cmd(argc,argv);

  string in;

  cmd.mapvar(in,"in");
  if ( in.empty() )
  {
    cout << "error: in=filename expected" << endl;
    return;
  }

  ifstream filein(in.c_str());
  assert(filein.good()==true);
  if (filein.good()==false)
    return;

  point3<double> nml;
  point3<double> pos;
  double radius;

  filein >> nml.x;
  filein >> nml.y;
  filein >> nml.z;
  filein >> pos.x;
  filein >> pos.y;
  filein >> pos.z;
  filein >> radius;

  nml.normalize();
  disk D1(nml,pos,radius);

  filein >> nml.x;
  filein >> nml.y;
  filein >> nml.z;
  filein >> pos.x;
  filein >> pos.y;
  filein >> pos.z;
  filein >> radius;

  nml.normalize();
  disk D2(nml,pos,radius);

  //gobjpush(new myaxes(1.0));

  gobjpush(new gobjglColor3ub(255,255,0));
  gobjMyCircle * cir = new gobjMyCircle();
  gobjMyCircleDraw * cird = 
    new gobjMyCircleDraw(D1.radius,D1.pos,D1.nml,*cir);
  gobjpush(cir);
  gobjpush(cird);

  D1ptr = &D1;
  D1cd = cird;

  gobjpush(new gobjglColor3ub(153,50,204));
  cird = new gobjMyCircleDraw(D2.radius,D2.pos,D2.nml,*cir);
  gobjpush(cird);

  D2ptr = &D2;
  D2cd = cird;

  diskintersection = new gobjContainer(true);
  gobjpush(diskintersection);


  menusystem * menu = 
    new menusystem(0,0,true,point2<GLint>(60,30),20);

  gobjSwitch<> * menuswitch = new gobjSwitch<>(menu,true);
  help = & menuswitch->isdrawn;
  gobjpush(menuswitch);

  menu->addfont12("Disk and Disk Intersection");
  menu->addnewline();
  menu->addnewline();
  menu->addfont10("'j' 'J' x position/normal"); 
  menu->addnewline();
  menu->addfont10("'k' 'K' y position/normal"); 
  menu->addnewline();
  menu->addfont10("'l' 'L' z position/normal"); 
  menu->addnewline();
  menu->addfont10("'m'  Toggle position/normal mode");
  menu->addnewline();
  menu->addfont10("'p'  Print the two disks to terminal.");
  menu->addnewline();
  menu->addfont10("'x' 'X' Vary disk 2's radius.");
  menu->addnewline();
  menu->addfont10("'h'  Toggle this help menu.");
  menu->addnewline();
  menu->addnewline();
  menu->addfont10("ESC      Quit");


  update02();

  zz.update();
  glutMainLoop();
}

void diskinttest::update02()
{
  assert(D1ptr!=0);
  assert(D2ptr!=0);

  D1ptr->nml.normalize();
  D2ptr->nml.normalize();

  point3<double> p0;
  point3<double> p1;
  diskintersection->nuke();
  if (D1ptr->intersects(p0,p1,*D2ptr))
  {
    diskintersection->push(new gobjglPushAttrib(GL_LIGHTING));
    diskintersection->push(new gobjglPushAttrib(GL_CURRENT_BIT));
    diskintersection->push(new gobjglDisable(GL_LIGHTING));
    diskintersection->push(new gobjglColor3ub(255,0,0));

    diskintersection->push(new gobjglBegin(GL_LINES));
    diskintersection->push(new gobjglVertex3d(p0));
    diskintersection->push(new gobjglVertex3d(p1));
    diskintersection->push(new gobjglEnd());

    diskintersection->push(new gobjglPopAttrib());
    diskintersection->push(new gobjglPopAttrib());
  }

  
  D1cd->axis = D1ptr->nml;
  D2cd->axis = D2ptr->nml;
  D1cd->center = D1ptr->pos;
  D2cd->center = D2ptr->pos;
  D2cd->radiusset(D2ptr->radius);

}

void diskinttest::keyboard03(unsigned char key, int x, int y)
{
  static double delta = 0.1;
  static bool mode=true;

  switch (key)
  {
    case 27:
      exit(0);
      break;

    case 't':
      transparency += delta;
      unitbound(transparency);
      break;
    case 'T':
      transparency -= delta;
      unitbound(transparency);
      break;

    case 'j': 
      if (mode)
        C1ptr->pos.x += delta; 
      else
        C1ptr->nml.x += delta;
    break;
    case 'J': 
      if (mode)
        C1ptr->pos.x -= delta; 
      else
        C1ptr->nml.x -= delta;
    break;
    case 'k': 
      if (mode)
        C1ptr->pos.y += delta; 
      else
        C1ptr->nml.y += delta;
      break;
    case 'K': 
      if (mode)
        C1ptr->pos.y -= delta; 
      else
        C1ptr->nml.y -= delta;
      break;
    case 'l': 
      if (mode)
        C1ptr->pos.z += delta; 
      else
        C1ptr->nml.z += delta;
      break;
    case 'L': 
      if (mode)
        C1ptr->pos.z -= delta; 
      else
        C1ptr->nml.z -= delta;
      break;
    
    case '+': delta *= 10.0; if (delta==0.0) delta=0.1; break;
    case '-': delta /= 10.0; break;

    case 'm': mode = !mode; break;

    case 'p':
      cout << (stringc)*C1ptr << endl;
      cout << (stringc)*C2ptr << endl;
      break;

    case 'x': C1ptr->radius += delta; break;
    case 'X': C1ptr->radius -= delta; break;

    case 'h': if (help!=0) *help = !*help; break;
  }

  update03();
  glutPostRedisplay();
}


void diskinttest::update03lineline()
{
  if (C1ptr==0)
    return;
  if (C2ptr==0)
    return;
  if (D1cd==0)
    return;
  if (D2cd==0)
    return;

  if (zero<double>::test(C1ptr->height))
    return;
  if (zero<double>::test(C2ptr->height))
    return;



  
  typedef point3<double> pt3;
  line<pt3,double> L1(C1ptr->nml,C1ptr->pos);
  L1.normalize(C1ptr->height);
  line<pt3,double> L2(C2ptr->nml,C2ptr->pos);
  L2.normalize(C2ptr->height);

  double t1;
  double t2;
  bool res;
  res = L1.lineD3minimized(t1,t2,L2);

  if (res==false)
    return;

  unitbound(t1);
  unitbound(t2);

  disk D1(L1.nml,L1(t1),C1ptr->radius);
  disk D2(L2.nml,L2(t2),C2ptr->radius);

  D1cd->center = D1.pos;
  D1cd->axis = D1.nml;
  D1cd->radiusset(D1.radius);
  D2cd->center = D2.pos;
  D2cd->axis = D2.nml;
  D2cd->radiusset(D2.radius);


  // Infinite cylinders intersection case only.
  point3<double> p0;
  point3<double> p1;
  cylinderscollide = D1.intersects(p0,p1,D2);


  cylinderintersection->push(new gobjglPushAttrib(GL_LIGHTING));
  cylinderintersection->push(new gobjglPushAttrib(GL_CURRENT_BIT));

  cylinderintersection->push(new gobjglDisable(GL_LIGHTING) );

  cylinderintersection->push(new gobjglBegin(GL_LINES));

  // Write the minimal line.
  //cylinderintersection->push(
  //  new gobjglColor4ub(127,255,212,(GLubyte)(floor(transparency*256))));
  cylinderintersection->push( new gobjglColor3ub(127,255,212));
  cylinderintersection->push(new gobjglVertex3d(L1(t1)));
  cylinderintersection->push(new gobjglVertex3d(L2(t2)));


  // Write cylinder1's axis.
  //cylinderintersection->push(
  //  new gobjglColor4ub(255,20,147,(GLubyte)(floor(transparency*256))));
  cylinderintersection->push( new gobjglColor3ub(255,20,147));
  cylinderintersection->push(new gobjglVertex3d(L1(0.0)));
  cylinderintersection->push(new gobjglVertex3d(L1(1.0)));

  // Write cylinder2's axis.
  //cylinderintersection->push(
  //  new gobjglColor4ub(255,140,0,(GLubyte)(floor(transparency*256))));
  cylinderintersection->push( new gobjglColor3ub(255,140,0));
  cylinderintersection->push(new gobjglVertex3d(L2(0.0)));
  cylinderintersection->push(new gobjglVertex3d(L2(1.0)));

  cylinderintersection->push(new gobjglEnd());

  cylinderintersection->push(new gobjglPopAttrib());
  cylinderintersection->push(new gobjglPopAttrib());







}


void diskinttest::update03()
{
  assert(C1ptr!=0);
  assert(C1cd!=0);
  assert(C2ptr!=0);
  assert(C2cd!=0);
  assert(cylinderintersection!=0);



  C1cd->baseRadius = C1ptr->radius;
  C1cd->topRadius = C1ptr->radius;
  C1cd->height = C1ptr->height;

  C2cd->baseRadius = C2ptr->radius;
  C2cd->topRadius = C2ptr->radius;
  C2cd->height = C2ptr->height;

  static uint c1=0;
  static uint c2=0;

  if (c1!=0)
    cylinderintersection->kill(c1);
  if (c2!=0)
    cylinderintersection->kill(c2);

  cylinderintersection->nuke();

  update03lineline();

  cylinderintersection->push(new gobjglEnable(GL_BLEND));
  cylinderintersection->push(
    new gobjglBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA) );


  cylinderintersection->push(new gobjglPushMatrix());

  cylinderintersection->push(new gobjglTranslated(C1ptr->pos));

  double th1;
  point3<double> axis;

  angles::rotateaboutaxis(th1,axis,C1ptr->nml);

  th1 *= angles::radtodeg;
  cylinderintersection->push(new gobjglRotated(th1,axis.x,axis.y,axis.z));

  cylinderintersection->push(new myaxes(1.0));
  if (cylinderscollide==false)
    cylinderintersection->push(
      new gobjglColor4f(0.0,0.0,1.0,transparency));
  else
    cylinderintersection->push(
      new gobjglColor4f(1.0,0.0,0.0,transparency));

  cylinderintersection->push(C1cd);
  c1 = cylinderintersection->vg.size()-1;

  cylinderintersection->push(new gobjglPopMatrix());


  cylinderintersection->push(new gobjglPushMatrix());

  cylinderintersection->push(new gobjglTranslated(C2ptr->pos));
  angles::rotateaboutaxis(th1,axis,C2ptr->nml);
  th1 *= angles::radtodeg;
  cylinderintersection->push(new gobjglRotated(th1,axis.x,axis.y,axis.z));

  cylinderintersection->push(new myaxes(1.0));

  if (cylinderscollide==false)
    cylinderintersection->push(
      new gobjglColor4f(0.0,1.0,1.0,transparency));
  else
    cylinderintersection->push(
      new gobjglColor4f(1.0,0.0,0.0,transparency));

  cylinderintersection->push(C2cd);
  c2 = cylinderintersection->vg.size()-1;

  cylinderintersection->push(new gobjglPopMatrix());



  cylinderintersection->push(new gobjglDisable(GL_BLEND));




}



void diskinttest::test03(int & argc, char** argv)
{
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(800,600);
  glutCreateWindow("");
  glutDisplayFunc(display01);
  glutKeyboardFunc(keyboard03);

  OpenGLinitialisation();

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glEnable(GL_NORMALIZE);

  zpr zz;

  xGraphics.set();
  gobjpush(new myaxes(1.0));

  commandline cmd(argc,argv);

  string in;
  cmd.mapvar(in,"in");
  if ( in.empty() )
  {
    cout << "error: in=filename expected" << endl;
    return;
  }

  ifstream filein(in.c_str());
  assert(filein.good()==true);
  if (filein.good()==false)
    return;

  point3<double> nml;
  point3<double> pos;
  double radius;
  double height;

  filein >> nml.x;
  filein >> nml.y;
  filein >> nml.z;
  filein >> pos.x;
  filein >> pos.y;
  filein >> pos.z;
  filein >> radius;
  filein >> height;

  cylinder C1(nml,pos,radius,height);

  filein >> nml.x;
  filein >> nml.y;
  filein >> nml.z;
  filein >> pos.x;
  filein >> pos.y;
  filein >> pos.z;
  filein >> radius;
  filein >> height;

  cylinder C2(nml,pos,radius,height);

//cout << (stringc)C1 << endl;
//cout << (stringc)C2 << endl;


  typedef point2<double> pt2;
  typedef point3<double> pt3;


  GLUquadricObj * quad = gluNewQuadric();
  C1cd = new 
    gobjgluCylinder(quad,C1.radius,C1.radius,C1.height,20,20);
  C2cd = new 
    gobjgluCylinder(quad,C2.radius,C2.radius,C2.height,20,20);
  C1ptr = & C1;
  C2ptr = & C2;

  gobjMyCircle * cir = new gobjMyCircle();
  gobjpush(cir);
  gobjMyCircleDraw * cird;
  cird = new gobjMyCircleDraw(C1.radius,C1.pos,C1.nml,*cir);
  gobjpush(cird);
  D1cd = cird;
  cird = new gobjMyCircleDraw(C2.radius,C2.pos,C2.nml,*cir);
  gobjpush(cird);
  D2cd = cird;



  cylinderintersection = new gobjContainer(true);
  gobjpush(cylinderintersection);


  menusystem * menu = 
    new menusystem(0,0,true,point2<GLint>(60,30),20);

  gobjSwitch<> * menuswitch = new gobjSwitch<>(menu,true);
  help = & menuswitch->isdrawn;
  gobjpush(menuswitch);

  menu->addfont12("Cylinder and Cylinder Intersection");
  menu->addnewline();
  menu->addfont12("STATUS:  Not complete - in progress.");
  menu->addnewline();
  menu->addnewline();
  menu->addfont10("'j' 'J' x position/normal"); 
  menu->addnewline();
  menu->addfont10("'k' 'K' y position/normal"); 
  menu->addnewline();
  menu->addfont10("'l' 'L' z position/normal"); 
  menu->addnewline();
  menu->addfont10("'m'  Toggle position/normal mode");
  menu->addnewline();
  menu->addfont10("'p'  Print the two cylinders to terminal.");
  menu->addnewline();
  menu->addfont10("'t'  Adjust the transparency.");
  menu->addnewline();
  menu->addfont10("'h'  Toggle this help menu.");
  menu->addnewline();
  menu->addnewline();
  menu->addfont10("ESC      Quit");


  update03();

  zz.update();
  glutMainLoop();
}




