#include "GModelIO_F.h"
#include "Draw.h"
#include "Options.h"
#include "Context.h"
#include "SelectBuffer.h"
#include "GUI_Projection.h"
#include "GUI_Extras.h"
#include "FFace.h"

extern GModel *GMODEL;
extern Context_T CTX;

#if defined(HAVE_FOURIER_MODEL)

#include "FPatch.h"
#include "PlaneProjectionSurface.h"
#include "ParaboloidProjectionSurface.h"
#include "CylindricalProjectionSurface.h"
#include "RevolvedParabolaProjectionSurface.h"

static FProjectionFace *createProjectionFaceFromName(char *name)
{
  int tag = GMODEL->numFace() + 1;
  FProjectionFace *f = 0;
  if(!strcmp(name, "plane"))
    f = new FProjectionFace(GMODEL, tag, new PlaneProjectionSurface(tag));
  else if(!strcmp(name, "paraboloid"))
    f = new FProjectionFace(GMODEL, tag, new ParaboloidProjectionSurface(tag));
  else if(!strcmp(name, "cylinder"))
    f = new FProjectionFace(GMODEL, tag, new CylindricalProjectionSurface(tag));
  else if(!strcmp(name, "revolvedParabola"))
    f = new FProjectionFace(GMODEL, tag, new RevolvedParabolaProjectionSurface(tag));
  else
    Msg(GERROR, "Unknown projection face `%s'", name);
  if(f){
    f->setVisibility(false);
    GMODEL->add(f);
  }
  return f;
}

uvPlot::uvPlot(int x, int y, int w, int h, const char *l)
  : Fl_Window(x, y, w, h, l), _dmin(0.), _dmax(0.)
{
  ColorTable_InitParam(2, &_colorTable);
  ColorTable_Recompute(&_colorTable);
}

void uvPlot::set(std::vector<double> &u, std::vector<double> &v, 
		 std::vector<double> &dist, std::vector<std::complex<double> > &f)
{ 
  _u = u; 
  _v = v;
  _dist = dist;
  _f = f;
  if(dist.empty()){
    _dmin = _dmax = 0.;
  }
  else{
    _dmin = _dmax = dist[0];
    for(unsigned int i = 1; i < dist.size(); i++){
      _dmin = std::min(_dmin, dist[i]);
      _dmax = std::max(_dmax, dist[i]);
    }
  }
  redraw();
}

void uvPlot::color(double d)
{
  int index;
  if(_dmin == _dmax)
    index = _colorTable.size / 2;
  else
    index = (int)((d - _dmin) * (_colorTable.size - 1) / (_dmax - _dmin));
  unsigned int color = _colorTable.table[index];
  int r = CTX.UNPACK_RED(color);
  int g = CTX.UNPACK_GREEN(color);
  int b = CTX.UNPACK_BLUE(color);
  fl_color(r, g, b);
}

void uvPlot::draw()
{
  // draw background
  fl_color(FL_WHITE);
  fl_rectf(0, 0, w(), h());

  // draw points in u,v space, colored by their distance to the
  // projection surface
  int pw = w();
  int ph = h() - (2 * GetFontSize() + 5);
  for(unsigned int i = 0; i < _u.size(); i++){
    int x = (int)(_u[i] * pw);
    int y = (int)(_v[i] * ph);
    color(_dist[i]);
    fl_rect(x, y, 3, 3);
  }

  // draw color bar
  for(int i = 0; i < w(); i++){
    int index = (int)(i * (_colorTable.size - 1) / w());
    unsigned int color = _colorTable.table[index];
    int r = CTX.UNPACK_RED(color);
    int g = CTX.UNPACK_GREEN(color);
    int b = CTX.UNPACK_BLUE(color);
    fl_color(r, g, b);
    fl_line(i, ph, i, ph + 10);
  }

  // draw labels
  fl_color(FL_BLACK);
  fl_font(FL_HELVETICA, GetFontSize());
  static char min[256], max[256], pts[256];
  sprintf(min, "%g", _dmin);
  sprintf(max, "%g", _dmax);
  sprintf(pts, "[%d pts]", _u.size());
  fl_draw(min, 5, h() - 5);
  fl_draw(pts, pw / 2 - (int)fl_width(pts) / 2, h() - 5);
  fl_draw(max, pw - (int)fl_width(max) - 5, h() - 5);
}

