// Gmsh - Copyright (C) 1997-2011 C. Geuzaine, J.-F. Remacle
//
// See the LICENSE.txt file for license information. Please report all
// bugs and problems to <gmsh@geuz.org>.

#include <stdlib.h>
#include <string.h>
#include "GmshMessage.h"
#include "Numeric.h"
#include "Geo.h"
#include "GModel.h"
#include "GeoInterpolation.h"
#include "Context.h"

#if defined(HAVE_MESH)
#include "Field.h"
#endif

static List_T *ListOfTransformedPoints = NULL;

// Comparison routines

int compareVertex(const void *a, const void *b)
{
  Vertex *q = *(Vertex **)a;
  Vertex *w = *(Vertex **)b;
  return abs(q->Num) - abs(w->Num);
}

static int comparePosition(const void *a, const void *b)
{
  Vertex *q = *(Vertex **)a;
  Vertex *w = *(Vertex **)b;

  // Warning: tolerance! (before 1.61, it was set to 1.e-10 * CTX::instance()->lc)
  double eps = CTX::instance()->geom.tolerance * CTX::instance()->lc; 

  if(q->Pos.X - w->Pos.X > eps) return 1;
  if(q->Pos.X - w->Pos.X < -eps) return -1;
  if(q->Pos.Y - w->Pos.Y > eps) return 1;
  if(q->Pos.Y - w->Pos.Y < -eps) return -1;
  if(q->Pos.Z - w->Pos.Z > eps) return 1;
  if(q->Pos.Z - w->Pos.Z < -eps) return -1;
  return 0;
}

static int compareSurfaceLoop(const void *a, const void *b)
{
  SurfaceLoop *q = *(SurfaceLoop **)a;
  SurfaceLoop *w = *(SurfaceLoop **)b;
  return q->Num - w->Num;
}

static int compareEdgeLoop(const void *a, const void *b)
{
  EdgeLoop *q = *(EdgeLoop **)a;
  EdgeLoop *w = *(EdgeLoop **)b;
  return q->Num - w->Num;
}

static int compareCurve(const void *a, const void *b)
{
  Curve *q = *(Curve **)a;
  Curve *w = *(Curve **)b;
  return q->Num - w->Num;
}

static int compareSurface(const void *a, const void *b)
{
  Surface *q = *(Surface **)a;
  Surface *w = *(Surface **)b;
  return q->Num - w->Num;
}

static int compareVolume(const void *a, const void *b)
{
  Volume *q = *(Volume **)a;
  Volume *w = *(Volume **)b;
  return q->Num - w->Num;
}

static int compareLevelSet(const void *a, const void *b)
{
  LevelSet *q = *(LevelSet **)a;
  LevelSet *w = *(LevelSet **)b;
  return q->Num - w->Num;
}

static int comparePhysicalGroup(const void *a, const void *b)
{
  PhysicalGroup *q = *(PhysicalGroup **)a;
  PhysicalGroup *w = *(PhysicalGroup **)b;
  int cmp = q->Typ - w->Typ;
  if(cmp)
    return cmp;
  else
    return q->Num - w->Num;
}

void Projette(Vertex *v, double mat[3][3])
{
  double X = v->Pos.X * mat[0][0] + v->Pos.Y * mat[0][1] + v->Pos.Z * mat[0][2];
  double Y = v->Pos.X * mat[1][0] + v->Pos.Y * mat[1][1] + v->Pos.Z * mat[1][2];
  double Z = v->Pos.X * mat[2][0] + v->Pos.Y * mat[2][1] + v->Pos.Z * mat[2][2];
  v->Pos.X = X;
  v->Pos.Y = Y;
  v->Pos.Z = Z;
}

// Basic entity creation/deletion functions

Vertex *Create_Vertex(int Num, double X, double Y, double Z, double lc, double u)
{
  Vertex *pV = new Vertex(X, Y, Z, lc);
  pV->w = 1.0;
  pV->Num = Num;
  GModel::current()->getGEOInternals()->MaxPointNum = 
    std::max(GModel::current()->getGEOInternals()->MaxPointNum, Num);
  pV->u = u;
  pV->geometry = 0;
  return pV;
}

Vertex *Create_Vertex(int Num, double u, double v, gmshSurface *surf, double lc)
{
  SPoint3 p = surf->point(u, v);
  Vertex *pV = new Vertex(p.x(), p.y(), p.z(), lc);
  pV->w = 1.0;
  pV->Num = Num;
  GModel::current()->getGEOInternals()->MaxPointNum = 
    std::max(GModel::current()->getGEOInternals()->MaxPointNum, Num);
  pV->u = u;
  pV->geometry = surf;
  pV->pntOnGeometry = SPoint2(u, v);
  surf->vertex_defined_on_surface = true;
  return pV;
}

static void Free_Vertex(void *a, void *b)
{
  Vertex *v = *(Vertex **)a;
  if(v) {
    delete v;
    v = NULL;
  }
}

PhysicalGroup *Create_PhysicalGroup(int Num, int typ, List_T *intlist)
{
  PhysicalGroup *p = new PhysicalGroup;
  p->Entities = List_Create(List_Nbr(intlist), 1, sizeof(int));
  p->Num = Num;
  GModel::current()->getGEOInternals()->MaxPhysicalNum = 
    std::max(GModel::current()->getGEOInternals()->MaxPhysicalNum, Num);
  p->Typ = typ;
  p->Visible = 1;
  for(int i = 0; i < List_Nbr(intlist); i++) {
    int j;
    List_Read(intlist, i, &j);
    List_Add(p->Entities, &j);
  }
  return p;
}

static void Free_PhysicalGroup(void *a, void *b)
{
  PhysicalGroup *p = *(PhysicalGroup **)a;
  if(p) {
    List_Delete(p->Entities);
    delete p;
    p = NULL;
  }
}

EdgeLoop *Create_EdgeLoop(int Num, List_T *intlist)
{
  EdgeLoop *l = new EdgeLoop;
  l->Curves = List_Create(List_Nbr(intlist), 1, sizeof(int));
  l->Num = Num;
  GModel::current()->getGEOInternals()->MaxLineLoopNum = 
    std::max(GModel::current()->getGEOInternals()->MaxLineLoopNum, Num);
  for(int i = 0; i < List_Nbr(intlist); i++) {
    int j;
    List_Read(intlist, i, &j);
    List_Add(l->Curves, &j);
  }
  return l;
}

static void Free_EdgeLoop(void *a, void *b)
{
  EdgeLoop *l = *(EdgeLoop **)a;
  if(l) {
    List_Delete(l->Curves);
    delete l;
    l = NULL;
  }
}

SurfaceLoop *Create_SurfaceLoop(int Num, List_T *intlist)
{
  SurfaceLoop *l = new SurfaceLoop;
  l->Surfaces = List_Create(List_Nbr(intlist), 1, sizeof(int));
  l->Num = Num;
  GModel::current()->getGEOInternals()->MaxSurfaceLoopNum = 
    std::max(GModel::current()->getGEOInternals()->MaxSurfaceLoopNum, Num);
  for(int i = 0; i < List_Nbr(intlist); i++) {
    int j;
    List_Read(intlist, i, &j);
    List_Add(l->Surfaces, &j);
  }
  return l;
}

static void Free_SurfaceLoop(void *a, void *b)
{
  SurfaceLoop *l = *(SurfaceLoop **)a;
  if(l) {
    List_Delete(l->Surfaces);
    delete l;
    l = NULL;
  }
}

static void direction(Vertex *v1, Vertex *v2, double d[3])
{
  d[0] = v2->Pos.X - v1->Pos.X;
  d[1] = v2->Pos.Y - v1->Pos.Y;
  d[2] = v2->Pos.Z - v1->Pos.Z;
}

void End_Curve(Curve *c)
{
  // if all control points of a curve are on the same geometry, then
  // the curve is also on the geometry
  if(c->Control_Points){
    int NN = List_Nbr(c->Control_Points);
    Vertex *pV;
    List_Read (c->Control_Points, 0, &pV);
    c->geometry = pV->geometry;
    for(int i = 1; i < NN; i++){
      List_Read (c->Control_Points, i, &pV);
      if(c->geometry != pV->geometry){
        c->geometry = 0;
        break;
      } 
    }
  }
  
  c->degenerated = false;
  
  if(c->Typ == MSH_SEGM_CIRC || c->Typ == MSH_SEGM_CIRC_INV ||
     c->Typ == MSH_SEGM_ELLI || c->Typ == MSH_SEGM_ELLI_INV) {

    // v[0] = first point
    // v[1] = center
    // v[2] = last point
    // v[3] = major axis point
    Vertex *v[4];
    if(List_Nbr(c->Control_Points) == 4)
      List_Read(c->Control_Points, 2, &v[3]);
    else
      v[3] = NULL;

    if(c->Typ == MSH_SEGM_CIRC_INV || c->Typ == MSH_SEGM_ELLI_INV) {
      List_Read(c->Control_Points, 0, &v[2]);
      List_Read(c->Control_Points, 1, &v[1]);
      if(!v[3])
        List_Read(c->Control_Points, 2, &v[0]);
      else
        List_Read(c->Control_Points, 3, &v[0]);
    }
    else {
      List_Read(c->Control_Points, 0, &v[0]);
      List_Read(c->Control_Points, 1, &v[1]);
      if(!v[3])
        List_Read(c->Control_Points, 2, &v[2]);
      else
        List_Read(c->Control_Points, 3, &v[2]);
    }

    double dir12[3], dir32[3], dir42[3] = {0., 0. , 0.};
    direction(v[1], v[0], dir12);
    direction(v[1], v[2], dir32);
    if(v[3])
      direction(v[1], v[3], dir42);

    // v0 = vector center->first pt
    // v2 = vector center->last pt
    // v3 = vector center->major axis pt
    Vertex v0, v2, v3;
    v0.Pos.X = dir12[0];
    v0.Pos.Y = dir12[1];
    v0.Pos.Z = dir12[2];
    v2.Pos.X = dir32[0];
    v2.Pos.Y = dir32[1];
    v2.Pos.Z = dir32[2];
    if(v[3]) {
      v3.Pos.X = dir42[0];
      v3.Pos.Y = dir42[1];
      v3.Pos.Z = dir42[2];
    }
    norme(dir12);
    norme(dir32);
    double n[3];
    prodve(dir12, dir32, n);
    norme(n);
    // use provided plane if unable to compute it from input points...
    if(fabs(n[0]) < 1.e-5 && fabs(n[1]) < 1.e-5 && fabs(n[2]) < 1.e-5) {
      n[0] = c->Circle.n[0];
      n[1] = c->Circle.n[1];
      n[2] = c->Circle.n[2];
      norme(n);
    }
    double m[3];
    prodve(n, dir12, m);
    norme(m);

    double mat[3][3];
    mat[2][0] = c->Circle.invmat[0][2] = n[0];
    mat[2][1] = c->Circle.invmat[1][2] = n[1];
    mat[2][2] = c->Circle.invmat[2][2] = n[2];
    mat[1][0] = c->Circle.invmat[0][1] = m[0];
    mat[1][1] = c->Circle.invmat[1][1] = m[1];
    mat[1][2] = c->Circle.invmat[2][1] = m[2];
    mat[0][0] = c->Circle.invmat[0][0] = dir12[0];
    mat[0][1] = c->Circle.invmat[1][0] = dir12[1];
    mat[0][2] = c->Circle.invmat[2][0] = dir12[2];

    // assume circle in z=0 plane
    if(CTX::instance()->geom.oldCircle) {
      if(n[0] == 0.0 && n[1] == 0.0) {
        mat[2][0] = c->Circle.invmat[0][2] = 0;
        mat[2][1] = c->Circle.invmat[1][2] = 0;
        mat[2][2] = c->Circle.invmat[2][2] = 1;
        mat[1][0] = c->Circle.invmat[0][1] = 0;
        mat[1][1] = c->Circle.invmat[1][1] = 1;
        mat[1][2] = c->Circle.invmat[2][1] = 0;
        mat[0][0] = c->Circle.invmat[0][0] = 1;
        mat[0][1] = c->Circle.invmat[1][0] = 0;
        mat[0][2] = c->Circle.invmat[2][0] = 0;
      }
    }

    Projette(&v0, mat);
    Projette(&v2, mat);
    if(v[3])
      Projette(&v3, mat);

    double R = sqrt(v0.Pos.X * v0.Pos.X + v0.Pos.Y * v0.Pos.Y);
    double R2 = sqrt(v2.Pos.X * v2.Pos.X + v2.Pos.Y * v2.Pos.Y);

    if(!R || !R2){
      // check radius
      Msg::Error("Zero radius in Circle/Ellipse %d", c->Num);
    }
    else if(!v[3] && fabs((R - R2) / (R + R2)) > 0.1){
      // check cocircular pts (allow 10% error)
      Msg::Error("Control points of Circle %d are not cocircular %g %g",
                 c->Num, R, R2);
    }

    // A1 = angle first pt
    // A3 = angle last pt
    // A4 = angle major axis
    double A3, A1, A4;
    double f1, f2;
    if(v[3]) {
      A4 = myatan2(v3.Pos.Y, v3.Pos.X);
      A4 = angle_02pi(A4);
      double x1 = v0.Pos.X * cos(A4) + v0.Pos.Y * sin(A4);
      double y1 = -v0.Pos.X * sin(A4) + v0.Pos.Y * cos(A4);
      double x3 = v2.Pos.X * cos(A4) + v2.Pos.Y * sin(A4);
      double y3 = -v2.Pos.X * sin(A4) + v2.Pos.Y * cos(A4);
      double sys[2][2], rhs[2], sol[2];
      sys[0][0] = x1 * x1;
      sys[0][1] = y1 * y1;
      sys[1][0] = x3 * x3;
      sys[1][1] = y3 * y3;
      rhs[0] = 1;
      rhs[1] = 1;
      sys2x2(sys, rhs, sol);
      if(sol[0] <= 0 || sol[1] <= 0) {
        Msg::Error("Ellipse %d is wrong", c->Num);
        A1 = A3 = 0.;
        f1 = f2 = R;
      }
      else {
        f1 = sqrt(1. / sol[0]);
        f2 = sqrt(1. / sol[1]);
        // myasin() permet de contourner les problemes de precision
        // sur y1/f2 ou y3/f2, qui peuvent legerement etre hors de
        // [-1,1]
        if(x1 < 0)
          A1 = -myasin(y1 / f2) + A4 + M_PI;
        else
          A1 = myasin(y1 / f2) + A4;
        if(x3 < 0)
          A3 = -myasin(y3 / f2) + A4 + M_PI;
        else
          A3 = myasin(y3 / f2) + A4;
      }
    }
    else {
      A1 = myatan2(v0.Pos.Y, v0.Pos.X);
      A3 = myatan2(v2.Pos.Y, v2.Pos.X);
      A4 = 0.;
      f1 = f2 = R;
    }

    A1 = angle_02pi(A1);
    A3 = angle_02pi(A3);
    if(A1 >= A3)
      A3 += 2 * M_PI;

    c->Circle.t1 = A1;
    c->Circle.t2 = A3;
    c->Circle.incl = A4;
    c->Circle.f1 = f1;
    c->Circle.f2 = f2;

    if(!CTX::instance()->expertMode && c->Num > 0 && A3 - A1 > 1.01 * M_PI){
      Msg::Error("Circle or ellipse arc %d greater than Pi (angle=%g)", c->Num, A3-A1);
      Msg::Error("(If you understand what this implies, you can disable this error");
      Msg::Error("message by selecting `Enable expert mode' in the option dialog.");
      Msg::Error("Otherwise, please subdivide the arc in smaller pieces.)");
    }

  }
}

void End_Surface(Surface *s)
{
  // if all generatrices of a surface are on the same geometry, then
  // the surface is also on the geometry
  if(List_Nbr(s->Generatrices)){
    Curve *c;
    int NN = List_Nbr(s->Generatrices);
    List_Read (s->Generatrices, 0, &c);
    s->geometry = c->geometry;
    for (int i = 1; i < NN; i++){
      List_Read (s->Generatrices, i, &c);
      if (c->geometry != s->geometry){
        s->geometry = 0;
        break;
      } 
    }
  }
}

