// 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 "meshGFaceQuadrilateralize.h"
#include "GmshMessage.h"
#include "Numeric.h"
#include "GFace.h"
#include "meshGFaceDelaunayInsertion.h"
#include "meshGFaceOptimize.h"
#include "meshGFaceBDS.h"
#include "BDS.h"
#include "SVector3.h"

class edgeFront {
 public:
  typedef std::set<BDS_Edge *>::const_iterator eiter;
 private:
  BDS_Mesh *m;
  GFace *gf;
  void getFrontEdges(BDS_Point *p, eiter & it1, eiter & it2) const;  
  void getFrontEdges(BDS_Point *p, std::vector<eiter> & f) const;
 public:
  std::set<BDS_Edge *> edges;  
  std::set<BDS_Edge*> stat[5];
  eiter begin() { return edges.begin(); }
  eiter end() { return edges.end(); }
  edgeFront(BDS_Mesh *_m, GFace *_gf) : m(_m), gf(_gf) {}
  // initiate edges in the front i.e.
  // take all edges that have one neighbor 
  // and all edges that have a quad and a triangle 
  // neighbor
  void initiate();
  // compute normal vector to an edge that points
  // inside the domain
  SVector3 normal(BDS_Edge *e) const;    
  // compute the state of a front edge
  // 0 both vertices have edge angles < 60 deg 
  // 1 e->p1 have and edge angle > 60 deg 
  // 2 e->p2 have and edge angle > 60 deg 
  // 3 both vertices have edge angles > 60 deg 
  int computeStatus(BDS_Edge *e) const;
  inline bool inFront(BDS_Edge *e) const{ return getStatus(e) != -1; }
  inline int getStatus(BDS_Edge *e) const{
    for(int i = 0; i < 5; i++){
      if(stat[i].find(e) != stat[i].end()) return i;
    }
    return -1;
  }
  // get one edge of the front that is tagged "tag"
  // and do whatever has to be done to remove it from
  // the front/tag. return false if the front is not empty.
  bool emptyFront(int tag);
  // update the status of the edge
  void updateStatus(BDS_Edge *e);
  // form a quad now
  bool formQuad(BDS_Edge *e, BDS_Edge *left, BDS_Edge *right);
  // delete the cavity delimitated by 4 edges
  void emptyCavity(BDS_Edge *bottom, BDS_Edge *top, BDS_Edge *left, BDS_Edge *right);
  // delete an edge from the front
  void deleteFromFront(BDS_Edge *e)
  {
    edges.erase(e);
    for(int i = 0; i < 5; i++){
      std::set<BDS_Edge*>::iterator it = stat[i].find(e);
      if(it != stat[i].end()){
        stat[i].erase(it);
        return;
      }
    }
  }
  // taking a point from the front, find the optimal edge
  BDS_Edge *findOptimalEdge(BDS_Point *p, BDS_Point *avoid);
};

void edgeFront::updateStatus(BDS_Edge *e)
{
  for(int i = 0; i < 5; i++){
    std::set<BDS_Edge*>::iterator it = stat[i].find(e);
    if(it !=stat[i].end()){
      stat[i].erase(it);
      stat[computeStatus(e)].insert(e);
      return;
    }
  }
  Msg::Error("Something wrong in updateStatus");
}

SVector3 norm_edge(BDS_Point *p1, BDS_Point *p2)
{
  SVector3 n(p2->X-p1->X,p2->Y-p1->Y,p2->Z-p1->Z);
  n.normalize();
  return n;
}

void recur_empty_cavity(BDS_Face *f,
                        BDS_Edge   *be[4],
                        BDS_Point *bv[4],
                        std::set<BDS_Face*> & faces, 
                        std::set<BDS_Edge*> & edges, 
                        std::set<BDS_Point*> & vertices)
{
  if(faces.find(f) != faces.end()) return;
  faces.insert(f);
  BDS_Edge *ee[3] = {f->e1,f->e2,f->e3};
  for(int i=0;i<3;i++){
    BDS_Edge *e = ee[i];
    if(e != be[0] && e != be[1] && e != be[2] && e != be[3]){
      edges.insert(e);
      BDS_Face *of = e->otherFace(f);
      recur_empty_cavity(of,be,bv,faces,edges,vertices);
    }
  }
}

