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

#include <stack>
#include "GmshConfig.h"
#include "meshGFaceElliptic.h"
#include "qualityMeasures.h"
#include "GFace.h"
#include "GEdge.h"
#include "GEdgeCompound.h"
#include "GVertex.h"
#include "GModel.h"
#include "MVertex.h"
#include "MTriangle.h"
#include "MQuadrangle.h"
#include "MLine.h"
#include "BackgroundMeshTools.h"
#include "Numeric.h"
#include "GmshMessage.h"
#include "Generator.h"
#include "Context.h"
#include "OS.h"
#include "SVector3.h"
#include "SPoint3.h"
#include "fullMatrix.h"
#include "CenterlineField.h"
#if defined(HAVE_ANN)
#include "ANN/ANN.h"
#endif

#define TRAN_QUAD(c1,c2,c3,c4,s1,s2,s3,s4,u,v) \
    (1.-u)*c4+u*c2+(1.-v)*c1+v*c3-((1.-u)*(1.-v)*s1+u*(1.-v)*s2+u*v*s3+(1.-u)*v*s4)

static void printQuads(GFace *gf, fullMatrix<SPoint2> uv,
                       std::vector<MQuadrangle*> quads,  int iter)
{
  if(!CTX::instance()->mesh.saveAll) return;

  char name[234];
  sprintf(name, "quadUV_%d_%d.pos", gf->tag(), iter);
  FILE *f = Fopen(name, "w");
  if(!f){
    Msg::Error("Could not open file '%s'", name);
    return;
  }

  fprintf(f,"View \"%s\" {\n",name);

  for (int i = 1; i < uv.size1()-1; i++)
    for (int j = 0; j < uv.size2(); j++)
      fprintf(f,"SP(%g,%g,%g) {%d};\n", uv(i,j).x(), uv(i,j).y(), 0.0, i);

  fprintf(f,"};\n");
  fclose(f);

  char name3[234];
  sprintf(name3, "quadXYZ_%d_%d.pos", gf->tag(), iter);
  FILE *f3 = Fopen(name3,"w");
  if(!f3){
    Msg::Error("Could not open file '%s'", name3);
    return;
  }

  fprintf(f3,"View \"%s\" {\n",name3);
  for (unsigned int i = 0; i < quads.size(); i++){
    quads[i]->writePOS(f3,true,false,false,false,false,false);
  }
  fprintf(f3,"};\n");
  fclose(f3);
}

static void printParamGrid(GFace *gf, std::vector<MVertex*> vert1, std::vector<MVertex*> vert2,
			   std::vector<MVertex*> e01, std::vector<MVertex*> e10,
			   std::vector<MVertex*> e23, std::vector<MVertex*> e32,
			   std::vector<MVertex*> e02, std::vector<MVertex*> e13,
			   std::vector<MQuadrangle*> quads)
{

  if(!CTX::instance()->mesh.saveAll) return;

  std::vector<SPoint2> p1,p2;
  for (unsigned int i = 0; i< vert1.size(); i++){
    SPoint2 pi;
    reparamMeshVertexOnFace(vert1[i], gf, pi);
    p1.push_back(pi);
  }
  for (unsigned int j = 0; j< vert2.size(); j++){
    SPoint2 pj;
    reparamMeshVertexOnFace(vert2[j], gf, pj);
    p2.push_back(pj);
  }

  char name[234];
  sprintf(name,"paramGrid_%d.pos", gf->tag());
  FILE *f = Fopen(name,"w");
  if(!f){
    Msg::Error("Could not open file '%s'", name);
    return;
  }

  fprintf(f,"View \"%s\" {\n",name);

  // for (unsigned int i = 0; i < p1.size(); i++)
  //   fprintf(f,"SP(%g,%g,%g) {%d};\n", p1[i].x(), p1[i].y(), 0.0, i);
  // for (unsigned int j = 0; j < p2.size(); j++)
  //   fprintf(f,"SP(%g,%g,%g) {%d};\n", p2[j].x(), p2[j].y(), 0.0, 100+j);

   for (unsigned int i = 0; i < p1.size()-1; i++)
     fprintf(f,"SL(%g,%g,%g,%g,%g,%g) {%d,%d};\n", p1[i].x(), p1[i].y(), 0.0, p1[i+1].x(), p1[i+1].y(), 0.0, 1, 1);
   for (unsigned int i = 0; i < p2.size()-1; i++)
     fprintf(f,"SL(%g,%g,%g,%g,%g,%g) {%d,%d};\n", p2[i].x(), p2[i].y(), 0.0, p2[i+1].x(), p2[i+1].y(), 0.0, 1, 1);
   fprintf(f,"SL(%g,%g,%g,%g,%g,%g) {%d,%d};\n", p1[p1.size()-1].x(), p1[ p1.size()-1].y(), 0.0, p1[0].x(), p1[0].y(), 0.0, 1, 1);
   fprintf(f,"SL(%g,%g,%g,%g,%g,%g) {%d,%d};\n", p2[p2.size()-1].x(), p2[ p2.size()-1].y(), 0.0, p2[0].x(), p2[0].y(), 0.0, 1, 1);

  fprintf(f,"};\n");
  fclose(f);

  char name2[234];
  sprintf(name2,"paramEdges_%d.pos", gf->tag());
  FILE *f2 = Fopen(name2,"w");
  if(!f2){
    Msg::Error("Could not open file '%s'", name2);
    return;
  }

  fprintf(f2,"View \"%s\" {\n",name2);
  for (unsigned int i = 0; i < e01.size(); i++){
     SPoint2 pi; reparamMeshVertexOnFace(e01[i], gf, pi);
    fprintf(f2,"SP(%g,%g,%g) {%d};\n", pi.x(), pi.y(), 0.0, 1);
  }

  for (unsigned int i = 0; i < e10.size(); i++){
    SPoint2 pi; reparamMeshVertexOnFace(e10[i], gf, pi);
    fprintf(f2,"SP(%g,%g,%g) {%d};\n", pi.x(), pi.y(), 0.0, 10);
  }

  for (unsigned int i = 0; i < e23.size(); i++){
    SPoint2 pi; reparamMeshVertexOnFace(e23[i], gf, pi);
    fprintf(f2,"SP(%g,%g,%g) {%d};\n", pi.x(), pi.y(), 0.0, 23);
  }

  for (unsigned int i = 0; i < e32.size(); i++){
    SPoint2 pi; reparamMeshVertexOnFace(e32[i], gf, pi);
    fprintf(f2,"SP(%g,%g,%g) {%d};\n", pi.x(), pi.y(), 0.0, 32);
  }

  for (unsigned int i = 0; i < e02.size(); i++){
    SPoint2 pi; reparamMeshVertexOnFace(e02[i], gf, pi);
    fprintf(f2,"SP(%g,%g,%g) {%d};\n", pi.x(), pi.y(), 0.0, 2);
  }

  for (unsigned int i = 0; i < e13.size(); i++){
    SPoint2 pi; reparamMeshVertexOnFace(e13[i], gf, pi);
    fprintf(f2,"SP(%g,%g,%g) {%d};\n", pi.x(), pi.y(), 0.0, 13);
  }
  fprintf(f2,"};\n");
  fclose(f2);

  char name3[234];
  sprintf(name3,"quadXYZ_%d.pos", gf->tag());
  FILE *f3 = Fopen(name3,"w");
  if(!f3){
    Msg::Error("Could not open file '%s'", name2);
    return;
  }

  fprintf(f3,"View \"%s\" {\n",name3);
  for (unsigned int i = 0; i < quads.size(); i++){
    quads[i]->writePOS(f3,true,false,false,false,false,false);
  }
  fprintf(f3,"};\n");
  fclose(f3);
}