projection::projection(FProjectionFace *f, int x, int y, int w, int h, int BB, int BH, 
		       projectionEditor *e) 
  : face(f)
{
  group = new Fl_Scroll(x, y, w, h);
  SBoundingBox3d bounds = GMODEL->bounds();
  ProjectionSurface *ps = f->GetProjectionSurface();
  currentParams = new double[ps->GetNumParameters() + 9];
  for(int i = 0; i < ps->GetNumParameters() + 9; i++){
    Fl_Value_Input *v = new Fl_Value_Input(x, y + i * BH, BB, BH);
    if(i < 3){ // scaling
      currentParams[i] = 1.;
      v->maximum(CTX.lc * 10.);
      v->minimum(CTX.lc / 100.);
      v->step(CTX.lc / 100.);
      v->label((i == 0) ? "X scale" : (i == 1) ? "Y scale" : "Z scale");
      v->value(currentParams[i]);
    }
    else if(i < 6){ //rotation
      currentParams[i] = 0.;
      v->maximum(-180.);
      v->minimum(180.);
      v->step(0.1);
      v->label((i == 3) ? "X rotation" : (i == 4) ? "Y rotation" : "Z rotation");
      v->value(currentParams[i]);
    }
    else if(i < 9){ // translation
      currentParams[i] = bounds.center()[i - 6];
      v->maximum(bounds.max()[i] + 10. * CTX.lc);
      v->minimum(bounds.min()[i] - 10. * CTX.lc);
      v->step(CTX.lc / 100.);
      v->label((i == 6) ? "X translation" : (i == 7) ? "Y translation" : 
	       "Z translation");
      v->value(currentParams[i]);
    }
    else{ // other parameters
      currentParams[i] = ps->GetParameter(i - 9);
      v->maximum(10. * CTX.lc);
      v->minimum(-10. * CTX.lc);
      v->step(CTX.lc / 100.);
      v->label(strdup(ps->GetLabel(i - 9).c_str()));
      v->value(currentParams[i]);
    }
    ps->SetOrigin(currentParams[6], currentParams[7], currentParams[8]);
    v->align(FL_ALIGN_RIGHT);
    v->callback(update_cb, e);
    parameters.push_back(v);
  }
  group->end();
  group->hide();
}