void edgeFront::emptyCavity(BDS_Edge *bottom, BDS_Edge *top, BDS_Edge *left, 
                            BDS_Edge *right)
{
  // not optimal for now, will be improved further away
  BDS_Face *f = 0;
  if(bottom->faces(0) && bottom->faces(0)->numEdges() == 3) f=bottom->faces(0);
  else if(bottom->faces(1))f = bottom->faces(1);

  std::set<BDS_Face*> m_faces;
  std::set<BDS_Edge*> m_edges;
  std::set<BDS_Point*> m_vertices;
  BDS_Edge   *be[4] = {bottom,top,left,right};
  BDS_Point *bv[4] = {bottom->commonvertex(left),
                      left->commonvertex(top),
                      top->commonvertex(right),
                      right->commonvertex(bottom)};

  recur_empty_cavity(f, be, bv, m_faces, m_edges, m_vertices);
  //  printf("%d edges %d faces\n",m_edges.size(),m_faces.size());
  for(std::set<BDS_Face*>::iterator it = m_faces.begin(); 
      it != m_faces.end(); ++it) m->del_face(*it);
  for(std::set<BDS_Edge*>::iterator it = m_edges.begin(); 
      it != m_edges.end(); ++it) m->del_edge(*it);
}


SVector3 edgeFront::normal(BDS_Edge*e) const
{
  BDS_Face *t = e->faces(0);
  if(e->numfaces() == 2 && e->faces(1)->numEdges() == 3)t=e->faces(1);

  /*
    points p1, p2 and p3
    p3 does NOT belong to the edge
    p = p1 (1-u-v) + p2 u + p3 v;
    J = [p2-p1 p3-p1 (p2-p1)x(p3-p1)]
    N = - J^-1 * {0,1,0}
    n = N/|N|    
   */
  BDS_Point *p1 = e->p1;
  BDS_Point *p2 = e->p2;
  BDS_Point *p3;
  if(e == t->e1)
    p3 = t->e2->commonvertex(t->e3);
  else if(e == t->e2)
    p3 = t->e1->commonvertex(t->e3);
  else if(e == t->e3)
    p3 = t->e2->commonvertex(t->e1);
  else {
    Msg::Error("Could not compute fron normal");
    return SVector3();
  }
  
  SVector3 t1(p2->X-p1->X,p2->Y-p1->Y,p2->Z-p1->Z);
  SVector3 t2(p3->X-p1->X,p3->Y-p1->Y,p3->Z-p1->Z);
  SVector3 t3 = crossprod(t1,t2);
  double m[3][3] = {{t1.x(),t2.x(),t3.x()},
                    {t1.y(),t2.y(),t3.y()},
                    {t1.z(),t2.z(),t3.z()}};
  double im[3][3];
  inv3x3(m,im);
  SVector3 n(im[1][0],im[1][1],im[1][2]);
  n.normalize();
  return n;
}

void edgeFront::getFrontEdges(BDS_Point *p, std::vector<eiter> & fe) const
{
  for(std::list<BDS_Edge*>::iterator itp = p->edges.begin(); 
      itp != p->edges.end(); ++ itp){
    eiter it = edges.find(*itp); 
    if(it != edges.end())
      fe.push_back(it);
  }
}

void edgeFront::getFrontEdges(BDS_Point *p, eiter & it1, eiter & it2) const
{
  int count = 0;
  for(std::list<BDS_Edge*>::iterator itp = p->edges.begin(); 
      itp != p->edges.end(); ++ itp){
    if(count == 0){
      it1 = edges.find(*itp); 
      if(it1 != edges.end()) count++;
    }
    else if(count == 1){
      it2 = edges.find(*itp); 
      if(it2 != edges.end()) return;
    }
  }  
  Msg::Error("point %d is in the front but has only %d edges\n",p->iD,count);
}

void edgeFront::initiate()
{
  edges.clear();
  for(int i = 0; i < 5; i++) stat[i].clear();
  std::list<BDS_Edge*>::iterator it = m->edges.begin();
  while(it != m->edges.end()){
    if(((*it)->numfaces() == 1 && (*it)->faces(0)->e4 == 0) ||
        ((*it)->numfaces() == 2 && (*it)->numTriangles() == 1)) {
      edges.insert(*it);
    }
    ++it;
  }
  for(eiter it2 = begin(); it2 !=end(); ++it2){
    int status = computeStatus(*it2);
    stat[status].insert(*it2);
  }
}

