// Copyright (c) 1995-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 <Bnd_Box2d.hxx>
#include <BndLib_Add2dCurve.hxx>
#include <BRepGProp_Face.hxx>
#include <Geom2d_BezierCurve.hxx>
#include <Geom2d_BSplineCurve.hxx>
#include <Geom2d_Line.hxx>
#include <Geom_BezierSurface.hxx>
#include <Geom_BSplineCurve.hxx>
#include <Geom_BSplineSurface.hxx>
#include <Geom_SurfaceOfLinearExtrusion.hxx>
#include <GeomAdaptor_Curve.hxx>
#include <gp_Pnt.hxx>
#include <gp_Pnt2d.hxx>
#include <gp_Vec.hxx>
#include <math.hxx>
#include <Precision.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>

static const Standard_Real Epsilon1 = Epsilon(1.);

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

Standard_Integer BRepGProp_Face::UIntegrationOrder() const
{

  Standard_Integer Nu;
  switch (mySurface.GetType())
  {

    case GeomAbs_Plane:
      Nu = 4;
      break;

    case GeomAbs_BezierSurface: {
      Nu = (*((Handle(Geom_BezierSurface)*)&((mySurface.Surface()).Surface())))->UDegree() + 1;
      Nu = Max(4, Nu);
    }
    break;
    case GeomAbs_BSplineSurface: {
      Standard_Integer a =
        (*((Handle(Geom_BSplineSurface)*)&((mySurface.Surface()).Surface())))->UDegree() + 1;
      Standard_Integer b =
        (*((Handle(Geom_BSplineSurface)*)&((mySurface.Surface()).Surface())))->NbUKnots() - 1;
      Nu = Max(4, a * b);
    }
    break;

    default:
      Nu = 9;
      break;
  }
  return Max(8, 2 * Nu);
}

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

Standard_Integer BRepGProp_Face::VIntegrationOrder() const
{
  Standard_Integer Nv;
  switch (mySurface.GetType())
  {

    case GeomAbs_Plane:
      Nv = 4;
      break;

    case GeomAbs_BezierSurface: {
      Nv = (*((Handle(Geom_BezierSurface)*)&((mySurface.Surface()).Surface())))->VDegree() + 1;
      Nv = Max(4, Nv);
    }
    break;

    case GeomAbs_BSplineSurface: {
      Standard_Integer a =
        (*((Handle(Geom_BSplineSurface)*)&((mySurface.Surface()).Surface())))->VDegree() + 1;
      Standard_Integer b =
        (*((Handle(Geom_BSplineSurface)*)&((mySurface.Surface()).Surface())))->NbVKnots() - 1;
      Nv = Max(4, a * b);
    }
    break;

    default:
      Nv = 9;
      break;
  }
  return Max(8, 2 * Nv);
}

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

Standard_Integer BRepGProp_Face::IntegrationOrder() const
{
  Standard_Integer N;

  switch (myCurve.GetType())
  {

    case GeomAbs_Line:
      N = 2;
      break;

    case GeomAbs_Circle:
    case GeomAbs_Ellipse:
    case GeomAbs_Hyperbola:
      N = 9;
      break;

    case GeomAbs_Parabola:
      N = 9;
      break;

    case GeomAbs_BezierCurve: {
      N = (*((Handle(Geom2d_BezierCurve)*)&(myCurve.Curve())))->Degree() + 1;
    }
    break;

    case GeomAbs_BSplineCurve: {
      Standard_Integer a = (*((Handle(Geom2d_BSplineCurve)*)&(myCurve.Curve())))->Degree() + 1;
      Standard_Integer b = (*((Handle(Geom2d_BSplineCurve)*)&(myCurve.Curve())))->NbKnots() - 1;
      N                  = a * b;
    }
    break;

    default:
      N = 9;
      break;
  }

  return Max(4, 2 * N);
}

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

