// Created: 2009-01-21
//
// Copyright (c) 2009-2013 OPEN CASCADE SAS
//
// This file is part of commercial software by OPEN CASCADE SAS,
// furnished in accordance with the terms and conditions of the contract
// and with the inclusion of this copyright notice.
// This file or any part thereof may not be provided or otherwise
// made available to any third party.
//
// No ownership title to the software is transferred hereby.
//
// OPEN CASCADE SAS makes no representation or warranties with respect to the
// performance of this software, and specifically disclaims any responsibility
// for any damages, special or consequential, connected with its use.

#include <Geom2dConvert_ApproxArcsSegments.hxx>

#include <Adaptor2d_Curve2d.hxx>
#include <ElCLib.hxx>
#include <GC_MakeArcOfCircle2d.hxx>
#include <GC_MakeSegment2d.hxx>
#include <GCPnts_QuasiUniformDeflection.hxx>
#include <Geom2d_Circle.hxx>
#include <Geom2d_Line.hxx>
#include <Geom2d_TrimmedCurve.hxx>
#include <NCollection_IncAllocator.hxx>
#include <Precision.hxx>
#include <Standard_Version.hxx>
#include <gp.hxx>
#include <gp_Ax2d.hxx>
#include <gp_Lin2d.hxx>

static const int    MAXPOINTS            = 100;
static const double MyCurvatureTolerance = 0.0001;

static bool checkContinuity(const occ::handle<Geom2d_Curve>& theCurve1,
                            const occ::handle<Geom2d_Curve>& theCurve2,
                            const double                     theAnglTol);

static Geom2dConvert_PPoint getParameter(const gp_XY&             theXY1,
                                         const double             theFirstPar,
                                         const double             theLastPar,
                                         const Adaptor2d_Curve2d& theCurve);

static bool isInflectionPoint(const double theParam, const Adaptor2d_Curve2d& theCurve);

static bool isInflectionPoint(const double                theParam,
                              const Geom2dConvert_PPoint& theFirstInf,
                              const Adaptor2d_Curve2d&    theCurve,
                              const double                theAnglTol);

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

Geom2dConvert_ApproxArcsSegments::Geom2dConvert_ApproxArcsSegments(
  const Adaptor2d_Curve2d& theCurve,
  const double             theTolerance,
  const double             theAngleTol)
    : myCurve(theCurve),
      myAlloc(new NCollection_IncAllocator(4000)),
      myTolerance(theTolerance),
      myAngleTolerance(theAngleTol),
      mySeqParams(myAlloc),
      myStatus(StatusNotDone)
{
  myExt[0] = Geom2dConvert_PPoint(myCurve.FirstParameter(), myCurve);
  myExt[1] = Geom2dConvert_PPoint(myCurve.LastParameter(), myCurve);

  switch (myCurve.GetType())
  {
    case GeomAbs_Line: {
      // Create a single line segment.
      const double aDist = myExt[0].Dist(myExt[1]);
      if (aDist > Precision::Confusion())
      {
        const gp_Ax2d anAx2d(myExt[0].Point(), gp_Vec2d(myExt[0].Point(), myExt[1].Point()));
        const occ::handle<Geom2d_Line> aLine = new Geom2d_Line(anAx2d);
        mySeqCurves.Append(new Geom2d_TrimmedCurve(aLine, 0., aDist));
        myStatus = StatusOK;
      }
    }
    break;
    case GeomAbs_Circle: {
      // Create a couple of arcs of equal size.
      const Geom2dConvert_PPoint aPP(.5 * (myExt[0].Parameter() + myExt[1].Parameter()), myCurve);
      occ::handle<Geom2d_Curve>  aCurve = makeCircle(myExt[0], aPP);
      if (!aCurve.IsNull())
      {
        mySeqCurves.Append(aCurve);
        aCurve = makeCircle(aPP, myExt[1]);
        if (!aCurve.IsNull())
        {
          mySeqCurves.Append(aCurve);
        }
      }
    }
    break;
    default:
      makeFreeform();
  }

  // Check status of the calculation
  if (myStatus == StatusNotDone)
  {
    if (!mySeqCurves.IsEmpty())
    {
      myStatus = StatusOK;
    }
    else
    {
      // std::cout << "GeomConv2d_Approx: no geometry converted." << std::endl;
      myStatus = StatusError;
    }
  }
}

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