Curve *Create_Curve(int Num, int Typ, int Order, List_T *Liste,
                    List_T *Knots, int p1, int p2, double u1, double u2)
{
  double matcr[4][4] = { {-0.5, 1.5, -1.5, 0.5},
                         {1.0, -2.5, 2.0, -0.5},
                         {-0.5, 0.0, 0.5, 0.0},
                         {0.0, 1.0, 0.0, 0.0} };
  double matbs[4][4] = { {-1.0, 3, -3, 1},
                         {3, -6, 3.0, 0},
                         {-3, 0.0, 3, 0.0},
                         {1, 4, 1, 0.0} };
  double matbez[4][4] = { {-1.0, 3, -3, 1},
                          {3, -6, 3.0, 0},
                          {-3, 3.0, 0, 0.0},
                          {1, 0, 0, 0.0} };

  Curve *pC = new Curve;
  pC->Color.type = 0;
  pC->Visible = 1;
  pC->Extrude = NULL;
  pC->Typ = Typ;
  pC->Num = Num;
  pC->meshMaster = Num; 
  GModel::current()->getGEOInternals()->MaxLineNum = 
    std::max(GModel::current()->getGEOInternals()->MaxLineNum, Num);
  pC->Method = MESH_UNSTRUCTURED;
  pC->degre = Order;
  pC->Circle.n[0] = 0.0;
  pC->Circle.n[1] = 0.0;
  pC->Circle.n[2] = 1.0;
  pC->geometry = 0;
  pC->nbPointsTransfinite = 0;
  pC->typeTransfinite = 0;
  pC->coeffTransfinite = 0.;

  if(Typ == MSH_SEGM_SPLN) {
    for(int i = 0; i < 4; i++)
      for(int j = 0; j < 4; j++)
        pC->mat[i][j] = matcr[i][j];
  }
  else if(Typ == MSH_SEGM_BSPLN) {
    for(int i = 0; i < 4; i++)
      for(int j = 0; j < 4; j++)
        pC->mat[i][j] = matbs[i][j] / 6.0;
  }
  else if(Typ == MSH_SEGM_BEZIER) {
    for(int i = 0; i < 4; i++)
      for(int j = 0; j < 4; j++)
        pC->mat[i][j] = matbez[i][j];
  }

  pC->ubeg = u1;
  pC->uend = u2;

  if(Knots) {
    pC->k = new float[List_Nbr(Knots)];
    double kmin = .0, kmax = 1.;
    List_Read(Knots, 0, &kmin);
    List_Read(Knots, List_Nbr(Knots) - 1, &kmax);
    pC->ubeg = kmin;
    pC->uend = kmax;
    for(int i = 0; i < List_Nbr(Knots); i++) {
      double d;
      List_Read(Knots, i, &d);
      pC->k[i] = (float)d;
    }
  }
  else
    pC->k = NULL;

  if(Liste) {
    pC->Control_Points = List_Create(List_Nbr(Liste), 1, sizeof(Vertex *));
    for(int j = 0; j < List_Nbr(Liste); j++) {
      int iPnt;
      List_Read(Liste, j, &iPnt);
      Vertex *v;
      if((v = FindPoint(iPnt)))
        List_Add(pC->Control_Points, &v);
      else{
        Msg::Error("Unknown control point %d in Curve %d", iPnt, pC->Num);
      }
    }
  }
  else {
    pC->Control_Points = NULL;
    pC->beg = NULL;
    pC->end = NULL;
    return pC;
  }

  if(p1 < 0) {
    List_Read(pC->Control_Points, 0, &pC->beg);
    List_Read(pC->Control_Points, List_Nbr(pC->Control_Points) - 1, &pC->end);
  }
  else {
    Vertex *v;
    if((v = FindPoint(p1))) {
      Msg::Info("Curve %d first control point %d ", pC->Num, v->Num);
      pC->beg = v;
    }
    else {
      Msg::Error("Unknown control point %d in Curve %d", p1, pC->Num);
    }
    if((v = FindPoint(p2))) {
      Msg::Info("Curve %d first control point %d ", pC->Num, v->Num);
      pC->end = v;
    }
    else {
      Msg::Error("Unknown control point %d in Curve %d", p2, pC->Num);
    }
  }

  End_Curve(pC);

  return pC;
}

static void Free_Curve(void *a, void *b)
{
  Curve *pC = *(Curve **)a;
  if(pC) {
    delete [] pC->k;
    List_Delete(pC->Control_Points);
    delete pC;
    pC = NULL;
  }
}

Surface *Create_Surface(int Num, int Typ)
{
  Surface *pS = new Surface;
  pS->Color.type = 0;
  pS->Visible = 1;
  pS->Num = Num;
  pS->geometry = 0;
  pS->InSphereCenter = 0;
  pS->meshMaster = Num; 

  GModel::current()->getGEOInternals()->MaxSurfaceNum = 
    std::max(GModel::current()->getGEOInternals()->MaxSurfaceNum, Num);
  pS->Typ = Typ;
  pS->Method = MESH_UNSTRUCTURED;
  pS->Recombine = 0;
  pS->RecombineAngle = 45;
  pS->Recombine_Dir = -1;
  pS->TransfiniteSmoothing = -1;
  pS->TrsfPoints = List_Create(4, 4, sizeof(Vertex *));
  pS->Generatrices = NULL;
  pS->EmbeddedPoints = NULL;
  pS->EmbeddedCurves = NULL;
  pS->Extrude = NULL;
  pS->geometry = NULL;
  return (pS);
}

static void Free_Surface(void *a, void *b)
{
  Surface *pS = *(Surface **)a;
  if(pS) {
    List_Delete(pS->TrsfPoints);
    List_Delete(pS->Generatrices);
    List_Delete(pS->EmbeddedCurves);
    List_Delete(pS->EmbeddedPoints);
    delete pS;
    pS = NULL;
  }
}

Volume *Create_Volume(int Num, int Typ)
{
  Volume *pV = new Volume;
  pV->Color.type = 0;
  pV->Visible = 1;
  pV->Num = Num;
  GModel::current()->getGEOInternals()->MaxVolumeNum = 
    std::max(GModel::current()->getGEOInternals()->MaxVolumeNum, Num);
  pV->Typ = Typ;
  pV->Method = MESH_UNSTRUCTURED;
  pV->QuadTri = NO_QUADTRI;
  pV->TrsfPoints = List_Create(6, 6, sizeof(Vertex *));
  pV->Surfaces = List_Create(1, 2, sizeof(Surface *));
  pV->SurfacesOrientations = List_Create(1, 2, sizeof(int));
  pV->SurfacesByTag = List_Create(1, 2, sizeof(int));
  pV->Extrude = NULL;
  return pV;
}

static void Free_Volume(void *a, void *b)
{
  Volume *pV = *(Volume **)a;
  if(pV) {
    List_Delete(pV->TrsfPoints);
    List_Delete(pV->Surfaces);
    List_Delete(pV->SurfacesOrientations);
    List_Delete(pV->SurfacesByTag);
    delete pV;
    pV = NULL;
  }
}

LevelSet *Create_LevelSet(int Num, gLevelset *l)
{
  LevelSet *pL = new LevelSet;
  pL->Num = Num;
  pL->ls = l;
  return pL;
}

static void Free_LevelSet(void *a, void *b)
{
  LevelSet *pL = *(LevelSet **)a;
  if(pL) {
    delete pL;
    pL = NULL;
  }
}

int NEWPOINT(void)
{
  return (GModel::current()->getGEOInternals()->MaxPointNum + 1);
}

int NEWLINE(void)
{
  if(CTX::instance()->geom.oldNewreg)
    return NEWREG();
  else
    return (GModel::current()->getGEOInternals()->MaxLineNum + 1);
}

int NEWLINELOOP(void)
{
  if(CTX::instance()->geom.oldNewreg)
    return NEWREG();
  else
    return (GModel::current()->getGEOInternals()->MaxLineLoopNum + 1);
}

int NEWSURFACE(void)
{
  if(CTX::instance()->geom.oldNewreg)
    return NEWREG();
  else
    return (GModel::current()->getGEOInternals()->MaxSurfaceNum + 1);
}

int NEWSURFACELOOP(void)
{
  if(CTX::instance()->geom.oldNewreg)
    return NEWREG();
  else
    return (GModel::current()->getGEOInternals()->MaxSurfaceLoopNum + 1);
}

int NEWVOLUME(void)
{
  if(CTX::instance()->geom.oldNewreg)
    return NEWREG();
  else
    return (GModel::current()->getGEOInternals()->MaxVolumeNum + 1);
}

int NEWFIELD(void)
{
#if defined(HAVE_MESH)
  return (GModel::current()->getFields()->maxId() + 1);
#else
  return 0;
#endif
}

int NEWPHYSICAL(void)
{
  if(CTX::instance()->geom.oldNewreg)
    return NEWREG();
  else
    return (GModel::current()->getGEOInternals()->MaxPhysicalNum + 1);
}

int NEWREG(void)
{
  return (std::max(GModel::current()->getGEOInternals()->MaxLineNum,
            std::max(GModel::current()->getGEOInternals()->MaxLineLoopNum,
              std::max(GModel::current()->getGEOInternals()->MaxSurfaceNum,
                std::max(GModel::current()->getGEOInternals()->MaxSurfaceLoopNum,
                  std::max(GModel::current()->getGEOInternals()->MaxVolumeNum,
                           GModel::current()->getGEOInternals()->MaxPhysicalNum)))))
          + 1);
}

static int compare2Lists(List_T *List1, List_T *List2,
                         int (*fcmp) (const void *a, const void *b))
{
  int i, found;

  if(!List_Nbr(List1) && !List_Nbr(List2))
    return 0;

  if(!List_Nbr(List1) || !List_Nbr(List2) || 
     (List_Nbr(List1) != List_Nbr(List2)))
    return List_Nbr(List1) - List_Nbr(List2);
  
  List_T *List1Prime = List_Create(List_Nbr(List1), 1, List1->size);
  List_T *List2Prime = List_Create(List_Nbr(List2), 1, List2->size);
  List_Copy(List1, List1Prime);
  List_Copy(List2, List2Prime);
  List_Sort(List1Prime, fcmp);
  List_Sort(List2Prime, fcmp);

  for(i = 0; i < List_Nbr(List1Prime); i++) {
    found = fcmp(List_Pointer(List1Prime, i), List_Pointer(List2Prime, i));
    if(found != 0) {
      List_Delete(List1Prime);
      List_Delete(List2Prime);
      return found;
    }
  }
  List_Delete(List1Prime);
  List_Delete(List2Prime);
  return 0;
}

static Vertex *FindPoint(int inum, Tree_T *t)
{
  Vertex C, *pc;
  pc = &C;
  pc->Num = inum;
  if(Tree_Query(t, &pc)) {
    return pc;
  }
  return NULL;
}

Vertex *FindPoint(int inum)
{
  return FindPoint(inum, GModel::current()->getGEOInternals()->Points);
}

static Curve *FindCurve(int inum, Tree_T *t)
{
  Curve C, *pc;
  pc = &C;
  pc->Num = inum;
  if(Tree_Query(t, &pc)) {
    return pc;
  }
  return NULL;
}

Curve *FindCurve(int inum)
{
  return FindCurve(inum, GModel::current()->getGEOInternals()->Curves);
}

static Surface *FindSurface(int inum, Tree_T *t)
{
  Surface S, *ps;
  ps = &S;
  ps->Num = inum;
  if(Tree_Query(t, &ps)) {
    return ps;
  }
  return NULL;
}

Surface *FindSurface(int inum)
{
  return FindSurface(inum, GModel::current()->getGEOInternals()->Surfaces);
}

Volume *FindVolume(int inum)
{
  Volume V, *pv;
  pv = &V;
  pv->Num = inum;
  if(Tree_Query(GModel::current()->getGEOInternals()->Volumes, &pv)) {
    return pv;
  }
  return NULL;
}

LevelSet *FindLevelSet(int inum)
{
  LevelSet L, *pl;
  pl = &L;
  pl->Num = inum; 
  if(Tree_Query(GModel::current()->getGEOInternals()->LevelSets, &pl)) {
    return pl;
  }
  return NULL;
}

EdgeLoop *FindEdgeLoop(int inum)
{
  EdgeLoop S, *ps;
  ps = &S;
  ps->Num = inum;
  if(Tree_Query(GModel::current()->getGEOInternals()->EdgeLoops, &ps)) {
    return ps;
  }
  return NULL;
}

SurfaceLoop *FindSurfaceLoop(int inum)
{
  SurfaceLoop S, *ps;
  ps = &S;
  ps->Num = inum;
  if(Tree_Query(GModel::current()->getGEOInternals()->SurfaceLoops, &ps)) {
    return ps;
  }
  return NULL;
}

PhysicalGroup *FindPhysicalGroup(int num, int type)
{
  PhysicalGroup P, *pp, **ppp;
  pp = &P;
  pp->Num = num;
  pp->Typ = type;
  if((ppp = (PhysicalGroup **)
      List_PQuery(GModel::current()->getGEOInternals()->PhysicalGroups, &pp,
                  comparePhysicalGroup))) {
    return *ppp;
  }
  return NULL;
}

static void GetAllEntityNumbers(int dim, std::set<int> &nums)
{
  GModel *m = GModel::current();
  switch(dim){
  case 0:
    {
      List_T *l = Tree2List(m->getGEOInternals()->Points);
      Vertex *p;
      for(int i = 0; i < List_Nbr(l); i++){
        List_Read(l, i, &p);
        nums.insert(p->Num);
      }
      List_Delete(l);
      for(GModel::viter it = m->firstVertex(); it != m->lastVertex(); it++)
        nums.insert((*it)->tag());
    }
    break;
  case 1:
    {
      List_T *l = Tree2List(m->getGEOInternals()->Curves);
      Curve *p;
      for(int i = 0; i < List_Nbr(l); i++){
        List_Read(l, i, &p);
        if(p->Num >= 0)
          nums.insert(p->Num);
      }
      List_Delete(l);
      for(GModel::eiter it = m->firstEdge(); it != m->lastEdge(); it++){
        if((*it)->tag() >= 0)
          nums.insert((*it)->tag());
      }
    }
    break;
  case 2:
    {
      List_T *l = Tree2List(m->getGEOInternals()->Surfaces);
      Surface *p;
      for(int i = 0; i < List_Nbr(l); i++){
        List_Read(l, i, &p);
        nums.insert(p->Num);
      }
      List_Delete(l);
      for(GModel::fiter it = m->firstFace(); it != m->lastFace(); it++)
        nums.insert((*it)->tag());
    }
    break;
  case 3:
    {
      List_T *l = Tree2List(m->getGEOInternals()->Volumes);
      Volume *p;
      for(int i = 0; i < List_Nbr(l); i++){
        List_Read(l, i, &p);
        nums.insert(p->Num);
      }
      List_Delete(l);
      for(GModel::riter it = m->firstRegion(); it != m->lastRegion(); it++)
        nums.insert((*it)->tag());
    }
    break;
  }
}

List_T *GetAllEntityNumbers(int dim)
{
  std::set<int> nums;
  GetAllEntityNumbers(dim, nums);
  List_T *l = List_Create(nums.size(), 1, sizeof(double));
  for(std::set<int>::iterator it = nums.begin(); it != nums.end(); it++){
    double a = *it;
    List_Add(l, &a);
  }
  return l;
}

static void CopyVertex(Vertex *v, Vertex *vv)
{
  vv->lc = v->lc;
  vv->u = v->u;
  vv->Pos.X = v->Pos.X;
  vv->Pos.Y = v->Pos.Y;
  vv->Pos.Z = v->Pos.Z;
}

static Vertex *DuplicateVertex(Vertex *v)
{
  if(!v) return NULL;
  Vertex *pv = Create_Vertex(NEWPOINT(), 0, 0, 0, 0, 0);
  CopyVertex(v, pv);
  Tree_Insert(GModel::current()->getGEOInternals()->Points, &pv);
  return pv;
}

static int compareAbsCurve(const void *a, const void *b)
{
  Curve *q = *(Curve **)a;
  Curve *w = *(Curve **)b;
  return abs(q->Num) - abs(w->Num);
}