projectionEditor::projectionEditor() 
{
  // construct GUI in terms of standard sizes
  const int BH = 2 * GetFontSize() + 1, BB = 7 * GetFontSize(), WB = 7;
  const int width = (int)(3.5 * BB), height = 24 * BH;
  
  // create all widgets (we construct this once, we never deallocate!)
  _window = new Dialog_Window(width, height, "Reparameterize");
  
  new Fl_Box(WB, WB + BH, BB / 2, BH, "Select:");
  
  Fl_Group *o = new Fl_Group(WB, WB, 2 * BB, 3 * BH);
  _select[0] = 
    new Fl_Round_Button(2 * WB + BB / 2, WB, BB, BH, "Points");
  _select[0]->value(1);
  _select[1] = 
    new Fl_Round_Button(2 * WB + BB / 2, WB + BH, BB, BH, "Elements");
  _select[2] = 
    new Fl_Round_Button(2 * WB + BB / 2, WB + 2 * BH, BB, BH, "Surfaces");
  for(int i = 0; i < 3; i++){
    _select[i]->callback(select_cb, this);
    _select[i]->type(FL_RADIO_BUTTON);
  }
  o->end();

  {  
    Fl_Toggle_Button *b1 = new Fl_Toggle_Button
      (width - WB - 3 * BB / 2, WB, 3 * BB / 2, BH, "Hide unselected");
    b1->callback(hide_cb);
    Fl_Button *b2 = new Fl_Button
      (width - WB - 3 * BB / 2, WB + BH, 3 * BB / 2, BH, "Save selection");
    b2->callback(save_selection_cb, this);
  }

  const int brw = (int)(1.25 * BB);

  _browser = new Fl_Hold_Browser(WB, 2 * WB + 3 * BH, brw, 5 * BH);
  _browser->callback(browse_cb, this);

  _paramWin[0] = 2 * WB + brw;
  _paramWin[1] = 2 * WB + 3 * BH;
  _paramWin[2] = width - 3 * WB - brw;
  _paramWin[3] = 6 * BH;
  _paramWin[4] = BB;
  _paramWin[5] = BH;

  {
    Fl_Button *b1 = new Fl_Button(WB, 2 * WB + 8 * BH, brw / 2, BH, "Load");
    b1->callback(load_projection_cb, this);
    Fl_Button *b2 = new Fl_Button(WB + brw / 2, 2 * WB + 8 * BH, brw / 2, BH, "Save");
    b2->callback(save_projection_cb, this);
  }

  int hard = 8;
  int uvw = width - 2 * WB - 2 * hard - 3 * WB;
  int uvh = height - 8 * WB - 14 * BH - 2 * hard;

  hardEdges[0] = new Fl_Toggle_Button(WB, 3 * WB + 9 * BH + hard, 
				      hard, uvh);
  hardEdges[1] = new Fl_Toggle_Button(width - 4 * WB - hard, 3 * WB + 9 * BH + hard,
				      hard, uvh);
  hardEdges[2] = new Fl_Toggle_Button(WB + hard, 3 * WB + 9 * BH, 
				      uvw, hard);
  hardEdges[3] = new Fl_Toggle_Button(WB + hard, height - 5 * WB - 5 * BH - hard, 
				      uvw, hard);
  for(int i = 0; i < 4; i++)
    hardEdges[i]->tooltip("Push to mark edge as `hard'");

  _uvPlot = new uvPlot(WB + hard, 3 * WB + 9 * BH + hard, uvw, uvh);
  _uvPlot->end();

  Fl_Slider *s = new Fl_Slider(width - 3 * WB, 3 * WB + 9 * BH + hard, 2 * WB, uvh);
  s->minimum(1.);
  s->maximum(0.);
  s->value(1.);
  s->callback(filter_cb, this);
  s->tooltip("Filter selection by distance to projection surface");
  
  modes[0] = new Fl_Value_Input(WB, height - 4 * WB - 5 * BH, BB  / 2, BH);
  modes[0]->tooltip("Number of Fourier modes along u");
  modes[1] = new Fl_Value_Input(WB + BB / 2, height - 4 * WB - 5 * BH, BB  / 2, BH, 
				"Fourier modes");
  modes[1]->tooltip("Number of Fourier modes along v");
  modes[2] = new Fl_Value_Input(WB, height - 4 * WB - 4 * BH, BB  / 2, BH);
  modes[2]->tooltip("Number of Chebyshev modes along u");
  modes[3] = new Fl_Value_Input(WB + BB / 2, height - 4 * WB - 4 * BH, BB  / 2, BH, 
				"Chebyshev modes");
  modes[3]->tooltip("Number of Chebyshev modes along v");
  for(int i = 0; i < 4; i++){
    modes[i]->value(8);
    modes[i]->maximum(128);
    modes[i]->minimum(1);
    modes[i]->step(1);
    modes[i]->align(FL_ALIGN_RIGHT);
  }    

  {
    Fl_Button *b = new Fl_Button(width - WB - BB, height - 4 * WB - 5 * BH, 
				  BB, 2 * BH, "Generate\nPatch");
    b->callback(compute_cb, this);
  }

  {
    int bb = (int)(0.37 * BB);
    new Fl_Box(WB, height - 3 * WB - 3 * BH, BB / 2, BH, "Delete:");
    Fl_Button *b1 = new Fl_Button(WB + BB / 2, height - 3 * WB - 3 * BH, 
				  bb, BH, "last");
    b1->callback(action_cb, (void*)"delete_last");
    Fl_Button *b2 = new Fl_Button(WB + BB / 2 + bb, height - 3 * WB - 3 * BH,
				  bb, BH, "all");
    b2->callback(action_cb, (void*)"delete_all");
    Fl_Button *b3 = new Fl_Button(WB + BB / 2 + 2 * bb, height - 3 * WB - 3 * BH,
				  bb, BH, "sel.");
    b3->callback(action_cb, (void*)"delete_select");
  }

  {
    int bb = (int)(0.37 * BB);
    int s = width - WB - BB / 2 - 3 * bb;
    new Fl_Box(s, height - 3 * WB - 3 * BH, BB / 2, BH, "Save:");
    Fl_Button *b1 = new Fl_Button(s + BB / 2, height - 3 * WB - 3 * BH,
				  bb, BH, "last");
    b1->callback(action_cb, (void*)"save_last");
    Fl_Button *b2 = new Fl_Button(s + BB / 2 + bb, height - 3 * WB - 3 * BH,
				  bb, BH, "all");
    b2->callback(action_cb, (void*)"save_all");
    Fl_Button *b3 = new Fl_Button(s + BB / 2 + 2 * bb, height - 3 * WB - 3 * BH,
				  bb, BH, "sel.");
    b3->callback(action_cb, (void*)"save_select");
  }

  {
    Fl_Button *b1 = new Fl_Button(WB, height - 2 * WB - 2 * BH, 
				  BB, BH, "Blend");
    Fl_Button *b2 = new Fl_Button(2 * WB + BB, height - 2 * WB - 2 * BH, 
				  BB, BH, "Intersect");
  }

  Fl_Button *b = new Fl_Button(width - WB - BB, height - WB - BH, BB, BH, "Cancel");
  b->callback(close_cb, _window);
  
  _window->end();
  _window->hotspot(_window);
  _window->resizable(_uvPlot);
  _window->size_range(width, (int)(0.85 * height));
}