void BRepGProp_Face::Bounds(Standard_Real& U1,
                            Standard_Real& U2,
                            Standard_Real& V1,
                            Standard_Real& V2) const
{
  U1 = mySurface.FirstUParameter();
  U2 = mySurface.LastUParameter();
  V1 = mySurface.FirstVParameter();
  V2 = mySurface.LastVParameter();
}

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

bool BRepGProp_Face::Load(const TopoDS_Edge& E)
{
  Standard_Real        a, b;
  Handle(Geom2d_Curve) C = BRep_Tool::CurveOnSurface(E, mySurface.Face(), a, b);
  if (C.IsNull())
  {
    return false;
  }
  if (E.Orientation() == TopAbs_REVERSED)
  {
    Standard_Real x = a;
    a               = C->ReversedParameter(b);
    b               = C->ReversedParameter(x);
    C               = C->Reversed();
  }
  myCurve.Load(C, a, b);
  return true;
}

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

void BRepGProp_Face::Load(const TopoDS_Face& F)
{
  TopoDS_Shape aLocalShape = F.Oriented(TopAbs_FORWARD);
  mySurface.Initialize(TopoDS::Face(aLocalShape));
  //  mySurface.Initialize(TopoDS::Face(F.Oriented(TopAbs_FORWARD)));
  mySReverse = (F.Orientation() == TopAbs_REVERSED);
}

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

void BRepGProp_Face::Normal(const Standard_Real U,
                            const Standard_Real V,
                            gp_Pnt&             P,
                            gp_Vec&             VNor) const
{
  gp_Vec D1U, D1V;
  mySurface.D1(U, V, P, D1U, D1V);
  VNor = D1U.Crossed(D1V);
  if (mySReverse)
    VNor.Reverse();
}

//  APO 17.04.2002 (OCC104)
// This is functions that calculate coeff. to optimize "integration order".
// They had been produced experimentally for some hard example.
static Standard_Real AS = -0.15, AL = -0.50, B = 1.0, C = 0.75, D = 0.25;

static inline Standard_Real SCoeff(const Standard_Real Eps)
{
  return Eps < 0.1 ? AS * (B + Log10(Eps)) + C : C;
}

static inline Standard_Real LCoeff(const Standard_Real Eps)
{
  return Eps < 0.1 ? AL * (B + Log10(Eps)) + D : D;
}

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

Standard_Integer BRepGProp_Face::SIntOrder(const Standard_Real Eps) const
{
  Standard_Integer Nv, Nu;
  switch (mySurface.GetType())
  {
    case GeomAbs_Plane:
      Nu = 1;
      Nv = 1;
      break;
    case GeomAbs_Cylinder:
      Nu = 2;
      Nv = 1;
      break;
    case GeomAbs_Cone:
      Nu = 2;
      Nv = 1;
      break;
    case GeomAbs_Sphere:
      Nu = 2;
      Nv = 2;
      break;
    case GeomAbs_Torus:
      Nu = 2;
      Nv = 2;
      break;
    case GeomAbs_BezierSurface:
      Nv = (*((Handle(Geom_BezierSurface)*)&((mySurface.Surface()).Surface())))->VDegree();
      Nu = (*((Handle(Geom_BezierSurface)*)&((mySurface.Surface()).Surface())))->UDegree();
      break;
    case GeomAbs_BSplineSurface:
      Nv = (*((Handle(Geom_BSplineSurface)*)&((mySurface.Surface()).Surface())))->VDegree();
      Nu = (*((Handle(Geom_BSplineSurface)*)&((mySurface.Surface()).Surface())))->UDegree();
      break;
    default:
      Nu = 2;
      Nv = 2;
      break;
  }
  return Min(RealToInt(Ceiling(SCoeff(Eps) * Max((Nu + 1), (Nv + 1)))), math::GaussPointsMax());
}

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