static void CopyCurve(Curve *c, Curve *cc, bool copyMeshingMethod)
{
  cc->Typ = c->Typ;
  if(copyMeshingMethod){
    cc->Method = c->Method;
    cc->nbPointsTransfinite = c->nbPointsTransfinite;
    cc->typeTransfinite = c->typeTransfinite;
    cc->coeffTransfinite = c->coeffTransfinite;
  }
  cc->l = c->l;
  for(int i = 0; i < 4; i++)
    for(int j = 0; j < 4; j++)
      cc->mat[i][j] = c->mat[i][j];
  cc->beg = c->beg;
  cc->end = c->end;
  cc->ubeg = c->ubeg;
  cc->uend = c->uend;
  cc->Control_Points = List_Create(List_Nbr(c->Control_Points), 1, sizeof(Vertex *));
  List_Copy(c->Control_Points, cc->Control_Points);
  End_Curve(cc);
}

static Curve *DuplicateCurve(Curve *c, bool copyMeshingMethod)
{
  Curve *pc = Create_Curve(NEWLINE(), 0, 1, NULL, NULL, -1, -1, 0., 1.);
  CopyCurve(c, pc, copyMeshingMethod);
  Tree_Insert(GModel::current()->getGEOInternals()->Curves, &pc);
  for(int i = 0; i < List_Nbr(c->Control_Points); i++) {
    Vertex *v;
    List_Read(pc->Control_Points, i, &v);
    Vertex *newv = DuplicateVertex(v);
    List_Write(pc->Control_Points, i, &newv);
  }
  pc->beg = DuplicateVertex(c->beg);
  pc->end = DuplicateVertex(c->end);
  CreateReversedCurve(pc);
  return pc;
}

static void CopySurface(Surface *s, Surface *ss, bool copyMeshingMethod)
{
  ss->Typ = s->Typ;
  if(copyMeshingMethod){
    ss->Method = s->Method;
    ss->Recombine = s->Recombine;
    ss->RecombineAngle = s->RecombineAngle;
    if(List_Nbr(s->TrsfPoints))
      Msg::Warning("Only automatic transfinite surface specifications can be copied");
  }
  ss->Generatrices = List_Create(List_Nbr(s->Generatrices), 1, sizeof(Curve *));
  ss->InSphereCenter = s->InSphereCenter; // FIXME: hack...
  List_Copy(s->Generatrices, ss->Generatrices);
  End_Surface(ss);
}

static Surface *DuplicateSurface(Surface *s, bool copyMeshingMethod)
{
  Surface *ps = Create_Surface(NEWSURFACE(), 0);
  CopySurface(s, ps, copyMeshingMethod);
  Tree_Insert(GModel::current()->getGEOInternals()->Surfaces, &ps);
  for(int i = 0; i < List_Nbr(ps->Generatrices); i++) {
    Curve *c;
    List_Read(ps->Generatrices, i, &c);
    Curve *newc = DuplicateCurve(c, copyMeshingMethod);
    List_Write(ps->Generatrices, i, &newc);
  }
  return ps;
}

static void CopyVolume(Volume *v, Volume *vv, bool copyMeshingMethod)
{
  vv->Typ = v->Typ;
  if(copyMeshingMethod){
    vv->Method = v->Method;
    vv->QuadTri = v->QuadTri;
    if(List_Nbr(v->TrsfPoints))
      Msg::Warning("Only automatic transfinite volume specifications can be copied");
  }
  List_Copy(v->Surfaces, vv->Surfaces);
  List_Copy(v->SurfacesOrientations, vv->SurfacesOrientations);
  List_Copy(v->SurfacesByTag, vv->SurfacesByTag);
}

static Volume *DuplicateVolume(Volume *v, bool copyMeshingMethod)
{
  Volume *pv = Create_Volume(NEWVOLUME(), 0);
  CopyVolume(v, pv, copyMeshingMethod);
  Tree_Insert(GModel::current()->getGEOInternals()->Volumes, &pv);
  for(int i = 0; i < List_Nbr(pv->Surfaces); i++) {
    Surface *s;
    List_Read(pv->Surfaces, i, &s);
    Surface *news = DuplicateSurface(s, copyMeshingMethod);
    List_Write(pv->Surfaces, i, &news);
  }
  return pv;
}

void CopyShape(int Type, int Num, int *New)
{
  Surface *s, *news;
  Curve *c, *newc;
  Vertex *v, *newv;
  Volume *vol, *newvol;

  switch (Type) {
  case MSH_POINT:
    if(!(v = FindPoint(Num))) {
      Msg::Error("Unknown vertex %d", Num);
      return;
    }
    newv = DuplicateVertex(v);
    *New = newv->Num;
    break;
  case MSH_SEGM_LINE:
  case MSH_SEGM_SPLN:
  case MSH_SEGM_BSPLN:
  case MSH_SEGM_BEZIER:
  case MSH_SEGM_CIRC:
  case MSH_SEGM_CIRC_INV:
  case MSH_SEGM_ELLI:
  case MSH_SEGM_ELLI_INV:
  case MSH_SEGM_NURBS:
    if(!(c = FindCurve(Num))) {
      Msg::Error("Unknown curve %d", Num);
      return;
    }
    newc = DuplicateCurve(c, CTX::instance()->geom.copyMeshingMethod);
    *New = newc->Num;
    break;
  case MSH_SURF_TRIC:
  case MSH_SURF_REGL:
  case MSH_SURF_PLAN:
    if(!(s = FindSurface(Num))) {
      Msg::Error("Unknown surface %d", Num);
      return;
    }
    news = DuplicateSurface(s, CTX::instance()->geom.copyMeshingMethod);
    *New = news->Num;
    break;
  case MSH_VOLUME:
    if(!(vol = FindVolume(Num))) {
      Msg::Error("Unknown volume %d", Num);
      return;
    }
    newvol = DuplicateVolume(vol, CTX::instance()->geom.copyMeshingMethod);
    *New = newvol->Num;
    break;
  default:
    Msg::Error("Impossible to copy entity %d (of type %d)", Num, Type);
    break;
  }
}

static void DeletePoint(int ip)
{
  Vertex *v = FindPoint(ip);
  if(!v)
    return;
  List_T *Curves = Tree2List(GModel::current()->getGEOInternals()->Curves);
  for(int i = 0; i < List_Nbr(Curves); i++) {
    Curve *c;
    List_Read(Curves, i, &c);
    for(int j = 0; j < List_Nbr(c->Control_Points); j++) {
      if(!compareVertex(List_Pointer(c->Control_Points, j), &v)){
        List_Delete(Curves);
        return;
      }
    }
  }
  List_Delete(Curves);
  if(v->Num == GModel::current()->getGEOInternals()->MaxPointNum)
    GModel::current()->getGEOInternals()->MaxPointNum--;
  Tree_Suppress(GModel::current()->getGEOInternals()->Points, &v);
  Free_Vertex(&v, NULL);
}

static void DeleteCurve(int ip)
{
  Curve *c = FindCurve(ip);
  if(!c)
    return;
  List_T *Surfs = Tree2List(GModel::current()->getGEOInternals()->Surfaces);
  for(int i = 0; i < List_Nbr(Surfs); i++) {
    Surface *s;
    List_Read(Surfs, i, &s);
    for(int j = 0; j < List_Nbr(s->Generatrices); j++) {
      if(!compareAbsCurve(List_Pointer(s->Generatrices, j), &c)){
        List_Delete(Surfs);
        return;
      }
    }
  }
  List_Delete(Surfs);
  if(c->Num == GModel::current()->getGEOInternals()->MaxLineNum)
    GModel::current()->getGEOInternals()->MaxLineNum--;
  Tree_Suppress(GModel::current()->getGEOInternals()->Curves, &c);
  Free_Curve(&c, NULL);
}

static void DeleteSurface(int is)
{
  Surface *s = FindSurface(is);
  if(!s)
    return;
  List_T *Vols = Tree2List(GModel::current()->getGEOInternals()->Volumes);
  for(int i = 0; i < List_Nbr(Vols); i++) {
    Volume *v;
    List_Read(Vols, i, &v);
    for(int j = 0; j < List_Nbr(v->Surfaces); j++) {
      if(!compareSurface(List_Pointer(v->Surfaces, j), &s)){
        List_Delete(Vols);
        return;
      }
    }
  }
  List_Delete(Vols);
  if(s->Num == GModel::current()->getGEOInternals()->MaxSurfaceNum)
    GModel::current()->getGEOInternals()->MaxSurfaceNum--;
  Tree_Suppress(GModel::current()->getGEOInternals()->Surfaces, &s);
  Free_Surface(&s, NULL);
}

static void DeleteVolume(int iv)
{
  Volume *v = FindVolume(iv);
  if(!v)
    return;
  if(v->Num == GModel::current()->getGEOInternals()->MaxVolumeNum)
    GModel::current()->getGEOInternals()->MaxVolumeNum--;
  Tree_Suppress(GModel::current()->getGEOInternals()->Volumes, &v);
  Free_Volume(&v, NULL);
}

void DeleteShape(int Type, int Num)
{
  switch (Type) {
  case MSH_POINT:
    DeletePoint(Num);
    break;
  case MSH_SEGM_LINE:
  case MSH_SEGM_SPLN:
  case MSH_SEGM_BSPLN:
  case MSH_SEGM_BEZIER:
  case MSH_SEGM_CIRC:
  case MSH_SEGM_CIRC_INV:
  case MSH_SEGM_ELLI:
  case MSH_SEGM_ELLI_INV:
  case MSH_SEGM_NURBS:
  case MSH_SEGM_COMPOUND:
    DeleteCurve(Num);
    DeleteCurve(-Num);
    break;
  case MSH_SURF_TRIC:
  case MSH_SURF_REGL:
  case MSH_SURF_PLAN:
  case MSH_SURF_COMPOUND:
    DeleteSurface(Num);
    break;
  case MSH_VOLUME:
  case MSH_VOLUME_COMPOUND:
    DeleteVolume(Num);
    break;
  case MSH_POINT_FROM_GMODEL:
    {
      GVertex *gv = GModel::current()->getVertexByTag(Num);
      if(gv) GModel::current()->remove(gv);
    }
    break;
  case MSH_SEGM_FROM_GMODEL:
    {
      GEdge *ge = GModel::current()->getEdgeByTag(Num);
      if(ge) GModel::current()->remove(ge);
    }
    break;
  case MSH_SURF_FROM_GMODEL:
    {
      GFace *gf = GModel::current()->getFaceByTag(Num);
      if(gf) GModel::current()->remove(gf);
    }
    break;
  case MSH_VOLUME_FROM_GMODEL:
    {
      GRegion *gr = GModel::current()->getRegionByTag(Num);
      if(gr) GModel::current()->remove(gr);
    }
    break;
  default:
    Msg::Error("Impossible to delete entity %d (of type %d)", Num, Type);
    break;
  }
}

static void ColorCurve(int ip, unsigned int col)
{
  Curve *c = FindCurve(ip);
  if(!c)
    return;
  c->Color.type = 1;
  c->Color.mesh = c->Color.geom = col;
}

static void ColorSurface(int is, unsigned int col)
{
  Surface *s = FindSurface(is);
  if(!s)
    return;
  s->Color.type = 1;
  s->Color.mesh = s->Color.geom = col;
}

static void ColorVolume(int iv, unsigned int col)
{
  Volume *v = FindVolume(iv);
  if(!v)
    return;
  v->Color.type = 1;
  v->Color.mesh = v->Color.geom = col;
}

void ColorShape(int Type, int Num, unsigned int Color)
{
  switch (Type) {
  case MSH_POINT:
    break;
  case MSH_SEGM_LINE:
  case MSH_SEGM_SPLN:
  case MSH_SEGM_BSPLN:
  case MSH_SEGM_BEZIER:
  case MSH_SEGM_CIRC:
  case MSH_SEGM_CIRC_INV:
  case MSH_SEGM_ELLI:
  case MSH_SEGM_ELLI_INV:
  case MSH_SEGM_NURBS:
  case MSH_SEGM_DISCRETE:
    ColorCurve(Num, Color);
    break;
  case MSH_SURF_TRIC:
  case MSH_SURF_REGL:
  case MSH_SURF_PLAN:
  case MSH_SURF_DISCRETE:
    ColorSurface(Num, Color);
    break;
  case MSH_VOLUME:
  case MSH_VOLUME_DISCRETE:
    ColorVolume(Num, Color);
    break;
  default:
    break;
  }
}

void VisibilityShape(int Type, int Num, int Mode)
{
  Vertex *v;
  Curve *c;
  Surface *s;
  Volume *V;

  switch (Type) {
  case MSH_POINT:
  case MSH_POINT_FROM_GMODEL:
    {
      if((v = FindPoint(Num))) v->Visible = Mode;
      GVertex *gv = GModel::current()->getVertexByTag(Num);
      if(gv) gv->setVisibility(Mode);
    }
    break;
  case MSH_SEGM_LINE:
  case MSH_SEGM_SPLN:
  case MSH_SEGM_BSPLN:
  case MSH_SEGM_BEZIER:
  case MSH_SEGM_CIRC:
  case MSH_SEGM_CIRC_INV:
  case MSH_SEGM_ELLI:
  case MSH_SEGM_ELLI_INV:
  case MSH_SEGM_NURBS:
  case MSH_SEGM_DISCRETE:
  case MSH_SEGM_COMPOUND:
  case MSH_SEGM_FROM_GMODEL:
    {
      if((c = FindCurve(Num))) c->Visible = Mode;
      GEdge *ge = GModel::current()->getEdgeByTag(Num);
      if(ge) ge->setVisibility(Mode);
    }
    break;
  case MSH_SURF_TRIC:
  case MSH_SURF_REGL:
  case MSH_SURF_PLAN:
  case MSH_SURF_DISCRETE:
  case MSH_SURF_COMPOUND:
  case MSH_SURF_FROM_GMODEL:
    {
      if((s = FindSurface(Num))) s->Visible = Mode;
      GFace *gf = GModel::current()->getFaceByTag(Num);
      if(gf) gf->setVisibility(Mode);
    }
    break;
  case MSH_VOLUME:
  case MSH_VOLUME_DISCRETE:
  case MSH_VOLUME_COMPOUND:
  case MSH_VOLUME_FROM_GMODEL:
    {
      if((V = FindVolume(Num))) V->Visible = Mode;
      GRegion *gr = GModel::current()->getRegionByTag(Num);
      if(gr) gr->setVisibility(Mode);
    }
    break;
  default:
    break;
  }
}

static int vmode;
static void vis_nod(void *a, void *b){ (*(Vertex **)a)->Visible = vmode; }
static void vis_cur(void *a, void *b){ (*(Curve **)a)->Visible = vmode; }
static void vis_sur(void *a, void *b){ (*(Surface **)a)->Visible = vmode; }
static void vis_vol(void *a, void *b){ (*(Volume **)a)->Visible = vmode; }

void VisibilityShape(char *str, int Type, int Mode)
{
  vmode = Mode;

  if(!strcmp(str, "all") || !strcmp(str, "*")) {
    switch (Type) {
    case 0:
      Tree_Action(GModel::current()->getGEOInternals()->Points, vis_nod); 
      for(GModel::viter it = GModel::current()->firstVertex(); 
          it != GModel::current()->lastVertex(); it++)
        (*it)->setVisibility(Mode);
      break;
    case 1: 
      Tree_Action(GModel::current()->getGEOInternals()->Curves, vis_cur);
      for(GModel::eiter it = GModel::current()->firstEdge(); 
          it != GModel::current()->lastEdge(); it++)
        (*it)->setVisibility(Mode);
      break;
    case 2: 
      Tree_Action(GModel::current()->getGEOInternals()->Surfaces, vis_sur); 
      for(GModel::fiter it = GModel::current()->firstFace(); 
          it != GModel::current()->lastFace(); it++)
        (*it)->setVisibility(Mode);
      break;
    case 3:
      Tree_Action(GModel::current()->getGEOInternals()->Volumes, vis_vol); 
      for(GModel::riter it = GModel::current()->firstRegion(); 
          it != GModel::current()->lastRegion(); it++)
        (*it)->setVisibility(Mode);
      break;
    }
  }
  else {
    VisibilityShape(Type, atoi(str), Mode);
  }
}

