// Created on: 1994-10-04
// Created by: Yves FRICAUD
// Copyright (c) 1994-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#include <BRep_Builder.hxx>
#include <BRep_Tool.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepLib.hxx>
#include <BRepMAT2d_Explorer.hxx>
#include <BRepTools_WireExplorer.hxx>
#include <GCE2d_MakeSegment.hxx>
#include <Geom2d_BezierCurve.hxx>
#include <Geom2d_BoundedCurve.hxx>
#include <Geom2d_Circle.hxx>
#include <Geom2d_Curve.hxx>
#include <Geom2d_Ellipse.hxx>
#include <Geom2d_Hyperbola.hxx>
#include <Geom2d_Line.hxx>
#include <Geom2d_OffsetCurve.hxx>
#include <Geom2d_Parabola.hxx>
#include <Geom2d_TrimmedCurve.hxx>
#include <Geom2dConvert.hxx>
#include <GeomAbs_CurveType.hxx>
#include <NCollection_Sequence.hxx>
#include <Precision.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Wire.hxx>
#include <TopTools_ShapeMapHasher.hxx>
#include <NCollection_IndexedDataMap.hxx>

//
//  Modified by Sergey KHROMOV - Thu Dec  5 10:38:14 2002 Begin
static TopoDS_Edge MakeEdge(const occ::handle<Geom2d_Curve>& theCurve,
                            const TopoDS_Face&               theFace,
                            const TopoDS_Vertex&             theVFirst,
                            const TopoDS_Vertex&             theVLast);
//  Modified by Sergey KHROMOV - Thu Dec  5 10:38:16 2002 End
//
static GeomAbs_CurveType                GetCurveType(const occ::handle<Geom2d_Curve>& theC2d);
static occ::handle<Geom2d_TrimmedCurve> AdjustCurveEnd(
  const occ::handle<Geom2d_BoundedCurve>& theC2d,
  const gp_Pnt2d                          theP,
  const bool                              isFirst);

//
//=================================================================================================

BRepMAT2d_Explorer::BRepMAT2d_Explorer()
{
  Clear();
}

//=================================================================================================

BRepMAT2d_Explorer::BRepMAT2d_Explorer(const TopoDS_Face& aFace)
{
  Perform(aFace);
}

//=================================================================================================

void BRepMAT2d_Explorer::Perform(const TopoDS_Face& aFace)
{
  Clear();
  myShape       = aFace;
  TopoDS_Face F = TopoDS::Face(aFace);
  F.Orientation(TopAbs_FORWARD);
  TopExp_Explorer Exp(F, TopAbs_WIRE);
  //  Modified by Sergey KHROMOV - Tue Nov 26 16:10:37 2002 Begin
  occ::handle<Geom_Surface> aSurf = BRep_Tool::Surface(F);
  TopoDS_Face               aNewF = BRepBuilderAPI_MakeFace(aSurf, Precision::Confusion());

  while (Exp.More())
  {
    Add(TopoDS::Wire(Exp.Current()), F, aNewF);
    Exp.Next();
  }

  BRepLib::BuildCurves3d(aNewF);

  myModifShapes.Add(aFace, aNewF);
  //   CheckConnection();
  //  Modified by Sergey KHROMOV - Tue Nov 26 16:10:38 2002 End
}

//=================================================================================================