occ::handle<Geom2d_Curve> Geom2dConvert_ApproxArcsSegments::makeCircle(
  const Geom2dConvert_PPoint& theFirst,
  const Geom2dConvert_PPoint& theLast) const
{
  occ::handle<Geom2d_Curve> aResult;
  gp_Pnt2d                  aPointM(0.0, 0.0);
  const double              aParaM = (theFirst.Parameter() + theLast.Parameter()) * .5;
  myCurve.D0(aParaM, aPointM);
  GC_MakeArcOfCircle2d aMakeArc1(theFirst.Point(), aPointM, theLast.Point());

  if (aMakeArc1.IsDone())
  {
    aResult = aMakeArc1.Value();
  }
  // else
  // std::cout << "makeCircle(): Circle not built" << std::endl;
  return aResult;
}

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

bool Geom2dConvert_ApproxArcsSegments::makeArc(const Geom2dConvert_PPoint&       theParam1,
                                               Geom2dConvert_PPoint&             theParam2,
                                               const bool                        isFirst,
                                               occ::handle<Geom2d_TrimmedCurve>& theCurve) const
{
  const gp_XY aP1(theParam1.Point());
  const gp_XY aP2(theParam2.Point());
  const gp_XY aVec(isFirst ? theParam1.D1() : -theParam1.D1());

  // Detect the sense (CCW means positive)
  const gp_XY aDelta = aP2 - aP1;
  double      aSense = aVec ^ aDelta;
  if (aSense > Precision::Angular())
  {
    aSense = 1.;
  }
  else if (aSense < -Precision::Angular())
  {
    aSense = -1.;
  }
  else
  {
    // std::cout << "makeArc(): Arc Not Built" << std::endl;
    return false;
  }

  // Find the centre of the circle
  const gp_XY  aMiddle     = (aP2 + aP1) * 0.5;
  const double prodP1V     = aP1 * aVec;
  const double prodDeM     = aDelta * aMiddle;
  const double vprodVD     = aVec ^ aDelta;
  const double aResolution = gp::Resolution();

  if (vprodVD < -aResolution || vprodVD > aResolution)
  {
    const gp_Pnt2d  aCenter((prodP1V * aDelta.Y() - prodDeM * aVec.Y()) / vprodVD,
                           (prodDeM * aVec.X() - prodP1V * aDelta.X()) / vprodVD);
    const double    aRad = (aCenter.Distance(aP1) + aCenter.Distance(aP2)) * 0.5;
    const gp_Ax22d  ax22d(aCenter, gp_Dir2d(gp_Dir2d::D::X), gp_Dir2d(gp_Dir2d::D::Y));
    const gp_Circ2d aCir(ax22d, aRad);
    const occ::handle<Geom2d_Circle> Circ = new Geom2d_Circle(aCir);

    // calculation parameters first and last points of arc.
    double anAlpha1, anAlpha2;
    if (isFirst)
    {
      anAlpha1 = ElCLib::Parameter(aCir, aP1);
      anAlpha2 = ElCLib::Parameter(aCir, aP2);
    }
    else
    {
      anAlpha1 = ElCLib::Parameter(aCir, aP2);
      anAlpha2 = ElCLib::Parameter(aCir, aP1);
      aSense   = -aSense;
    }

    if (fabs(anAlpha1 - anAlpha2) < 1e-100)
    {
      // very small value, just to avoid exact match
      return false;
    }

    // Reverse the circle if the sense is negative
    if (aSense < 0.)
    {
      anAlpha1 = Circ->ReversedParameter(anAlpha1);
      anAlpha2 = Circ->ReversedParameter(anAlpha2);
      Circ->Reverse();
    }
    theCurve = new Geom2d_TrimmedCurve(Circ, anAlpha1, anAlpha2);
    // Correct the direction in the opposite point
    const gp_XY aRadV = theParam2.Point() - aCenter.XY();
    theParam2.SetD1(gp_XY(-aRadV.Y() * aSense, aRadV.X() * aSense));
    return true;
  }

  // Algorithm failed, possibly because aVec is normal to the chorde
  return false;
}

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