void projectionEditor::load(FProjectionFace *face, std::string tag)
{
  ProjectionSurface *ps = face->GetProjectionSurface();
  _browser->add(tag.size() ? tag.c_str() : ps->GetName().c_str());
  projection *p =  new projection(face, _paramWin[0], _paramWin[1], _paramWin[2],
				  _paramWin[3], _paramWin[4], _paramWin[5], this);
  _projections.push_back(p);
  _window->add(p->group);
}

int projectionEditor::getSelectionMode() 
{ 
  if(_select[0]->value())
    return ENT_POINT;
  else if(_select[2]->value())
    return ENT_SURFACE;
  return ENT_ALL;
}

projection *projectionEditor::getCurrentProjection()
{
  for(int i = 1; i <= _browser->size(); i++)
    if(_browser->selected(i)) return _projections[i - 1];
  return 0;
}

projection *projectionEditor::getLastProjection()
{
  return _projections[_projections.size() - 1];
}

void browse_cb(Fl_Widget *w, void *data)
{
  projectionEditor *e = (projectionEditor*)data;

  std::vector<projection*> &projections(e->getProjections());
  for(unsigned int i = 0; i < projections.size(); i++){
    projections[i]->face->setVisibility(false);
    projections[i]->group->hide();
  }
  
  projection *p = e->getCurrentProjection();
  if(p){
    p->face->setVisibility(true);
    p->group->show();
  }

  update_cb(0, data);
}