Curve *CreateReversedCurve(Curve *c)
{
  Curve *newc = Create_Curve(-c->Num, c->Typ, 1, NULL, NULL, -1, -1, 0., 1.);

  if(List_Nbr(c->Control_Points)){
    newc->Control_Points =
      List_Create(List_Nbr(c->Control_Points), 1, sizeof(Vertex *));
    if(c->Typ == MSH_SEGM_ELLI || c->Typ == MSH_SEGM_ELLI_INV) {
      Vertex *e1, *e2, *e3, *e4;
      List_Read(c->Control_Points, 0, &e1);
      List_Read(c->Control_Points, 1, &e2);
      List_Read(c->Control_Points, 2, &e3);
      List_Read(c->Control_Points, 3, &e4);
      List_Add(newc->Control_Points, &e4);
      List_Add(newc->Control_Points, &e2);
      List_Add(newc->Control_Points, &e3);
      List_Add(newc->Control_Points, &e1);
    }
    else
      List_Invert(c->Control_Points, newc->Control_Points);
  }

  if(c->Typ == MSH_SEGM_NURBS && c->k) {
    newc->k = new float[c->degre + List_Nbr(c->Control_Points) + 1];
    for(int i = 0; i < c->degre + List_Nbr(c->Control_Points) + 1; i++)
      newc->k[c->degre + List_Nbr(c->Control_Points) - i] = c->k[i];
  }
  
  if(c->Typ == MSH_SEGM_CIRC)
    newc->Typ = MSH_SEGM_CIRC_INV;
  if(c->Typ == MSH_SEGM_CIRC_INV)
    newc->Typ = MSH_SEGM_CIRC;
  if(c->Typ == MSH_SEGM_ELLI)
    newc->Typ = MSH_SEGM_ELLI_INV;
  if(c->Typ == MSH_SEGM_ELLI_INV)
    newc->Typ = MSH_SEGM_ELLI;

  newc->beg = c->end;
  newc->end = c->beg;
  newc->Method = c->Method;
  newc->nbPointsTransfinite = c->nbPointsTransfinite;
  newc->typeTransfinite = -c->typeTransfinite;
  newc->coeffTransfinite = c->coeffTransfinite;
  newc->degre = c->degre;
  newc->ubeg = 1. - c->uend;
  newc->uend = 1. - c->ubeg;
  End_Curve(newc);

  Curve **pc;
  if((pc = (Curve **)Tree_PQuery(GModel::current()->getGEOInternals()->Curves, &newc))) {
    Free_Curve(&newc, NULL);
    return *pc;
  }
  else{
    Tree_Add(GModel::current()->getGEOInternals()->Curves, &newc);
    return newc;
  }
}

int recognize_seg(int typ, List_T *liste, int *seg)
{
  List_T *temp = Tree2List(GModel::current()->getGEOInternals()->Curves);
  int beg, end;
  List_Read(liste, 0, &beg);
  List_Read(liste, List_Nbr(liste) - 1, &end);
  for(int i = 0; i < List_Nbr(temp); i++) {
    Curve *pc;
    List_Read(temp, i, &pc);
    if(pc->Typ == typ && pc->beg->Num == beg && pc->end->Num == end) {
      List_Delete(temp);
      *seg = pc->Num;
      return 1;
    }
  }
  List_Delete(temp);
  return 0;
}

int recognize_loop(List_T *liste, int *loop)
{
  int res = 0;
  *loop = 0;
  List_T *temp = Tree2List(GModel::current()->getGEOInternals()->EdgeLoops);
  for(int i = 0; i < List_Nbr(temp); i++) {
    EdgeLoop *pe;
    List_Read(temp, i, &pe);
    if(!compare2Lists(pe->Curves, liste, fcmp_absint)) {
      res = 1;
      *loop = pe->Num;
      break;
    }
  }
  List_Delete(temp);
  return res;
}

int recognize_surfloop(List_T *liste, int *loop)
{
  int res = 0;
  *loop = 0;
  List_T *temp = Tree2List(GModel::current()->getGEOInternals()->SurfaceLoops);
  for(int i = 0; i < List_Nbr(temp); i++) {
    EdgeLoop *pe;
    List_Read(temp, i, &pe);
    if(!compare2Lists(pe->Curves, liste, fcmp_absint)) {
      res = 1;
      *loop = pe->Num;
      break;
    }
  }
  List_Delete(temp);
  return res;
}

// Linear applications

static void SetTranslationMatrix(double matrix[4][4], double T[3])
{
  for(int i = 0; i < 4; i++) {
    for(int j = 0; j < 4; j++) {
      matrix[i][j] = (i == j) ? 1.0 : 0.0;
    }
  }
  for(int i = 0; i < 3; i++)
    matrix[i][3] = T[i];
}

static void SetSymmetryMatrix(double matrix[4][4], double A, double B, double C,
                              double D)
{
  double F = -2.0 / (A * A + B * B + C * C);
  matrix[0][0] = 1. + A * A * F;
  matrix[0][1] = A * B * F;
  matrix[0][2] = A * C * F;
  matrix[0][3] = A * D * F;
  matrix[1][0] = A * B * F;
  matrix[1][1] = 1. + B * B * F;
  matrix[1][2] = B * C * F;
  matrix[1][3] = B * D * F;
  matrix[2][0] = A * C * F;
  matrix[2][1] = B * C * F;
  matrix[2][2] = 1. + C * C * F;
  matrix[2][3] = C * D * F;
  matrix[3][0] = B * C * F;
  matrix[3][1] = 0.0;
  matrix[3][2] = 0.0;
  matrix[3][3] = 1.0;
}

static void SetDilatationMatrix(double matrix[4][4], double T[3], double A)
{
  matrix[0][0] = A;
  matrix[0][1] = 0.0;
  matrix[0][2] = 0.0;
  matrix[0][3] = T[0] * (1.0 - A);
  matrix[1][0] = 0.0;
  matrix[1][1] = A;
  matrix[1][2] = 0.0;
  matrix[1][3] = T[1] * (1.0 - A);
  matrix[2][0] = 0.0;
  matrix[2][1] = 0.0;
  matrix[2][2] = A;
  matrix[2][3] = T[2] * (1.0 - A);
  matrix[3][0] = 0.0;
  matrix[3][1] = 0.0;
  matrix[3][2] = 0.0;
  matrix[3][3] = 1.0;
}

static void GramSchmidt(double v1[3], double v2[3], double v3[3])
{
  double tmp[3];
  norme(v1);
  prodve(v3, v1, tmp);
  norme(tmp);
  v2[0] = tmp[0];
  v2[1] = tmp[1];
  v2[2] = tmp[2];
  prodve(v1, v2, v3);
  norme(v3);
}

static void SetRotationMatrix(double matrix[4][4], double Axe[3], double alpha)
{
  double t1[3], t2[3];
  if(Axe[0] != 0.0) {
    t1[0] = 0.0;
    t1[1] = 1.0;
    t1[2] = 0.0;
    t2[0] = 0.0;
    t2[1] = 0.0;
    t2[2] = 1.0;
  }
  else if(Axe[1] != 0.0) {
    t1[0] = 1.0;
    t1[1] = 0.0;
    t1[2] = 0.0;
    t2[0] = 0.0;
    t2[1] = 0.0;
    t2[2] = 1.0;
  }
  else {
    t1[0] = 1.0;
    t1[1] = 0.0;
    t1[2] = 0.0;
    t2[0] = 0.0;
    t2[1] = 1.0;
    t2[2] = 0.0;
  }
  GramSchmidt(Axe, t1, t2);
  double rot[3][3], plan[3][3], invplan[3][3];
  plan[0][0] = Axe[0];
  plan[0][1] = Axe[1];
  plan[0][2] = Axe[2];
  plan[1][0] = t1[0];
  plan[1][1] = t1[1];
  plan[1][2] = t1[2];
  plan[2][0] = t2[0];
  plan[2][1] = t2[1];
  plan[2][2] = t2[2];
  rot[2][2] = cos(alpha);
  rot[2][1] = sin(alpha);
  rot[2][0] = 0.;
  rot[1][2] = -sin(alpha);
  rot[1][1] = cos(alpha);
  rot[1][0] = 0.;
  rot[0][2] = 0.;
  rot[0][1] = 0.;
  rot[0][0] = 1.;
  int i, j, k;
  for(i = 0; i < 3; i++)
    for(j = 0; j < 3; j++)
      invplan[i][j] = plan[j][i];
  double interm[3][3];
  for(i = 0; i < 3; i++)
    for(j = 0; j < 3; j++) {
      interm[i][j] = 0.0;
      for(k = 0; k < 3; k++)
        interm[i][j] += invplan[i][k] * rot[k][j];
    }
  for(i = 0; i < 4; i++)
    for(j = 0; j < 4; j++)
      matrix[i][j] = 0.0;
  for(i = 0; i < 3; i++)
    for(j = 0; j < 3; j++) {
      for(k = 0; k < 3; k++)
        matrix[i][j] += interm[i][k] * plan[k][j];
    }
  matrix[3][3] = 1.0;
}

static void vecmat4x4(double mat[4][4], double vec[4], double res[4])
{
  for(int i = 0; i < 4; i++) {
    res[i] = 0.0;
    for(int j = 0; j < 4; j++) {
      res[i] += mat[i][j] * vec[j];
    }
  }
}

#if 0
static void printCurve(Curve *c)
{
  Vertex *v;
  int N = List_Nbr(c->Control_Points);
  Msg::Debug("Curve %d %d cp (%d->%d)", c->Num, N, c->beg->Num, c->end->Num);
  for(int i = 0; i < N; i++) {
    List_Read(c->Control_Points, i, &v);
    Msg::Debug("Vertex %d (%g,%g,%g,%g)", v->Num, v->Pos.X, v->Pos.Y,
               v->Pos.Z, v->lc);
  }
}

static void printSurface(Surface *s)
{
  Curve *c;
  int N = List_Nbr(s->Generatrices);

  Msg::Debug("Surface %d, %d generatrices", s->Num, N);
  for(int i = 0; i < N; i++) {
    List_Read(s->Generatrices, i, &c);
    printCurve(c);
  }
}
#endif

static void ApplyTransformationToPoint(double matrix[4][4], Vertex *v,
                                       bool end_curve_surface=false)
{
  double pos[4], vec[4];

  if(!ListOfTransformedPoints)
    ListOfTransformedPoints = List_Create(50, 50, sizeof(int));

  if(!List_Search(ListOfTransformedPoints, &v->Num, fcmp_absint)) {
    List_Add(ListOfTransformedPoints, &v->Num);
  }
  else
    return;

  vec[0] = v->Pos.X;
  vec[1] = v->Pos.Y;
  vec[2] = v->Pos.Z;
  vec[3] = v->w;
  vecmat4x4(matrix, vec, pos);
  v->Pos.X = pos[0];
  v->Pos.Y = pos[1];
  v->Pos.Z = pos[2];
  v->w = pos[3];

  // Warning: in theory we should always redo these checks if
  // end_curve_surface is true; but in practice this is so slow for
  // big models that we need to provide a way to bypass it (which is
  // OK if the guy who builds the geometry knowns what he's
  // doing). Instead of adding one more option, let's just bypass all
  // the checks if auto_coherence==0...
  if(CTX::instance()->geom.autoCoherence && end_curve_surface){
    List_T *All = Tree2List(GModel::current()->getGEOInternals()->Curves);
    for(int i = 0; i < List_Nbr(All); i++) {
      Curve *c;
      List_Read(All, i, &c);
      for(int j = 0; j < List_Nbr(c->Control_Points); j++) {
        Vertex *pv = *(Vertex **)List_Pointer(c->Control_Points, j);
        if(pv->Num == v->Num){
          End_Curve(c);
          break;
        }
      }
    }
    List_Delete(All);
  }
}

static void ApplyTransformationToCurve(double matrix[4][4], Curve *c)
{
  if(!c->beg || !c->end){
    Msg::Error("Cannot transform curve with no begin/end points");
    return;
  }

  ApplyTransformationToPoint(matrix, c->beg);
  ApplyTransformationToPoint(matrix, c->end);

  for(int i = 0; i < List_Nbr(c->Control_Points); i++) {
    Vertex *v;
    List_Read(c->Control_Points, i, &v);
    ApplyTransformationToPoint(matrix, v);
  }
  End_Curve(c);
}

static void ApplyTransformationToSurface(double matrix[4][4], Surface *s)
{
  for(int i = 0; i < List_Nbr(s->Generatrices); i++) {
    Curve *c;
    List_Read(s->Generatrices, i, &c);
    Curve *cc = FindCurve(abs(c->Num));
    ApplyTransformationToCurve(matrix, cc);
  }
  End_Surface(s);
}

static void ApplyTransformationToVolume(double matrix[4][4], Volume *v)
{
  for(int i = 0; i < List_Nbr(v->Surfaces); i++) {
    Surface *s;
    List_Read(v->Surfaces, i, &s);
    ApplyTransformationToSurface(matrix, s);
  }
}

static void ApplicationOnShapes(double matrix[4][4], List_T *shapes)
{
  Vertex *v;
  Curve *c;
  Surface *s;
  Volume *vol;

  List_Reset(ListOfTransformedPoints);

  for(int i = 0; i < List_Nbr(shapes); i++) {
    Shape O;
    List_Read(shapes, i, &O);
    switch (O.Type) {
    case MSH_POINT:
      v = FindPoint(O.Num);
      if(v)
        ApplyTransformationToPoint(matrix, v, true);
      else
        Msg::Error("Unknown point %d", O.Num);
      break;
    case MSH_SEGM_LINE:
    case MSH_SEGM_SPLN:
    case MSH_SEGM_BSPLN:
    case MSH_SEGM_BEZIER:
    case MSH_SEGM_CIRC:
    case MSH_SEGM_CIRC_INV:
    case MSH_SEGM_ELLI:
    case MSH_SEGM_ELLI_INV:
    case MSH_SEGM_NURBS:
      c = FindCurve(O.Num);
      if(c)
        ApplyTransformationToCurve(matrix, c);
      else
        Msg::Error("Unknown curve %d", O.Num);
      break;
    case MSH_SURF_REGL:
    case MSH_SURF_TRIC:
    case MSH_SURF_PLAN:
      s = FindSurface(O.Num);
      if(s)
        ApplyTransformationToSurface(matrix, s);
      else
        Msg::Error("Unknown surface %d", O.Num);
      break;
    case MSH_VOLUME:
      vol = FindVolume(O.Num);
      if(vol)
        ApplyTransformationToVolume(matrix, vol);
      else
        Msg::Error("Unknown volume %d", O.Num);
      break;
    default:
      Msg::Error("Impossible to transform entity %d (of type %d)", O.Num,
                 O.Type);
      break;
    }
  }

  List_Reset(ListOfTransformedPoints);
}

void TranslateShapes(double X, double Y, double Z, List_T *shapes)
{
  double T[3], matrix[4][4];

  T[0] = X;
  T[1] = Y;
  T[2] = Z;
  SetTranslationMatrix(matrix, T);
  ApplicationOnShapes(matrix, shapes);

  if(CTX::instance()->geom.autoCoherence)
    ReplaceAllDuplicates();
}

void DilatShapes(double X, double Y, double Z, double A, List_T *shapes)
{
  double T[3], matrix[4][4];

  T[0] = X;
  T[1] = Y;
  T[2] = Z;
  SetDilatationMatrix(matrix, T, A);
  ApplicationOnShapes(matrix, shapes);

  if(CTX::instance()->geom.autoCoherence)
    ReplaceAllDuplicates();
}

void RotateShapes(double Ax, double Ay, double Az,
                  double Px, double Py, double Pz,
                  double alpha, List_T *shapes)
{
  double A[3], T[3], matrix[4][4];

  T[0] = -Px;
  T[1] = -Py;
  T[2] = -Pz;
  SetTranslationMatrix(matrix, T);
  ApplicationOnShapes(matrix, shapes);

  A[0] = Ax;
  A[1] = Ay;
  A[2] = Az;
  SetRotationMatrix(matrix, A, alpha);
  ApplicationOnShapes(matrix, shapes);

  T[0] = Px;
  T[1] = Py;
  T[2] = Pz;
  SetTranslationMatrix(matrix, T);
  ApplicationOnShapes(matrix, shapes);

  if(CTX::instance()->geom.autoCoherence)
    ReplaceAllDuplicates();
}

