#define DEG_TO_RAD (M_PI/180.0)

#include "WorldBuilder.hxx"

using namespace std;

WorldBuilder::WorldBuilder() {
  GUID = 1;
  lattice = "cubic";
  fillSolid = false;

  cursor = new Point3D(0,0,CatomSim::catom_radius);
  legalcursor = false;
  mode=-1;
  scrollmode=0;
  cz=CatomSim::catom_radius;

  modelReq.done = true;
  GUIDChangeReq=false;
  
  catomColor.r = 200;
  catomColor.g = 200;
  catomColor.b = 200;

  clearReq = false;
}


WorldBuilder::~WorldBuilder() {
  delete cursor;
}


void WorldBuilder::setColor(int r, int g, int b)
{
  catomColor.r = r/256;
  catomColor.g = g/256;
  catomColor.b = b/256;
}

void WorldBuilder::setWorld(CatomWorld* wp) {
  worldPtr = wp;
}

void WorldBuilder::setScrollMode(int m){
  scrollmode = m;
}

void WorldBuilder::setLattice(string l) {
  lattice = l;
}

void WorldBuilder::setMode(int i) {
  mode = i;
}

void WorldBuilder::setFillStyle(bool solid){
  fillSolid = solid;
}


bool WorldBuilder::buttonPressed(unsigned int button, int x, int y) {
  if(button == Button1) {
    unsigned long int target;
    switch (mode) {
    case 0:
      if(legalcursor && worldPtr->catomAt(*cursor) == 0){
	
	CatomSim *c = new CatomSim(GUID,*cursor,0,0,0,catomColor.r,catomColor.g,catomColor.b,0);
	worldPtr->addCatom(c, GUID);
	delete c;
	
	GUID++;

	/*	list<Point3D> neighbors = latticeNeighbors(*cursor, lattice);
	list<Point3D>::iterator n_i;
	for(n_i = neighbors.begin(); n_i != neighbors.end(); n_i++){
	  CatomSim *c = new CatomSim(GUID,*n_i,0,0,0,catomColor.r,catomColor.g,catomColor.b,0);
	  worldPtr->addCatom(c, GUID);
	  delete c;
	  GUID++;
	}
	*/
	moveCursor();
	char msg[100];
	sprintf(msg, "%d", worldPtr->num_catoms);
	update_widget("catomCountLabel", "GtkLabel", msg);
	return true;
      }
      break;
    case 1:
      target = worldPtr->catomAt(*cursor);
      if(target != 0){
	worldPtr->deleteCatom(target);
	moveCursor();
	char msg[100];
	sprintf(msg, "%d", worldPtr->num_catoms);
	update_widget("catomCountLabel", "GtkLabel", msg);
	return true;
      }
      break;
    case 2:
      unsigned long int target;
      target = worldPtr->catomAt(*cursor);
      if(target == 0){
	update_widget("GUIDEntry", "GtkEntry",  "");
      }
      else{
	char l[100];
	sprintf(l, "%lu", target);
	update_widget("GUIDEntry", "GtkEntry", l);
      }
      moveCursor();
      return true;
      break;
    }
    return true;
  }
  else if(button == Button5 && mode != -1 && scrollmode == 1) {
    cz+=CatomSim::catom_radius;
    moveCursor();
    return true;
  }
  else if(button == Button4 && mode != -1 && scrollmode == 1) {
    cz-=CatomSim::catom_radius;
    if(cz<CatomSim::catom_radius) cz=CatomSim::catom_radius;
    moveCursor();
    return true;
    }

  return false;
}


bool WorldBuilder::mouseMoved(int x, int y){
  cx=x;
  cy=y;
  moveCursor();
  return false;
}