static void createQuadsFromUV(GFace *gf, fullMatrix<SPoint2> &uv,
			      std::vector<std::vector<MVertex*> > &tab,
			      std::vector<MQuadrangle*> &newq,  std::vector<MVertex*> &newv)
{
  newq.clear();
  newv.clear();

  int M = uv.size1();
  int N = uv.size2();

  for (int i = 1; i < M-1; i++){
    for (int j = 0; j < N-1; j++){
      GPoint gp = gf->point(uv(i,j));
      if (!gp.succeeded()){
  	printf("** QUADS FROM UV new vertex not created p=%g %g \n", uv(i,j).x(), uv(i,j).y()); //exit(1);
      }
      tab[i][j] = new MFaceVertex(gp.x(),gp.y(),gp.z(),gf,gp.u(),gp.v());
    }
  }
  for (int i = 1; i < M-1; i++) tab[i][N-1] =  tab[i][0];

  //create vertices
  for (int i = 1; i < M-1; i++)
    for (int j = 0; j < N-1; j++)
      newv.push_back(tab[i][j]);

  // create quads
  for (int i=0;i<M-1;i++){
    for (int j=0;j<N-1;j++){
      MQuadrangle *qnew = new MQuadrangle (tab[i][j],tab[i][j+1],tab[i+1][j+1],tab[i+1][j]);
      newq.push_back(qnew);
    }
  }

  return;

}
static std::vector<MVertex*> saturateEdgeRegular (GFace *gf, SPoint2 p1, SPoint2 p2,
						  int M, std::vector<SPoint2> &pe)
{
  std::vector<MVertex*> pts;
  for (int i=1;i<M;i++){
    double s = ((double)i/((double)(M)));
    SPoint2 p = p1 + (p2-p1)*s;
    pe.push_back(p);

    GPoint gp = gf->point(p);
    if (!gp.succeeded()) printf("** SATURATE EDGE new vertex not created p=%g %g \n", p.x(), p.y());
    MVertex *v = new MFaceVertex(gp.x(),gp.y(),gp.z(),gf,gp.u(),gp.v());

    if (!v){ pts.clear(); pts.resize(0); return pts;}
    pts.push_back(v);
  }
  return pts;
}