void SymmetryShapes(double A, double B, double C, double D, List_T *shapes)
{
  double matrix[4][4];

  SetSymmetryMatrix(matrix, A, B, C, D);
  ApplicationOnShapes(matrix, shapes);

  if(CTX::instance()->geom.autoCoherence)
    ReplaceAllDuplicates();
}

class ShapeLessThan{
 public:
  bool operator()(Shape *v1, Shape *v2) const
  {
    if(std::abs(v1->Num) < std::abs(v2->Num)) return true;
    return false;
  }
};

void BoundaryShapes(List_T *shapes, List_T *shapesBoundary, bool combined)
{
  for(int i = 0; i < List_Nbr(shapes); i++) {
    Shape O;
    List_Read(shapes, i, &O);
    switch (O.Type) {
    case MSH_POINT:
    case MSH_POINT_BND_LAYER:
    case MSH_POINT_FROM_GMODEL:
      return;
      break;
    case MSH_SEGM_LINE:
    case MSH_SEGM_SPLN:
    case MSH_SEGM_CIRC:
    case MSH_SEGM_CIRC_INV:
    case MSH_SEGM_ELLI:
    case MSH_SEGM_ELLI_INV:
    case MSH_SEGM_BSPLN:
    case MSH_SEGM_NURBS:
    case MSH_SEGM_BEZIER:
    case MSH_SEGM_BND_LAYER:
    case MSH_SEGM_DISCRETE:
      {
        Curve *c = FindCurve(O.Num);
        if(c){
          if(c->beg){
            Shape sh;
            sh.Type = MSH_POINT;
            sh.Num = c->beg->Num;
            List_Add(shapesBoundary, &sh);
          }
          if(c->end){
            Shape sh;
            sh.Type = MSH_POINT;
            sh.Num = c->end->Num;
            List_Add(shapesBoundary, &sh);
          }
        }
        else
          Msg::Error("Unknown curve %d", O.Num);
      }
      break;
    case MSH_SEGM_FROM_GMODEL:
      {
        GEdge *ge = GModel::current()->getEdgeByTag(O.Num);
        if(ge){
          if(ge->getBeginVertex()){
            Shape sh;
            sh.Type = MSH_POINT_FROM_GMODEL;
            sh.Num = ge->getBeginVertex()->tag();
            List_Add(shapesBoundary, &sh);
          }
          if(ge->getEndVertex()){
            Shape sh;
            sh.Type = MSH_POINT_FROM_GMODEL;
            sh.Num = ge->getEndVertex()->tag();
            List_Add(shapesBoundary, &sh);
          }
        }
        else
          Msg::Error("Unknown curve %d", O.Num);
      }
      break;
    case MSH_SURF_PLAN:
    case MSH_SURF_REGL:
    case MSH_SURF_TRIC:
    case MSH_SURF_BND_LAYER:
    case MSH_SURF_DISCRETE:
      {
        Surface *s = FindSurface(O.Num);
        if(s){
          for(int j = 0; j < List_Nbr(s->Generatrices); j++){
            Curve *c;
            List_Read(s->Generatrices, j, &c);
            Shape sh;
            sh.Type = c->Typ;
            sh.Num = c->Num;
            List_Add(shapesBoundary, &sh);
          }
        }
        else
          Msg::Error("Unknown surface %d", O.Num);
      }
      break;
    case MSH_SURF_FROM_GMODEL:
      {
        GFace *gf = GModel::current()->getFaceByTag(O.Num);
        if(gf){
          std::list<GEdge*> edges(gf->edges());
          for(std::list<GEdge*>::iterator it = edges.begin(); it != edges.end(); it++){
            Shape sh;
            sh.Type = MSH_SEGM_FROM_GMODEL;
            sh.Num = (*it)->tag();
            List_Add(shapesBoundary, &sh);
          }
        }
        else
          Msg::Error("Unknown surface %d", O.Num);
      }
      break;
    case MSH_VOLUME:
    case MSH_VOLUME_DISCRETE:
      {
        Volume *v = FindVolume(O.Num);
        if(v){
          for(int j = 0; j < List_Nbr(v->Surfaces); j++){
            Surface *s;
            List_Read(v->Surfaces, j, &s);
            Shape sh;
            sh.Type = s->Typ;
            sh.Num = s->Num;
            List_Add(shapesBoundary, &sh);
          }
        }
        else
          Msg::Error("Unknown volume %d", O.Num);
      }
      break;
    case MSH_VOLUME_FROM_GMODEL:
      {
        GRegion *gr = GModel::current()->getRegionByTag(O.Num);
        if(gr){
          std::list<GFace*> faces(gr->faces());
          for(std::list<GFace*>::iterator it = faces.begin(); it != faces.end(); it++){
            Shape sh;
            sh.Type = MSH_SURF_FROM_GMODEL;
            sh.Num = (*it)->tag();
            List_Add(shapesBoundary, &sh);
          }
        }
        else
          Msg::Error("Unknown volume %d", O.Num);
      }
      break;
    default:
      Msg::Error("Impossible to take boundary of entity %d (of type %d)", O.Num,
                 O.Type);
      break;
    }
  }

  if(combined){
    // compute boundary of the combined shapes
    std::set<Shape*, ShapeLessThan> combined;
    for(int i = 0; i < List_Nbr(shapesBoundary); i++){
      Shape *s = (Shape*)List_Pointer(shapesBoundary, i);
      std::set<Shape*, ShapeLessThan>::iterator it = combined.find(s);
      if(it == combined.end())
        combined.insert(s);
      else
        combined.erase(it);
    }
    List_T *tmp = List_Create(combined.size(), 10, sizeof(Shape));
    for(std::set<Shape*, ShapeLessThan>::iterator it = combined.begin(); 
        it != combined.end(); it++)
      List_Add(tmp, *it);
    List_Reset(shapesBoundary);
    List_Copy(tmp, shapesBoundary);
    List_Delete(tmp);
  }
}

// Extrusion routines

void ProtudeXYZ(double &x, double &y, double &z, ExtrudeParams *e)
{
  double matrix[4][4];
  double T[3];
  Vertex v(x, y, z);

  T[0] = -e->geo.pt[0];
  T[1] = -e->geo.pt[1];
  T[2] = -e->geo.pt[2];
  SetTranslationMatrix(matrix, T);
  List_Reset(ListOfTransformedPoints);
  ApplyTransformationToPoint(matrix, &v);

  SetRotationMatrix(matrix, e->geo.axe, e->geo.angle);
  List_Reset(ListOfTransformedPoints);
  ApplyTransformationToPoint(matrix, &v);

  T[0] = -T[0];
  T[1] = -T[1];
  T[2] = -T[2];
  SetTranslationMatrix(matrix, T);
  List_Reset(ListOfTransformedPoints);
  ApplyTransformationToPoint(matrix, &v);

  x = v.Pos.X;
  y = v.Pos.Y;
  z = v.Pos.Z;

  List_Reset(ListOfTransformedPoints);
}

static int Extrude_ProtudePoint(int type, int ip,
                                double T0, double T1, double T2,
                                double A0, double A1, double A2,
                                double X0, double X1, double X2, double alpha,
                                Curve **pc, Curve **prc, int final, 
                                ExtrudeParams *e)
{
  double matrix[4][4], T[3], Ax[3], d;
  Vertex V, *pv, *newp, *chapeau;
  Curve *c;
  int i;

  pv = &V;
  pv->Num = ip;
  *pc = *prc = NULL;
  if(!Tree_Query(GModel::current()->getGEOInternals()->Points, &pv))
    return 0;

  Msg::Debug("Extrude Point %d", ip);

  chapeau = DuplicateVertex(pv);

  switch (type) {
  case TRANSLATE:
    T[0] = T0;
    T[1] = T1;
    T[2] = T2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToPoint(matrix, chapeau);
    if(!comparePosition(&pv, &chapeau))
      return pv->Num;
    c = Create_Curve(NEWLINE(), MSH_SEGM_LINE, 1, NULL, NULL, -1, -1, 0., 1.);
    c->Control_Points = List_Create(2, 1, sizeof(Vertex *));
    c->Extrude = new ExtrudeParams;
    c->Extrude->fill(type, T0, T1, T2, A0, A1, A2, X0, X1, X2, alpha);
    if(e)
      c->Extrude->mesh = e->mesh;
    List_Add(c->Control_Points, &pv);
    List_Add(c->Control_Points, &chapeau);
    c->beg = pv;
    c->end = chapeau;
    break;
  case BOUNDARY_LAYER:
    chapeau->Typ = MSH_POINT_BND_LAYER;
    if(e) chapeau->boundaryLayerIndex = e->mesh.BoundaryLayerIndex;
    c = Create_Curve(NEWLINE(), MSH_SEGM_BND_LAYER, 1, NULL, NULL, -1, -1, 0., 1.);
    c->Control_Points = List_Create(2, 1, sizeof(Vertex *));
    c->Extrude = new ExtrudeParams;
    c->Extrude->fill(type, T0, T1, T2, A0, A1, A2, X0, X1, X2, alpha);
    if(e) c->Extrude->mesh = e->mesh;
    List_Add(c->Control_Points, &pv);
    List_Add(c->Control_Points, &chapeau);
    c->beg = pv;
    c->end = chapeau;
    break;
  case ROTATE:
    T[0] = -X0;
    T[1] = -X1;
    T[2] = -X2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToPoint(matrix, chapeau);
    Ax[0] = A0;
    Ax[1] = A1;
    Ax[2] = A2;
    SetRotationMatrix(matrix, Ax, alpha);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToPoint(matrix, chapeau);
    T[0] = X0;
    T[1] = X1;
    T[2] = X2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToPoint(matrix, chapeau);
    if(!comparePosition(&pv, &chapeau))
      return pv->Num;
    c = Create_Curve(NEWLINE(), MSH_SEGM_CIRC, 1, NULL, NULL, -1, -1, 0., 1.);
    c->Control_Points = List_Create(3, 1, sizeof(Vertex *));
    c->Extrude = new ExtrudeParams;
    c->Extrude->fill(type, T0, T1, T2, A0, A1, A2, X0, X1, X2, alpha);
    if(e)
      c->Extrude->mesh = e->mesh;
    List_Add(c->Control_Points, &pv);
    // compute circle center
    newp = DuplicateVertex(pv);
    Ax[0] = A0;
    Ax[1] = A1;
    Ax[2] = A2;
    norme(Ax);
    T[0] = pv->Pos.X - X0;
    T[1] = pv->Pos.Y - X1;
    T[2] = pv->Pos.Z - X2;
    prosca(T, Ax, &d);
    newp->Pos.X = X0 + d * Ax[0];
    newp->Pos.Y = X1 + d * Ax[1]; 
    newp->Pos.Z = X2 + d * Ax[2]; 
    List_Add(c->Control_Points, &newp);
    List_Add(c->Control_Points, &chapeau);
    c->beg = pv;
    c->end = chapeau;
    break;
  case TRANSLATE_ROTATE:
    d = CTX::instance()->geom.extrudeSplinePoints;
    d = d ? d : 1;
    c = Create_Curve(NEWLINE(), MSH_SEGM_SPLN, 1, NULL, NULL, -1, -1, 0., 1.);
    c->Control_Points =
      List_Create(CTX::instance()->geom.extrudeSplinePoints + 1, 1, sizeof(Vertex *));
    c->Extrude = new ExtrudeParams;
    c->Extrude->fill(type, T0, T1, T2, A0, A1, A2, X0, X1, X2, alpha);
    if(e)
      c->Extrude->mesh = e->mesh;
    List_Add(c->Control_Points, &pv);
    c->beg = pv;
    for(i = 0; i < CTX::instance()->geom.extrudeSplinePoints; i++) {
      if(i)
        chapeau = DuplicateVertex(chapeau);
      T[0] = -X0;
      T[1] = -X1;
      T[2] = -X2;
      SetTranslationMatrix(matrix, T);
      List_Reset(ListOfTransformedPoints);
      ApplyTransformationToPoint(matrix, chapeau);
      Ax[0] = A0;
      Ax[1] = A1;
      Ax[2] = A2;
      SetRotationMatrix(matrix, Ax, alpha / d);
      List_Reset(ListOfTransformedPoints);
      ApplyTransformationToPoint(matrix, chapeau);
      T[0] = X0;
      T[1] = X1;
      T[2] = X2;
      SetTranslationMatrix(matrix, T);
      List_Reset(ListOfTransformedPoints);
      ApplyTransformationToPoint(matrix, chapeau);
      T[0] = T0 / d;
      T[1] = T1 / d;
      T[2] = T2 / d;
      SetTranslationMatrix(matrix, T);
      List_Reset(ListOfTransformedPoints);
      ApplyTransformationToPoint(matrix, chapeau);
      List_Add(c->Control_Points, &chapeau);
    }
    c->end = chapeau;
    break;
  default:
    Msg::Error("Unknown extrusion type");
    return pv->Num;
  }

  End_Curve(c);
  Tree_Add(GModel::current()->getGEOInternals()->Curves, &c);
  CreateReversedCurve(c);
  *pc = c;
  *prc = FindCurve(-c->Num);

  List_Reset(ListOfTransformedPoints);

  if(CTX::instance()->geom.autoCoherence && final)
    ReplaceAllDuplicates();

  return chapeau->Num;
}

static int Extrude_ProtudeCurve(int type, int ic,
                                double T0, double T1, double T2,
                                double A0, double A1, double A2,
                                double X0, double X1, double X2, double alpha,
                                Surface **ps, int final, 
                                ExtrudeParams *e)
{
  double matrix[4][4], T[3], Ax[3];
  Curve *CurveBeg, *CurveEnd;
  Curve *ReverseChapeau, *ReverseBeg, *ReverseEnd;
  Curve *pc, *revpc, *chapeau;
  Surface *s;

  pc = FindCurve(ic);
  revpc = FindCurve(-ic);
  *ps = NULL;

  if(!pc || !revpc){
    return 0;
  }

  if(!pc->beg || !pc->end){
    Msg::Error("Cannot extrude curve with no begin/end points");
    return 0;
  }

  Msg::Debug("Extrude Curve %d", ic);

  chapeau = DuplicateCurve(pc, false);

  chapeau->Extrude = new ExtrudeParams(COPIED_ENTITY);
  chapeau->Extrude->fill(type, T0, T1, T2, A0, A1, A2, X0, X1, X2, alpha);
  chapeau->Extrude->geo.Source = pc->Num;
  if(e)
    chapeau->Extrude->mesh = e->mesh;

  switch (type) {
  case TRANSLATE:
    T[0] = T0;
    T[1] = T1;
    T[2] = T2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToCurve(matrix, chapeau);
    break;
  case BOUNDARY_LAYER:
    chapeau->Typ = MSH_SEGM_BND_LAYER;
    if(chapeau->beg){
      chapeau->beg->Typ = MSH_POINT_BND_LAYER;
      if(e) chapeau->beg->boundaryLayerIndex = e->mesh.BoundaryLayerIndex;
    }
    if(chapeau->end){
      chapeau->end->Typ = MSH_POINT_BND_LAYER;
      if(e) chapeau->end->boundaryLayerIndex = e->mesh.BoundaryLayerIndex;
    }
    for(int i = 0; i < List_Nbr(chapeau->Control_Points); i++){
      Vertex *v;
      List_Read(chapeau->Control_Points, i, &v);
      if(e) v->boundaryLayerIndex = e->mesh.BoundaryLayerIndex;
    }
    revpc = FindCurve(-chapeau->Num);
    if(revpc) revpc->Typ = MSH_SEGM_BND_LAYER;
    break;
  case ROTATE:
    T[0] = -X0;
    T[1] = -X1;
    T[2] = -X2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToCurve(matrix, chapeau);
    Ax[0] = A0;
    Ax[1] = A1;
    Ax[2] = A2;
    SetRotationMatrix(matrix, Ax, alpha);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToCurve(matrix, chapeau);
    T[0] = X0;
    T[1] = X1;
    T[2] = X2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToCurve(matrix, chapeau);
    break;
  case TRANSLATE_ROTATE:
    T[0] = -X0;
    T[1] = -X1;
    T[2] = -X2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToCurve(matrix, chapeau);
    Ax[0] = A0;
    Ax[1] = A1;
    Ax[2] = A2;
    SetRotationMatrix(matrix, Ax, alpha);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToCurve(matrix, chapeau);
    T[0] = X0;
    T[1] = X1;
    T[2] = X2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToCurve(matrix, chapeau);
    T[0] = T0;
    T[1] = T1;
    T[2] = T2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToCurve(matrix, chapeau);
    break;
  default:
    Msg::Error("Unknown extrusion type");
    return pc->Num;
  }

  Extrude_ProtudePoint(type, pc->beg->Num, T0, T1, T2,
                       A0, A1, A2, X0, X1, X2, alpha,
                       &CurveBeg, &ReverseBeg, 0, e);
  Extrude_ProtudePoint(type, pc->end->Num, T0, T1, T2,
                       A0, A1, A2, X0, X1, X2, alpha,
                       &CurveEnd, &ReverseEnd, 0, e);

  if(!CurveBeg && !CurveEnd){
    return pc->Num;
  }

  if(type == BOUNDARY_LAYER)
    s = Create_Surface(NEWSURFACE(), MSH_SURF_BND_LAYER);
  else if(!CurveBeg || !CurveEnd)
    s = Create_Surface(NEWSURFACE(), MSH_SURF_TRIC);
  else
    s = Create_Surface(NEWSURFACE(), MSH_SURF_REGL);

  s->Generatrices = List_Create(4, 1, sizeof(Curve *));
  s->Extrude = new ExtrudeParams;
  s->Extrude->fill(type, T0, T1, T2, A0, A1, A2, X0, X1, X2, alpha);
  s->Extrude->geo.Source = pc->Num;
  if(e)
    s->Extrude->mesh = e->mesh;

  ReverseChapeau = FindCurve(-chapeau->Num);

  if(!CurveBeg) {
    List_Add(s->Generatrices, &pc);
    List_Add(s->Generatrices, &CurveEnd);
    List_Add(s->Generatrices, &ReverseChapeau);
  }
  else if(!CurveEnd) {
    List_Add(s->Generatrices, &ReverseChapeau);
    List_Add(s->Generatrices, &ReverseBeg);
    List_Add(s->Generatrices, &pc);
  }
  else {
    List_Add(s->Generatrices, &pc);
    List_Add(s->Generatrices, &CurveEnd);
    List_Add(s->Generatrices, &ReverseChapeau);
    List_Add(s->Generatrices, &ReverseBeg);
  }

  End_Surface(s);
  Tree_Add(GModel::current()->getGEOInternals()->Surfaces, &s);

  List_Reset(ListOfTransformedPoints);

  *ps = s;

  if(CTX::instance()->geom.autoCoherence && final)
    ReplaceAllDuplicates();

  return chapeau->Num;
}