occ::handle<Geom2d_TrimmedCurve> Geom2dConvert_ApproxArcsSegments::makeLine(
  Geom2dConvert_PPoint& theFirst,
  Geom2dConvert_PPoint& theLast,
  const bool            isCheck) const
{
  occ::handle<Geom2d_TrimmedCurve> aResult;

  gp_XY aSlope = theLast.Point() - theFirst.Point();
  if (fabs(aSlope.SquareModulus()) < gp::Resolution())
  {
    return aResult;
  }
  gp_Dir2d aDirLine(aSlope);

  if (isCheck)
  {
    if (theFirst.D1().SquareModulus() < gp::Resolution()
        || theLast.D1().SquareModulus() < gp::Resolution())
    {
      return aResult;
    }

    // Angular continuity (G1) is only checked when the end of the line is not
    // on the extremity of the curve
    double absAngle[2] = {0., 0.};
    if (theFirst != myExt[0])
    {
      const double anAng = aDirLine.Angle(theFirst.D1());
      absAngle[0]        = (anAng > 0. ? anAng : -anAng);
    }
    if (theLast != myExt[1])
    {
      const double anAng = aDirLine.Angle(theLast.D1());
      absAngle[1]        = (anAng > 0. ? anAng : -anAng);
    }

    // if the derivatives in the end points differ from the derivative line
    // more than value of the specified continuity tolerance
    // then a biarc should be built instead of a line.
    const double aContTolerance = std::max(myAngleTolerance, 0.01);
    if (absAngle[0] > aContTolerance || absAngle[1] > aContTolerance)
    {
      // std::cout << "makeLine(): Line not built" << std::endl;
      return aResult;
    }
  } // end if (isCheck)

  // building segment of line
  GC_MakeSegment2d aMakeSeg(theFirst.Point(), theLast.Point());
  if (aMakeSeg.IsDone())
  {
    occ::handle<Geom2d_TrimmedCurve> aCurve = aMakeSeg.Value();
    if (checkCurve(aCurve, theFirst.Parameter(), theLast.Parameter()))
    {
      aResult = aCurve;
      // correct the derivatives fields in both arguments
      const gp_XY aNewD1(theLast.Point() - theFirst.Point());
      theFirst.SetD1(aNewD1);
      theLast.SetD1(aNewD1);
    }
  }
  // else
  // std::cout << "makeLine(): Line not built" << std::endl;
  return aResult;
}

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

bool Geom2dConvert_ApproxArcsSegments::makeFreeform()
{
  NCollection_Sequence<Geom2dConvert_PPoint> seqParamPoints(myAlloc);
  Geom2dConvert_PPoint*                      aPrevParam = &myExt[0];

  // calculation of the inflection points.
  getLinearParts(seqParamPoints);
  const bool isNoInfPoints = seqParamPoints.IsEmpty();

  NCollection_Sequence<occ::handle<Geom2d_Curve>> aSeqLinearParts;
  bool                                            isDone(true);
  int                                             i;
  for (i = 1; i < seqParamPoints.Length(); i += 2)
  {
    occ::handle<Geom2d_Curve> aLineCurve;
    Geom2dConvert_PPoint&     aParam0 = seqParamPoints.ChangeValue(i);
    Geom2dConvert_PPoint&     aParam1 = seqParamPoints.ChangeValue(i + 1);
    if (aParam0 != aParam1)
    {
      // linear part of the curve lies between odd and even values of i.
      // parameters from parameter's sequence.
      aLineCurve = makeLine(aParam0, aParam1, false);
    }
    aSeqLinearParts.Append(aLineCurve);
  }

  for (i = 1; i < seqParamPoints.Length(); i += 2)
  {
    // approximation for non-linear part preceding the linear part
    if (seqParamPoints(i) != *aPrevParam)
    {
      const int aLastInd = mySeqCurves.Length();
      isDone             = makeApproximation(*aPrevParam, seqParamPoints(i));
      if (isDone && aLastInd && mySeqCurves.Length() > aLastInd)
      {
        isDone = checkContinuity(mySeqCurves.Value(aLastInd),
                                 mySeqCurves.Value(aLastInd + 1),
                                 myAngleTolerance);
      }
      if (!isDone)
      {
        myStatus = StatusError;
        break;
      }
    }

    const occ::handle<Geom2d_Curve>& aCurve = aSeqLinearParts.Value((i + 1) / 2);
    if (!aCurve.IsNull())
    {
      mySeqCurves.Append(aCurve);
    }
    else
    {
      Geom2dConvert_PPoint& aParam0  = seqParamPoints.ChangeValue(i);
      Geom2dConvert_PPoint& aParam1  = seqParamPoints.ChangeValue(i + 1);
      const int             aLastInd = mySeqCurves.Length();
      isDone                         = makeApproximation(aParam0, aParam1);
      if (isDone && aLastInd && mySeqCurves.Length() > aLastInd)
      {
        isDone = checkContinuity(mySeqCurves.Value(aLastInd),
                                 mySeqCurves.Value(aLastInd + 1),
                                 myAngleTolerance);
      }

      if (!isDone)
      {
        myStatus = StatusError;
        // std::cout << "makeOther: Line not built" << std::endl;
        break;
      }
    }
    aPrevParam = &seqParamPoints(i + 1);
  }

  // approximation for non-linear part following the last linear part
  if (isDone && (*aPrevParam != myExt[1]))
  {
    // Case of a closed edge like an ellipse
    if (isNoInfPoints && (myExt[0].Point() - myExt[1].Point()).Modulus() < myTolerance)
    {
      Geom2dConvert_PPoint aPPoint(0.5 * (myExt[0].Parameter() + myExt[1].Parameter()), myCurve);
      isDone = makeApproximation(myExt[0], aPPoint);
      if (isDone)
      {
        isDone = makeApproximation(aPPoint, myExt[1]);
      }
    }
    else
    {
      isDone = makeApproximation(*aPrevParam, myExt[1]);
    }
    if (!isDone)
    {
      myStatus = StatusError;
      // std::cout << "makeOther: Line not built" << std::endl;
    }
  }

  return (mySeqCurves.Length() && myStatus != StatusError);
}

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