void BRepMAT2d_Explorer::Add(const TopoDS_Wire& Spine,
                             const TopoDS_Face& aFace,
                             TopoDS_Face&       aNewFace)
{
  //  Modified by skv - Wed Jun 23 12:23:01 2004 Integration Begin
  //  Taking into account side of bisecting loci construction.
  //   TopoDS_Wire                         aWFwd = TopoDS::Wire(Spine.Oriented(TopAbs_FORWARD));
  //   BRepTools_WireExplorer              anExp(aWFwd, aFace);
  BRepTools_WireExplorer anExp(Spine, aFace);
  //  Modified by skv - Wed Jun 23 12:23:02 2004 Integration End
  NCollection_IndexedDataMap<TopoDS_Shape, TopoDS_Shape, TopTools_ShapeMapHasher> anOldNewE;
  if (!anExp.More())
    return;

  //  Modified by Sergey KHROMOV - Tue Nov 26 14:25:46 2002 Begin
  // This method is totally rewroted to include check
  // of connection and creation of a new spine.
  NewContour();
  myIsClosed(currentContour) = (Spine.Closed()) ? true : false;

  TopoDS_Edge                      aFirstEdge = anExp.Current();
  TopoDS_Edge                      aPrevEdge  = aFirstEdge;
  double                           UFirst, ULast, aD;
  occ::handle<Geom2d_Curve>        C2d;
  occ::handle<Geom2d_TrimmedCurve> CT2d;
  occ::handle<Geom2d_TrimmedCurve> aFirstCurve;
  gp_Pnt2d                         aPFirst;
  gp_Pnt2d                         aPLast;
  gp_Pnt2d                         aPCurFirst;
  //  Modified by skv - Mon Jul 11 19:00:25 2005 Integration Begin
  //  Set the confusion tolerance in accordance with the further algo
  //   double               aTolConf   = Precision::Confusion();
  double aTolConf = 1.e-8;
  //  Modified by skv - Mon Jul 11 19:00:25 2005 Integration End
  bool isModif = false;

  // Treatment of the first edge of a wire.
  anOldNewE.Add(aFirstEdge, aFirstEdge);
  C2d  = BRep_Tool::CurveOnSurface(aFirstEdge, aFace, UFirst, ULast);
  CT2d = new Geom2d_TrimmedCurve(C2d, UFirst, ULast);

  if (aFirstEdge.Orientation() == TopAbs_REVERSED)
    CT2d->Reverse();

  aPFirst = CT2d->Value(CT2d->FirstParameter());
  aPLast  = CT2d->Value(CT2d->LastParameter());

  Add(CT2d);
  aFirstCurve = CT2d;
  anExp.Next();

  // Treatment of the next edges:
  for (; anExp.More(); anExp.Next())
  {
    const TopoDS_Edge& anEdge = anExp.Current();

    anOldNewE.Add(anEdge, anEdge);
    C2d  = BRep_Tool::CurveOnSurface(anEdge, aFace, UFirst, ULast);
    CT2d = new Geom2d_TrimmedCurve(C2d, UFirst, ULast);

    if (anEdge.Orientation() == TopAbs_REVERSED)
      CT2d->Reverse();

    aPCurFirst = CT2d->Value(CT2d->FirstParameter());
    //
    aD = aPLast.Distance(aPCurFirst);
    if (aD > aTolConf)
    {
      // There are two ways how to fill holes:
      //     First,  to create a line between these two points.
      //     Second, create a BSpline curve and to add the last point of the previous
      //             curve as the first pole of the current one. Second method which
      //             is worse was performed before and leaved here. Otherwise too much
      //             code should be rewritten.
      isModif = true;
      //
      int                              aNbC  = theCurves.Value(currentContour).Length();
      occ::handle<Geom2d_BoundedCurve> CPrev = occ::down_cast<Geom2d_BoundedCurve>(
        theCurves.ChangeValue(currentContour).ChangeValue(aNbC));
      //
      GeomAbs_CurveType TCPrev = GetCurveType(CPrev);
      GeomAbs_CurveType TCCurr = GetCurveType(CT2d);
      //
      if (TCCurr <= TCPrev)
      {
        CT2d = AdjustCurveEnd(CT2d, aPLast, true);
        // Creation of new edge.
        TopoDS_Edge   aNewEdge;
        TopoDS_Vertex aVf = TopExp::FirstVertex(anEdge);
        TopoDS_Vertex aVl = TopExp::LastVertex(anEdge);

        if (anEdge.Orientation() == TopAbs_FORWARD)
          aNewEdge = MakeEdge(CT2d, aNewFace, aVf, aVl);
        else
          aNewEdge = MakeEdge(CT2d->Reversed(), aNewFace, aVf, aVl);

        aNewEdge.Orientation(anEdge.Orientation());

        anOldNewE.ChangeFromKey(anEdge) = aNewEdge;
      }
      else
      {
        gp_Pnt2d aP = CT2d->Value(CT2d->FirstParameter());
        CPrev       = AdjustCurveEnd(CPrev, aP, false);
        theCurves.ChangeValue(currentContour).ChangeValue(aNbC) = CPrev;
        // Change previous edge
        TopoDS_Edge   aNewEdge;
        TopoDS_Vertex aVf = TopExp::FirstVertex(aPrevEdge);
        TopoDS_Vertex aVl = TopExp::LastVertex(aPrevEdge);

        if (aPrevEdge.Orientation() == TopAbs_FORWARD)
          aNewEdge = MakeEdge(CPrev, aNewFace, aVf, aVl);
        else
          aNewEdge = MakeEdge(CPrev->Reversed(), aNewFace, aVf, aVl);

        aNewEdge.Orientation(aPrevEdge.Orientation());

        anOldNewE.ChangeFromKey(aPrevEdge) = aNewEdge;
      }
    }

    aPLast = CT2d->Value(CT2d->LastParameter());
    Add(CT2d);
    aPrevEdge = anEdge;
  }

  // Check of the distance between the first and the last point of wire
  // if the wire is closed.
  if (myIsClosed(currentContour) && aPLast.Distance(aPFirst) > aTolConf)
  {
    isModif = true;

    //
    int                              aNbC = theCurves.Value(currentContour).Length();
    occ::handle<Geom2d_BoundedCurve> CPrev =
      occ::down_cast<Geom2d_BoundedCurve>(theCurves.ChangeValue(currentContour).ChangeValue(aNbC));
    //
    GeomAbs_CurveType TCPrev = GetCurveType(CPrev);
    GeomAbs_CurveType TCCurr = GetCurveType(aFirstCurve);
    //
    if (TCCurr <= TCPrev)
    {
      aFirstCurve = AdjustCurveEnd(aFirstCurve, aPLast, true);
      theCurves.ChangeValue(currentContour).ChangeValue(1) = aFirstCurve;
      // Creation of new edge.
      TopoDS_Edge   aNewEdge;
      TopoDS_Vertex aVf = TopExp::FirstVertex(aFirstEdge);
      TopoDS_Vertex aVl = TopExp::LastVertex(aFirstEdge);

      if (aFirstEdge.Orientation() == TopAbs_FORWARD)
        aNewEdge = MakeEdge(aFirstCurve, aNewFace, aVf, aVl);
      else
        aNewEdge = MakeEdge(aFirstCurve->Reversed(), aNewFace, aVf, aVl);

      aNewEdge.Orientation(aFirstEdge.Orientation());

      anOldNewE.ChangeFromKey(aFirstEdge) = aNewEdge;
    }
    else
    {
      gp_Pnt2d aP = aFirstCurve->Value(aFirstCurve->FirstParameter());
      CPrev       = AdjustCurveEnd(CPrev, aP, false);
      theCurves.ChangeValue(currentContour).ChangeValue(aNbC) = CPrev;
      // Change previous edge
      TopoDS_Edge   aNewEdge;
      TopoDS_Vertex aVf = TopExp::FirstVertex(aPrevEdge);
      TopoDS_Vertex aVl = TopExp::LastVertex(aPrevEdge);

      if (aPrevEdge.Orientation() == TopAbs_FORWARD)
        aNewEdge = MakeEdge(CPrev, aNewFace, aVf, aVl);
      else
        aNewEdge = MakeEdge(CPrev->Reversed(), aNewFace, aVf, aVl);

      aNewEdge.Orientation(aPrevEdge.Orientation());

      anOldNewE.ChangeFromKey(aPrevEdge) = aNewEdge;
    }
  }

  TopoDS_Wire  aNewWire;
  BRep_Builder aBuilder;

  if (isModif)
  {
    int i;
    int aNbEdges = anOldNewE.Extent();

    aBuilder.MakeWire(aNewWire);

    for (i = 1; i <= aNbEdges; i++)
    {
      const TopoDS_Shape& aKey     = anOldNewE.FindKey(i);
      const TopoDS_Shape& aNewEdge = anOldNewE.FindFromIndex(i);

      aBuilder.Add(aNewWire, aNewEdge);
      myModifShapes.Add(aKey, aNewEdge);
    }

    if (myIsClosed(currentContour))
      aNewWire.Closed(true);

    //  Modified by skv - Fri Nov 12 17:22:12 2004 Integration Begin
    //  The orientation of wire is already taken into account.
    //    aNewWire.Orientation(Spine.Orientation());
    //  Modified by skv - Fri Nov 12 17:22:12 2004 Integration End
    myModifShapes.Add(Spine, aNewWire);
  }
  else
    aNewWire = Spine;

  aBuilder.Add(aNewFace, aNewWire);
  //  Modified by Sergey KHROMOV - Tue Nov 26 14:25:53 2002 End
}