static int Extrude_ProtudeSurface(int type, int is,
                                  double T0, double T1, double T2,
                                  double A0, double A1, double A2,
                                  double X0, double X1, double X2, double alpha,
                                  Volume **pv, ExtrudeParams *e)
{
  double matrix[4][4], T[3], Ax[3];
  Curve *c, *c2;
  int i;
  Surface *s, *ps, *chapeau;

  *pv = NULL;

  // 'is' can be negative, to signify that the surface orientation
  // should be reverted. This orientation information is only used at
  // the moment when creating boundary layers
  if(!(ps = FindSurface(std::abs(is))))
    return 0;

  Msg::Debug("Extrude Surface %d", is);

  chapeau = DuplicateSurface(ps, false);
  chapeau->Extrude = new ExtrudeParams(COPIED_ENTITY);
  chapeau->Extrude->fill(type, T0, T1, T2, A0, A1, A2, X0, X1, X2, alpha);
  chapeau->Extrude->geo.Source = is; // not ps->Num: we need the sign info
  if(e)
    chapeau->Extrude->mesh = e->mesh;

  for(i = 0; i < List_Nbr(chapeau->Generatrices); i++) {
    List_Read(ps->Generatrices, i, &c2);
    List_Read(chapeau->Generatrices, i, &c);
    if(c->Num < 0)
      if(!(c = FindCurve(-c->Num))) {
        Msg::Error("Unknown curve %d", -c->Num);
        return ps->Num;
      }
    c->Extrude = new ExtrudeParams(COPIED_ENTITY);
    c->Extrude->fill(type, T0, T1, T2, A0, A1, A2, X0, X1, X2, alpha);
    // don't take the abs(): the sign of c2->Num is important (used
    // when copying the mesh in the extrusion routine)
    c->Extrude->geo.Source = c2->Num;
    if(e)
      c->Extrude->mesh = e->mesh;
  }

  // FIXME: this is a really ugly hack for backward compatibility, so
  // that we don't screw up the old .geo files too much. (Before
  // version 1.54, we didn't always create new volumes during "Extrude
  // Surface". Now we do, but with "CTX::instance()->geom.oldNewreg==1", this
  // bumps the NEWREG() counter, and thus changes the whole automatic
  // numbering sequence.) So we locally force oldNewreg to 0: in most
  // cases, since we define points, curves, etc., before defining
  // volumes, the NEWVOLUME() call below will return a fairly low
  // number, that will not interfere with the other numbers...
  int tmp = CTX::instance()->geom.oldNewreg;
  CTX::instance()->geom.oldNewreg = 0;
  Volume *v = Create_Volume(NEWVOLUME(), MSH_VOLUME);
  CTX::instance()->geom.oldNewreg = tmp;

  v->Extrude = new ExtrudeParams;
  v->Extrude->fill(type, T0, T1, T2, A0, A1, A2, X0, X1, X2, alpha);
  v->Extrude->geo.Source = is;
  if(e)
    v->Extrude->mesh = e->mesh;
  int ori = -1;
  List_Add(v->Surfaces, &ps);
  List_Add(v->SurfacesOrientations, &ori);
  ori = 1;
  List_Add(v->Surfaces, &chapeau);
  List_Add(v->SurfacesOrientations, &ori);

  for(i = 0; i < List_Nbr(ps->Generatrices); i++) {
    List_Read(ps->Generatrices, i, &c);
    Extrude_ProtudeCurve(type, c->Num, T0, T1, T2, A0, A1, A2, X0, X1, X2,
                         alpha, &s, 0, e);
    if(s){
      if(c->Num < 0)
        ori = -1;
      else
        ori = 1;
      List_Add(v->Surfaces, &s);
      List_Add(v->SurfacesOrientations, &ori);
    }
  }

  switch (type) {
  case TRANSLATE:
    T[0] = T0;
    T[1] = T1;
    T[2] = T2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToSurface(matrix, chapeau);
    break;
  case BOUNDARY_LAYER:
    chapeau->Typ = MSH_SURF_BND_LAYER;
    for(int i = 0; i < List_Nbr(chapeau->Generatrices); i++) {
      List_Read(chapeau->Generatrices, i, &c);
      c->Typ = MSH_SEGM_BND_LAYER;
      c = FindCurve(-c->Num);
      c->Typ = MSH_SEGM_BND_LAYER;
      if(c->beg){
        c->beg->Typ = MSH_POINT_BND_LAYER;
        if(e) c->beg->boundaryLayerIndex = e->mesh.BoundaryLayerIndex;
      }
      if(c->end){
        c->end->Typ = MSH_POINT_BND_LAYER;
        if(e) c->end->boundaryLayerIndex = e->mesh.BoundaryLayerIndex;
      }
      for(int i = 0; i < List_Nbr(c->Control_Points); i++){
        Vertex *v;
        List_Read(c->Control_Points, i, &v);
        if(e) v->boundaryLayerIndex = e->mesh.BoundaryLayerIndex;
      }
    }
    break;
  case ROTATE:
    T[0] = -X0;
    T[1] = -X1;
    T[2] = -X2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToSurface(matrix, chapeau);
    Ax[0] = A0;
    Ax[1] = A1;
    Ax[2] = A2;
    SetRotationMatrix(matrix, Ax, alpha);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToSurface(matrix, chapeau);
    T[0] = X0;
    T[1] = X1;
    T[2] = X2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToSurface(matrix, chapeau);
    break;
  case TRANSLATE_ROTATE:
    T[0] = -X0;
    T[1] = -X1;
    T[2] = -X2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToSurface(matrix, chapeau);
    Ax[0] = A0;
    Ax[1] = A1;
    Ax[2] = A2;
    SetRotationMatrix(matrix, Ax, alpha);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToSurface(matrix, chapeau);
    T[0] = X0;
    T[1] = X1;
    T[2] = X2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToSurface(matrix, chapeau);
    T[0] = T0;
    T[1] = T1;
    T[2] = T2;
    SetTranslationMatrix(matrix, T);
    List_Reset(ListOfTransformedPoints);
    ApplyTransformationToSurface(matrix, chapeau);
    break;
  default:
    Msg::Error("Unknown extrusion type");
    return ps->Num;
  }

  // this is done only for backward compatibility with the old
  // numbering scheme
  Tree_Suppress(GModel::current()->getGEOInternals()->Surfaces, &chapeau);
  chapeau->Num = NEWSURFACE();
  chapeau->meshMaster = chapeau->Num;
  GModel::current()->getGEOInternals()->MaxSurfaceNum = chapeau->Num;
  Tree_Add(GModel::current()->getGEOInternals()->Surfaces, &chapeau);

  Tree_Add(GModel::current()->getGEOInternals()->Volumes, &v);

  *pv = v;

  if(CTX::instance()->geom.autoCoherence)
    ReplaceAllDuplicates();

  List_Reset(ListOfTransformedPoints);

  return chapeau->Num;
}

void ExtrudeShape(int extrude_type, int shape_type, int shape_num,
                  double T0, double T1, double T2,
                  double A0, double A1, double A2,
                  double X0, double X1, double X2, double alpha,
                  ExtrudeParams *e,
                  List_T *list_out)
{
  Shape shape;
  shape.Type = shape_type;
  shape.Num = shape_num;
  List_T *tmp = List_Create(1, 1, sizeof(Shape));
  List_Add(tmp, &shape);
  ExtrudeShapes(extrude_type, tmp,
                T0, T1, T2,
                A0, A1, A2,
                X0, X1, X2, alpha,
                e,
                list_out);
  List_Delete(tmp);
}

void ExtrudeShapes(int type, List_T *list_in, 
                   double T0, double T1, double T2,
                   double A0, double A1, double A2,
                   double X0, double X1, double X2, double alpha,
                   ExtrudeParams *e,
                   List_T *list_out)
{
  for(int i = 0; i < List_Nbr(list_in); i++){
    Shape shape;
    List_Read(list_in, i, &shape);
    switch(shape.Type){
    case MSH_POINT:
      {
        Curve *pc = 0, *prc = 0;
        Shape top;
        top.Num = Extrude_ProtudePoint(type, shape.Num, T0, T1, T2,
                                       A0, A1, A2, X0, X1, X2, alpha,
                                       &pc, &prc, 1, e);
        top.Type = MSH_POINT;
        List_Add(list_out, &top);
        if(pc){
          Shape body;
          body.Num = pc->Num;
          body.Type = pc->Typ;
          List_Add(list_out, &body);
        }
      }
      break;
    case MSH_SEGM_LINE:
    case MSH_SEGM_SPLN:
    case MSH_SEGM_BSPLN:
    case MSH_SEGM_BEZIER:
    case MSH_SEGM_CIRC:
    case MSH_SEGM_CIRC_INV:
    case MSH_SEGM_ELLI:
    case MSH_SEGM_ELLI_INV:
    case MSH_SEGM_NURBS:
      {
        Surface *ps = 0;
        Shape top;
        top.Num = Extrude_ProtudeCurve(type, shape.Num, T0, T1, T2,
                                       A0, A1, A2, X0, X1, X2, alpha,
                                       &ps, 1, e);
        Curve *pc = FindCurve(top.Num);
        top.Type = pc ? pc->Typ : 0;
        List_Add(list_out, &top);
        if(ps){
          Shape body;
          body.Num = ps->Num;
          body.Type = ps->Typ;
          List_Add(list_out, &body);
          if(CTX::instance()->geom.extrudeReturnLateral){
            for(int j = 0; j < List_Nbr(ps->Generatrices); j++){
              Curve *c;
              List_Read(ps->Generatrices, j, &c);
              if(abs(c->Num) != shape.Num && abs(c->Num) != top.Num){
                Shape side;
                side.Num = c->Num;
                side.Type = c->Typ;
                List_Add(list_out, &side);
              }
            }
          }
        }
      }
      break;
    case MSH_SURF_REGL:
    case MSH_SURF_TRIC:
    case MSH_SURF_PLAN:
    case MSH_SURF_DISCRETE:
      {
        Volume *pv = 0;
        Shape top;
        top.Num = Extrude_ProtudeSurface(type, shape.Num, T0, T1, T2,
                                         A0, A1, A2, X0, X1, X2, alpha,
                                         &pv, e);
        Surface *ps = FindSurface(top.Num);
        top.Type = ps ? ps->Typ : 0;

        List_Add(list_out, &top);
        if(pv){
          Shape body;
          body.Num = pv->Num;
          body.Type = pv->Typ;
          List_Add(list_out, &body);
          if(CTX::instance()->geom.extrudeReturnLateral){
            for(int j = 0; j < List_Nbr(pv->Surfaces); j++){
              Surface *s;
              List_Read(pv->Surfaces, j, &s);
              if(abs(s->Num) != shape.Num && abs(s->Num) != top.Num){
                Shape side;
                side.Num = s->Num;
                side.Type = s->Typ;
                List_Add(list_out, &side);
              }
            }
          }
        }
      }
      break;
    default:
      Msg::Error("Impossible to extrude entity %d (of type %d)",
                 shape.Num, shape.Type);
      break;
    }
  }
}

// Duplicate removal

static int compareTwoPoints(const void *a, const void *b)
{
  Vertex *q = *(Vertex **)a;
  Vertex *w = *(Vertex **)b;

  if(q->Typ != w->Typ) 
    return q->Typ - w->Typ;

  if(q->boundaryLayerIndex != w->boundaryLayerIndex) 
    return q->boundaryLayerIndex - w->boundaryLayerIndex;

  return comparePosition(a, b);
}

static int compareTwoCurves(const void *a, const void *b)
{
  Curve *c1 = *(Curve **)a;
  Curve *c2 = *(Curve **)b;
  int comp;

  if(c1->Typ != c2->Typ){
    if((c1->Typ == MSH_SEGM_CIRC && c2->Typ == MSH_SEGM_CIRC_INV) ||
       (c1->Typ == MSH_SEGM_CIRC_INV && c2->Typ == MSH_SEGM_CIRC) ||
       (c1->Typ == MSH_SEGM_ELLI && c2->Typ == MSH_SEGM_ELLI_INV) ||
       (c1->Typ == MSH_SEGM_ELLI_INV && c2->Typ == MSH_SEGM_ELLI)){
      // this is still ok
    }
    else
      return c1->Typ - c2->Typ;
  }

  if(List_Nbr(c1->Control_Points) != List_Nbr(c2->Control_Points))
    return List_Nbr(c1->Control_Points) - List_Nbr(c2->Control_Points);
  
  if(!List_Nbr(c1->Control_Points)){
    if(!c1->beg || !c2->beg)
      return 1;
    comp = compareVertex(&c1->beg, &c2->beg);
    if(comp)
      return comp;
    if(!c1->end || !c2->end)
      return 1;
    comp = compareVertex(&c1->end, &c2->end);
    if(comp)
      return comp;
  }
  else {
    for(int i = 0; i < List_Nbr(c1->Control_Points); i++){
      Vertex *v1, *v2;
      List_Read(c1->Control_Points, i, &v1);
      List_Read(c2->Control_Points, i, &v2);
      comp = compareVertex(&v1, &v2);
      if(comp)
        return comp;
    }
  }
  return 0;
}