static std::vector<MVertex*> saturateEdgeHarmonic (GFace *gf, SPoint2 p1, SPoint2 p2,
						  double H,  double L,
						  int M, std::vector<SPoint2> &pe)
{
  std::vector<MVertex*> pts;
  for (int i=1;i<M;i++){
    double y = ((double)(i))*H/M;
    double Yy = cosh(M_PI*y/L) - tanh(M_PI*H/L)*sinh(M_PI*y/L);
    double YyH = cosh(M_PI*H/L) - tanh(M_PI*H/L)*sinh(M_PI*H/L);
    double s = (1 - Yy)/(1.-YyH);

    SPoint2 p = p1 + (p2-p1)*s;
    pe.push_back(p);

    GPoint gp = gf->point(p);
    if (!gp.succeeded()) printf("** SATURATE EDGE new vertex not created p=%g %g \n", p.x(), p.y());
    MVertex *v = new MFaceVertex(gp.x(),gp.y(),gp.z(),gf,gp.u(),gp.v());

    if (!v){ pts.clear(); pts.resize(0); return pts;}
    pts.push_back(v);
  }
  return pts;
}

static void transfiniteSmoother(GFace* gf,
				fullMatrix<SPoint2> &uv,
				std::vector<std::vector<MVertex*> > &tab,
				std::vector<MQuadrangle*> &newq,
				std::vector<MVertex*> &newv,
				bool isPeriodic=false)
{
  int M = uv.size1();
  int N = uv.size2();
  int jStart = isPeriodic ? 0 : 1;

  int numSmooth = 150;
  fullMatrix<SPoint2> uvold = uv;
  for(int k = 0; k < numSmooth; k++){
    double norm = 0.0;
    for (int i=1; i<M-1; i++){
      for (int j = jStart; j<N-1; j++){
  	int jm1 = (j==0) ? N-2: j-1;
  	int jp1 = (isPeriodic ) ? (j+1)%(N-1) : j+1;
  	double alpha = 0.25 * (SQU(uv(i,jp1).x() - uv(i,jm1).x()) +
  			       SQU(uv(i,jp1).y() - uv(i,jm1).y())) ;
  	double gamma = 0.25 * (SQU(uv(i+1,j).x() - uv(i-1,j).x()) +
  			       SQU(uv(i+1,j).y() - uv(i-1,j).y()));
  	double beta = 0.0625 *
  	  ((uv(i+1,j).x() - uv(i-1,j).x()) * (uv(i,jp1).x() - uv(i,jm1).x()) +
  	   (uv(i+1,j).y() - uv(i-1,j).y()) * (uv(i,jp1).y() - uv(i,jm1).y()));
  	double uk = 0.5 *
  	  (alpha * (uv(i+1,j).x() + uv(i-1,j).x()) +
  	   gamma * (uv(i,jp1).x() + uv(i,jm1).x()) -
  	   2. * beta * (uv(i+1,jp1).x() - uv(i-1,jp1).x() -
  			uv(i+1,jm1).x() + uv(i-1,jm1).x())) / (alpha + gamma);
  	double vk = 0.5 *
  	  (alpha * (uv(i+1,j).y() + uv(i-1,j).y()) +
  	   gamma * (uv(i,jp1).y()+ uv(i,jm1).y()) -
  	   2. * beta * (uv(i+1,jp1).y() - uv(i-1,jp1).y() -
  			uv(i+1,jm1).y() + uv(i-1,jm1).y())) / (alpha + gamma);
  	uvold(i,j) = uv(i,j);
  	norm += sqrt((uv(i,j).x()-uk)*(uv(i,j).x()-uk)+(uv(i,j).y()-vk)*(uv(i,j).y()-vk));
  	uv(i,j) = SPoint2(uk,vk);
        }
      }
    if (isPeriodic){
      for (int i = 1; i<M-1; i++) {
  	uv(i,N-1) = uv(i,0);
  	uvold(i,N-1) = uvold(i,0);
      }
    }

    if(k%20==0){
      printf("Transfinite smoother iter (%d) = %g \n", k, norm);
      createQuadsFromUV(gf, uv, tab, newq, newv);
      printQuads(gf, uv, newq, k+1);
    }

  }

  //final print
  createQuadsFromUV(gf, uv, tab, newq, newv);
  printQuads(gf, uv, newq, numSmooth);
}

