// Created by: Peter KURNEV
// Copyright (c) 1999-2015 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 <BOPTools_AlgoTools2D.hxx>

#include <gp_Vec2d.hxx>
#include <gp_Dir2d.hxx>

#include <Geom2d_TrimmedCurve.hxx>
#include <GeomLib.hxx>

#include <GeomAPI_ProjectPointOnCurve.hxx>

#include <BRep_Builder.hxx>
#include <BRep_Tool.hxx>
#include <BRepLib.hxx>

#include <TopExp_Explorer.hxx>

#include <IntTools_Context.hxx>
#include <IntTools_Tools.hxx>

#include <BOPTools_AlgoTools.hxx>

static int  UpdateClosedPCurve(const TopoDS_Edge&,
                               const TopoDS_Edge&,
                               const TopoDS_Face&,
                               const occ::handle<IntTools_Context>&);
static bool IsClosed(const TopoDS_Edge&, const TopoDS_Face&);

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

int BOPTools_AlgoTools2D::AttachExistingPCurve(const TopoDS_Edge&                   theE2, // old
                                               const TopoDS_Edge&                   theE1, // new
                                               const TopoDS_Face&                   theF,
                                               const occ::handle<IntTools_Context>& aCtx)
{
  bool                      bIsToReverse, bIsClosed, bComp;
  int                       iRet;
  double                    aTol, aT11, aT12, aT21, aT22, aTolPPC;
  double                    aTolSP, aTMax;
  occ::handle<Geom2d_Curve> aC2Dold, aC2DoldC;
  occ::handle<Geom2d_Curve> aC2DT;
  BRep_Builder              aBB;
  //
  iRet = 0;
  //
  TopoDS_Face aF = theF;
  aF.Orientation(TopAbs_FORWARD);
  TopoDS_Edge aE1 = theE1;
  aE1.Orientation(TopAbs_FORWARD);
  TopoDS_Edge aE2 = theE2;
  aE2.Orientation(TopAbs_FORWARD);
  //
  aC2Dold = BRep_Tool::CurveOnSurface(aE2, aF, aT21, aT22);
  if (aC2Dold.IsNull())
  {
    iRet = 1;
    return iRet;
  }
  //
  aC2DoldC = occ::down_cast<Geom2d_Curve>(aC2Dold->Copy());
  //
  bIsToReverse = BOPTools_AlgoTools::IsSplitToReverse(aE1, aE2, aCtx);
  if (bIsToReverse)
  {
    double aT21r, aT22r;
    //
    aC2DoldC->Reverse();
    //
    aT21r = aC2DoldC->ReversedParameter(aT21);
    aT22r = aC2DoldC->ReversedParameter(aT22);
    aT21  = aT22r;
    aT22  = aT21r;
  }
  //
  aC2DT = new Geom2d_TrimmedCurve(aC2DoldC, aT21, aT22);
  //
  aTolPPC = Precision::PConfusion();
  //
  occ::handle<Geom_Curve> aCE1 = BRep_Tool::Curve(aE1, aT11, aT12);
  //
  GeomLib::SameRange(aTolPPC, aC2DT, aT21, aT22, aT11, aT12, aC2DT);
  //
  if (aC2DT.IsNull())
  {
    iRet = 2;
    return iRet;
  }
  //
  // check the curves on same parameter to prevent
  // big tolerance increasing
  occ::handle<Geom_Surface> aSF = BRep_Tool::Surface(aF);
  //
  bComp = IntTools_Tools::ComputeTolerance(aCE1, aC2DT, aSF, aT11, aT12, aTolSP, aTMax, aTolPPC);
  if (!bComp)
  {
    iRet = 3;
    return iRet;
  }
  //
  aTol = BRep_Tool::Tolerance(aE1);
  //
  if ((aTolSP > 10. * aTol) && aTolSP > 0.1)
  {
    iRet = 4;
    return iRet;
  }
  //
  // create a temporary edge to make same parameter pcurve
  TopoDS_Edge aE1T;
  aBB.MakeEdge(aE1T, aCE1, aTol);
  aBB.Range(aE1T, aT11, aT12);
  aBB.SameRange(aE1T, false);
  aBB.SameParameter(aE1T, false);
  //
  aBB.UpdateEdge(aE1T, aC2DT, aF, aTol);
  try
  {
    BRepLib::SameParameter(aE1T);
    BRepLib::SameRange(aE1T);
  }
  catch (Standard_Failure const&)
  {
    iRet = 6;
    return iRet;
  }
  //
  bIsClosed = IsClosed(aE2, aF);
  if (bIsClosed)
  {
    iRet = UpdateClosedPCurve(aE2, aE1T, aF, aCtx);
    if (iRet)
    {
      iRet = 5;
      return iRet;
    }
  }
  // transfer pcurve(s) from the temporary edge to the new edge
  aBB.Transfert(aE1T, aE1);
  // update tolerance of vertices
  double          aNewTol = BRep_Tool::Tolerance(aE1T);
  TopoDS_Iterator it(aE1);
  for (; it.More(); it.Next())
    aBB.UpdateVertex(TopoDS::Vertex(it.Value()), aNewTol);
  return iRet;
}

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