//=================================================================================================

//  Modified by Sergey KHROMOV - Tue Nov 26 17:21:44 2002 Begin
// void BRepMAT2d_Explorer::CheckConnection()
// {
//   for (int i = 1; i <= theCurves.Length(); i++)
//     for (int j = 2; j <= theCurves(i).Length(); j++)
//       {
// 	gp_Pnt2d P1 = theCurves(i)(j-1)->Value( theCurves(i)(j-1)->LastParameter() );
// 	gp_Pnt2d P2 = theCurves(i)(j)->Value( theCurves(i)(j)->FirstParameter() );
// 	if (P1.Distance( P2 ) > Precision::Confusion())
// 	  {
// 	    Handle( Geom2d_BSplineCurve ) BCurve;
// 	    if (theCurves(i)(j)->DynamicType() != STANDARD_TYPE(Geom2d_BSplineCurve))
// 	      BCurve = Geom2dConvert::CurveToBSplineCurve( theCurves(i)(j) );
// 	    else
// 	      BCurve = Handle( Geom2d_BSplineCurve )::DownCast( theCurves(i)(j) );
// 	    BCurve->SetPole( 1, P1 );
// 	    theCurves(i)(j) = new Geom2d_TrimmedCurve( BCurve, BCurve->FirstParameter(),
// BCurve->LastParameter() );
// 	  }
//       }
// }
//  Modified by Sergey KHROMOV - Tue Nov 26 17:21:29 2002 End