void Geom2dConvert_ApproxArcsSegments::getLinearParts(
  NCollection_Sequence<Geom2dConvert_PPoint>& theSeqPar)
{
  int i;
  // Fill the sequences with values along the curve
  mySeqParams.Clear();
  Adaptor2d_Curve2d&            myCurveMut = const_cast<Adaptor2d_Curve2d&>(myCurve);
  GCPnts_QuasiUniformDeflection aQUDefAlgo(myCurveMut, myTolerance * 0.5);
  bool                          isUniformDone = aQUDefAlgo.IsDone();

  gp_XY aLastPnt(myExt[0].Point());
  if (isUniformDone)
  {
    for (i = 1; i <= aQUDefAlgo.NbPoints(); i++)
    {
      const Geom2dConvert_PPoint aPP(aQUDefAlgo.Parameter(i), myCurve);
      mySeqParams.Append(aPP);
      aLastPnt = aPP.Point();
    }
  }
  else
  {
    const double aParamStep = (myExt[1].Parameter() - myExt[0].Parameter()) / MAXPOINTS;
    for (i = 1; i <= MAXPOINTS; i++)
    {
      const double               aParam = myExt[0].Parameter() + aParamStep * i;
      const Geom2dConvert_PPoint aPP(aParam, myCurve);
      mySeqParams.Append(aPP);
      aLastPnt = aPP.Point();
    }
  }

  // check if the curve may be linearised
  gp_XY        aDir  = myExt[1].Point() - myExt[0].Point();
  const double aMod2 = aDir.SquareModulus();
  if (aMod2 > Precision::Confusion())
  {
    bool isLinear = true;
    aDir /= sqrt(aMod2);
    for (i = 1; i <= mySeqParams.Length(); i++)
    {
      // Distance from point "i" to the segment between two extremities
      const double aDist = aDir ^ (mySeqParams(i).Point() - myExt[0].Point());
      if (aDist > myTolerance * 0.5 || aDist < -myTolerance * 0.5)
      {
        isLinear = false;
        break;
      }
    }
    if (isLinear)
    {
      theSeqPar.Append(myExt[0]);
      theSeqPar.Append(myExt[1]);
      return;
    }
  }

  // check if point for First Parameter is inflection point.
  int                  indStartLinear(0);
  Geom2dConvert_PPoint aLastInflParam  = myExt[0];
  Geom2dConvert_PPoint aFirstInflParam = myExt[0];

  // Getting further inflection points with step by parameter.
  // The point with index 1 is the same as myExt[0]
  for (i = 1; i <= mySeqParams.Length(); i++)
  {
    const Geom2dConvert_PPoint& aCurParam = mySeqParams(i);
    if (indStartLinear)
    {
      bool isStillInflectionFirst =
        isInflectionPoint(aFirstInflParam.Parameter(), aCurParam, myCurve, myAngleTolerance);
      if (isInflectionPoint(aCurParam.Parameter(), aFirstInflParam, myCurve, myAngleTolerance))
      {
        aLastInflParam = mySeqParams(i);
        while (!isStillInflectionFirst)
        {
          if (++indStartLinear >= i)
          {
            indStartLinear = 0;
            break;
          }
          aFirstInflParam = mySeqParams(indStartLinear);
          isStillInflectionFirst =
            isInflectionPoint(aFirstInflParam.Parameter(), aCurParam, myCurve, myAngleTolerance);
        }
      }
      else
      {
        // Add the interval in the output sequence
        // The interval is added only if it is more than 10 times the tolerance
        aLastInflParam = findInflection(aLastInflParam, aCurParam);
        if (!isInflectionPoint(aFirstInflParam.Parameter(),
                               aLastInflParam,
                               myCurve,
                               myAngleTolerance))
        {
          aFirstInflParam = findInflection(aLastInflParam, aFirstInflParam);
        }
        const double aDist((aFirstInflParam.Point() - aLastInflParam.Point()).Modulus());
        if (aFirstInflParam.Parameter() < aLastInflParam.Parameter() && aDist > 10 * myTolerance)
        {
          theSeqPar.Append(aFirstInflParam);
          theSeqPar.Append(aLastInflParam);
        }
        indStartLinear = 0;
      }
    }
    else if (isInflectionPoint(aCurParam.Parameter(), myCurve))
    {
      aLastInflParam = aCurParam;
      if (i > 1)
      {
        aFirstInflParam = findInflection(aCurParam, mySeqParams(i - 1));
      }
      indStartLinear = i;
    }
  }

  const double aDist((aFirstInflParam.Point() - myExt[1].Point()).Modulus());
  if (indStartLinear && aDist > 10 * myTolerance)
  {
    theSeqPar.Append(aFirstInflParam);
    theSeqPar.Append(myExt[1]);
  }
}

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

