// Copyright (c) 2021 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 <BRepLib_ValidateEdge.hxx>

#include <Adaptor3d_CurveOnSurface.hxx>
#include <BRepCheck.hxx>
#include <Extrema_LocateExtPC.hxx>
#include <GeomLib_CheckCurveOnSurface.hxx>

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

BRepLib_ValidateEdge::BRepLib_ValidateEdge(Handle(Adaptor3d_Curve)          theReferenceCurve,
                                           Handle(Adaptor3d_CurveOnSurface) theOtherCurve,
                                           Standard_Boolean                 theSameParameter)
    : myReferenceCurve(theReferenceCurve),
      myOtherCurve(theOtherCurve),
      mySameParameter(theSameParameter),
      myControlPointsNumber(22),
      myToleranceForChecking(0),
      myCalculatedDistance(0),
      myExitIfToleranceExceeded(Standard_False),
      myIsDone(Standard_False),
      myIsExactMethod(Standard_False),
      myIsMultiThread(Standard_False)
{
}

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

Standard_Boolean BRepLib_ValidateEdge::CheckTolerance(Standard_Real theToleranceToCheck)
{
  return correctTolerance(theToleranceToCheck) > myCalculatedDistance;
}

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

Standard_Real BRepLib_ValidateEdge::GetMaxDistance()
{
  Standard_Real aCorrectedTolerance = myCalculatedDistance * 1.00001;
  return aCorrectedTolerance;
}

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

void BRepLib_ValidateEdge::UpdateTolerance(Standard_Real& theToleranceToUpdate)
{
  Standard_Real aCorrectedTolerance = myCalculatedDistance * 1.00001;
  if (aCorrectedTolerance > theToleranceToUpdate)
  {
    theToleranceToUpdate = aCorrectedTolerance;
  }
}

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

Standard_Real BRepLib_ValidateEdge::correctTolerance(Standard_Real theTolerance)
{
  const Handle(Adaptor3d_Surface)& aSurface          = myOtherCurve->GetSurface();
  Standard_Real                    aCurvePrecision   = BRepCheck::PrecCurve(*myReferenceCurve);
  Standard_Real                    aSurfacePrecision = BRepCheck::PrecSurface(aSurface);
  Standard_Real                    aToleranceDelta =
    (aCurvePrecision > aSurfacePrecision) ? aCurvePrecision : aSurfacePrecision;
  Standard_Real aCorrectedTolerance = theTolerance + aToleranceDelta;
  return aCorrectedTolerance;
}

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

void BRepLib_ValidateEdge::SetExitIfToleranceExceeded(Standard_Real theToleranceForChecking)
{
  myExitIfToleranceExceeded = Standard_True;
  myToleranceForChecking    = correctTolerance(theToleranceForChecking);
}

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

void BRepLib_ValidateEdge::Process()
{
  if (myIsExactMethod && mySameParameter)
  {
    processExact();
  }
  else
  {
    processApprox();
  }
}

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