//=================================================================================================

void BRepMAT2d_Explorer::Clear()
{
  theCurves.Clear();
  currentContour = 0;
  //  Modified by Sergey KHROMOV - Wed Mar  6 16:07:55 2002 Begin
  myIsClosed.Clear();
  myModifShapes.Clear();
  //  Modified by Sergey KHROMOV - Wed Mar  6 16:07:55 2002 End
}

//=================================================================================================

void BRepMAT2d_Explorer::NewContour()
{
  NCollection_Sequence<occ::handle<Geom2d_Curve>> Contour;
  theCurves.Append(Contour);
  //  Modified by Sergey KHROMOV - Wed Mar  6 16:12:05 2002 Begin
  myIsClosed.Append(false);
  //  Modified by Sergey KHROMOV - Wed Mar  6 16:12:05 2002 End
  currentContour++;
}

//=================================================================================================

void BRepMAT2d_Explorer::Add(const occ::handle<Geom2d_Curve>& aCurve)
{
  theCurves.ChangeValue(currentContour).Append(aCurve);
}

//=================================================================================================

int BRepMAT2d_Explorer::NumberOfContours() const
{
  return theCurves.Length();
}

//=================================================================================================

int BRepMAT2d_Explorer::NumberOfCurves(const int IndexContour) const
{
  return theCurves.Value(IndexContour).Length();
}

//=================================================================================================

void BRepMAT2d_Explorer::Init(const int IndexContour)
{
  currentContour = IndexContour;
  current        = 1;
}

//=================================================================================================

bool BRepMAT2d_Explorer::More() const
{
  return (current <= NumberOfCurves(currentContour));
}

//=================================================================================================

void BRepMAT2d_Explorer::Next()
{
  current++;
}

//=================================================================================================

occ::handle<Geom2d_Curve> BRepMAT2d_Explorer::Value() const
{
  return theCurves.Value(currentContour).Value(current);
}

//=================================================================================================

TopoDS_Shape BRepMAT2d_Explorer::Shape() const
{
  return myShape;
}

//=================================================================================================

const NCollection_Sequence<occ::handle<Geom2d_Curve>>& BRepMAT2d_Explorer::Contour(
  const int IC) const
{
  return theCurves.Value(IC);
}

//  Modified by Sergey KHROMOV - Wed Mar  6 17:40:07 2002 Begin
//=================================================================================================

bool BRepMAT2d_Explorer::IsModified(const TopoDS_Shape& aShape) const
{
  if (myModifShapes.Contains(aShape))
  {
    const TopoDS_Shape& aNewShape = myModifShapes.FindFromKey(aShape);
    const bool          isSame    = aNewShape.IsSame(aShape);

    return !isSame;
  }

  return false;
}

//=================================================================================================

TopoDS_Shape BRepMAT2d_Explorer::ModifiedShape(const TopoDS_Shape& aShape) const
{
  if (myModifShapes.Contains(aShape))
  {
    const TopoDS_Shape& aNewShape = myModifShapes.FindFromKey(aShape);

    return aNewShape;
  }

  return aShape;
}

//=================================================================================================

const NCollection_Sequence<bool>& BRepMAT2d_Explorer::GetIsClosed() const
{
  return myIsClosed;
}

