// 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 <sstream>
#include "GModel.h"
#include "GFace.h"
#include "GFaceCompound.h"
#include "gmshEdge.h"
#include "Geo.h"
#include "GeoInterpolation.h"
#include "GmshMessage.h"
#include "Context.h"
#include "decasteljau.h"

gmshEdge::gmshEdge(GModel *m, Curve *edge, GVertex *v1, GVertex *v2)
  : GEdge(m, edge->Num, v1, v2), c(edge)
{
  resetMeshAttributes();
}

bool gmshEdge::degenerate(int dim) const
{
  if (c->beg == c->end &&
      c->Typ ==  MSH_SEGM_LINE &&
      List_Nbr(c->Control_Points) == 0){
    Msg::Info("Model Edge %d is degenerate", tag());
    return true;
  }
  return false;
}

void gmshEdge::resetMeshAttributes()
{
  meshAttributes.method = c->Method;
  meshAttributes.nbPointsTransfinite = c->nbPointsTransfinite;
  meshAttributes.typeTransfinite = c->typeTransfinite;
  meshAttributes.coeffTransfinite = c->coeffTransfinite;
  meshAttributes.extrude = c->Extrude;
  meshAttributes.reverseMesh = c->ReverseMesh;
}

Range<double> gmshEdge::parBounds(int i) const
{
 return Range<double>(c->ubeg, c->uend);
}

GPoint gmshEdge::point(double par) const
{
  Vertex a = InterpolateCurve(c, par, 0);
  return GPoint(a.Pos.X, a.Pos.Y, a.Pos.Z, this, par);
}

SVector3 gmshEdge::firstDer(double par) const
{
  Vertex a = InterpolateCurve(c, par, 1);
  return SVector3(a.Pos.X, a.Pos.Y, a.Pos.Z);
}

SVector3 gmshEdge::secondDer(double par) const
{
  Vertex a = InterpolateCurve(c, par, 2);
  return SVector3(a.Pos.X, a.Pos.Y, a.Pos.Z);
}

GEntity::GeomType gmshEdge::geomType() const
{
  switch (c->Typ){
  case MSH_SEGM_LINE : return Line;
  case MSH_SEGM_CIRC :
  case MSH_SEGM_CIRC_INV : return Circle;
  case MSH_SEGM_ELLI:
  case MSH_SEGM_ELLI_INV: return Ellipse;
  case MSH_SEGM_BSPLN:
  case MSH_SEGM_BEZIER:
  case MSH_SEGM_NURBS:
  case MSH_SEGM_SPLN: return Nurb;
  case MSH_SEGM_BND_LAYER: return BoundaryLayerCurve;
  case MSH_SEGM_DISCRETE: return DiscreteCurve;
  default : return Unknown;
  }
}

std::string gmshEdge::getAdditionalInfoString()
{
  if(List_Nbr(c->Control_Points) > 0){
    std::ostringstream sstream;
    sstream << "{";
    for(int i = 0; i < List_Nbr(c->Control_Points); i++){
      if(i) sstream << " ";
      Vertex *v;
      List_Read(c->Control_Points, i, &v);
      sstream << v->Num;
    }
    sstream << "}";

    if(meshAttributes.method == MESH_TRANSFINITE){
      sstream << " transfinite (" << meshAttributes.nbPointsTransfinite;
      int type = meshAttributes.typeTransfinite;
      if(std::abs(type) == 1)
        sstream << ", progression " << sign(type) * meshAttributes.coeffTransfinite;
      else if(std::abs(type) == 2)
        sstream << ", bump " << meshAttributes.coeffTransfinite;
      sstream << ")";
    }
    if(meshAttributes.extrude)
      sstream << " extruded";
    if(meshAttributes.reverseMesh)
      sstream << " reversed";

    return sstream.str();
  }
  else
    return GEdge::getAdditionalInfoString();
}

int gmshEdge::minimumMeshSegments () const
{
  int np;
  if(geomType() == Line)
    np = GEdge::minimumMeshSegments();
  else if(geomType() == Circle || geomType() == Ellipse)
    np = (int)(fabs(c->Circle.t1 - c->Circle.t2) *
                 (double)CTX::instance()->mesh.minCircPoints / M_PI) - 1;
  else
    np = CTX::instance()->mesh.minCurvPoints - 1;
  return std::max(np, meshAttributes.minimumMeshSegments);
}