static double angle3Points(BDS_Point *p1, BDS_Point *p2, BDS_Point *p3)
{
  SVector3 a(p1->X - p2->X, p1->Y - p2->Y, p1->Z - p2->Z);
  SVector3 b(p3->X - p2->X, p3->Y - p2->Y, p3->Z - p2->Z);
  SVector3 c = crossprod(a,b);
  double sinA = c.norm();
  double cosA = dot(a,b);
  //  printf("%d %d %d -> %g %g\n",p1->iD,p2->iD,p3->iD,cosA,sinA);
  return atan2(sinA, cosA);
}

int edgeFront::computeStatus(BDS_Edge *e) const
{
  eiter it11, it12, it21,it22;
  getFrontEdges(e->p1, it11, it12);  
  getFrontEdges(e->p2, it21, it22);  
  
  BDS_Edge *e1 = (*it11) == e ? *it12 : *it11;
  BDS_Edge *e2 = (*it21) == e ? *it22 : *it21;
  
  double angle1 = angle3Points((*it11)->othervertex(e->p1), e->p1,
                               (*it12)->othervertex(e->p1));
  double angle2 = angle3Points((*it21)->othervertex(e->p2), e->p2,
                               (*it22)->othervertex(e->p2));

  SVector3 n1 = normal(e);
  SVector3 n2 = norm_edge(e->p1, e1->othervertex(e->p1));
  SVector3 n3 = norm_edge(e->p2, e2->othervertex(e->p2));
  if(dot(n1,n2) < 0)angle1 = M_PI;
  if(dot(n1,n3) < 0)angle2 = M_PI;

  const double angleLimit = 3 * M_PI/4.;

  if(angle1 > angleLimit && angle2 > angleLimit) return 0;
  if(angle1 > angleLimit && angle2 < angleLimit) return 1;
  if(angle1 < angleLimit && angle2 > angleLimit) return 2;
  return 3;
}

bool edgeFront::formQuad(BDS_Edge *e, BDS_Edge *left, BDS_Edge *right)
{

  printf("e (%d,%d), l(%d,%d), r(%d,%d)\n",
         e->p1->iD,e->p2->iD,
         left->p1->iD,left->p2->iD,
         right->p1->iD,right->p2->iD);
  
  //  outputScalarField(m->triangles, "deb_before.pos", 0);

  std::vector<BDS_Point*> toUpdate;

  BDS_Point *pleft  = left->othervertex(e->p1);
  BDS_Point *pright = right->othervertex(e->p2);
  
  // recover edge pleft->pright
  BDS_Edge *top = m->find_edge(pleft,pright);

  /*
    We first have to ensure that, if left or right
    are closing the front, then even number of edges should remain in
    both left and right parts of the front
   */

  if(!top) {
    //    printf("recover\n");
    //    top = m->recover_edge_fast(pleft,pright);
    //    if(!top)
    bool _fatallyFailed;
    top = m->recover_edge(pleft->iD, pright->iD,_fatallyFailed);
    //    printf("recover done %p\n",top);
    if(!top) return false;
  }
  // delete internal triangles
  emptyCavity(e, top, left, right);
  // add the quad
  m->add_quadrangle(e, left, top, right);
  // update the edge status
  // bottom edge leaves the front
  deleteFromFront(e);
  // top edge becomes part of the front

  /*  printf("(%d,%d),(%d,%d),(%d,%d),(%d,%d)\n",
         e->p1->iD,e->p2->iD,
         left->p1->iD,left->p2->iD,
         top->p1->iD,top->p2->iD,
         right->p1->iD,right->p2->iD);
  */
  //  outputScalarField(m->triangles, "deb.pos", 0);


  // if left edge was in the front, then it leaves the front
  // because it has either 2 neighboring quads or one quad
  // and the void
  // if the left edge was not in the front, then it becomes
  // part of it. 
  //  printf("coucou1\n");
  if(inFront(left)) deleteFromFront(left);
  else edges.insert(left);

  //  printf("coucou2\n");
  if(inFront(right)) deleteFromFront(right);
  else edges.insert(right);

  //  printf("coucou3\n");
  if(inFront(top)) deleteFromFront(top);
  else edges.insert(top);
  
  toUpdate.push_back(e->p1);
  toUpdate.push_back(e->p2);
  toUpdate.push_back(pleft);
  toUpdate.push_back(pright);

  for(unsigned int i = 0; i < toUpdate.size(); i++){
    toUpdate[i]->config_modified = true;
    //bool done = 
    m->smooth_point_parametric(toUpdate[i], gf);
    //    printf("smooth done %d (g %d)\n",done,toUpdate[i]->g->classif_degree);
  }

  for(unsigned int i = 0; i < toUpdate.size(); i++){
    BDS_Point *p = toUpdate[i];
    for(std::list<BDS_Edge*>::iterator itp = p->edges.begin(); 
        itp != p->edges.end(); ++ itp){
      if(inFront(*itp)){
        updateStatus(*itp);     
      } 
    }
  }  
  return true;
}