int UpdateClosedPCurve(const TopoDS_Edge&                   aEold,
                       const TopoDS_Edge&                   aEnew,
                       const TopoDS_Face&                   aF,
                       const occ::handle<IntTools_Context>& aCtx)
{
  bool                             bUClosed, bRevOrder;
  int                              aNbPoints, iRet;
  double                           aTS1, aTS2, aTS, aScPr, aUS1, aVS1, aUS2, aVS2, aT, aU, aV;
  double                           aT1, aT2, aTol;
  gp_Pnt2d                         aP2DS1, aP2DS2, aP2D;
  gp_Vec2d                         aV2DT, aV2D, aV2DS1, aV2DS2;
  gp_Pnt                           aP;
  occ::handle<Geom2d_Curve>        aC2D, aC2DS1, aC2DS2, aC2Dnew, aC2DoldCT;
  occ::handle<Geom2d_Curve>        aC2Dold;
  occ::handle<Geom2d_TrimmedCurve> aC2DTnew;
  occ::handle<Geom_Surface>        aS;
  TopoDS_Edge                      aES;
  BRep_Builder                     aBB;
  //
  iRet = 0;
  aTol = BRep_Tool::Tolerance(aEnew);
  //
  // aC2DoldCT is alone p-curve of aEnew that we've built
  // The task is to build closed p-curves for aEnew
  aC2DoldCT = BRep_Tool::CurveOnSurface(aEnew, aF, aT1, aT2);
  //
  // aC2Dold is p-curve of aEold
  aC2Dold = BRep_Tool::CurveOnSurface(aEold, aF, aT1, aT2);
  //
  // As aEold is closed on aF, it is possible to retrieve
  // the two p-curves:
  //  aC2DS1 -first p-curve
  //  aC2DS2 -second p-curve
  aES = aEold;
  aES.Orientation(TopAbs_FORWARD);
  aC2DS1 = BRep_Tool::CurveOnSurface(aES, aF, aTS1, aTS2);
  //
  aES.Orientation(TopAbs_REVERSED);
  aC2DS2 = BRep_Tool::CurveOnSurface(aES, aF, aTS1, aTS2);
  //
  aTS = BOPTools_AlgoTools2D::IntermediatePoint(aTS1, aTS2);
  //
  aC2DS1->D1(aTS, aP2DS1, aV2DS1);
  aC2DS2->D1(aTS, aP2DS2, aV2DS2);
  //
  // aV2DS12 - translation vector
  gp_Vec2d        aV2DS12(aP2DS1, aP2DS2);
  gp_Dir2d        aD2DS12(aV2DS12);
  const gp_Dir2d& aD2DX = gp::DX2d();
  //
  // Directoion of closeness: U-Closed or V-Closed
  aScPr    = aD2DS12 * aD2DX;
  bUClosed = true;
  if (fabs(aScPr) < aTol)
  {
    bUClosed = !bUClosed;
  }
  //
  aP2DS1.Coord(aUS1, aVS1);
  aP2DS2.Coord(aUS2, aVS2);
  //
  // aP - some 3D-point on seam edge of the surface aS
  aS = BRep_Tool::Surface(aF);
  aS->D0(aUS1, aVS1, aP);
  //
  GeomAPI_ProjectPointOnCurve& aProjPC = aCtx->ProjPC(aEnew);
  //
  aProjPC.Perform(aP);
  aNbPoints = aProjPC.NbPoints();
  if (!aNbPoints)
  {
    iRet = 1;
    return iRet;
  }
  //
  // aT - parameter for aP on the curves of aEnew
  aT = aProjPC.LowerDistanceParameter();
  //
  aC2D = aC2DoldCT;
  aC2D->D1(aT, aP2D, aV2D);
  aP2D.Coord(aU, aV);
  //
  aC2Dnew  = occ::down_cast<Geom2d_Curve>(aC2D->Copy());
  aC2DTnew = new Geom2d_TrimmedCurve(aC2Dnew, aT1, aT2);
  //
  aV2DT = aV2DS12;
  if (!bUClosed)
  { // V Closed
    if (fabs(aV - aVS2) < aTol)
    {
      aV2DT.Reverse();
    }
  }
  else
  { // U Closed
    if (fabs(aU - aUS2) < aTol)
    {
      aV2DT.Reverse();
    }
  }
  //
  // Translate aC2DTnew
  aC2DTnew->Translate(aV2DT);
  //
  // 4 Order the 2D curves
  bRevOrder = false;
  aScPr     = aV2D * aV2DS1;
  if (aScPr < 0.)
  {
    bRevOrder = !bRevOrder;
  }
  //
  if (!bRevOrder)
  {
    aBB.UpdateEdge(aEnew, aC2D, aC2DTnew, aF, aTol);
  }
  else
  {
    aBB.UpdateEdge(aEnew, aC2DTnew, aC2D, aF, aTol);
  }
  return iRet;
}

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

bool IsClosed(const TopoDS_Edge& aE, const TopoDS_Face& aF)
{
  bool bRet;
  //
  bRet = BRep_Tool::IsClosed(aE, aF);
  if (bRet)
  {
    int iCnt;
    //
    iCnt = 0;
    TopExp_Explorer aExp(aF, TopAbs_EDGE);
    for (; (aExp.More() || iCnt == 2); aExp.Next())
    {
      const TopoDS_Shape& aEx = aExp.Current();
      if (aEx.IsSame(aE))
      {
        ++iCnt;
      }
    }
    bRet = (iCnt == 2);
  }
  return bRet;
}