Geom2dConvert_PPoint Geom2dConvert_ApproxArcsSegments::findInflection(
  const Geom2dConvert_PPoint& theParamIsInfl,
  const Geom2dConvert_PPoint& theParamNoInfl) const
{
  double aLower(theParamIsInfl.Parameter());
  double anUpper(theParamNoInfl.Parameter());
  double aTest(0.);
  for (int i = 0; i < 3; i++)
  { // 3 iterations
    aTest = (aLower + anUpper) * 0.5;
    if (isInflectionPoint(aTest, theParamIsInfl, myCurve, myAngleTolerance))
    {
      aLower = aTest;
    }
    else
    {
      anUpper = aTest;
    }
  }
  return Geom2dConvert_PPoint(aTest, myCurve);
}

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

bool Geom2dConvert_ApproxArcsSegments::makeApproximation(Geom2dConvert_PPoint& theFirstParam,
                                                         Geom2dConvert_PPoint& theLastParam)
{
  // if difference between parameters is less than Precision::PConfusion
  // approximation was not made.
  bool isDone = false;
  if (theLastParam != theFirstParam)
  {
    const double aDistance = (theFirstParam.Point() - theLastParam.Point()).Modulus();
    if (aDistance < myTolerance)
    {
      const occ::handle<Geom2d_Curve> aCurve = makeLine(theFirstParam, theLastParam, true);
      isDone                                 = !aCurve.IsNull();
      if (isDone && mySeqCurves.Length())
      {
        isDone = checkContinuity(mySeqCurves.Last(), aCurve, myAngleTolerance);
      }
      if (isDone || aDistance < Precision::Confusion())
      {
        mySeqCurves.Append(aCurve);
        return isDone;
      }
    }
    // calculate biarc
    isDone = calculateBiArcs(theFirstParam, theLastParam);

    // if biarc was not calculated calculation is repeated on half the interval.
    if (!isDone)
    {
      Geom2dConvert_PPoint aParaM(theFirstParam.Parameter()
                                    + (theLastParam.Parameter() - theFirstParam.Parameter()) * 0.55,
                                  myCurve);
      isDone = makeApproximation(theFirstParam, aParaM);
      if (isDone)
      {
        isDone = makeApproximation(aParaM, theLastParam);
      }
    }
  }
  return isDone;
}

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