void update_cb(Fl_Widget *w, void *data)
{
  projectionEditor *e = (projectionEditor*)data;

  // get all parameters from GUI and modify projection surface accordingly

  projection *p = e->getCurrentProjection();
  if(p){
    ProjectionSurface *ps = p->face->GetProjectionSurface();
    ps->Rescale(p->parameters[0]->value() / p->currentParams[0],
		p->parameters[1]->value() / p->currentParams[1],
		p->parameters[2]->value() / p->currentParams[2]);
    ps->Rotate(p->parameters[3]->value() - p->currentParams[3],
	       p->parameters[4]->value() - p->currentParams[4],
	       p->parameters[5]->value() - p->currentParams[5]);
    ps->Translate(p->parameters[6]->value() - p->currentParams[6],
		  p->parameters[7]->value() - p->currentParams[7],
		  p->parameters[8]->value() - p->currentParams[8]);
    for (int i = 0; i < 9; i++)
      p->currentParams[i] = p->parameters[i]->value();
    for (int i = 9; i < 9 + ps->GetNumParameters(); i++)
      ps->SetParameter(i - 9, p->parameters[i]->value());
    p->face->computeGraphicsRep(64, 64); // FIXME: hardcoded for now!
   
    // project all selected points and update u,v display
    std::vector<double> u, v, dist;
    std::vector<std::complex<double> > f;
    std::vector<GEntity*> &ent(e->getEntities());
    for(unsigned int i = 0; i < ent.size(); i++){
      if(ent[i]->getSelection()){
	GVertex *gv = dynamic_cast<GVertex*>(ent[i]);
	if(!gv)
	  Msg(GERROR, "Problem in point selection processing");
	else{
	  double uu, vv, p[3], n[3];
	  ps->OrthoProjectionOnSurface(gv->x(), gv->y(), gv->z(), uu, vv);
	  if(uu >= 0. && uu <= 1. && vv >= 0. && vv <= 1.){
	    ps->F(uu, vv, p[0], p[1], p[2]);
	    ps->GetUnitNormal(uu, vv, n[0], n[1], n[2]);
	    double dx = gv->x() - p[0], dy = gv->y() - p[1], dz = gv->z() - p[2];
	    u.push_back(uu);
	    v.push_back(vv);
	    dist.push_back(sqrt(dx * dx + dy * dy + dz * dz));
	    f.push_back(dx * n[0] + dy * n[1] + dz * n[2]);
	  }
	}
      }
    }
    // deal with elements here
    e->uv()->set(u, v, dist, f);
  }

  Draw();
}

void select_cb(Fl_Widget *w, void *data)
{
  projectionEditor *e = (projectionEditor*)data;

  int what = e->getSelectionMode();
  char *str;

  switch(what){
  case ENT_ALL: CTX.pick_elements = 1; str = "Elements"; break;
  case ENT_POINT: CTX.pick_elements = 0; str = "Points"; break;
  case ENT_SURFACE: CTX.pick_elements = 0; str = "Surfaces"; break;
  default: return;
  }

  std::vector<GVertex*> vertices;
  std::vector<GEdge*> edges;
  std::vector<GFace*> faces;
  std::vector<GRegion*> regions;
  std::vector<MElement*> elements;

  std::vector<MElement*> &ele(e->getElements());
  std::vector<GEntity*> &ent(e->getEntities());

  while(1) {
    CTX.mesh.changed = ENT_ALL;
    Draw();

    if(ele.size() || ent.size())
      Msg(ONSCREEN, "Select %s\n"
	  "[Press 'e' to end selection, 'u' to undo last selection or 'q' to abort]", str);
    else
      Msg(ONSCREEN, "Select %s\n"
	  "[Press 'e' to end selection or 'q' to abort]", str);

    char ib = SelectEntity(what, vertices, edges, faces, regions, elements);
    if(ib == 'l') {
      if(CTX.pick_elements){
	for(unsigned int i = 0; i < elements.size(); i++){
	  if(elements[i]->getVisibility() != 2){
	    elements[i]->setVisibility(2); ele.push_back(elements[i]);
	  }
	}
      }
      else{
	for(unsigned int i = 0; i < vertices.size(); i++){
	  if(vertices[i]->getSelection() != 1){
	    vertices[i]->setSelection(1); ent.push_back(vertices[i]);
	  }
	}
	for(unsigned int i = 0; i < faces.size(); i++){
	  if(faces[i]->getSelection() != 1){
	    faces[i]->setSelection(1); ent.push_back(faces[i]);
	  }
	}
      }
    }
    if(ib == 'r') {
      if(CTX.pick_elements){
	for(unsigned int i = 0; i < elements.size(); i++)
	  elements[i]->setVisibility(1);
      }
      else{
	for(unsigned int i = 0; i < vertices.size(); i++)
	  vertices[i]->setSelection(0);
	for(unsigned int i = 0; i < faces.size(); i++)
	  faces[i]->setSelection(0);
      }
    }
    if(ib == 'u') {
      if(CTX.pick_elements){
	if(ele.size()){
	  ele[ele.size() - 1]->setVisibility(1); ele.pop_back();
	}
      }
      else{
	if(ent.size()){
	  ent[ent.size() - 1]->setSelection(0); ent.pop_back();
	}
      }
    }
    if(ib == 'e') {
      ZeroHighlight();
      ele.clear();
      ent.clear();
    }
    if(ib == 'q') {
      ZeroHighlight();
      ele.clear();
      ent.clear();
      break;
    }
    update_cb(0, data);
  }

  CTX.mesh.changed = ENT_ALL;
  CTX.pick_elements = 0;
  Draw();  
  Msg(ONSCREEN, "");
}