BDS_Edge *edgeFront::findOptimalEdge(BDS_Point *p, BDS_Point *avoid)
{
  eiter it1, it2;
  getFrontEdges(p, it1, it2);
  // compute bissector of front edges, this is the optimal direction
  SVector3 n1 = normal(*it1);
  SVector3 n2 = normal(*it2);
  SVector3 n = n1 + n2;
  n.normalize();

  //  printf("POINT %g %g %g N %g %g %g\n",p->X,p->Y,p->Z,n.x(),n.y(),n.z());
  
  double lowerBound = cos(M_PI / 6.0);
  BDS_Edge *found = 0;
  for(std::list<BDS_Edge*>::iterator itp = p->edges.begin(); 
      itp != p->edges.end(); ++ itp){
    // the edge is not in the front and is not bounded by quads only
    if(*it1 != *itp && *it2 != *itp && (*itp)->numTriangles()){
      BDS_Point *q = (*itp)->othervertex(p);
      SVector3 d(q->X - p->X, q->Y - p->Y, q->Z - p->Z);
      d.normalize();
      double COS = dot(n,d);
      if(COS > lowerBound && q != avoid){
        lowerBound = COS;
        found = *itp;
      } 
    }    
  }
  if(found) return found;
  else{
    std::list<BDS_Face*> ts;
    double x[2];
    const double L = 0.5*sqrt(3.0)*((*it2)->length() * (*it1)->length());
    p->getTriangles(ts);
    std::list<BDS_Face*>::iterator it = ts.begin();
    std::list<BDS_Face*>::iterator ite = ts.end();
    while(it != ite) {
      BDS_Face *t = *it;
      if(!t->e4){
        BDS_Edge *e = t->oppositeEdge(p);
        if(e->numfaces() == 2){
          BDS_Face *f = e->otherFace(t);
          if(!f->e4){
            BDS_Point *target = f->oppositeVertex(e);
            // ONLY WORKS IN 2D for now !!!!!!!!!!!!!!!!!!!
            Intersect_Edges_2d(e->p1->X, e->p1->Y,
                               e->p2->X, e->p2->Y,
                               p->X, p->Y,
                               p->X + n.x(), p->Y + n.y(), x);
            if(x[0] >= 0 && x[0] <= 1){
              SVector3 d(target->X-p->X,target->Y-p->Y,target->Z-p->Z);
              d.normalize();
              double COS = dot(n,d);
              double L2 = sqrt((target->X - p->X) *(target->X - p->X) +
                               (target->X - p->Y) *(target->X - p->Y) +
                               (target->X - p->Z) *(target->X - p->Z) );
              
              // swapping the edge alllow to find an edgge that has the right direction and
              // right size
              if(COS > cos(M_PI/6.0) && L2 < L){
                m->swap_edge(e, BDS_SwapEdgeTestQuality(false,false));
                BDS_Edge *newE = m->find_edge(p,target);
                //            printf("swapping -> %p\n",newE);
                return newE;          
              }
              // split the edge
              else{          
                BDS_Point *mid;
                mid  = m->add_point(++m->MAXPOINTNUMBER,(1.-x[0])*e->p1->u + x[0]*e->p2->u,
                                    (1.-x[0])*e->p1->v + x[0]*e->p2->v,gf);
                mid->lc() = 0.5 * (p->lc() +  target->lc());
                mid->g = e->p1->g;
                m->split_edge(e, mid);
                BDS_Edge *newE = m->find_edge(p,mid);
                //            printf("splitting -> %p %p\n",newE,e->p1->g);
                //            m->cleanup();
                return newE;
              }
            }
          }
        }
      }
      ++it;
    }    
  }
  printf("zarbi\n");
  return 0;
}