bool Geom2dConvert_ApproxArcsSegments::calculateBiArcs(Geom2dConvert_PPoint& theFirstParam,
                                                       Geom2dConvert_PPoint& theLastParam)
{
  const double aResolution = gp::Resolution();

  if (theFirstParam.D1().SquareModulus() < aResolution
      || theLastParam.D1().SquareModulus() < aResolution)
  {
    // std::cout << "calculateBiArcs(): bad initial data" << std::endl;
    return false;
  }
  const gp_XY aPnt[2] = {theFirstParam.Point(), theLastParam.Point()};
  gp_Dir2d    aDir[2] = {theFirstParam.D1(), theLastParam.D1()};

  // Try to approximate the curve by a single arc. The criterion for that is
  // more rigid if the curve is the entire input curve
  // (possible pb. connecting with other boundaries)
  const gp_Vec2d aDelta(aPnt[1] - aPnt[0]);
  double         anAngle1 = aDelta.Angle(gp_Vec2d(aDir[0]));
  if (anAngle1 < 0.)
  {
    anAngle1 = -anAngle1;
  }
  double anAngle2 = aDelta.Angle(gp_Vec2d(aDir[1]));
  if (anAngle2 < 0.)
  {
    anAngle2 = -anAngle2;
  }

  // in the case when two angles are equal one arc can be built.
  double anAngleThreshold(Precision::Angular() * 10.);
  if (theFirstParam != myExt[0] || theLastParam != myExt[1])
  {
    anAngleThreshold = myAngleTolerance * 0.1;
  }
  if (fabs(anAngle1 - anAngle2) < anAngleThreshold)
  {
    occ::handle<Geom2d_TrimmedCurve> aCurve;
    // protect the theLastParam from modification of D1, when
    // the created arc is rejected.
    Geom2dConvert_PPoint aLastParam(theLastParam);
    if (!makeArc(theFirstParam, aLastParam, true, aCurve))
    {
      return false;
    }
    if (checkCurve(aCurve, theFirstParam.Parameter(), aLastParam.Parameter()))
    {
      theLastParam = aLastParam;
      mySeqCurves.Append(aCurve);
      return true;
    }
  }

  // if one arc was not built or for other cases biarc will be built
  // method for building biarc was taken from article Ahmad H. Nasri et al.
  // "A Recursive Subdivision Algorithm for Piecewise Circular Spline",
  // Computer Graphics Forum, 2001.

  // definition of point of intersection two tangent directions in the points
  // corresponding FirstParameter and LastParameter.
  aDir[1].Reverse();

  // Direct calculation of intersection point, replaces a class call below
  const double aProd[3] = {aPnt[0] ^ aDir[0].XY(), aPnt[1] ^ aDir[1].XY(), aDir[1] ^ aDir[0].XY()};
  gp_XY        aIntPoint((aProd[0] * aDir[1].X() - aProd[1] * aDir[0].X()) / aProd[2],
                  (aProd[0] * aDir[1].Y() - aProd[1] * aDir[0].Y()) / aProd[2]);
  const gp_XY  aDiff[2] = {aIntPoint - aPnt[0], aIntPoint - aPnt[1]};
  if (aDiff[0] * aDir[0].XY() < 0. || aDiff[1] * aDir[1].XY() < 0.)
  {
    return false;
  }

  // calculation middle point for building biarc.
  const double ad1  = aDiff[0].Modulus();
  const double ad2  = aDiff[1].Modulus();
  const double ad12 = aDelta.Magnitude();

  const double aB1 = ad1 / (ad1 + ad2);
  if (fabs(aB1 - 0.5) < 0.0001)
  {
    return false;
  }

  gp_XY aXY[2] = {aPnt[0] + aDir[0].XY() * ad12 * ad1 / (ad12 + ad1 + ad2),
                  aPnt[1] + aDir[1].XY() * ad12 * ad2 / (ad12 + ad1 + ad2)};

  const gp_XY          aXYmidArc(aXY[0] + aB1 * (aXY[1] - aXY[0]));
  Geom2dConvert_PPoint aParamMidArc =
    getParameter(aXYmidArc, theFirstParam.Parameter(), theLastParam.Parameter(), myCurve);

  // building first arc from biarc.
  occ::handle<Geom2d_TrimmedCurve> aCurve1, aCurve2;
  if (!makeArc(theFirstParam, aParamMidArc, true, aCurve1))
  {
    return false;
  }

  if (!checkCurve(aCurve1, theFirstParam.Parameter(), aParamMidArc.Parameter()))
  {
    return false;
  }

  // building second arc from biarc.
  if (makeArc(theLastParam, aParamMidArc, false, aCurve2))
  {
    if (checkCurve(aCurve2, aParamMidArc.Parameter(), theLastParam.Parameter()))
    {
      mySeqCurves.Append(aCurve1);
      mySeqCurves.Append(aCurve2);
      return true;
    }
  }
  return false;
}

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