//elliptic surface grid generator
//see eq. 9.26 page 9-24 in handbook of grid generation
static void ellipticSmoother( GFace* gf,
			      fullMatrix<SPoint2> &uv,
			      std::vector<std::vector<MVertex*> > &tab,
			      std::vector<MQuadrangle*> &newq,
			      std::vector<MVertex*> &newv,
			      bool isPeriodic=false)
{
  printQuads(gf, uv, newq, 0);

  int nbSmooth = 70;
  int M = uv.size1();
  int N = uv.size2();
  int jStart = isPeriodic ? 0 : 1;
  int jEnd   = N-1;

  fullMatrix<SPoint2> uvold = uv;
  for (int k = 0; k< nbSmooth; k++){
    double norm = 0.0;
    for (int i=1; i<M-1; i++){
      for (int j = jStart; j<jEnd; j++){
	int jm1 = (j==0) ? N-2: j-1;
	int jp1 = (isPeriodic ) ? (j+1)%(N-1) : j+1;
	Pair<SVector3, SVector3> der = gf->firstDer(uv(i,j));
	SVector3 xu = der.first();
	SVector3 xv = der.second();
	// double g11 = dot(xu,xu);
	// double g12 = dot(xu,xv);
	// double g22 = dot(xv,xv);
	SVector3 xuu = SVector3();
	SVector3 xvv = SVector3();
	SVector3 xuv = SVector3();
	gf->secondDer(uv(i,j), &xuu, &xvv, &xuv);
	double g11_bar = dot(xu,xu);
	double g12_bar = dot(xu,xv);
	double g22_bar = dot(xv,xv);
	double dxsi = 1.;
	double deta = 1.;
	double u_xsi = (uv(i,jp1).x()-uv(i,j).x())/dxsi;
	double u_eta = (uv(i+1,j).x()-uv(i,j).x())/deta;
	double v_xsi = (uv(i,jp1).y()-uv(i,j).y())/dxsi;
	double v_eta = (uv(i+1,j).y()-uv(i,j).y())/deta;
	double g11 = g11_bar*u_xsi*u_xsi+2.*g12_bar*u_xsi*v_xsi+g22_bar*v_xsi*v_xsi;
	double g12 = g11_bar*u_xsi*u_eta+g12_bar*(u_xsi*v_eta+u_eta*v_xsi)+g22_bar*v_xsi*v_eta;
	double g22 = g11_bar*u_eta*u_eta+2.*g12_bar*u_eta*v_eta+g22_bar*v_eta*v_eta;
	double jac = u_xsi*v_eta-u_eta*v_xsi; //sqrt(g11*g22-g12*g12);
	double jac_bar = sqrt(g11_bar*g22_bar-g12_bar*g12_bar);
	double g11_bar_u = 2.*dot(xu,xuu);
	double g11_bar_v = 2.*dot(xu,xuv);
	double g22_bar_u = 2.*dot(xv,xuv);
	double g22_bar_v = 2.*dot(xv,xvv);
	double g12_bar_u = dot(xu,xuv)+dot(xv,xuu);
	double g12_bar_v = dot(xu,xvv)+dot(xv,xuv);
	double jac_bar_u = 1./(2.*jac_bar)*(g11_bar*g22_bar_u+g22_bar*g11_bar_u-2.*g12_bar*g12_bar_u);
	double jac_bar_v = 1./(2.*jac_bar)*(g11_bar*g22_bar_v+g22_bar*g11_bar_v-2.*g12_bar*g12_bar_v);
	double lapl_u = 1./jac_bar*(jac_bar*(g22_bar_u-g12_bar_v)-(g22_bar*jac_bar_u-g12_bar*jac_bar_v));
	double lapl_v = 1./jac_bar*(jac_bar*(g11_bar_v-g12_bar_u)-(g11_bar*jac_bar_v-g12_bar*jac_bar_u));
	double over =  1./(4.*(g11+g22));
	double uk = over*(2.*g22*(uv(i+1,j).x()+uv(i-1,j).x())+
			  2.*g11*(uv(i,jp1).x()+uv(i,jm1).x())-
			  2*g12*(uv(i+1,jp1).x()-uv(i-1,jp1).x()-uv(i+1,jm1).x()+uv(i-1,jm1).x())-
			  2.*jac*jac*lapl_u);
	double vk = over*(2.*g22*(uv(i+1,j).y()+uv(i-1,j).y())+
			  2.*g11*(uv(i,jp1).y()+uv(i,jm1).y())-
			  2.*g12*(uv(i+1,jp1).y()-uv(i-1,jp1).y()-uv(i+1,jm1).y()+uv(i-1,jm1).y())-
			  2.*jac*jac*lapl_v);
	uvold(i,j) = uv(i,j);
	norm += sqrt((uv(i,j).x()-uk)*(uv(i,j).x()-uk)+(uv(i,j).y()-vk)*(uv(i,j).y()-vk));
	uv(i,j) = SPoint2(uk,vk);
      }
    }
    if (isPeriodic){
      for (int i = 1; i<M-1; i++) {
	uv(i,N-1) = uv(i,0);
	uvold(i,N-1) = uvold(i,0);
      }
    }

    //under-relaxation
    // double omega = 0.8;
    // for (int i=0; i<M; i++){
    //   for (int j = 0; j<N; j++){
    // 	uv(i,j) = SPoint2(omega*uv(i,j).x() + (1.-omega)*uvold(i,j).x(),
    // 			  omega*uv(i,j).y() + (1.-omega)*uvold(i,j).y());
    //   }
    // }


    if(k%10==0){
      printf("Elliptic smoother iter (%d) = %g \n", k, norm);
      createQuadsFromUV(gf, uv, tab, newq, newv);
      printQuads(gf, uv, newq, k+1);
    }

  }

  createQuadsFromUV(gf, uv, tab, newq, newv);
  printQuads(gf, uv, newq, nbSmooth);
}