Standard_Integer BRepGProp_Face::SUIntSubs() const
{
  Standard_Integer N;
  switch (mySurface.GetType())
  {
    case GeomAbs_Plane:
      N = 2;
      break;
    case GeomAbs_Cylinder:
      N = 4;
      break;
    case GeomAbs_Cone:
      N = 4;
      break;
    case GeomAbs_Sphere:
      N = 4;
      break;
    case GeomAbs_Torus:
      N = 4;
      break;
    case GeomAbs_BezierSurface:
      N = 2;
      break;
    case GeomAbs_BSplineSurface:
      N = (*((Handle(Geom_BSplineSurface)*)&((mySurface.Surface()).Surface())))->NbUKnots();
      break;
    default:
      N = 2;
      break;
  }
  return N - 1;
}

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

Standard_Integer BRepGProp_Face::SVIntSubs() const
{
  Standard_Integer N;
  switch (mySurface.GetType())
  {
    case GeomAbs_Plane:
      N = 2;
      break;
    case GeomAbs_Cylinder:
      N = 2;
      break;
    case GeomAbs_Cone:
      N = 2;
      break;
    case GeomAbs_Sphere:
      N = 3;
      break;
    case GeomAbs_Torus:
      N = 4;
      break;
    case GeomAbs_BezierSurface:
      N = 2;
      break;
    case GeomAbs_BSplineSurface:
      N = (*((Handle(Geom_BSplineSurface)*)&((mySurface.Surface()).Surface())))->NbVKnots();
      break;
    default:
      N = 2;
      break;
  }
  return N - 1;
}

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

void BRepGProp_Face::UKnots(TColStd_Array1OfReal& Knots) const
{
  switch (mySurface.GetType())
  {
    case GeomAbs_Plane:
      Knots(1) = mySurface.FirstUParameter();
      Knots(2) = mySurface.LastUParameter();
      break;
    case GeomAbs_Cylinder:
    case GeomAbs_Cone:
    case GeomAbs_Sphere:
    case GeomAbs_Torus:
      Knots(1) = 0.0;
      Knots(2) = M_PI * 2.0 / 3.0;
      Knots(3) = M_PI * 4.0 / 3.0;
      Knots(4) = M_PI * 6.0 / 3.0;
      break;
    case GeomAbs_BSplineSurface:
      (*((Handle(Geom_BSplineSurface)*)&((mySurface.Surface()).Surface())))->UKnots(Knots);
      break;
    default:
      Knots(1) = mySurface.FirstUParameter();
      Knots(2) = mySurface.LastUParameter();
      break;
  }
}

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

void BRepGProp_Face::VKnots(TColStd_Array1OfReal& Knots) const
{
  switch (mySurface.GetType())
  {
    case GeomAbs_Plane:
    case GeomAbs_Cylinder:
    case GeomAbs_Cone:
      Knots(1) = mySurface.FirstUParameter();
      Knots(2) = mySurface.LastUParameter();
      break;
    case GeomAbs_Sphere:
      Knots(1) = -M_PI / 2.0;
      Knots(2) = 0.0;
      Knots(3) = +M_PI / 2.0;
      break;
    case GeomAbs_Torus:
      Knots(1) = 0.0;
      Knots(2) = M_PI * 2.0 / 3.0;
      Knots(3) = M_PI * 4.0 / 3.0;
      Knots(4) = M_PI * 6.0 / 3.0;
      break;
    case GeomAbs_BSplineSurface:
      (*((Handle(Geom_BSplineSurface)*)&((mySurface.Surface()).Surface())))->VKnots(Knots);
      break;
    default:
      Knots(1) = mySurface.FirstUParameter();
      Knots(2) = mySurface.LastUParameter();
      break;
  }
}

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