static int compareTwoSurfaces(const void *a, const void *b)
{
  Surface *s1 = *(Surface **)a;
  Surface *s2 = *(Surface **)b;

  // checking types is the "right thing" to do (see e.g. compareTwoCurves)
  // but it would break backward compatibility (see e.g. tutorial/t2.geo),
  // so let's just do it for boundary layer surfaces for now:
  if(s1->Typ == MSH_SURF_BND_LAYER || s2->Typ == MSH_SURF_BND_LAYER){
    if(s1->Typ != s2->Typ) return s1->Typ - s2->Typ;
  }
  
  // if both surfaces have no generatrices, stay on the safe side and
  // assume they are different
  if(!List_Nbr(s1->Generatrices) && !List_Nbr(s2->Generatrices))
    return 1;

  return compare2Lists(s1->Generatrices, s2->Generatrices, compareAbsCurve);
}

static void MaxNumPoint(void *a, void *b)
{
  Vertex *v = *(Vertex **)a;
  GModel::current()->getGEOInternals()->MaxPointNum = 
    std::max(GModel::current()->getGEOInternals()->MaxPointNum, v->Num);
}

static void MaxNumCurve(void *a, void *b)
{
  Curve *c = *(Curve **)a;
  GModel::current()->getGEOInternals()->MaxLineNum = 
    std::max(GModel::current()->getGEOInternals()->MaxLineNum, c->Num);
}

static void MaxNumSurface(void *a, void *b)
{
  Surface *s = *(Surface **)a;
  GModel::current()->getGEOInternals()->MaxSurfaceNum =
    std::max(GModel::current()->getGEOInternals()->MaxSurfaceNum, s->Num);
}

static void ReplaceDuplicatePoints()
{
  // FIXME: This routine is in fact logically wrong (the
  // compareTwoPoints function used in the avl tree is not a
  // appropriate comparison function). The fix is simple (use a multi
  // dimensional tree, e.g., MVertexPositionSet), but fixing the
  // routine would break backward compatibility with old .geo
  // files. This will be fixed in the new abstract GModel CAD creation
  // routines.
  Vertex *v, *v2, **pv, **pv2;
  Curve *c;
  Surface *s;
  Volume *vol;
  Tree_T *points2delete = Tree_Create(sizeof(Vertex *), compareVertex);
  Tree_T *allNonDuplicatedPoints = Tree_Create(sizeof(Vertex *), compareTwoPoints);

  // Create unique points

  int start = Tree_Nbr(GModel::current()->getGEOInternals()->Points);

  List_T *All = Tree2List(GModel::current()->getGEOInternals()->Points);
  for(int i = 0; i < List_Nbr(All); i++) {
    List_Read(All, i, &v);
    if(!Tree_Search(allNonDuplicatedPoints, &v)) {
      Tree_Insert(allNonDuplicatedPoints, &v);
    }
    else {
      Tree_Suppress(GModel::current()->getGEOInternals()->Points, &v);
      Tree_Insert(points2delete, &v);
    }
  }
  List_Delete(All);

  int end = Tree_Nbr(GModel::current()->getGEOInternals()->Points);

  if(start == end) {
    Tree_Delete(points2delete);
    Tree_Delete(allNonDuplicatedPoints);
    return;
  }

  Msg::Debug("Removed %d duplicate points", start - end);

  if(CTX::instance()->geom.oldNewreg) {
    GModel::current()->getGEOInternals()->MaxPointNum = 0;
    Tree_Action(GModel::current()->getGEOInternals()->Points, MaxNumPoint);
  }

  // Replace old points in curves

  All = Tree2List(GModel::current()->getGEOInternals()->Curves);
  for(int i = 0; i < List_Nbr(All); i++) {
    List_Read(All, i, &c);
    // replace begin/end points
    if(!Tree_Query(allNonDuplicatedPoints, &c->beg))
      Msg::Error("Weird point %d in Coherence", c->beg->Num);
    if(!Tree_Query(allNonDuplicatedPoints, &c->end))
      Msg::Error("Weird point %d in Coherence", c->end->Num);
    // replace control points
    for(int j = 0; j < List_Nbr(c->Control_Points); j++) {
      pv = (Vertex **)List_Pointer(c->Control_Points, j);
      if(!(pv2 = (Vertex **)Tree_PQuery(allNonDuplicatedPoints, pv)))
        Msg::Error("Weird point %d in Coherence", (*pv)->Num);
      else
        List_Write(c->Control_Points, j, pv2);
    }
    // replace extrusion sources
    if(c->Extrude && c->Extrude->geo.Mode == EXTRUDED_ENTITY){
      v2 = FindPoint(std::abs(c->Extrude->geo.Source), points2delete);
      if(v2){
        if(!(pv2 = (Vertex **)Tree_PQuery(allNonDuplicatedPoints, &v2)))
          Msg::Error("Weird point %d in Coherence", v2->Num);
        else
          c->Extrude->geo.Source = (*pv2)->Num;
      }
    }
  }
  List_Delete(All);

  // Replace old points in surfaces

  All = Tree2List(GModel::current()->getGEOInternals()->Surfaces);
  for(int i = 0; i < List_Nbr(All); i++) {
    List_Read(All, i, &s);
    // replace transfinite corners
    for(int j = 0; j < List_Nbr(s->TrsfPoints); j++){
      pv = (Vertex **)List_Pointer(s->TrsfPoints, j);
      if(!(pv2 = (Vertex **)Tree_PQuery(allNonDuplicatedPoints, pv)))
        Msg::Error("Weird point %d in Coherence", (*pv)->Num);
      else
        List_Write(s->TrsfPoints, j, pv2);
    }
  }
  List_Delete(All);
  
  // Replace old points in volumes

  All = Tree2List(GModel::current()->getGEOInternals()->Volumes);
  for(int i = 0; i < List_Nbr(All); i++) {
    List_Read(All, i, &vol);
    // replace transfinite corners
    for(int j = 0; j < List_Nbr(vol->TrsfPoints); j++){
      pv = (Vertex **)List_Pointer(vol->TrsfPoints, j);
      if(!(pv2 = (Vertex **)Tree_PQuery(allNonDuplicatedPoints, pv)))
        Msg::Error("Weird point %d in Coherence", (*pv)->Num);
      else
        List_Write(vol->TrsfPoints, j, pv2);
    }
  }
  List_Delete(All);

  // TODO: replace old points in physical groups

  Tree_Action(points2delete, Free_Vertex);
  Tree_Delete(points2delete);
  Tree_Delete(allNonDuplicatedPoints);
}

static void ReplaceDuplicateCurves()
{
  Curve *c, *c2, **pc, **pc2;
  Surface *s;
  Tree_T *curves2delete = Tree_Create(sizeof(Curve *), compareCurve);
  Tree_T *allNonDuplicatedCurves = Tree_Create(sizeof(Curve *), compareTwoCurves);

  // Create unique curves

  int start = Tree_Nbr(GModel::current()->getGEOInternals()->Curves);

  List_T *All = Tree2List(GModel::current()->getGEOInternals()->Curves);
  for(int i = 0; i < List_Nbr(All); i++) {
    List_Read(All, i, &c);
    if(c->Num > 0) {
      if(!Tree_Search(allNonDuplicatedCurves, &c)) {
        Tree_Insert(allNonDuplicatedCurves, &c);
        if(!(c2 = FindCurve(-c->Num))) {
          Msg::Error("Unknown curve %d", -c->Num);
          List_Delete(All);
          return;
        }
        Tree_Insert(allNonDuplicatedCurves, &c2);
      }
      else {
        Tree_Suppress(GModel::current()->getGEOInternals()->Curves, &c);
        if(!(c2 = FindCurve(-c->Num))) {
          Msg::Error("Unknown curve %d", -c->Num);
          break;
        }
        Tree_Suppress(GModel::current()->getGEOInternals()->Curves, &c2);
        Tree_Insert(curves2delete, &c);
        Tree_Insert(curves2delete, &c2);
      }
    }
  }
  List_Delete(All);

  int end = Tree_Nbr(GModel::current()->getGEOInternals()->Curves);

  if(start == end) {
    Tree_Delete(curves2delete);
    Tree_Delete(allNonDuplicatedCurves);
    return;
  }

  Msg::Debug("Removed %d duplicate curves", start - end);

  if(CTX::instance()->geom.oldNewreg) {
    GModel::current()->getGEOInternals()->MaxLineNum = 0;
    Tree_Action(GModel::current()->getGEOInternals()->Curves, MaxNumCurve);
  }

  // Replace old curves in curves

  All = Tree2List(GModel::current()->getGEOInternals()->Curves);
  for(int i = 0; i < List_Nbr(All); i++) {
    List_Read(All, i, &c);
    // replace extrusion sources
    if(c->Extrude && c->Extrude->geo.Mode == COPIED_ENTITY){
      c2 = FindCurve(std::abs(c->Extrude->geo.Source), curves2delete);
      if(c2){
        if(!(pc2 = (Curve **)Tree_PQuery(allNonDuplicatedCurves, &c2)))
          Msg::Error("Weird curve %d in Coherence", c2->Num);
        else
          c->Extrude->geo.Source = (*pc2)->Num;
      }
    }
  }
  List_Delete(All);

  // Replace old curves in surfaces

  All = Tree2List(GModel::current()->getGEOInternals()->Surfaces);
  for(int i = 0; i < List_Nbr(All); i++) {
    List_Read(All, i, &s);
    // replace bounding curves
    for(int j = 0; j < List_Nbr(s->Generatrices); j++) {
      pc = (Curve **)List_Pointer(s->Generatrices, j);
      if(!(pc2 = (Curve **)Tree_PQuery(allNonDuplicatedCurves, pc)))
        Msg::Error("Weird curve %d in Coherence", (*pc)->Num);
      else {
        List_Write(s->Generatrices, j, pc2);
        // arghhh: check compareTwoCurves!
        End_Curve(*pc2);
      }
    }
    // replace extrusion sources
    if(s->Extrude && s->Extrude->geo.Mode == EXTRUDED_ENTITY){
      c2 = FindCurve(std::abs(s->Extrude->geo.Source), curves2delete);
      if(c2){
        if(!(pc2 = (Curve **)Tree_PQuery(allNonDuplicatedCurves, &c2)))
          Msg::Error("Weird curve %d in Coherence", c2->Num);
        else
          s->Extrude->geo.Source = (*pc2)->Num;
      }
    }
  }
  List_Delete(All);

  // TODO: replace old curves in physical groups

  Tree_Action(curves2delete, Free_Curve);
  Tree_Delete(curves2delete);
  Tree_Delete(allNonDuplicatedCurves);
}

static void ReplaceDuplicateSurfaces()
{
  Surface *s, *s2, **ps, **ps2;
  Volume *vol;
  Tree_T *surfaces2delete = Tree_Create(sizeof(Surface *), compareSurface);
  Tree_T *allNonDuplicatedSurfaces = Tree_Create(sizeof(Surface *), compareTwoSurfaces);

  // Create unique surfaces

  int start = Tree_Nbr(GModel::current()->getGEOInternals()->Surfaces);

  List_T *All = Tree2List(GModel::current()->getGEOInternals()->Surfaces);
  for(int i = 0; i < List_Nbr(All); i++) {
    List_Read(All, i, &s);
    if(s->Num > 0) {
      if(!Tree_Search(allNonDuplicatedSurfaces, &s)) {
        Tree_Insert(allNonDuplicatedSurfaces, &s);
      }
      else {
        Tree_Suppress(GModel::current()->getGEOInternals()->Surfaces, &s);
        Tree_Insert(surfaces2delete, &s);
      }
    }
  }
  List_Delete(All);

  int end = Tree_Nbr(GModel::current()->getGEOInternals()->Surfaces);

  if(start == end) {
    Tree_Delete(surfaces2delete);
    Tree_Delete(allNonDuplicatedSurfaces);
    return;
  }

  Msg::Debug("Removed %d duplicate surfaces", start - end);

  if(CTX::instance()->geom.oldNewreg) {
    GModel::current()->getGEOInternals()->MaxSurfaceNum = 0;
    Tree_Action(GModel::current()->getGEOInternals()->Surfaces, MaxNumSurface);
  } 

  // Replace old surfaces in surfaces

  All = Tree2List(GModel::current()->getGEOInternals()->Surfaces);
  for(int i = 0; i < List_Nbr(All); i++) {
    List_Read(All, i, &s);
    // replace extrusion sources
    if(s->Extrude && s->Extrude->geo.Mode == COPIED_ENTITY){
      s2 = FindSurface(std::abs(s->Extrude->geo.Source), surfaces2delete);
      if(s2){
        if(!(ps2 = (Surface **)Tree_PQuery(allNonDuplicatedSurfaces, &s2)))
          Msg::Error("Weird surface %d in Coherence", s2->Num);
        else
          s->Extrude->geo.Source = (*ps2)->Num;
      }
    }
  }
  List_Delete(All);

  // Replace old surfaces in volumes

  All = Tree2List(GModel::current()->getGEOInternals()->Volumes);
  for(int i = 0; i < List_Nbr(All); i++) {
    List_Read(All, i, &vol);
    // replace bounding surfaces
    for(int j = 0; j < List_Nbr(vol->Surfaces); j++) {
      ps = (Surface **)List_Pointer(vol->Surfaces, j);
      if(!(ps2 = (Surface **)Tree_PQuery(allNonDuplicatedSurfaces, ps)))
        Msg::Error("Weird surface %d in Coherence", (*ps)->Num);
      else
        List_Write(vol->Surfaces, j, ps2);
    }
    // replace extrusion sources
    if(vol->Extrude && vol->Extrude->geo.Mode == EXTRUDED_ENTITY){
      s2 = FindSurface(std::abs(vol->Extrude->geo.Source), surfaces2delete);
      if(s2){
        if(!(ps2 = (Surface **)Tree_PQuery(allNonDuplicatedSurfaces, &s2)))
          Msg::Error("Weird surface %d in Coherence", s2->Num);
        else
          vol->Extrude->geo.Source = (*ps2)->Num;
      }
    }
  }
  List_Delete(All);

  // TODO: replace old surfaces in physical groups

  Tree_Action(surfaces2delete, Free_Surface);
  Tree_Delete(surfaces2delete);
  Tree_Delete(allNonDuplicatedSurfaces);
}

void ReplaceAllDuplicates()
{
  ReplaceDuplicatePoints();
  ReplaceDuplicateCurves();
  ReplaceDuplicateSurfaces();
}

// Projection of a point on a surface

struct PointSurface{
  Vertex *p;
  Surface *s;
};

static void projectPS(fullVector<double> &x, fullVector<double> &res, void *data)
{
  PointSurface *ps = (PointSurface*)data;
  Vertex c = InterpolateSurface(ps->s, x(0), x(1), 0, 0);
  Vertex du = InterpolateSurface(ps->s, x(0), x(1), 1, 1);
  Vertex dv = InterpolateSurface(ps->s, x(0), x(1), 1, 2);
  res(0) =
    (c.Pos.X - ps->p->Pos.X) * du.Pos.X +
    (c.Pos.Y - ps->p->Pos.Y) * du.Pos.Y +
    (c.Pos.Z - ps->p->Pos.Z) * du.Pos.Z;
  res(1) =
    (c.Pos.X - ps->p->Pos.X) * dv.Pos.X +
    (c.Pos.Y - ps->p->Pos.Y) * dv.Pos.Y +
    (c.Pos.Z - ps->p->Pos.Z) * dv.Pos.Z;
}