//create initial grid points MxN using transfinite interpolation
/*
  c4          N              c3
  +--------------------------+
  |       |      |    |      |
  |       |      |    |      |
  +--------------------------+  M
  |       |      |    |      |
  |       |      |    |      |
  +--------------------------+
 c1                          c2
             N
*/
static void createRegularGrid (GFace *gf,
			       MVertex *v1, SPoint2 &c1,
			       std::vector<MVertex*> &e12, std::vector<SPoint2> &pe12, int sign12,
			       MVertex *v2, SPoint2 &c2,
			       std::vector<MVertex*> &e23, std::vector<SPoint2> &pe23, int sign23,
			       MVertex *v3, SPoint2 &c3,
			       std::vector<MVertex*> &e34,std::vector<SPoint2> &pe34, int sign34,
			       MVertex *v4, SPoint2 &c4,
			       std::vector<MVertex*> &e41, std::vector<SPoint2> &pe41,int sign41,
			       std::vector<MQuadrangle*> &q,
			       std::vector<MVertex*> &newv,
			       fullMatrix<SPoint2> &uv,
			       std::vector<std::vector<MVertex*> > &tab)
{
  int M = e23.size();
  int N = e12.size();

  uv.resize(M+2,N+2);

  //std::vector<std::vector<MVertex*> > tab(M+2);
  tab.resize(M+2);
  for(int i = 0; i <= M+1; i++) tab[i].resize(N+2);

  tab[0][0] = v1;      uv(0,0) = c1;
  tab[0][N+1] = v2;    uv(0,N+1) = c2;
  tab[M+1][N+1] = v3;  uv(M+1,N+1) = c3;
  tab[M+1][0] = v4;    uv(M+1,0) = c4;

  for (int i=0;i<N;i++){
    tab[0][i+1]   = sign12 > 0 ? e12 [i] : e12 [N-i-1];
    tab[M+1][i+1] = sign34 < 0 ? e34 [i] : e34 [N-i-1];
    uv(0,i+1)   = sign12 > 0 ? pe12 [i] : pe12 [N-i-1];
    uv(M+1,i+1) = sign34 < 0 ? pe34 [i] : pe34 [N-i-1];
  }
  for (int i=0;i<M;i++){
    tab[i+1][N+1] = sign23 > 0 ? e23 [i] : e23 [M-i-1];
    tab[i+1][0]   = sign41 < 0 ? e41 [i] : e41 [M-i-1];
    uv(i+1,N+1) = sign23 > 0 ? pe23 [i] : pe23 [M-i-1];
    uv(i+1,0)   = sign41 < 0 ? pe41 [i] : pe41 [M-i-1];
  }

  for (int i=0;i<N;i++){
    for (int j=0;j<M;j++){
      double u = (double) (i+1)/ ((double)(N+1));
      double v = (double) (j+1)/ ((double)(M+1));
      SPoint2 p12 = (sign12 >0) ? pe12[i] : pe12 [N-1-i];
      SPoint2 p23 = (sign23 >0) ? pe23[j] : pe23 [M-1-j];
      SPoint2 p34 = (sign34 <0) ? pe34[i] : pe34 [N-1-i];
      SPoint2 p41 = (sign41 <0) ? pe41[j] : pe41 [M-1-j];
      double Up = TRAN_QUAD(p12.x(), p23.x(), p34.x(), p41.x(),
			   c1.x(),c2.x(),c3.x(),c4.x(),u,v);
      double Vp = TRAN_QUAD(p12.y(), p23.y(), p34.y(), p41.y(),
			   c1.y(),c2.y(),c3.y(),c4.y(),u,v);
      if ((p12.x() && p12.y() == -1.0) || (p23.x() && p23.y() == -1.0) ||
          (p34.x() && p34.y() == -1.0) || (p41.x() && p41.y() == -1.0)) {
        Msg::Error("Wrong param -1");
        return;
      }

      SPoint2 pij(Up,Vp);

      GPoint gp = gf->point(pij);
      if (!gp.succeeded()) printf("** INITIAL GRID new vertex not created p=%g %g \n", Up, Vp);
      MVertex *vnew = new MFaceVertex(gp.x(),gp.y(),gp.z(),gf,gp.u(),gp.v());
      newv.push_back(vnew);

      uv(j+1,i+1) = pij;
      tab[j+1][i+1] = vnew;
    }
  }

  // create quads
  for (int i=0;i<=N;i++){
    for (int j=0;j<=M;j++){
      MQuadrangle *qnew = new MQuadrangle (tab[j][i],tab[j][i+1],tab[j+1][i+1],tab[j+1][i]);
      q.push_back(qnew);
    }
  }

}