void filter_cb(Fl_Widget *w, void *data)
{
  Fl_Slider *slider = (Fl_Slider*)w;
  projectionEditor *e = (projectionEditor*)data;
  projection *p = e->getCurrentProjection();
  if(p){
    SBoundingBox3d bbox = GMODEL->bounds();
    double lc = norm(SVector3(bbox.max(), bbox.min()));
    double threshold = slider->value() * lc;
    ProjectionSurface *ps = p->face->GetProjectionSurface();
    std::vector<GEntity*> &ent(e->getEntities());
    for(unsigned int i = 0; i < ent.size(); i++){
      GVertex *gv = dynamic_cast<GVertex*>(ent[i]);
      if(gv){
	double uu, vv, p[3], n[3];
	ps->OrthoProjectionOnSurface(gv->x(), gv->y(), gv->z(), uu, vv);
	ps->F(uu, vv, p[0], p[1], p[2]);
	double dx = gv->x() - p[0], dy = gv->y() - p[1], dz = gv->z() - p[2];
	if(uu >= 0. && uu <= 1. && vv >= 0. && vv < 1. &&
	   sqrt(dx * dx + dy * dy + dz * dz) < threshold)
	  gv->setSelection(true);
	else
	  gv->setSelection(false);
      }
    }
    // deal with elements here
  }
  update_cb(0, data);
}

void close_cb(Fl_Widget *w, void *data)
{
  if(data) ((Fl_Window *) data)->hide();
}

void hide_cb(Fl_Widget *w, void *data)
{
  CTX.hide_unselected = !CTX.hide_unselected;
  CTX.mesh.changed = ENT_ALL;
  Draw();
}

void save_selection_cb(Fl_Widget *w, void *data)
{
  projectionEditor *e = (projectionEditor*)data;
  std::vector<GEntity*> &ent(e->getEntities());
  if(file_chooser(0, 1, "Save Selection", "*.geo")){
    FILE *fp = fopen(file_chooser_get_name(1), "w");
    if(!fp){
      Msg(GERROR, "Unable to open file `%s'", file_chooser_get_name(1));
      return;
    }
    // maybe we should save as mesh file
    for(unsigned int i = 0; i < ent.size(); i++){
      GVertex *gv = dynamic_cast<GVertex*>(ent[i]);
      if(gv && gv->getSelection())
	fprintf(fp, "Point(%d) = {%.16g,%.16g,%.16g,1};\n", gv->tag(), 
		gv->x(), gv->y(), gv->z());
    }
    // deal with elements here
    fclose(fp);
  }
}