void BRepLib_ValidateEdge::processApprox()
{
  myIsDone                                  = Standard_True;
  Standard_Real aSquareToleranceForChecking = myToleranceForChecking * myToleranceForChecking;
  Standard_Real aReferenceFirstParam        = myReferenceCurve->FirstParameter();
  Standard_Real aReferenceLastParam         = myReferenceCurve->LastParameter();
  Standard_Real anOtherFirstParam           = myOtherCurve->FirstParameter();
  Standard_Real anOtherLastParam            = myOtherCurve->LastParameter();
  Standard_Real aMaxSquareDistance          = 0.;

  Standard_Integer aControlPointsNumber = (myControlPointsNumber < 1) ? 1 : myControlPointsNumber;
  Standard_Boolean anIsProjection =
    (!mySameParameter || Abs(anOtherFirstParam - aReferenceFirstParam) > Precision::PConfusion()
     || Abs(anOtherLastParam - aReferenceLastParam) > Precision::PConfusion());

  if (!anIsProjection)
  {
    for (Standard_Integer i = 0; i <= aControlPointsNumber; ++i)
    {
      Standard_Real aControlPointParam =
        ((aControlPointsNumber - i) * aReferenceFirstParam + i * aReferenceLastParam)
        / aControlPointsNumber;
      gp_Pnt        aReferencePoint = myReferenceCurve->Value(aControlPointParam);
      gp_Pnt        anOtherPoint    = myOtherCurve->Value(aControlPointParam);
      Standard_Real aSquareDistance = aReferencePoint.SquareDistance(anOtherPoint);
      if (aSquareDistance > aMaxSquareDistance)
      {
        aMaxSquareDistance = aSquareDistance;
      }
      // Stop process for best performance
      if (myExitIfToleranceExceeded && aMaxSquareDistance > aSquareToleranceForChecking)
      {
        myCalculatedDistance = Sqrt(aMaxSquareDistance);
        return;
      }
    }
  }
  else
  {
    gp_Pnt        aReferencePoint = myReferenceCurve->Value(aReferenceFirstParam);
    gp_Pnt        anOtherPoint    = myOtherCurve->Value(anOtherFirstParam);
    Standard_Real aSquareDistance = aReferencePoint.SquareDistance(anOtherPoint);
    if (aSquareDistance > aMaxSquareDistance)
    {
      aMaxSquareDistance = aSquareDistance;
    }
    if (myExitIfToleranceExceeded && aMaxSquareDistance > aSquareToleranceForChecking)
    {
      myCalculatedDistance = Sqrt(aMaxSquareDistance);
      return;
    }

    aReferencePoint = myReferenceCurve->Value(aReferenceLastParam);
    anOtherPoint    = myOtherCurve->Value(anOtherLastParam);
    aSquareDistance = aReferencePoint.SquareDistance(anOtherPoint);
    if (aSquareDistance > aMaxSquareDistance)
    {
      aMaxSquareDistance = aSquareDistance;
    }
    if (myExitIfToleranceExceeded && aMaxSquareDistance > aSquareToleranceForChecking)
    {
      myCalculatedDistance = Sqrt(aMaxSquareDistance);
      return;
    }

    Extrema_LocateExtPC aReferenceExtrema, anOtherExtrema;
    aReferenceExtrema.Initialize(*myReferenceCurve,
                                 aReferenceFirstParam,
                                 aReferenceLastParam,
                                 myReferenceCurve->Resolution(Precision::Confusion()));
    anOtherExtrema.Initialize(*myOtherCurve,
                              anOtherFirstParam,
                              anOtherLastParam,
                              myOtherCurve->Resolution(Precision::Confusion()));
    for (Standard_Integer i = 1; i < aControlPointsNumber; i++)
    {
      Standard_Real aReferenceParam =
        ((aControlPointsNumber - i) * aReferenceFirstParam + i * aReferenceLastParam)
        / aControlPointsNumber;
      gp_Pnt        aReferenceExtremaPoint = myReferenceCurve->Value(aReferenceParam);
      Standard_Real anOtherParam =
        ((aControlPointsNumber - i) * anOtherFirstParam + i * anOtherLastParam)
        / aControlPointsNumber;
      gp_Pnt anOtherExtremaPoint = myOtherCurve->Value(anOtherParam);

      aReferenceExtrema.Perform(anOtherExtremaPoint, aReferenceParam);
      if (aReferenceExtrema.IsDone())
      {
        if (aReferenceExtrema.SquareDistance() > aMaxSquareDistance)
        {
          aMaxSquareDistance = aReferenceExtrema.SquareDistance();
        }
        if (myExitIfToleranceExceeded && aMaxSquareDistance > aSquareToleranceForChecking)
        {
          myCalculatedDistance = Sqrt(aMaxSquareDistance);
          return;
        }
      }
      else
      {
        myIsDone = Standard_False;
        // Stop process for best performance
        return;
      }

      anOtherExtrema.Perform(aReferenceExtremaPoint, anOtherParam);
      if (anOtherExtrema.IsDone())
      {
        if (anOtherExtrema.SquareDistance() > aMaxSquareDistance)
        {
          aMaxSquareDistance = anOtherExtrema.SquareDistance();
        }
        if (myExitIfToleranceExceeded && aMaxSquareDistance > aSquareToleranceForChecking)
        {
          myCalculatedDistance = Sqrt(aMaxSquareDistance);
          return;
        }
      }
      else
      {
        myIsDone = Standard_False;
        // Stop process for best performance
        return;
      }
    }
  }
  myCalculatedDistance = Sqrt(aMaxSquareDistance);
}

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

void BRepLib_ValidateEdge::processExact()
{
  GeomLib_CheckCurveOnSurface aCheckCurveOnSurface(myReferenceCurve);
  aCheckCurveOnSurface.SetParallel(myIsMultiThread);
  aCheckCurveOnSurface.Perform(myOtherCurve);
  myIsDone = aCheckCurveOnSurface.IsDone();
  if (myIsDone)
  {
    myCalculatedDistance = aCheckCurveOnSurface.MaxDistance();
  }
}