Standard_Integer BRepGProp_Face::LIntOrder(const Standard_Real Eps) const
{
  Bnd_Box2d aBox;

  BndLib_Add2dCurve::Add(myCurve, 1.e-7, aBox);
  Standard_Real aXmin, aXmax, aYmin, aYmax;
  aBox.Get(aXmin, aYmin, aXmax, aYmax);
  Standard_Real aVmin = mySurface.FirstVParameter();
  Standard_Real aVmax = mySurface.LastVParameter();

  Standard_Real dv  = (aVmax - aVmin);
  Standard_Real anR = (dv > Epsilon1 ? Min((aYmax - aYmin) / dv, 1.) : 1.);

  //  Standard_Integer anRInt = Max(RealToInt(Ceiling(SVIntSubs()*anR)), 2);
  Standard_Integer anRInt = RealToInt(Ceiling(SVIntSubs() * anR));
  Standard_Integer aLSubs = LIntSubs();

  //  Standard_Real NL, NS = Max(SIntOrder(1.0)*anRInt/LIntSubs(), 1);
  Standard_Real NL, NS = Max(SIntOrder(1.) * anRInt / aLSubs, 1);
  switch (myCurve.GetType())
  {
    case GeomAbs_Line:
      NL = 1;
      break;
    case GeomAbs_Circle:
      NL = 2 * 3;
      break; // correction for the spans of converted curve
    case GeomAbs_Ellipse:
      NL = 2 * 3;
      break; //
    case GeomAbs_Parabola:
      NL = 2 * 3;
      break;
    case GeomAbs_Hyperbola:
      NL = 3 * 3;
      break;
    case GeomAbs_BezierCurve:
      NL = (*((Handle(Geom2d_BezierCurve)*)&(myCurve.Curve())))->Degree();
      break;
    case GeomAbs_BSplineCurve:
      NL = (*((Handle(Geom2d_BSplineCurve)*)&(myCurve.Curve())))->Degree();
      break;
    default:
      NL = 3 * 3;
      break;
  }

  NL = Max(NL, NS);

  Standard_Integer nn = RealToInt(aLSubs <= 4 ? Ceiling(LCoeff(Eps) * (NL + 1)) : NL + 1);

  // return Min(RealToInt(Ceiling(LCoeff(Eps)*(NL+1)*NS)), math::GaussPointsMax());
  return Min(nn, math::GaussPointsMax());
}

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

Standard_Integer BRepGProp_Face::LIntSubs() const
{
  Standard_Integer N;
  switch (myCurve.GetType())
  {
    case GeomAbs_Line:
      N = 2;
      break;
    case GeomAbs_Circle:
    case GeomAbs_Ellipse:
      N = 4;
      break;
    case GeomAbs_Parabola:
    case GeomAbs_Hyperbola:
      N = 2;
      break;
    case GeomAbs_BSplineCurve:
      N = (*((Handle(Geom2d_BSplineCurve)*)&(myCurve.Curve())))->NbKnots();
      break;
    default:
      N = 2;
      break;
  }
  return N - 1;
}

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

void BRepGProp_Face::LKnots(TColStd_Array1OfReal& Knots) const
{
  switch (myCurve.GetType())
  {
    case GeomAbs_Line:
      Knots(1) = myCurve.FirstParameter();
      Knots(2) = myCurve.LastParameter();
      break;
    case GeomAbs_Circle:
    case GeomAbs_Ellipse:
      Knots(1) = 0.0;
      Knots(2) = M_PI * 2.0 / 3.0;
      Knots(3) = M_PI * 4.0 / 3.0;
      Knots(4) = M_PI * 6.0 / 3.0;
      break;
    case GeomAbs_Parabola:
    case GeomAbs_Hyperbola:
      Knots(1) = myCurve.FirstParameter();
      Knots(2) = myCurve.LastParameter();
      break;
    case GeomAbs_BSplineCurve:
      (*((Handle(Geom2d_BSplineCurve)*)&(myCurve.Curve())))->Knots(Knots);
      break;
    default:
      Knots(1) = myCurve.FirstParameter();
      Knots(2) = myCurve.LastParameter();
      break;
  }
}

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