void load_projection_cb(Fl_Widget *w, void *data)
{
  projectionEditor *e = (projectionEditor*)data;
  if(file_chooser(0, 0, "Load Projection", "*.pro")){
    FILE *fp = fopen(file_chooser_get_name(1), "r");
    if(!fp){
      Msg(GERROR, "Unable to open file `%s'", file_chooser_get_name(1));
      return;
    }
    int num;
    if(!fscanf(fp, "%d", &num)){
      Msg(GERROR, "Bad projection file format");
      return;
    }
    for(int proj = 0; proj < num; proj++){
      char name[256], tag[256];
      if(!fscanf(fp, "%s", tag) || !fscanf(fp, "%s", name)){
	Msg(GERROR, "Bad projection file format");
	return;
      }
      FProjectionFace *face = createProjectionFaceFromName(name);
      if(face){
	e->load(face, tag);
	projection *p = e->getLastProjection();
	if(p){
	  for(unsigned int i = 0; i < p->parameters.size(); i++){
	    double val;
	    if(!fscanf(fp, "%lf", &val)){
	      Msg(GERROR, "Missing paramater for projection `%s'", name);
	      break;
	    }
	    p->parameters[i]->value(val);
	  }
	}
      }
    }
    fclose(fp);
    update_cb(0, data);
  }
}

void save_projection_cb(Fl_Widget *w, void *data)
{
  projectionEditor *e = (projectionEditor*)data;
  projection *p = e->getCurrentProjection();
  if(p){
    ProjectionSurface *ps = p->face->GetProjectionSurface();
    if(file_chooser(0, 1, "Save Projection", "*.pro")){
      FILE *fp = fopen(file_chooser_get_name(1), "w");
      if(!fp){
	Msg(GERROR, "Unable to open file `%s'", file_chooser_get_name(1));
	return;
      }
      fprintf(fp, "1\n%s\n%s\n", ps->GetName().c_str(), ps->GetName().c_str());
      for(unsigned int i = 0; i < p->parameters.size(); i++)
	fprintf(fp, "%.16g\n", p->parameters[i]->value());
      fclose(fp);
    }
  }
}

void compute_cb(Fl_Widget *w, void *data)
{
  projectionEditor *e = (projectionEditor*)data;

  projection *p = e->getCurrentProjection();
  if(p){
    // get the projection data
    std::vector<double> u, v, dist;
    std::vector<std::complex<double> > f;
    e->uv()->get(u, v, dist, f);
    if(f.empty()) return;

    int uModes = (int)e->modes[0]->value();
    int vModes = (int)e->modes[1]->value();

    if(f.size() < uModes * vModes){
      Msg(GERROR, "Number of points < uModes * vModes");
      return;
    }

    int uM = (int)e->modes[2]->value();
    int vM = (int)e->modes[3]->value();
    int h0 = e->hardEdges[0]->value();
    int h1 = e->hardEdges[1]->value();
    int h2 = e->hardEdges[2]->value();
    int h3 = e->hardEdges[3]->value();

    // create the Fourier faces (with boundaries)
    ProjectionSurface *ps = p->face->GetProjectionSurface();
    if(ps->IsUPeriodic()) {
      Patch* patchL = new FPatch(0, ps->clone(), u, v, f, 3, uModes, vModes,
				 uM, vM, h0, h1, h2, h3);
      patchL->SetMinU(-0.35);
      patchL->SetMaxU(0.35);
      makeGFace(patchL);
      Patch* patchR = new FPatch(0, ps->clone(), u, v, f, 3, uModes, vModes,
				 uM, vM, h0, h1, h2, h3);
      patchR->SetMinU(0.15);
      patchR->SetMaxU(0.85);
      makeGFace(patchR);
    }
    else if (ps->IsVPeriodic()) {
      Patch* patchL = new FPatch(0, ps->clone(), u, v, f, 3, uModes, vModes, 
				 uM, vM, h0, h1, h2, h3);
      patchL->SetMinV(-0.35);
      patchL->SetMaxV(0.35);
      makeGFace(patchL);
      Patch* patchR = new FPatch(0, ps->clone(), u, v, f, 3, uModes, vModes,
				 uM, vM, h0, h1, h2, h3);
      patchR->SetMinV(0.15);
      patchR->SetMaxV(0.85);
      makeGFace(patchR);
    }
    else {
      Patch* patch = new FPatch(0, ps->clone(), u, v, f, 3, uModes, vModes, 
				uM, vM, h0, h1, h2, h3);
      makeGFace(patch);
    }
  }

  Draw();
}