int gmshEdge::minimumDrawSegments () const
{
  int n = List_Nbr(c->Control_Points) - 1;
  if(!n) n = GEdge::minimumDrawSegments();

  if(geomType() == Line && !c->geometry)
    return n;
  else
    return CTX::instance()->geom.numSubEdges * n;
}

SPoint2 gmshEdge::reparamOnFace(const GFace *face, double epar,int dir) const
{
  Surface *s = (Surface*) face->getNativePtr();

  bool periodic = (c->end == c->beg);
  if(s->geometry){
    switch (c->Typ) {
    case MSH_SEGM_LINE:
      {
        Vertex *v[3];
        List_Read(c->Control_Points, 0, &v[1]);
        List_Read(c->Control_Points, 1, &v[2]);
        SPoint2 p = v[1]->pntOnGeometry +
          (v[2]->pntOnGeometry - v[1]->pntOnGeometry) * epar;
        return p;
      }
    case MSH_SEGM_BSPLN:
    case MSH_SEGM_BEZIER:
      {
        int NbControlPoints = List_Nbr(c->Control_Points);
        int NbCurves = NbControlPoints + (periodic ? -1 : 1);
        int iCurve = (int)floor(epar * (double)NbCurves);
        if(iCurve >= NbCurves)
          iCurve = NbCurves - 1;
        else if (iCurve < 0) // ? does it happen ?
          iCurve = 0;
        double t1 = (double)(iCurve) / (double)(NbCurves);
        double t2 = (double)(iCurve+1) / (double)(NbCurves);
        double t = (epar - t1) / (t2 - t1);
        Vertex *v[4];
        for(int j = 0; j < 4; j ++ ){
          int k = iCurve - (periodic ? 1 : 2) + j;
          if(k < 0)
            k = periodic ? k + NbControlPoints - 1 : 0;
          if(k >= NbControlPoints)
            k = periodic ? k - NbControlPoints + 1: NbControlPoints - 1;
          List_Read(c->Control_Points, k, &v[j]);
        }
        return InterpolateCubicSpline(v, t, c->mat, t1, t2, c->geometry,0);
      }
    case MSH_SEGM_SPLN :
      {
        Vertex temp1, temp2;
        int N = List_Nbr(c->Control_Points);
        int i = (int)((double)(N - 1) * epar);
        if(i < 0)
          i = 0;
        if(i >= N - 1)
          i = N - 2;
        double t1 = (double)(i) / (double)(N - 1);
        double t2 = (double)(i + 1) / (double)(N - 1);
        double t = (epar - t1) / (t2 - t1);
        Vertex *v[4];
        List_Read(c->Control_Points, i, &v[1]);
        List_Read(c->Control_Points, i + 1, &v[2]);
        if(!i) {
          if(c->beg == c->end){
            List_Read(c->Control_Points,N-2,&v[0]);
          }
          else{
            v[0] = &temp1;
            v[0]->pntOnGeometry = v[1]->pntOnGeometry * 2. - v[2]->pntOnGeometry;
          }
        }
        else{
          List_Read(c->Control_Points, i - 1, &v[0]);
        }
        if(i == N - 2) {
          if(c->beg == c->end){
            List_Read(c->Control_Points,1,&v[3]);
          }
          else{
            v[3] = &temp2;
            v[3]->pntOnGeometry = v[2]->pntOnGeometry * 2. - v[1]->pntOnGeometry;
          }
        }
        else{
          List_Read(c->Control_Points, i + 2, &v[3]);
        }
        return InterpolateCubicSpline(v, t, c->mat, t1, t2, c->geometry,0);
      }
    default:
      Msg::Error("Unknown edge type in reparamOnFace");
      return SPoint2();
    }
  }


  if(s->Typ ==  MSH_SURF_REGL){
    Curve *C[4];
    for(int i = 0; i < 4; i++)
      List_Read(s->Generatrices, i, &C[i]);

    double U, V;
    if (C[0]->Num == c->Num) {
      U = (epar - C[0]->ubeg) / (C[0]->uend - C[0]->ubeg) ;
      V = 0;
    }
    else if (C[0]->Num == -c->Num) {
      U = (C[0]->uend - epar - C[0]->ubeg) / (C[0]->uend - C[0]->ubeg) ;
      V = 0;
    }
    else if (C[1]->Num == c->Num) {
      V = (epar - C[1]->ubeg) / (C[1]->uend - C[1]->ubeg) ;
      U = 1;
    }
    else if (C[1]->Num == -c->Num) {
      V = (C[1]->uend - epar - C[1]->ubeg) / (C[1]->uend - C[1]->ubeg) ;
      U = 1;
    }
    else if (C[2]->Num == c->Num) {
      U =  1 - (epar - C[2]->ubeg) / (C[2]->uend - C[2]->ubeg) ;
      V = 1;
    }
    else if (C[2]->Num == -c->Num) {
      U =  1 - ( C[2]->uend -epar - C[2]->ubeg) / (C[2]->uend - C[2]->ubeg) ;
      V = 1;
    }
    else if (C[3]->Num == c->Num) {
      V = 1-(epar - C[3]->ubeg) / (C[3]->uend - C[3]->ubeg) ;
      U = 0;
    }
    else if (C[3]->Num == -c->Num) {
      V = 1-(C[3]->uend - epar - C[3]->ubeg) / (C[3]->uend - C[3]->ubeg) ;
      U = 0;
    }
    else{
      Msg::Info("Reparameterizing edge %d on face %d", c->Num, s->Num);
      return GEdge::reparamOnFace(face, epar, dir);
    }
    return SPoint2(U, V);
  }
  else if (s->Typ == MSH_SURF_TRIC){
    Curve *C[3];
    for(int i = 0; i < 3; i++)
      List_Read(s->Generatrices, i, &C[i]);
    double U, V;
    if(CTX::instance()->geom.oldRuledSurface){
      if (C[0]->Num == c->Num) {
        U = (epar - C[0]->ubeg) / (C[0]->uend - C[0]->ubeg) ;
        V = 0;
      }
      else if (C[0]->Num == -c->Num) {
        U = (C[0]->uend - epar - C[0]->ubeg) / (C[0]->uend - C[0]->ubeg) ;
        V = 0;
      }
      else if (C[1]->Num == c->Num) {
        V = (epar - C[1]->ubeg) / (C[1]->uend - C[1]->ubeg) ;
        U = 1;
      }
      else if (C[1]->Num == -c->Num) {
        V = (C[1]->uend - epar - C[1]->ubeg) / (C[1]->uend - C[1]->ubeg) ;
        U = 1;
      }
      else if (C[2]->Num == c->Num) {
        U = 1-(epar - C[2]->ubeg) / (C[2]->uend - C[2]->ubeg) ;
        V = 1;
      }
      else if (C[2]->Num == -c->Num) {
        U = 1-(C[2]->uend - epar - C[2]->ubeg) / (C[2]->uend - C[2]->ubeg) ;
        V = 1;
      }
      else{
        Msg::Info("Reparameterizing edge %d on face %d", c->Num, s->Num);
        return GEdge::reparamOnFace(face, epar, dir);
      }

    }
    else{
      // FIXME: workaround for exact extrusions
      bool hack = false;
      if(CTX::instance()->geom.exactExtrusion && s->Extrude &&
         s->Extrude->geo.Mode == EXTRUDED_ENTITY && s->Typ != MSH_SURF_PLAN)
        hack = true;
      if (C[0]->Num == c->Num) {
        U = (epar - C[0]->ubeg) / (C[0]->uend - C[0]->ubeg) ;
        V = 0;
      }
      else if (C[0]->Num == -c->Num) {
        U = (C[0]->uend - epar - C[0]->ubeg) / (C[0]->uend - C[0]->ubeg) ;
        V = 0;
      }
      else if (C[1]->Num == c->Num) {
        V = (epar - C[1]->ubeg) / (C[1]->uend - C[1]->ubeg) ;
        U = 1;
      }
      else if (C[1]->Num == -c->Num) {
        V = (C[1]->uend - epar - C[1]->ubeg) / (C[1]->uend - C[1]->ubeg) ;
        U = 1;
      }
      else if (C[2]->Num == c->Num) {
        U = 1-(epar - C[2]->ubeg) / (C[2]->uend - C[2]->ubeg) ;
        V = hack ? 1 : U;
      }
      else if (C[2]->Num == -c->Num) {
        U = 1-(C[2]->uend - epar - C[2]->ubeg) / (C[2]->uend - C[2]->ubeg) ;
        V = hack ? 1 : U;
      }
      else{
        Msg::Info("Reparameterizing edge %d on face %d", c->Num, s->Num);
        return GEdge::reparamOnFace(face, epar, dir);
      }
    }
    return SPoint2(U, V);
  }
  else{
    return GEdge::reparamOnFace(face, epar, dir);
  }
}