//=======================================================================
// function : MakeEdge
// purpose  : Creation of an edge by 2d curve, face and two vertices.
//=======================================================================

TopoDS_Edge MakeEdge(const occ::handle<Geom2d_Curve>& theCurve,
                     const TopoDS_Face&               theFace,
                     const TopoDS_Vertex&             theVFirst,
                     const TopoDS_Vertex&             theVLast)
{
  TopoDS_Edge      aNewEdge;
  BRep_Builder     aBuilder;
  constexpr double aTol  = Precision::Confusion();
  double           aFPar = theCurve->FirstParameter();
  double           aLPar = theCurve->LastParameter();

  aBuilder.MakeEdge(aNewEdge);
  aBuilder.UpdateEdge(aNewEdge, theCurve, theFace, aTol);
  aBuilder.Add(aNewEdge, theVFirst.Oriented(TopAbs_FORWARD));
  aBuilder.Add(aNewEdge, theVLast.Oriented(TopAbs_REVERSED));
  aBuilder.Range(aNewEdge, aFPar, aLPar);

  return aNewEdge;
}

//  Modified by Sergey KHROMOV - Wed Mar  6 17:40:14 2002 End
//
//=================================================================================================

GeomAbs_CurveType GetCurveType(const occ::handle<Geom2d_Curve>& theC2d)
{
  GeomAbs_CurveType          aTypeCurve = GeomAbs_OtherCurve;
  occ::handle<Standard_Type> TheType    = theC2d->DynamicType();
  if (TheType == STANDARD_TYPE(Geom2d_TrimmedCurve))
  {
    TheType = occ::down_cast<Geom2d_TrimmedCurve>(theC2d)->BasisCurve()->DynamicType();
  }

  if (TheType == STANDARD_TYPE(Geom2d_Circle))
  {
    aTypeCurve = GeomAbs_Circle;
  }
  else if (TheType == STANDARD_TYPE(Geom2d_Line))
  {
    aTypeCurve = GeomAbs_Line;
  }
  else if (TheType == STANDARD_TYPE(Geom2d_Ellipse))
  {
    aTypeCurve = GeomAbs_Ellipse;
  }
  else if (TheType == STANDARD_TYPE(Geom2d_Parabola))
  {
    aTypeCurve = GeomAbs_Parabola;
  }
  else if (TheType == STANDARD_TYPE(Geom2d_Hyperbola))
  {
    aTypeCurve = GeomAbs_Hyperbola;
  }
  else if (TheType == STANDARD_TYPE(Geom2d_BezierCurve))
  {
    aTypeCurve = GeomAbs_BezierCurve;
  }
  else if (TheType == STANDARD_TYPE(Geom2d_BSplineCurve))
  {
    aTypeCurve = GeomAbs_BSplineCurve;
  }
  else if (TheType == STANDARD_TYPE(Geom2d_OffsetCurve))
  {
    aTypeCurve = GeomAbs_OffsetCurve;
  }
  else
  {
    aTypeCurve = GeomAbs_OtherCurve;
  }
  return aTypeCurve;
}

//=================================================================================================

occ::handle<Geom2d_TrimmedCurve> AdjustCurveEnd(const occ::handle<Geom2d_BoundedCurve>& theC2d,
                                                const gp_Pnt2d                          theP,
                                                const bool                              isFirst)
{
  GeomAbs_CurveType aType = GetCurveType(theC2d);
  if (aType == GeomAbs_Line)
  {
    // create new line
    if (isFirst)
    {
      gp_Pnt2d aP = theC2d->Value(theC2d->LastParameter());
      return GCE2d_MakeSegment(theP, aP);
    }
    else
    {
      gp_Pnt2d aP = theC2d->Value(theC2d->FirstParameter());
      return GCE2d_MakeSegment(aP, theP);
    }
  }
  else
  {
    // Convert to BSpline and adjust first pole
    occ::handle<Geom2d_BSplineCurve> BCurve =
      Geom2dConvert::CurveToBSplineCurve(theC2d, Convert_QuasiAngular);
    if (isFirst)
    {
      BCurve->SetPole(1, theP);
      return new Geom2d_TrimmedCurve(BCurve, BCurve->FirstParameter(), BCurve->LastParameter());
    }
    else
    {
      BCurve->SetPole(BCurve->NbPoles(), theP);
      return new Geom2d_TrimmedCurve(BCurve, BCurve->FirstParameter(), BCurve->LastParameter());
    }
  }
}