void BRepGProp_Face::Load(const Standard_Boolean IsFirstParam, const GeomAbs_IsoType theIsoType)
{
  Standard_Real aLen;
  Standard_Real aU1;
  Standard_Real aU2;
  Standard_Real aV1;
  Standard_Real aV2;
  gp_Pnt2d      aLoc;
  gp_Dir2d      aDir;

  Bounds(aU1, aU2, aV1, aV2);

  if (theIsoType == GeomAbs_IsoU)
  {
    aLen = aV2 - aV1;

    if (IsFirstParam)
    {
      aLoc.SetCoord(aU1, aV2);
      aDir.SetCoord(0., -1.);
    }
    else
    {
      aLoc.SetCoord(aU2, aV1);
      aDir.SetCoord(0., 1.);
    }
  }
  else if (theIsoType == GeomAbs_IsoV)
  {
    aLen = aU2 - aU1;

    if (IsFirstParam)
    {
      aLoc.SetCoord(aU1, aV1);
      aDir.SetCoord(1., 0.);
    }
    else
    {
      aLoc.SetCoord(aU2, aV2);
      aDir.SetCoord(-1., 0.);
    }
  }
  else
    return;

  Handle(Geom2d_Curve) aLin = new Geom2d_Line(aLoc, aDir);

  myCurve.Load(aLin, 0., aLen);
}

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

static void GetRealKnots(const Standard_Real                  theMin,
                         const Standard_Real                  theMax,
                         const Handle(TColStd_HArray1OfReal)& theKnots,
                         Handle(TColStd_HArray1OfReal)&       theRealKnots)
{
  Standard_Integer        i       = theKnots->Lower() - 1;
  Standard_Integer        iU      = theKnots->Upper();
  Standard_Integer        aStartI = 0;
  Standard_Integer        aEndI   = 0;
  constexpr Standard_Real aTol    = Precision::Confusion();

  while (++i < iU)
  {
    if (aStartI == 0 && theKnots->Value(i) > theMin + aTol)
      aStartI = i;

    if (aEndI == 0 && theKnots->Value(i + 1) > theMax - aTol)
      aEndI = i;

    if (aStartI != 0 && aEndI != 0)
      break;
  }

  if (aStartI == 0)
    aStartI = iU;

  Standard_Integer aNbNode = Max(0, aEndI - aStartI + 1) + 2;
  Standard_Integer j;

  theRealKnots = new TColStd_HArray1OfReal(1, aNbNode);
  theRealKnots->SetValue(1, theMin);
  theRealKnots->SetValue(aNbNode, theMax);

  for (i = 2, j = aStartI; j <= aEndI; i++, j++)
    theRealKnots->SetValue(i, theKnots->Value(j));
}

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

static void GetCurveKnots(const Standard_Real            theMin,
                          const Standard_Real            theMax,
                          const Geom2dAdaptor_Curve&     theCurve,
                          Handle(TColStd_HArray1OfReal)& theKnots)
{
  Standard_Boolean isSBSpline = theCurve.GetType() == GeomAbs_BSplineCurve;

  if (isSBSpline)
  {
    Handle(Geom2d_BSplineCurve)   aCrv;
    Standard_Integer              aNbKnots;
    Handle(TColStd_HArray1OfReal) aCrvKnots;

    aCrv      = Handle(Geom2d_BSplineCurve)::DownCast(theCurve.Curve());
    aNbKnots  = aCrv->NbKnots();
    aCrvKnots = new TColStd_HArray1OfReal(1, aNbKnots);
    aCrv->Knots(aCrvKnots->ChangeArray1());
    GetRealKnots(theMin, theMax, aCrvKnots, theKnots);
  }
  else
  {
    theKnots = new TColStd_HArray1OfReal(1, 2);
    theKnots->SetValue(1, theMin);
    theKnots->SetValue(2, theMax);
  }
}

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