bool Geom2dConvert_ApproxArcsSegments::calculateLines(Geom2dConvert_PPoint& theFirstParam,
                                                      Geom2dConvert_PPoint& theLastParam)
{
  Geom2dConvert_PPoint* aPrevParam = &theFirstParam;
  for (int i = 1; i <= mySeqParams.Length(); i++)
  {
    Geom2dConvert_PPoint& aCurParam = mySeqParams.ChangeValue(i);
    if (aCurParam.Parameter() < (*aPrevParam).Parameter())
    {
      continue;
    }
    if (aCurParam.Parameter() > theLastParam.Parameter())
    {
      break;
    }

    // build line segment
    if (aCurParam != *aPrevParam)
    {
      const double aDistance = (aCurParam.Point() - (*aPrevParam).Point()).Modulus();
      if (aDistance > myTolerance)
      {
        const occ::handle<Geom2d_Curve> aCurve = makeLine(*aPrevParam, aCurParam, false);
        if (aCurve.IsNull())
        {
          return false;
        }

        mySeqCurves.Append(aCurve);
        aPrevParam = &mySeqParams(i);
      }
    }
  }
  return true;
}

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

bool Geom2dConvert_ApproxArcsSegments::checkCurve(const occ::handle<Geom2d_Curve>& aCurve,
                                                  const double                     theFirstParam,
                                                  const double theLastParam) const
{
  if (aCurve.IsNull())
  {
    return false; // check fails on empty input
  }
  bool isUniformDone = !mySeqParams.IsEmpty();
  // calculation sequence of the parameters or step by parameter.
  int    aNbPnts    = (isUniformDone ? mySeqParams.Length() : MAXPOINTS);
  double aParamStep = (theLastParam - theFirstParam) / MAXPOINTS;

  occ::handle<Geom2d_Curve>        aCurve1  = aCurve;
  occ::handle<Geom2d_TrimmedCurve> aTrCurve = occ::down_cast<Geom2d_TrimmedCurve>(aCurve);
  if (!aTrCurve.IsNull())
  {
    aCurve1 = aTrCurve->BasisCurve();
  }
  gp_Lin2d                 aLin2d;
  gp_Circ2d                aCirc2d;
  occ::handle<Geom2d_Line> aGeomLine = occ::down_cast<Geom2d_Line>(aCurve1);
  bool                     isLine    = (!aGeomLine.IsNull());
  bool                     isCircle  = (!isLine);
  if (isLine)
  {
    aLin2d = aGeomLine->Lin2d();
  }
  else
  {
    occ::handle<Geom2d_Circle> aGeomCircle = occ::down_cast<Geom2d_Circle>(aCurve1);
    isCircle                               = (!aGeomCircle.IsNull());
    if (isCircle)
    {
      aCirc2d = aGeomCircle->Circ2d();
    }
    else
    {
      return false;
    }
  }

  // calculation of the max deflection points from CurveAdaptor from Geom curve.
  bool isLess = true;
  int  i      = 1;
  for (; i <= aNbPnts && isLess; i++)
  {

    double aParam =
      (isUniformDone ? mySeqParams.Value(i).Parameter() : (theFirstParam + i * aParamStep));
    if (aParam < (theFirstParam - Precision::PConfusion())
        || aParam > (theLastParam + Precision::PConfusion()))
    {
      continue;
    }

    // getting point from adaptor curve by specified parameter.
    gp_Pnt2d aPointAdaptor(0., 0.);
    gp_Pnt2d aProjPoint(0., 0.);
    myCurve.D0(aParam, aPointAdaptor);
    double aParameterCurve = 0.0;

    // getting point from geom curve by specified parameter.
    if (isLine)
    {
      aParameterCurve = ElCLib::Parameter(aLin2d, aPointAdaptor);
      aProjPoint      = ElCLib::Value(aParameterCurve, aLin2d);
    }
    else if (isCircle)
    {

      aParameterCurve = ElCLib::Parameter(aCirc2d, aPointAdaptor);
      aProjPoint      = ElCLib::Value(aParameterCurve, aCirc2d);
    }

    isLess = (aProjPoint.Distance(aPointAdaptor) < myTolerance + Precision::PConfusion());
  }
  return isLess;
}

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