void gmshEdge::writeGEO(FILE *fp)
{
  if(!c || c->Num < 0 || c->Typ == MSH_SEGM_DISCRETE) return;
  switch (c->Typ) {
  case MSH_SEGM_LINE:
    fprintf(fp, "Line(%d) = ", c->Num);
    break;
  case MSH_SEGM_CIRC:
  case MSH_SEGM_CIRC_INV:
    fprintf(fp, "Circle(%d) = ", c->Num);
    break;
  case MSH_SEGM_ELLI:
  case MSH_SEGM_ELLI_INV:
    fprintf(fp, "Ellipse(%d) = ", c->Num);
    break;
  case MSH_SEGM_NURBS:
    fprintf(fp, "Nurbs(%d) = {", c->Num);
    for(int i = 0; i < List_Nbr(c->Control_Points); i++) {
      Vertex *v;
      List_Read(c->Control_Points, i, &v);
      if(!i)
        fprintf(fp, "%d", v->Num);
      else
        fprintf(fp, ", %d", v->Num);
      if(i % 8 == 7 && i != List_Nbr(c->Control_Points) - 1)
        fprintf(fp, "\n");
    }
    fprintf(fp, "}\n");
    fprintf(fp, "  Knots {");
    for(int j = 0; j < List_Nbr(c->Control_Points) + c->degre + 1; j++) {
      if(!j)
        fprintf(fp, "%.16g", c->k[j]);
      else
        fprintf(fp, ", %.16g", c->k[j]);
      if(j % 5 == 4 && j != List_Nbr(c->Control_Points) + c->degre)
        fprintf(fp, "\n        ");
    }
    fprintf(fp, "}\n");
    fprintf(fp, "  Order %d;\n", c->degre);
    return;
  case MSH_SEGM_SPLN:
    fprintf(fp, "Spline(%d) = ", c->Num);
    break;
  case MSH_SEGM_BSPLN:
    fprintf(fp, "BSpline(%d) = ", c->Num);
    break;
  case MSH_SEGM_BEZIER:
    fprintf(fp, "Bezier(%d) = ", c->Num);
    break;
  default:
    Msg::Error("Unknown curve type %d", c->Typ);
    return;
  }
  for(int i = 0; i < List_Nbr(c->Control_Points); i++) {
    Vertex *v;
    List_Read(c->Control_Points, i, &v);
    if(i)
      fprintf(fp, ", %d", v->Num);
    else
      fprintf(fp, "{%d", v->Num);
    if(i % 8 == 7)
      fprintf(fp, "\n");
  }
  fprintf(fp, "};\n");

  if(meshAttributes.method == MESH_TRANSFINITE){
    fprintf(fp, "Transfinite Line {%d} = %d",
            tag() * (meshAttributes.typeTransfinite > 0 ? 1 : -1),
            meshAttributes.nbPointsTransfinite);
    if(meshAttributes.typeTransfinite){
      if(std::abs(meshAttributes.typeTransfinite) == 1)
        fprintf(fp, "Using Progression ");
      else if(std::abs(meshAttributes.typeTransfinite) == 2)
        fprintf(fp, "Using Bump ");
      fprintf(fp, "%g", meshAttributes.coeffTransfinite);
    }
    fprintf(fp, ";\n");
  }

  if(meshAttributes.reverseMesh)
    fprintf(fp, "Reverse Line {%d};\n", tag());
}