void BRepGProp_Face::GetUKnots(const Standard_Real            theUMin,
                               const Standard_Real            theUMax,
                               Handle(TColStd_HArray1OfReal)& theUKnots) const
{
  Standard_Boolean isSBSpline = mySurface.GetType() == GeomAbs_BSplineSurface;
  Standard_Boolean isCBSpline = Standard_False;

  if (!isSBSpline)
  {
    // Check the basis curve of the surface of linear extrusion.
    if (mySurface.GetType() == GeomAbs_SurfaceOfExtrusion)
    {
      GeomAdaptor_Curve    aCurve;
      Handle(Geom_Surface) aSurf = mySurface.Surface().Surface();

      aCurve.Load(Handle(Geom_SurfaceOfLinearExtrusion)::DownCast(aSurf)->BasisCurve());
      isCBSpline = aCurve.GetType() == GeomAbs_BSplineCurve;
    }
  }

  if (myIsUseSpan && (isSBSpline || isCBSpline))
  {
    // Using span decomposition for BSpline.
    Handle(TColStd_HArray1OfReal) aKnots;
    Standard_Integer              aNbKnots;

    if (isSBSpline)
    {
      // Get U knots of BSpline surface.
      Handle(Geom_Surface)        aSurf = mySurface.Surface().Surface();
      Handle(Geom_BSplineSurface) aBSplSurf;

      aBSplSurf = Handle(Geom_BSplineSurface)::DownCast(aSurf);
      aNbKnots  = aBSplSurf->NbUKnots();
      aKnots    = new TColStd_HArray1OfReal(1, aNbKnots);
      aBSplSurf->UKnots(aKnots->ChangeArray1());
    }
    else
    {
      // Get U knots of BSpline curve - basis curve of
      // the surface of linear extrusion.
      GeomAdaptor_Curve         aCurve;
      Handle(Geom_Surface)      aSurf = mySurface.Surface().Surface();
      Handle(Geom_BSplineCurve) aBSplCurve;

      aCurve.Load(Handle(Geom_SurfaceOfLinearExtrusion)::DownCast(aSurf)->BasisCurve());
      aBSplCurve = aCurve.BSpline();
      aNbKnots   = aBSplCurve->NbKnots();
      aKnots     = new TColStd_HArray1OfReal(1, aNbKnots);
      aBSplCurve->Knots(aKnots->ChangeArray1());
    }

    // Compute number of knots inside theUMin and theUMax.
    GetRealKnots(theUMin, theUMax, aKnots, theUKnots);
  }
  else
  {
    // No span decomposition.
    theUKnots = new TColStd_HArray1OfReal(1, 2);
    theUKnots->SetValue(1, theUMin);
    theUKnots->SetValue(2, theUMax);
  }
}

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

void BRepGProp_Face::GetTKnots(const Standard_Real            theTMin,
                               const Standard_Real            theTMax,
                               Handle(TColStd_HArray1OfReal)& theTKnots) const
{
  Standard_Boolean isBSpline = mySurface.GetType() == GeomAbs_BSplineSurface;

  if (myIsUseSpan && isBSpline)
  {
    // Using span decomposition for BSpline.
    Handle(TColStd_HArray1OfReal) aSurfKnots;
    Standard_Integer              aNbKnots;

    // Get V knots of BSpline surface.
    Handle(Geom_Surface)        aSurf = mySurface.Surface().Surface();
    Handle(Geom_BSplineSurface) aBSplSurf;

    aBSplSurf  = Handle(Geom_BSplineSurface)::DownCast(aSurf);
    aNbKnots   = aBSplSurf->NbVKnots();
    aSurfKnots = new TColStd_HArray1OfReal(1, aNbKnots);
    aBSplSurf->VKnots(aSurfKnots->ChangeArray1());

    //     Handle(TColStd_HArray1OfReal) aCurveKnots;

    //     GetCurveKnots(theTMin, theTMax, myCurve, aCurveKnots);
    //    GetRealCurveKnots(aCurveKnots, aSurfKnots, myCurve, theTKnots);
    GetCurveKnots(theTMin, theTMax, myCurve, theTKnots);
  }
  else
  {
    theTKnots = new TColStd_HArray1OfReal(1, 2);
    theTKnots->SetValue(1, theTMin);
    theTKnots->SetValue(2, theTMax);
  }
}