bool edgeFront::emptyFront(int tag)
{
  // front edges tagged "tag" is empty
  if(stat[tag].size() == 0) return true;
  BDS_Edge *e = *(stat[tag].begin());
  BDS_Edge *left, *right = 0;
  eiter it1, it2;

  std::vector<eiter> fe1, fe2;
 
  printf("front edges %d %d tag %d\n", e->p1->iD, e->p2->iD, tag);

  switch(tag){
    // both left and right neighboring edges are
    // sufficiently angled in order to allow to
    // form the quad
  case 3:
    {
      getFrontEdges(e->p1, it1, it2);
      if(*it1 == e) left=*it2;
      else left = *it1;
      getFrontEdges(e->p2, it1, it2);
      if(*it1 == e) right = *it2;
      else right = *it1;
      //      printf("case 3\n");
    }
    break;
    // right edge is angled with current edge
    // we therefore find a new front edge in the
    // mesh that allows to move to tag 3
  case 2:
    getFrontEdges(e->p1, it1, it2);
    if(*it1 == e) left = *it2;
    else left = *it1;
    //    printf("case 2 left edge %p\n",left);
    right = findOptimalEdge(e->p2, left->othervertex(e->p1));
    if(right) getFrontEdges(right->othervertex(e->p2), fe2);
    break;
    // left edge is angled with current edge
    // we therefore find a new front edge in the
    // mesh that allows to move to tag 3
  case 1:
    getFrontEdges(e->p2, it1, it2);
    if(*it2 == e) right = *it1;
    else right = *it2;
    //    printf("case 1 right edge %p %p\n",e,right);
    left = findOptimalEdge(e->p1, right->othervertex(e->p2));
    if(left) getFrontEdges(left->othervertex(e->p1), fe1);
    break;
    // no neighboring edge is angled with current edge
    // we therefore find a new front edge in the
    // mesh that allows to move to tag 3
  case 0:
    left = findOptimalEdge(e->p1, 0);
    if(left) right= findOptimalEdge(e->p2, left->othervertex(e->p1));
    if(right) getFrontEdges(right->othervertex(e->p2), fe2);
    if(left) getFrontEdges(left->othervertex(e->p1), fe1);
    //    printf("strange\n");
    break;
  default:
    Msg::Error("Unknown case in emptyFront");
    return false;
  }

  if(fe1.size() || fe2.size() || !left || !right || !formQuad(e, left, right)){
    //    printf("front cloeses : algo has to be finished\n");
    stat[tag].erase(stat[tag].begin());
    stat[4].insert(e);
  }
  return false;
}

static int numQuads(BDS_Mesh *m)
{
  int N = 0;
  for(std::list<BDS_Face*>::iterator it = m->triangles.begin();
      it != m->triangles.end(); ++it){
    if((*it)->e4) N++;
  }
  return N;
}

int gmshQMorph(GFace *gf)
{
  // assert first that there exist a triangulation of
  // the face  
  if(!gf->triangles.size()){
    Msg::Warning("Cannot Quadrilaterize a face that has not been triangulated");
    return -1;
  }

  // create data structures for mesh manipulations
  std::list<GFace*> l; l.push_back(gf);
  BDS_Mesh *pm = gmsh2BDS(l);

  // create the front
  edgeFront front(pm,gf);
  front.initiate();
  
  int ITER = 1;
  
  // empty the front for front edges tagged 3, 2, 1 then 0
  int _numQuads = 0;
  while(1){
    if(front.emptyFront(3)){
      if(front.emptyFront(2)){
        if(front.emptyFront(1)){
          if(front.emptyFront(0)){
            int ns;
            smoothVertexPass(gf,*pm,ns,false);
            printf("nex row iter %6d->>>\n",ITER);
            front.initiate();
            int _numQuadsNew = numQuads(pm);
            if(front.edges.size() == 0 || _numQuads == _numQuadsNew)
              break;
            _numQuads = _numQuadsNew;
          }
        }
      }
    }
    ITER++;
    if(1 || ITER%100 == 0){
      char name[256];
      sprintf(name,"qmorph-face%d-iter%d.pos",gf->tag(),ITER);
      outputScalarField(pm->triangles, name, 0);
    }
    //    if(ITER == 1123)break;
  }
  // delete the BDS
  delete pm;
  return 1;
}