//create initial grid points MxN using transfinite interpolation
/*
  c2           N              c2
  +--------------------------+
  |       |      |    |      |
  |       |      |    |      |
  +--------------------------+  M
  |       |      |    |      |
  |       |      |    |      |
  +--------------------------+
 c0                          c0
              N
*/
static void createRegularGridPeriodic  (GFace *gf,int sign2,
					MVertex *v0, SPoint2 &c0,
					MVertex *v2, SPoint2 &c2,
					std::vector<MVertex*> &e02, std::vector<SPoint2> &pe02,
					std::vector<MVertex*> &e00, std::vector<SPoint2> &pe00,
					std::vector<MVertex*> &e22, std::vector<SPoint2> &pe22,
					std::vector<MQuadrangle*> &q,
					std::vector<MVertex*> &newv,
					fullMatrix<SPoint2> &uv,
					std::vector<std::vector<MVertex*> > &tab)
{

  int M = e02.size();
  int N = e00.size();

  uv.resize(M+2,N+2);

  char name3[234];
  sprintf(name3,"quadParam_%d.pos", gf->tag());
  FILE *f3 = Fopen(name3,"w");

  if(f3) fprintf(f3,"View \"%s\" {\n",name3);

  tab.resize(M+2);
  for(int i = 0; i < M+2; i++) tab[i].resize(N+2);

  //periodic boundary mesh vertices
  tab[0][0] = v0;      uv(0,0) = c0;
  tab[0][N+1] = v0;    uv(0,N+1) = c0;
  tab[M+1][N+1] = v2;  uv(M+1,N+1) = c2;
  tab[M+1][0] = v2;    uv(M+1,0) = c2;
  for (int i=0;i<N;i++){
    tab[0][i+1]   =   e00 [i];  uv(0,i+1) = pe00 [i]  ;
    tab[M+1][i+1] = (sign2 > 0) ?    e22 [i]  : e22 [N-i-1]  ;
    uv(M+1,i+1)  =  (sign2 > 0) ?    pe22 [i] : pe22 [N-i-1] ;
  }
  for (int i=0;i<M;i++){
    tab[i+1][N+1] =  e02 [i];      uv(i+1,N+1)   =  pe02 [i] ;
    tab[i+1][0]   =  e02 [i];      uv(i+1,0)     =  pe02 [i];
  }

  //interior mesh_vertices
  for (int i=0;i<N;i++){
    SPoint2 p0 =  pe00[i]  ;
    SPoint2 p2 =  (sign2>0) ?    pe22[i] : pe22 [N-i-1] ;
    std::vector<SPoint2> pei;
    std::vector<MVertex*> ei = saturateEdgeRegular(gf,p0,p2,M+1,pei);//M+1
    for (int j=0;j<M;j++){
      SPoint2 pij = pei[j];
      GPoint gp = gf->point(pij);
      if (!gp.succeeded()) printf("** INITIAL GRID new vertex not created p=%g %g \n", pij.x(), pij.y());
      tab[j+1][i+1] = new MFaceVertex(gp.x(),gp.y(),gp.z(),gf,gp.u(),gp.v());
      uv(j+1,i+1) = pij;
    }
  }

  //create vertices
  for (int i=0;i<N+1;i++)
    for (int j=1;j<M+1;j++)
      newv.push_back(tab[j][i]);

  // create quads
  for (int i=0;i<=N;i++){
    for (int j=0;j<=M;j++){
      MQuadrangle *qnew = new MQuadrangle (tab[j][i],tab[j][i+1],tab[j+1][i+1],tab[j+1][i]);
      q.push_back(qnew);
    }
  }

  //print
  if(f3){
    for (int i=0;i<N+2;i++)
      for (int j=0;j<M+2;j++)
        fprintf(f3,"SP(%g,%g,%g) {%d};\n",   uv(j,i).x(), uv(j,i).y(), 0.0, j);
    fprintf(f3,"};\n");
    fclose(f3);
  }

}

static void updateFaceQuads(GFace *gf, std::vector<MQuadrangle*> &quads, std::vector<MVertex*> &newv)
{
  for (unsigned int i = 0; i < quads.size(); i++){
    gf->quadrangles.push_back(quads[i]);
  }
  for(unsigned int i = 0; i < newv.size(); i++){
    gf->mesh_vertices.push_back(newv[i]);
  }
}