void delete_fourier(GFace *gf)
{
  if(gf->getNativeType() != GEntity::FourierModel) return;

  // don't actually delete the data so we can add `undo' later
  std::list<GVertex*> vertices = gf->vertices();
  for(std::list<GVertex*>::iterator it = vertices.begin(); it != vertices.end(); it++)
    GMODEL->remove(*it);

  std::list<GEdge*> edges = gf->edges();
  for(std::list<GEdge*>::iterator it = edges.begin(); it != edges.end(); it++)
    GMODEL->remove(*it);

  GMODEL->remove(gf);
}

void action_cb(Fl_Widget *w, void *data)
{
  std::string what((char*)data);
  std::vector<GFace*> faces;

  if(what == "delete_last" || what == "save_last"){
    int id = -1;
    for(GModel::fiter it = GMODEL->firstFace(); it != GMODEL->lastFace(); it++)
      if((*it)->getNativeType() == GEntity::FourierModel) 
	id = std::max(id, (*it)->tag());
    if(id > 0) faces.push_back(GMODEL->faceByTag(id));
  }
  else if(what == "delete_all" || what == "save_all"){
    for(GModel::fiter it = GMODEL->firstFace(); it != GMODEL->lastFace(); it++)
      if((*it)->getNativeType() == GEntity::FourierModel)
	faces.push_back(*it);
  }
  else if(what == "delete_select" || what == "save_select"){
    Msg(ONSCREEN, "Select Surface\n[Press 'e' to end selection 'q' to abort]");
    std::vector<GVertex*> vertices;
    std::vector<GEdge*> edges;
    std::vector<GFace*> faces;
    std::vector<GRegion*> regions;
    std::vector<MElement*> elements;
    char ib = SelectEntity(ENT_SURFACE, vertices, edges, faces, regions, elements);
    if(ib == 'l') faces.insert(faces.end(), faces.begin(), faces.end());
    Msg(ONSCREEN, "");
  }

  if(what[0] == 'd'){
    for(unsigned int i = 0; i < faces.size(); i++) 
      delete_fourier(faces[i]);
  }
  else{
    char *filename = "patches.fm";
    FILE *fp = fopen(filename, "w+");
    if(!fp){
      printf("Unable to open file '%s'\n", filename);
      return;
    }
    fprintf(fp, "%d\n", (int)faces.size());
    for(unsigned int i = 0; i < faces.size(); i++){
      FFace* ff = (FFace*)faces[i];
      ff->GetFMFace()->GetPatch()->Export(fp);
    }
    fclose(fp);
  }

  Draw();
}

void mesh_parameterize_cb(Fl_Widget* w, void* data)
{
  // display geometry surfaces
  opt_geometry_surfaces(0, GMSH_SET | GMSH_GUI, 1);

  // create the (static) editor
  static projectionEditor *editor = 0;
  if(!editor){
    editor = new projectionEditor();
    editor->load(createProjectionFaceFromName("plane"));
    editor->load(createProjectionFaceFromName("paraboloid"));
    editor->load(createProjectionFaceFromName("cylinder"));
    editor->load(createProjectionFaceFromName("revolvedParabola"));
  }
  editor->show();
}

#else

void mesh_parameterize_cb(Fl_Widget* w, void* data)
{
  Msg(GERROR, "You must compile FourierModel to reparameterize meshes");
}

#endif