bool ProjectPointOnSurface(Surface *s, Vertex &p, double uv[2])
{
  fullVector<double> x(2);
  x(0) = uv[0];
  x(1) = uv[1];
  PointSurface ps = {&p, s};

  Vertex pp = InterpolateSurface(s, uv[0], uv[1], 0, 0);
  double d2 = 
    (pp.Pos.X - p.Pos.X) * (pp.Pos.X - p.Pos.X) + 
    (pp.Pos.Y - p.Pos.Y) * (pp.Pos.Y - p.Pos.Y) + 
    (pp.Pos.Z - p.Pos.Z) * (pp.Pos.Z - p.Pos.Z) ;
  if(d2 < 1.e-12) return true;

  double UMIN = 0.;
  double UMAX = 1.;
  double VMIN = 0.;
  double VMAX = 1.;
  int ITER = 0;
  while(1) {
    bool success = newton_fd(projectPS, x, &ps);
    if(success && x(0) >= UMIN && x(0) <= UMAX && x(1) >= VMIN && x(1) <= VMAX){
      p = InterpolateSurface(s, x(0), x(1), 0, 0);
      uv[0] = x(0);
      uv[1] = x(1);
      if (ITER > 0)
        Msg::Info("ProjectPoint (%g,%g,%g) On Surface %d converged after %d iterations",
                  p.Pos.X, p.Pos.Y, p.Pos.Z, s->Num, ITER);
      return true;
    }
    x(0) = UMIN + (UMAX - UMIN) * ((rand() % 10000) / 10000.);
    x(1) = VMIN + (VMAX - VMIN) * ((rand() % 10000) / 10000.);
    if(ITER++ > 100) break;
  }
  {
    int NSAMPLES = 500;
    double uok = 0.5, vok = 0.5;
    double dmin = 1.e22;
    for (int i = 0; i < NSAMPLES; i++){
      const double U = i / (double)(NSAMPLES - 1);
      for (int j = 0; j < NSAMPLES; j++){
        const double V = j / (double)(NSAMPLES - 1);
        Vertex pp = InterpolateSurface(s, U, V, 0, 0);
        double d2 =
          (pp.Pos.X - p.Pos.X) * (pp.Pos.X - p.Pos.X) + 
          (pp.Pos.Y - p.Pos.Y) * (pp.Pos.Y - p.Pos.Y) + 
          (pp.Pos.Z - p.Pos.Z) * (pp.Pos.Z - p.Pos.Z);
        if (d2 < dmin) {
          dmin = d2;
          uok = U;
          vok = V;
        }
      }
    }
    p = InterpolateSurface(s, uok, vok, 0, 0);
    uv[0] = uok;
    uv[1] = vok;
    if (ITER > 0)
      Msg::Info("Brute force method used for projection of point (%g %g %g) on surface %d",
                p.Pos.X, p.Pos.Y, p.Pos.Z, s->Num);
    return true;
  }
  return false;
}

// Split line

static Curve *_create_splitted_curve(Curve *c,List_T *nodes)
{
  int  beg, end;
  List_Read(nodes, 0, &beg);
  List_Read(nodes, List_Nbr(nodes) - 1, &end);
  int id = NEWLINE();
  Curve *cnew = NULL;
  switch(c->Typ){
  case MSH_SEGM_LINE:
    cnew = Create_Curve(id, c->Typ, 1, nodes, NULL, -1, -1, 0., 1.);
    break;
  case MSH_SEGM_SPLN:
    cnew = Create_Curve(id, c->Typ, 3, nodes, NULL, -1, -1, 0., 1.);
    break;
  case MSH_SEGM_BSPLN:
    cnew = Create_Curve(id, c->Typ, 2, nodes, NULL, -1, -1, 0., 1.);
    break;
  default : //should never reach this point...
    Msg::Error("Cannot split a curve with type %i", c->Typ);
    return NULL;
  }
  Tree_Add(GModel::current()->getGEOInternals()->Curves, &cnew);
  CreateReversedCurve(cnew);
  return cnew;
}

bool SplitCurve(int line_id, List_T *vertices_id, List_T *shapes)
{
  Curve *c = FindCurve(line_id);
  if(!c){
    Msg::Error("Curve %i does not exists", line_id);
    return false;
  }
  switch (c->Typ){
  case MSH_SEGM_LINE:
  case MSH_SEGM_SPLN:
  case MSH_SEGM_BSPLN:
    break;
  default:
    Msg::Error("Cannot split curve %i with type %i", line_id, c->Typ);
    return false;
  }
  std::set<int> v_break;
  for(int i = 0; i < List_Nbr(vertices_id); i++){
    int id;
    List_Read(vertices_id, i, &id);
    v_break.insert(id);
  }
  bool is_periodic = (c->beg == c->end);
  bool first_periodic = true;
  bool last_periodic = false;
  List_T *new_list = List_Create(1, List_Nbr(c->Control_Points) / 10, sizeof(int));
  Vertex *pv;
  for (int i = 0; i < List_Nbr(c->Control_Points); i++){
    List_Read(c->Control_Points, i, &pv);
    List_Add(new_list, &pv->Num);
    if(v_break.find(pv->Num) != v_break.end() && List_Nbr(new_list) > 1){
      if(last_periodic)
        break;
      if(!(is_periodic&&first_periodic)){
        Curve *cnew = _create_splitted_curve(c, new_list);
        List_Add(shapes, &cnew);
      }
      first_periodic = false;
      List_Reset(new_list);
      List_Add(new_list, &pv->Num);
    }
    if( i== (List_Nbr(c->Control_Points) - 1) && is_periodic && ! first_periodic){
      i = 0;
      last_periodic = true;
    }
  }
  if(List_Nbr(new_list) > 1){
    Curve *cnew = _create_splitted_curve(c, new_list);
    List_Add(shapes, &cnew);
  }
  // replace original curve by the new curves in all surfaces (and for
  // the opposite curve)
  List_T *rshapes = List_Create(2, 1, sizeof(Shape));
  int N = List_Nbr(shapes);
  for(int i = 0; i < List_Nbr(shapes); i++){
    Curve *cc, *rcc;
    List_Read(shapes, N - i - 1, &cc);
    rcc=FindCurve(-cc->Num);
    List_Add(rshapes, &rcc);
  }
  List_T *Surfs = Tree2List(GModel::current()->getGEOInternals()->Surfaces);
  for(int i = 0; i < List_Nbr(Surfs); i++) {
    Surface *s;
    List_Read(Surfs, i, &s);
    for(int j = 0; j < List_Nbr(s->Generatrices); j++) {
      Curve *surface_curve;
      List_Read(s->Generatrices, j, &surface_curve);
      if(surface_curve->Num == c->Num){
        List_Remove(s->Generatrices, j);
        List_Insert_In_List(shapes, j, s->Generatrices);
        j += List_Nbr(shapes) - 1;
      }
      else if(surface_curve->Num == -c->Num){
        List_Remove(s->Generatrices, j);
        List_Insert_In_List(rshapes, j, s->Generatrices);
        j += List_Nbr(shapes) - 1;
      }
    }
  }
  List_Delete(Surfs);
  DeleteShape(c->Typ, c->Num);
  List_Delete(new_list);
  List_Delete(rshapes);
  return true;
}

// Intersect a curve with a surface

struct CurveSurface{
  Curve *c;
  Surface *s;
};

static void intersectCS(fullVector<double> &uvt, fullVector<double> &res, void *data)
{
  CurveSurface *cs = (CurveSurface*)data;
  Vertex vs = InterpolateSurface(cs->s, uvt(0), uvt(1), 0, 0);
  Vertex vc = InterpolateCurve(cs->c, uvt(2), 0);
  res(0) = vs.Pos.X - vc.Pos.X;
  res(1) = vs.Pos.Y - vc.Pos.Y;
  res(2) = vs.Pos.Z - vc.Pos.Z;
}


bool IntersectCurvesWithSurface(List_T *curve_ids, int surface_id, List_T *shapes)
{
  Surface *s = FindSurface(surface_id);
  if(!s){
    Msg::Error("Unknown surface %d", surface_id);
    return false;
  }
  for(int i = 0; i < List_Nbr(curve_ids); i++){
    double curve_id;
    List_Read(curve_ids, i, &curve_id);
    Curve *c = FindCurve((int)curve_id);
    if(c){
      CurveSurface cs = {c, s};
      fullVector<double> uvt(3);
      uvt(0) = 0.5;
      uvt(1) = 0.5;
      uvt(2) = 0.5;
      if(newton_fd(intersectCS, uvt, &cs)){
        Vertex p = InterpolateCurve(c, uvt(2), 0);
        Vertex *v = Create_Vertex(NEWPOINT(), p.Pos.X, p.Pos.Y, p.Pos.Z, p.lc, p.u);
        Tree_Insert(GModel::current()->getGEOInternals()->Points, &v);
        Shape sh;
        sh.Type = MSH_POINT;
        sh.Num = v->Num;
        List_Add(shapes, &sh);
      }
    }
    else{
      Msg::Error("Uknown curve %d", (int)curve_id);
      return false;
    }
  }
  return true;
}

// Bunch of utility routines

void sortEdgesInLoop(int num, List_T *edges)
{
  // This function sorts the edges in an EdgeLoop and detects any
  // subloops. Warning: the input edges are supposed to be *oriented*
  // (Without this sort, it is very difficult to write general
  // scriptable surface generation in complex cases)
  Curve *c, *c0, *c1, *c2;
  int nbEdges = List_Nbr(edges);
  List_T *temp = List_Create(nbEdges, 1, sizeof(Curve *));

  for(int i = 0; i < nbEdges; i++) {
    int j;
    List_Read(edges, i, &j);
    if((c = FindCurve(j))){
      List_Add(temp, &c);
      if(c->Typ == MSH_SEGM_DISCRETE){
        Msg::Warning("Aborting line loop sort for discrete edge: hope you know "
                     "what you're doing ;-)");
        return;
      }
    }
    else{
      return;
      Msg::Error("Unknown curve %d in line loop %d", j, num);
    }
  }
  List_Reset(edges);

  int j = 0, k = 0;
  c0 = c1 = *(Curve **)List_Pointer(temp, 0);
  List_Add(edges, &c1->Num);
  List_PSuppress(temp, 0);
  while(List_Nbr(edges) < nbEdges) {
    for(int i = 0; i < List_Nbr(temp); i++) {
      c2 = *(Curve **)List_Pointer(temp, i);
      if(c1->end == c2->beg) {
        List_Add(edges, &c2->Num);
        List_PSuppress(temp, i);
        c1 = c2;
        if(c2->end == c0->beg) {
          if(List_Nbr(temp)) {
            Msg::Info("Starting subloop %d in Line Loop %d (are you sure about this?)",
                      ++k, num);
            c0 = c1 = *(Curve **)List_Pointer(temp, 0);
            List_Add(edges, &c1->Num);
            List_PSuppress(temp, 0);
          }
        }
        break;
      }
    }
    if(j++ > nbEdges) {
      Msg::Error("Line Loop %d is wrong", num);
      break;
    }
  }
  List_Delete(temp);
}

void setSurfaceEmbeddedPoints(Surface *s, List_T *points)
{
  if(!s->EmbeddedPoints)
    s->EmbeddedPoints = List_Create(4, 4, sizeof(Vertex *));
  int nbPoints = List_Nbr(points);
  for(int i = 0; i < nbPoints; i++) {
    double iPoint;
    List_Read(points, i, &iPoint);
    Vertex *v = FindPoint((int)iPoint);
    if(v)
      List_Add(s->EmbeddedPoints, &v);
    else
      Msg::Error("Unknown point %d", iPoint);
  }
}

void setSurfaceEmbeddedCurves(Surface *s, List_T *curves)
{
  if (!s->EmbeddedCurves)
    s->EmbeddedCurves = List_Create(4, 4, sizeof(Curve *));
  int nbCurves = List_Nbr(curves);
  for(int i = 0; i < nbCurves; i++) {
    double iCurve;
    List_Read(curves, i, &iCurve);
    Curve *c = FindCurve((int)iCurve);
    if(c)
      List_Add(s->EmbeddedCurves, &c);
    else
      Msg::Error("Unknown curve %d", (int)iCurve);
  }
}

void setSurfaceGeneratrices(Surface *s, List_T *loops)
{
  int nbLoop = List_Nbr(loops);
  s->Generatrices = List_Create(4, 4, sizeof(Curve *));
  for(int i = 0; i < nbLoop; i++) {
    int iLoop;
    List_Read(loops, i, &iLoop);
    EdgeLoop *el;
    if(!(el = FindEdgeLoop(abs(iLoop)))) {
      Msg::Error("Unknown line loop %d", iLoop);
      List_Delete(s->Generatrices);
      s->Generatrices = NULL;
      return;
    }
    else {
      int ic;
      Curve *c;
      if((i == 0 && iLoop > 0) || // exterior boundary
         (i != 0 && iLoop < 0)){  // hole
        for(int j = 0; j < List_Nbr(el->Curves); j++) {
          List_Read(el->Curves, j, &ic);
          ic *= sign(iLoop);
          if(i != 0) ic *= -1; // hole
          if(!(c = FindCurve(ic))) {
            Msg::Error("Unknown curve %d", ic);
            List_Delete(s->Generatrices);
            s->Generatrices = NULL;
            return;
          }
          else
            List_Add(s->Generatrices, &c);
        }
      }
      else{
        for(int j = List_Nbr(el->Curves)-1; j >= 0; j--) {
          List_Read(el->Curves, j, &ic);
          ic *= sign(iLoop);
          if(i != 0) ic *= -1; // hole
          if(!(c = FindCurve(ic))) {
            Msg::Error("Unknown curve %d", ic);
            List_Delete(s->Generatrices);
            s->Generatrices = NULL;
            return;
          }
          else
            List_Add(s->Generatrices, &c);
        }
      }
    }
  }
}

void setVolumeSurfaces(Volume *v, List_T *loops)
{
  List_Reset(v->Surfaces);
  List_Reset(v->SurfacesOrientations);
  List_Reset(v->SurfacesByTag);
  for(int i = 0; i < List_Nbr(loops); i++) {
    int il;
    List_Read(loops, i, &il);
    SurfaceLoop *sl;
    if(!(sl = FindSurfaceLoop(abs(il)))) {
      Msg::Error("Unknown surface loop %d", il);
      return;
    }
    else {
      for(int j = 0; j < List_Nbr(sl->Surfaces); j++) {
        int is;
        List_Read(sl->Surfaces, j, &is);
        Surface *s = FindSurface(abs(is));
        if(s) {
          // contrary to curves in edge loops, we don't actually
          // create "negative" surfaces. So we just store the signs in
          // another list
          List_Add(v->Surfaces, &s);
          int tmp = sign(is) * sign(il);
          if(i > 0) tmp *= -1; // this is a hole
          List_Add(v->SurfacesOrientations, &tmp);
        }
        else{
          GFace *gf = GModel::current()->getFaceByTag(abs(is));
          if(gf) {
            List_Add(v->SurfacesByTag, &is);
          }
          else{
            Msg::Error("Unknown surface %d", is);
            return;
          }
        }
      }
    }
  }
}

// GEO_Internals routines

void GEO_Internals::alloc_all()
{
  MaxPointNum = MaxLineNum = MaxLineLoopNum = MaxSurfaceNum = 0;
  MaxSurfaceLoopNum = MaxVolumeNum = MaxPhysicalNum = 0;
  Points = Tree_Create(sizeof(Vertex *), compareVertex);
  Curves = Tree_Create(sizeof(Curve *), compareCurve);
  EdgeLoops = Tree_Create(sizeof(EdgeLoop *), compareEdgeLoop);
  Surfaces = Tree_Create(sizeof(Surface *), compareSurface);
  SurfaceLoops = Tree_Create(sizeof(SurfaceLoop *), compareSurfaceLoop);
  Volumes = Tree_Create(sizeof(Volume *), compareVolume);
  LevelSets = Tree_Create(sizeof(LevelSet *), compareLevelSet);
  PhysicalGroups = List_Create(5, 5, sizeof(PhysicalGroup *));
}

void GEO_Internals::free_all()
{
  MaxPointNum = MaxLineNum = MaxLineLoopNum = MaxSurfaceNum = 0;
  MaxSurfaceLoopNum = MaxVolumeNum = MaxPhysicalNum = 0;
  Tree_Action(Points, Free_Vertex); Tree_Delete(Points);
  Tree_Action(Curves, Free_Curve); Tree_Delete(Curves);
  Tree_Action(EdgeLoops, Free_EdgeLoop); Tree_Delete(EdgeLoops);
  Tree_Action(Surfaces, Free_Surface); Tree_Delete(Surfaces);
  Tree_Action(SurfaceLoops, Free_SurfaceLoop); Tree_Delete(SurfaceLoops);
  Tree_Action(Volumes, Free_Volume); Tree_Delete(Volumes);
  Tree_Action(LevelSets, Free_LevelSet); Tree_Delete(LevelSets);
  List_Action(PhysicalGroups, Free_PhysicalGroup); List_Delete(PhysicalGroups);
}

void GEO_Internals::reset_physicals()
{
  List_Action(PhysicalGroups, Free_PhysicalGroup); 
  List_Reset(PhysicalGroups);
}