static bool computeRingVertices(GFace *gf, Centerline *center,
				std::vector<MVertex*> &vert1, std::vector<MVertex*> &vert2,
				int &N, int &M, int &close_ind, int &sign2,
				double &arc, double &length)
{
#if defined(HAVE_ANN)
  std::list<GEdge*> bedges = gf->edges();
  std::list<GEdge*>::iterator itb = bedges.begin();
  std::list<GEdge*>::iterator ite = bedges.end(); ite--;
  GEdge *ge1 =  (*itb)->getCompound();
  GEdge *ge2 =  (*ite)->getCompound();
  N = ge1->mesh_vertices.size() + 1;
  int N2 = ge2->mesh_vertices.size() + 1;
  if (N != N2 || N%2 != 0){
    Msg::Error("You should an even number nbPoints in centerline =%d \n", N);
    exit(1);
    return false;
  }

  vert1.push_back(ge1->getBeginVertex()->mesh_vertices[0]);
  vert2.push_back(ge2->getBeginVertex()->mesh_vertices[0]);
  for(int i = 0; i < N-1; i++) {
    vert1.push_back(ge1->mesh_vertices[i]);
    vert2.push_back(ge2->mesh_vertices[i]);
  }
  SPoint2 pv1; reparamMeshVertexOnFace(vert1[0], gf, pv1);
  SPoint2 pv2; reparamMeshVertexOnFace(vert2[0], gf, pv2);
  SPoint2 pv1b; reparamMeshVertexOnFace(vert1[1], gf, pv1b);
  SPoint2 pv2b; reparamMeshVertexOnFace(vert2[1], gf, pv2b);
  SPoint2 pv1c; reparamMeshVertexOnFace(vert1[2], gf, pv1c);
  SPoint2 pv2c; reparamMeshVertexOnFace(vert2[2], gf, pv2c);
  SVector3 vec1(pv1b.x()-pv1.x(),pv1b.y()-pv1.y() , 0.0);
  SVector3 vec1b(pv1c.x()-pv1b.x(),pv1c.y()-pv1b.y() ,0.0);
  SVector3 vec2(pv2b.x()-pv2.x(),pv2b.y()-pv2.y() ,0.0);
  SVector3 vec2b(pv2c.x()-pv2b.x(),pv2c.y()-pv2b.y() , 0.0);
  sign2 =  (dot(crossprod(vec1,vec1b),crossprod(vec2,vec2b)) < 0)  ?   -1 : +1 ;
  double n1 = sqrt(pv1.x()*pv1.x()+pv1.y()*pv1.y());
  double n2 = sqrt(pv2.x()*pv2.x()+pv2.y()*pv2.y());
  std::vector<MVertex*> vert_temp;
  if (n2 > n1) {
    vert_temp = vert1;
    vert1.clear();
    vert1 = vert2;
    vert2.clear();
    vert2 = vert_temp;
  }
  double rad = center->operator()(vert1[0]->x(), vert1[0]->y(), vert1[0]->z());

  ANNpointArray nodes = annAllocPts(N, 3);
  ANNidxArray index  = new ANNidx[1];
  ANNdistArray dist = new ANNdist[1];
  for (int ind = 0; ind < N; ind++){
    SPoint2 pp2; reparamMeshVertexOnFace(vert2[ind], gf, pp2);
    nodes[ind][0] =  pp2.x();
    nodes[ind][1] =  pp2.y();
    nodes[ind][2] =  0.0;
  }
  ANNkd_tree *kdtree = new ANNkd_tree(nodes, N, 3);
  SPoint2 pp1; reparamMeshVertexOnFace(vert1[0], gf, pp1);
  double xyz[3] = {pp1.x(), pp1.y(),0.0};
  kdtree->annkSearch(xyz, 1, index, dist);
  close_ind = index[0];

  SPoint2 p1; reparamMeshVertexOnFace(vert1[0], gf, p1);
  SPoint2 p2; reparamMeshVertexOnFace(vert2[close_ind], gf, p2);
  GPoint gp_i(vert1[0]->x(), vert1[0]->y(), vert1[0]->z());
  SPoint2 p1b; reparamMeshVertexOnFace(vert1[N/2], gf, p1b);
  SPoint2 p2b; reparamMeshVertexOnFace(vert2[(close_ind+N/2)%N], gf, p2b);
  GPoint gp_ib(vert1[N/2]->x(), vert1[N/2]->y(), vert1[N/2]->z());
  length = 0.0;
  double lengthb = 0.0;
  int nb = 100;
  for (int i = 0; i< nb; i++){
    SPoint2 pii(p1.x() + (double)(i+1)/nb*(p2.x()-p1.x()), p1.y() + (double)(i+1)/nb*(p2.y()-p1.y()));
    GPoint gp_ii = gf->point(pii);
    SPoint2 piib(p1b.x() + (double)(i+1)/nb*(p2b.x()-p1b.x()), p1b.y() + (double)(i+1)/nb*(p2b.y()-p1b.y()));
    GPoint gp_iib = gf->point(piib);
    length  += gp_i.distance(gp_ii);
    lengthb += gp_ib.distance(gp_iib);
    gp_i = gp_ii;
    gp_ib = gp_iib;
  }

  arc = 2*M_PI*rad;
  double lc =  arc/N;
  M = (int)(0.5*(length+lengthb)/lc) ;

  delete kdtree;
  delete[]index;
  delete[]dist;
  annDeallocPts(nodes);

  return true;
#else
  return false;
#endif
}

//vert1 is the outer circle
//vert2 is the inner circle
//         - - - -
//       -         -
//     -      -      -
//   v0-  v2-   -     -
//     -      -      -
//      -          -
//         - - - -
bool createRegularTwoCircleGridPeriodic (Centerline *center, GFace *gf)
{
#if defined(HAVE_ANN)
  std::vector<MVertex*> vert1, vert2;
  int N, M, close_ind, sign2;
  double arc, length;
  bool success = computeRingVertices(gf, center, vert1, vert2, N, M, close_ind, sign2, arc,length);
  if(!success) return false;

  MVertex *v0 = vert1[0]; SPoint2 p0; reparamMeshVertexOnFace(v0, gf, p0);
  MVertex *v2 = vert2[close_ind]; SPoint2 p2; reparamMeshVertexOnFace(v2, gf, p2);

  //printf("grid N = %d M = %d\n", N , M);
  std::vector<MVertex*> e00,e22;//edges without first and last vertex
  for (int i=1;i<N;i++) e00.push_back(vert1[i]);
  for (int i=1;i<N;i++) e22.push_back(vert2[(close_ind+i)%N]);

  std::vector<SPoint2> pe00, pe22;
  for (unsigned int i = 0; i < e00.size(); i++){
     SPoint2 p00; reparamMeshVertexOnFace(e00[i], gf, p00);
     SPoint2 p22; reparamMeshVertexOnFace(e22[i], gf, p22);
     pe00.push_back(p00);
     pe22.push_back(p22);
  }

  std::vector<SPoint2> pe02;
  std::vector<MVertex*> e02 = saturateEdgeRegular(gf,p0,p2,M+1,pe02);

  std::vector<MQuadrangle*> quads;
  std::vector<MVertex*> newv;
  fullMatrix<SPoint2> uv;
  std::vector<std::vector<MVertex*> > tab;

  createRegularGridPeriodic (gf,sign2,
  			     v0, p0,
  			     v2, p2,
  			     e02, pe02,
  			     e00, pe00,
  			     e22, pe22,
  			     quads, newv, uv, tab);

  printQuads(gf, uv, quads, 0);

  transfiniteSmoother(gf, uv, tab, quads, newv, true);
  //ellipticSmoother(gf, uv, tab, quads, newv, true);

  updateFaceQuads(gf, quads, newv);

  //exit(1);
  //printParamGrid(gf, vert1, vert2, e00,e22,e02,e02,e02,e02, quads);

  return true;
#else
  return false;
#endif
}