void WorldBuilder::moveCursor(){
  legalcursor = true;
  if(mode==-1) return;
  float cpos[4];
  float cvec[4];  
  convert2Dto3D(cx, cy, cpos, cvec);
  
  float x = cpos[0];
  float y = cpos[1];
  float z = cpos[2];

  float dx = cvec[0];
  float dy = cvec[1];
  float dz = cvec[2];
  
  const dReal *dpos;
  Catom *ctm = NULL;
  float md;
  float dist_sq = -1.0f;
  
  Point3D projection;
  Point3D minproj;
  float t;
  float mindist;
 
  hash_map<const unsigned long, CatomSim *, hash<const unsigned long>, equl>::iterator c_i;
  for (c_i = worldPtr->catomHash.begin(); c_i != worldPtr->catomHash.end(); c_i++ ) {
    dpos = dGeomGetPosition(c_i->second->shape);
    Point3D point(dpos[0], dpos[1], dpos[2]);
   
    //we will project the point onto the line segment
    //then we will measure the distance from the point to the projection
    //find the projected length by dividing the dotproduct by the length
    //find the param by dividing by length again
    
    Point3D linevector(dx, dy, dz);
    Point3D initial(x, y, z);
    double param = linevector.dot(point -initial)/pow(linevector.getR(),2);
  
    Point3D projection(dx*param + x, dy*param + y, dz*param + z);
    
    md = point.distanceFrom(projection);

    if (md < CatomSim::catom_radius) {
      t = (x-point.getX())*(x-point.getX()) + (y-point.getY())*(y-point.getY()) + (z-point.getZ())*(z-point.getZ());
      if ((c_i->first!=GUID) && (t < dist_sq || dist_sq < 0.0f)) {
	dist_sq = t;
	ctm = &(c_i->second->C);
	minproj = projection;
	mindist = md;
      }
    }
  }
  
  if (ctm && (pow(minproj.getZ()-z, 2)<pow(cz-z, 2))) {       
    Point3D base(ctm->getLocation());
    Point3D exact(base);
    
    if(mode == 0){
      if(mindist>.5*CatomSim::catom_radius){
	x=minproj.getX();
	y=minproj.getY();
	z=minproj.getZ();
      }
      dist_sq = -1.0f;
      list<Point3D> neighbors = latticeNeighbors(base, lattice);
      list<Point3D>::iterator n_i;
      for(n_i = neighbors.begin(); n_i != neighbors.end(); n_i++){
	t = (n_i->getX()-x)*(n_i->getX()-x) + (n_i->getY()-y)*(n_i->getY()-y) + (n_i->getZ()-z)*(n_i->getZ()-z);
	if ((t < dist_sq || dist_sq < 0.0f) && (worldPtr->catomAt(*n_i) == 0) && (n_i->getZ() > 0)) {
	  dist_sq = t;
	  exact = *n_i;
	}
      }
    }
    cursor->setData(exact);  
  }
  else if (cvec[2]!=0) {
    double param = (cz-cpos[2])/cvec[2];
    if(param >= 0){
      Point3D exact(cpos[0]+param*cvec[0], cpos[1]+param*cvec[1], cpos[2]+param*cvec[2]); 
      cursor->setData(snapToLattice(exact, lattice));
    }
    else {
      legalcursor=false;
    }
  }

}