bool checkContinuity(const occ::handle<Geom2d_Curve>& theCurve1,
                     const occ::handle<Geom2d_Curve>& theCurve2,
                     const double                     theAngleTol)
{
  gp_Vec2d v11, v21;
  gp_Pnt2d p1, p2;
  theCurve1->D1(theCurve1->LastParameter(), p1, v11);
  theCurve2->D1(theCurve2->FirstParameter(), p2, v21);

  // check continuity with the specified tolerance.
  return (v11.IsParallel(v21, theAngleTol));
}

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

Geom2dConvert_PPoint getParameter(const gp_XY&             theXY1,
                                  const double             theFirstParam,
                                  const double             theLastParam,
                                  const Adaptor2d_Curve2d& theCurve)
{
  Geom2dConvert_PPoint aResult;
  double               prevParam = theFirstParam;
  double               af1       = theFirstParam;
  double               af2       = theLastParam;

  // for finding nearest point use method half division.
  double aMinDist = RealLast();
  int    i        = 1;
  for (; i <= MAXPOINTS; i++)
  {
    aResult       = Geom2dConvert_PPoint(af1, theCurve);
    double adist1 = (theXY1 - aResult.Point()).Modulus();
    if (adist1 < Precision::Confusion())
    {
      return aResult;
    }

    aResult       = Geom2dConvert_PPoint(af2, theCurve);
    double adist2 = (theXY1 - aResult.Point()).Modulus();
    if (adist2 < Precision::Confusion())
    {
      return aResult;
    }

    if (aMinDist <= adist2 - Precision::Confusion() && aMinDist <= adist1 - Precision::Confusion())
    {
      break;
    }

    if (adist1 < adist2 - Precision::Confusion())
    {
      prevParam = af1;
      aMinDist  = adist1;
      af2       = (af1 + af2) * 0.5;
    }
    else
    {
      prevParam = af2;
      aMinDist  = adist2;
      af1       = (af1 + af2) * 0.5;
    }
  }
  aResult = Geom2dConvert_PPoint(prevParam, theCurve);
  return aResult;
}

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

bool isInflectionPoint(const double theParam, const Adaptor2d_Curve2d& theCurve)
{
  gp_Pnt2d aP1;
  gp_Vec2d aD1, aD2;
  theCurve.D2(theParam, aP1, aD1, aD2);
  const double aSqMod     = aD1.XY().SquareModulus();
  const double aCurvature = fabs(aD1.XY() ^ aD2.XY()) / (aSqMod * sqrt(aSqMod));
  return (aCurvature < MyCurvatureTolerance);
}

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

bool isInflectionPoint(const double                theParam,
                       const Geom2dConvert_PPoint& theFirstInfl,
                       const Adaptor2d_Curve2d&    theCurve,
                       const double                theAngleTol)
{
  gp_Pnt2d aP1;
  gp_Vec2d aD1, aD2;
  theCurve.D2(theParam, aP1, aD1, aD2);
  const double aSqMod     = aD1.XY().SquareModulus();
  const double aCurvature = fabs(aD1.XY() ^ aD2.XY()) / (aSqMod * sqrt(aSqMod));
  double       aContAngle = fabs(gp_Vec2d(aP1.XY() - theFirstInfl.Point()).Angle(aD1));
  aContAngle              = std::min(aContAngle, fabs(M_PI - aContAngle));
  return (aCurvature < MyCurvatureTolerance && aContAngle < theAngleTol);
}