//vert1 is the outer circle
//vert2 is the inner circle
//         - - - -
//       -         -
//     -      -      -
//   v0-  v2-   -v3   -v1
//     -      -      -
//      -          -
//         - - - -

bool createRegularTwoCircleGrid (Centerline *center, GFace *gf)
{
#if defined(HAVE_ANN)
  std::vector<MVertex*> vert1, vert2;
  int N, M, close_ind, sign2;
  double arc, length;
  bool success = computeRingVertices(gf, center, vert1, vert2, N, M, close_ind, sign2, arc,length);
  if(!success) return false;

  MVertex *v0 = vert1[0]; SPoint2 p0; reparamMeshVertexOnFace(v0, gf, p0);
  MVertex *v1 = vert1[N/2]; SPoint2 p1; reparamMeshVertexOnFace(v1, gf, p1);
  MVertex *v2 = vert2[close_ind]; SPoint2 p2; reparamMeshVertexOnFace(v2, gf, p2);
  MVertex *v3 = vert2[(close_ind+N/2)%N]; SPoint2 p3; reparamMeshVertexOnFace(v3, gf, p3);

  printf("grid N = %d M = %d\n", N , M);
  std::vector<MVertex*> e01,e10,e23,e32;//edges without first and last vertex
  for (int i=1;i<N/2;i++) e01.push_back(vert1[i]);
  for (int i=N/2+1;i<N;i++) e10.push_back(vert1[i]);
  for (int i=1;i<N/2;i++) e23.push_back(vert2[(close_ind+i)%N]);
  for (int i=N/2+1;i<N;i++) e32.push_back(vert2[(close_ind+i)%N]);

  std::vector<SPoint2> pe01, pe10, pe23, pe32;
  for (unsigned int i = 0; i < e01.size(); i++){
     SPoint2 p01; reparamMeshVertexOnFace(e01[i], gf, p01);
     SPoint2 p10; reparamMeshVertexOnFace(e10[i], gf, p10);
     SPoint2 p23; reparamMeshVertexOnFace(e23[i], gf, p23);
     SPoint2 p32; reparamMeshVertexOnFace(e32[i], gf, p32);
     pe01.push_back(p01);
     pe10.push_back(p10);
     pe23.push_back(p23);
     pe32.push_back(p32);
  }

  std::vector<SPoint2> pe02, pe13;
  std::vector<MVertex*> e02 = saturateEdgeHarmonic (gf,p0,p2,length, arc, M+1, pe02);
  std::vector<MVertex*> e13 = saturateEdgeHarmonic (gf,p1,p3,length, arc, M+1, pe13);

  std::vector<MVertex*> e_inner1 = e23;
  std::vector<MVertex*> e_inner2 = e32;
  std::vector<SPoint2> pe_inner1 = pe23;
  std::vector<SPoint2> pe_inner2 = pe32;
  if (sign2 <0){
    e_inner1 = e32;
    e_inner2 = e23;
    pe_inner1 = pe32;
    pe_inner2 = pe23;
  }

  std::vector<MQuadrangle*> quads, quadS1, quadS2;
  std::vector<MVertex*> newv, newv1, newv2;
  fullMatrix<SPoint2> uv1, uv2;
  std::vector<std::vector<MVertex*> > tab1, tab2;
  createRegularGrid (gf,
  		     v0, p0,
  		     e01, pe01, +1,
  		     v1,p1,
  		     e13, pe13, +1,
  		     v3,p3,
		     e_inner1, pe_inner1, -sign2,
  		     v2,p2,
  		     e02, pe02, -1,
  		     quads, newv, uv1, tab1);

  createRegularGrid (gf,
  		     v0,p0,
  		     e02, pe02,  +1,
  		     v2,p2,
  		     e_inner2, pe_inner2, -sign2,
  		     v3,p3,
  		     e13, pe13, -1,
  		     v1,p1,
  		     e10, pe10, +1,
  		     quads, newv, uv2, tab2);

  printQuads(gf, uv1, quads, 0);

  ellipticSmoother(gf, uv1, tab1, quadS1, newv1);
  ellipticSmoother(gf, uv2, tab2, quadS2, newv2);

  quads.clear();
  for (unsigned int i= 0; i< quadS1.size(); i++) quads.push_back(quadS1[i]);
  for (unsigned int i= 0; i< quadS2.size(); i++) quads.push_back(quadS2[i]);
  //updateFaceQuads(gf, quads, newv);

  printParamGrid(gf, vert1, vert2, e01,e10,e23,e32,e02,e13, quads);

  return true;
#else
  return false;
#endif
}