void WorldBuilder::updateBuilder() {

  //if a clear req has been made, clear world
  if (clearReq){
    worldPtr->clearCatoms();
    clearReq=false;
    char msg2[100];
    sprintf(msg2, "%d", worldPtr->num_catoms);
    update_widget("catomCountLabel", "GtkLabel", msg2);    
  }

  //If a model request has been made, process it
  if (!modelReq.done) {
    modelReq.done = true;

    if(modelReq.filename != ""){
      list<Point3D>model = (loadModel(modelReq.filename, modelReq.size, lattice, modelReq.precision, modelReq.solid));
      list<Point3D>::iterator iter;
      int s = model.size();
      
      //progress bar
      int bar = 0;
      int counter =0;
      char msg4[50];
      sprintf(msg4, "Checking %d catoms for overlaps...", s);
      update_widget("catomizeBar", "GtkProgressBar", msg4);
      
      iter=model.begin();
      while(iter!=model.end()) {
	Point3D loc = *iter;
	
	if(modelReq.flipYZ){
	  double temp = loc.getZ();
	  loc.setZ(loc.getY());
	  loc.setY(temp);
	}
	
	if(modelReq.atCursor && legalcursor){
	  Point3D correction(CatomSim::catom_radius, CatomSim::catom_radius*3, CatomSim::catom_radius);
	  loc+= *cursor - correction;
	}

	if(worldPtr->catomAt(loc)){
	  list<Point3D>::iterator temp = iter;
	  iter++;
	  model.erase(temp);
	}
	else{
	  iter++;
	}
	counter++;
	
	//progress bar
	while(int(counter/(s/100+1))>bar+1) {
	  bar++;
	  update_widget("catomizeBar", "GtkProgressBar", ((float)counter)/((float)s));
	} 
      } 
      update_widget("catomizeBar", "GtkProgressBar", 0);
      update_widget("catomizeBar", "GtkProgressBar", "");

      s = model.size();

      //progress bar
      bar = 0;
      counter = 0;

      char msg[50];
      sprintf(msg, "Loading %d catoms into world", s);
      update_widget("catomizeBar", "GtkProgressBar", msg);

      for(iter=model.begin();iter!=model.end();iter++) {

	Point3D loc = *iter;
	
	if(modelReq.flipYZ){
	  double temp = loc.getZ();
	  loc.setZ(loc.getY());
	  loc.setY(temp);
	}

	if(modelReq.atCursor && legalcursor){
	  Point3D correction(CatomSim::catom_radius, CatomSim::catom_radius*3, CatomSim::catom_radius);
	  loc+= *cursor - correction;
	}
	
	CatomSim *c = new CatomSim(GUID,loc,0,0,0,catomColor.r,catomColor.g,catomColor.b,0);
	worldPtr->addCatom(c, GUID);
	GUID++;
	delete c;

	counter++;

	//progress bar
	while(int(counter/(s/100+1))>bar+1) {
	  bar++;
	  update_widget("catomizeBar", "GtkProgressBar", ((float)counter)/((float)s));
	} 
      } 
      update_widget("catomizeBar", "GtkProgressBar", 0);
      update_widget("catomizeBar", "GtkProgressBar", "");
      char msg3[100];
      sprintf(msg3, "%d", worldPtr->num_catoms);
      update_widget("catomCountLabel", "GtkLabel", msg3);
    }
  }
  
  //If a GUID change has been requested, change it
  if(GUIDChangeReq) {
    unsigned long int target;
    GUIDChangeReq=false;
    target = worldPtr->catomAt(*cursor);
    if(target != 0){
      CatomSim* c(worldPtr->getCatom(target));
      CatomSim* old(worldPtr->getCatom(newGUID));
      if(old){
	if(display_gtk_yes_no_box("This GUID is already used. Click 'Yes' to cause the catoms to trade GUIDs; click 'No' to cancel.")){
	  worldPtr->swap(target, newGUID);
	}
      }
      else {
	worldPtr->addCatom(c, newGUID);
	worldPtr->deleteCatom(target);
	if(GUID<=newGUID){
	  GUID = newGUID+1;
	}
      }
    }
  }
  
  //update Gtk cursor position readout
  if(legalcursor){
    char msg[100];
    sprintf(msg, "(%.1f, %.1f, %.1f)", cursor->getX(), cursor->getY(), cursor->getZ());
    update_widget("cursorPos", "GtkLabel", msg);
  }
  else{
    update_widget("cursorPos", "GtkLabel", "");
  }
  
  //draw cursor/shadow on screen
  glPushMatrix();
  glTranslatef(cursor->getX(), cursor->getY(), cursor->getZ());
  glEnable (GL_NORMALIZE);
  glShadeModel (GL_SMOOTH);
  
  switch (mode) {
  case -1:
    dsSetColorAlpha(0,0,0,0);
    break;
  case 0:
    dsSetColorAlpha(0, 1, 0, .75);
    break;
  case 1:
    dsSetColorAlpha(1, 0, 0, .5);
    break;
  case 2:
    dsSetColorAlpha(0, 0, 1, .5);
    break;
  }

  setupDrawingMode();

  glScaled (1.1,1.1,1.1);
  drawSphere();
  glScaled (1/1.1,1/1.1,1/1.1);
  glTranslatef(0, 0, -cursor->getZ());
  glScaled (.5,.5,.5);
  dsSetColorAlpha(0, 0, 0, .2);
  drawSphere();

  glPopMatrix();
  glDisable (GL_NORMALIZE);
}


void WorldBuilder::import(string filename, int size, float precision, bool flipYZ, bool atCursor){
  //set up a model request
  //(to be processed when the main thread updates the builder)
  modelReq.done = false;
  modelReq.filename = filename;
  modelReq.size = size;
  modelReq.precision = precision;
  modelReq.solid = fillSolid;
  modelReq.flipYZ = flipYZ;
  modelReq.atCursor = atCursor;
}


void WorldBuilder::requestGUIDChange(unsigned long int newg){
  GUIDChangeReq=true;
  newGUID=newg;
}


void WorldBuilder::clearAll(){
  clearReq = true;
}


void WorldBuilder::saveWorldAs(string filename){
 
  // Open the file for writing
  worldPtr->myFile.fileOpen((char*)filename.c_str(), true );

  // iterate through the catom objects and save them to the file
  hash_map<const unsigned long, CatomSim *, 
    hash<const unsigned long>, equl>::iterator catom_i;

  vector<CodeModule*>::iterator mod_i;

  worldPtr->myFile.fileWrite_ModuleHeader();
  worldPtr->myFile.fileWrite_BodyStart();

  for(catom_i = worldPtr->catomHash.begin(); catom_i != worldPtr->catomHash.end(); catom_i++) {

    // Get the GUID
    uint32 id = catom_i->first;

    // _sim version 1 ( Simple )
    assert( catom_i->second->updateFileModule( worldPtr->myModule->getContent(), 
					       worldPtr->myModule->getVersion() ) );

    // TODO: tell the catom codemodules to all update their StateFile::Modules
    for ( mod_i = catom_i->second->codeModules.begin(); 
	  mod_i != catom_i->second->codeModules.end(); 
	  mod_i++ ) {
      (*mod_i)->saveModule();
    }

    // Write the new content to the file
    worldPtr->myFile.fileWrite_Content( id );
  }

  worldPtr->myFile.fileClose();    
}