static inline SPoint3 curveGetPoint(Curve *c, int i)
{
  Vertex *v;
  List_Read(c->Control_Points, i , &v);
  return SPoint3(v->Pos.X, v->Pos.Y, v->Pos.Z);
}

void gmshEdge::discretize(double tol, std::vector<SPoint3> &pts, std::vector<double> &ts)
{
  switch(c->Typ) {
    case MSH_SEGM_LINE :
      {
        int NPt = List_Nbr(c->Control_Points);
        pts.resize(NPt);
        ts.resize(NPt);
        for (int i = 0; i < NPt; ++i) {
          pts[i]= curveGetPoint(c, i);
          ts[i] = i / (double) (NPt - 1);
        }
        return;
      }
    case MSH_SEGM_BEZIER :
      {
        int NbCurves = (List_Nbr(c->Control_Points) - 1) / 3;
        for (int iCurve = 0; iCurve < NbCurves; ++iCurve) {
          double t1 = (iCurve) / (double)(NbCurves);
          double t2 = (iCurve+1) / (double)(NbCurves);
          SPoint3 pt[4];
          for(int i = 0; i < 4; i++) {
            pt[i] = curveGetPoint(c, iCurve * 3 + i);
          }
          std::vector<double> lts;
          std::vector<SPoint3> lpts;
          decasteljau(tol, pt[0], pt[1], pt[2], pt[3], lpts, lts);
          for (size_t i = (iCurve == 0 ? 0 : 1); i < lpts.size(); ++i) {
            pts.push_back(lpts[i]);
            ts.push_back(t1 + lts[i] * (t2 - t1));
          }
        }
        break;
      }
    case MSH_SEGM_BSPLN:
      {
        bool periodic = (c->end == c->beg);
        int NbControlPoints = List_Nbr(c->Control_Points);
        int NbCurves = NbControlPoints + (periodic ? -1 : 1);
        SPoint3 pt[4];
        for (int iCurve = 0; iCurve < NbCurves; ++iCurve) {
          double t1 = (iCurve) / (double)(NbCurves);
          double t2 = (iCurve+1) / (double)(NbCurves);
          for(int i = 0; i < 4; i++) {
            int k;
            if (periodic) {
              k = (iCurve - 1 + i) % (NbControlPoints - 1);
              if (k < 0)
                k += NbControlPoints - 1;
            }
            else {
              k = std::max(0, std::min(iCurve - 2 + i, NbControlPoints -1));
            }
            pt[i] = curveGetPoint(c, k);
          }
          SPoint3 bpt[4] = {
            (pt[0] + pt[1] * 4 + pt[2]) * (1./6.),
            (pt[1] * 2 + pt[2]) * (1./3.),
            (pt[1] + pt[2] * 2) * (1./3.),
            (pt[1] + pt[2] * 4 + pt[3]) * (1./6.)
          };
          std::vector<double> lts;
          std::vector<SPoint3> lpts;
          decasteljau(tol, bpt[0], bpt[1], bpt[2], bpt[3], lpts, lts);
          for (size_t i = (iCurve == 0 ? 0 : 1); i < lpts.size(); ++i) {
            pts.push_back(lpts[i]);
            ts.push_back(t1 + lts[i] * (t2 - t1));
          }
        }
        break;
      }
    case MSH_SEGM_SPLN:
      {
        int NbCurves = List_Nbr(c->Control_Points) - 1;
        SPoint3 pt[4];
        for (int iCurve = 0; iCurve < NbCurves; ++iCurve) {
          double t1 = (iCurve) / (double)(NbCurves);
          double t2 = (iCurve+1) / (double)(NbCurves);
          pt[1] = curveGetPoint(c, iCurve);
          pt[2] = curveGetPoint(c, iCurve + 1);
          if(iCurve == 0) {
            if(c->beg == c->end)
              pt[0] = curveGetPoint(c, NbCurves - 1);
            else
              pt[0] = SPoint3(pt[1] * 2 - pt[2]);
          }
          else
            pt[0] = curveGetPoint(c, iCurve - 1);
          if(iCurve == NbCurves - 1) {
            if(c->beg == c->end)
              pt[3] = curveGetPoint(c, 1);
            else
              pt[3] = SPoint3(2 * pt[2] - pt[1]);
          }
          else
            pt[3] = curveGetPoint(c, iCurve + 2);
          SPoint3 bpt[4] = {
            pt[1],
            (pt[1] * 6 + pt[2] - pt[0]) * (1./6.),
            (pt[2] * 6 - pt[3] + pt[1]) * (1./6.),
            pt[2]
          };
          std::vector<double> lts;
          std::vector<SPoint3> lpts;
          decasteljau(tol, bpt[0], bpt[1], bpt[2], bpt[3], lpts, lts);
          for (size_t i = (iCurve == 0 ? 0 : 1); i < lpts.size(); ++i) {
            pts.push_back(lpts[i]);
            ts.push_back(t1 + lts[i] * (t2 - t1));
          }
        }
        break;
      }
    default :
      GEdge::discretize(tol, pts, ts);
  }
}
