// File Extrema_ExtPS.cxx
//-----------------------------------------------------------------
// modif JMB le 17 Mai 1999.
//           on trimme la surface a +/- 10000 plutot qu'a l'infini 
//           (sinon ca raise dans les Math)
//           voir bug ID260065 
//-----------------------------------------------------------------

#include <Extrema_ExtPS.ixx>
#include <Extrema_GenExtPS.hxx>
#include <StdFail_NotDone.hxx>
#include <Standard_NotImplemented.hxx>
#include <Precision.hxx>
#include <GeomAbs_CurveType.hxx>
#include <Adaptor3d_SurfaceOfLinearExtrusion.hxx>
#include <Adaptor3d_SurfaceOfRevolution.hxx>
#include <ElCLib.hxx>
#include <gp_Pnt.hxx>
#include <gp_Pln.hxx>
#include <gp_Cylinder.hxx>
#include <gp_Cone.hxx>
#include <gp_Sphere.hxx>
#include <gp_Torus.hxx>
#include <Extrema_ExtPExtS.hxx>
#include <Extrema_ExtPRevS.hxx>
#include <Extrema_POnSurf.hxx>
#include <GeomAbs_IsoType.hxx>

//=======================================================================
//function : IsoIsDeg
//purpose  : 
//=======================================================================

static Standard_Boolean IsoIsDeg  (const Adaptor3d_Surface& S,
				   const Standard_Real      Param,
				   const GeomAbs_IsoType    IT,
				   const Standard_Real      TolMin,
				   const Standard_Real      TolMax) 
{
    Standard_Real U1=0.,U2=0.,V1=0.,V2=0.,T;
    Standard_Boolean Along = Standard_True;
    U1 = S.FirstUParameter();
    U2 = S.LastUParameter();
    V1 = S.FirstVParameter();
    V2 = S.LastVParameter();
    gp_Vec D1U,D1V;
    gp_Pnt P;
    Standard_Real Step,D1NormMax;
    if (IT == GeomAbs_IsoV) 
    {
      Step = (U2 - U1)/10;
      D1NormMax=0.;
      for (T=U1;T<=U2;T=T+Step) 
      {
        S.D1(T,Param,P,D1U,D1V);
        D1NormMax=Max(D1NormMax,D1U.Magnitude());
      }

      if (D1NormMax >TolMax || D1NormMax < TolMin ) 
           Along = Standard_False;
    }
    else 
    {
      Step = (V2 - V1)/10;
      D1NormMax=0.;
      for (T=V1;T<=V2;T=T+Step) 
      {
	S.D1(Param,T,P,D1U,D1V);
        D1NormMax=Max(D1NormMax,D1V.Magnitude());
      }

      if (D1NormMax >TolMax || D1NormMax < TolMin ) 
           Along = Standard_False;


    }
    return Along;
}

//=======================================================================
//function : TreatSolution
//purpose  : 
//=======================================================================

void Extrema_ExtPS::TreatSolution (const Extrema_POnSurf& PS,
				   const Standard_Real Val)
{
  Standard_Real U, V;
  PS.Parameter(U, V);
  if (myS->IsUPeriodic()) {
    U = ElCLib::InPeriod(U, myuinf, myuinf+myS->UPeriod());
  }
  if (myS->IsVPeriodic()) {
    V = ElCLib::InPeriod(V, myvinf, myvinf+myS->VPeriod());
  }
  if ((myuinf-U) <= mytolu && (U-myusup) <= mytolu &&
      (myvinf-V) <= mytolv && (V-myvsup) <= mytolv) {
    myPoints.Append(Extrema_POnSurf (U, V, PS.Value()));
    myvalue.Append(Val);
  }
}


//=======================================================================
//function : Extrema_ExtPS
//purpose  : 
//=======================================================================

Extrema_ExtPS::Extrema_ExtPS() 
{
  myDone = Standard_False;
}


//=======================================================================
//function : Extrema_ExtPS
//purpose  : 
//=======================================================================

Extrema_ExtPS::Extrema_ExtPS(const gp_Pnt&          P,
			     const Adaptor3d_Surface& S,
			     const Standard_Real    TolU,
			     const Standard_Real    TolV)

{
  Initialize(S, S.FirstUParameter(), S.LastUParameter(), 
	        S.FirstVParameter(), S.LastVParameter(), 
	        TolU, TolV);
  Perform(P);
}

//=======================================================================
//function : Extrema_ExtPS
//purpose  : 
//=======================================================================

Extrema_ExtPS::Extrema_ExtPS(const gp_Pnt&          P,
			     const Adaptor3d_Surface& S,
			     const Standard_Real    Uinf,	
			     const Standard_Real    Usup,
			     const Standard_Real    Vinf,	
			     const Standard_Real    Vsup,
			     const Standard_Real    TolU,
			     const Standard_Real    TolV)

{
  Initialize(S, Uinf, Usup, Vinf, Vsup, TolU, TolV);
  Perform(P);
}


//=======================================================================
//function : Initialize
//purpose  : 
//=======================================================================

void Extrema_ExtPS::Initialize(const Adaptor3d_Surface& S,
			       const Standard_Real    Uinf,	
			       const Standard_Real    Usup,
			       const Standard_Real    Vinf,	
			       const Standard_Real    Vsup,
			       const Standard_Real    TolU,
			       const Standard_Real    TolV)
{
  myS = (Adaptor3d_SurfacePtr)&S;
  myuinf = Uinf;
  myusup = Usup;
  myvinf = Vinf;
  myvsup = Vsup;

  if (Precision::IsNegativeInfinite(myuinf)) myuinf = -1e10;
  if (Precision::IsPositiveInfinite(myusup)) myusup = 1e10;
  if (Precision::IsNegativeInfinite(myvinf)) myvinf = -1e10;
  if (Precision::IsPositiveInfinite(myvsup)) myvsup = 1e10;

  mytolu = TolU;
  mytolv = TolV;
  mytype = myS->GetType();

  Standard_Boolean isB = ( myS->GetType() == GeomAbs_BSplineSurface ||
			   myS->GetType() == GeomAbs_BezierSurface );

  Standard_Integer nbU = (isB) ? 44 : 32;
  Standard_Integer nbV = (isB) ? 44 : 32;

  Standard_Boolean  bUIsoIsDeg = IsoIsDeg(S, myuinf, GeomAbs_IsoU, 0., 1.e-9) ||
                                 IsoIsDeg(S, myusup, GeomAbs_IsoU, 0., 1.e-9);
  Standard_Boolean  bVIsoIsDeg = IsoIsDeg(S, myvinf, GeomAbs_IsoV, 0., 1.e-9) ||
                                 IsoIsDeg(S, myvsup, GeomAbs_IsoV, 0., 1.e-9);

  if(bUIsoIsDeg) nbU = 300;
  if(bVIsoIsDeg) nbV = 300;

  myExtPS.Initialize(*myS, nbU, nbV, myuinf, myusup, myvinf, myvsup, mytolu, mytolv);
}

//=======================================================================
//function : Perform
//purpose  : 
//=======================================================================

void Extrema_ExtPS::Perform(const gp_Pnt& P)
{
  myPoints.Clear();
  myvalue.Clear();
  Standard_Integer i;
  P11 = myS->Value(myuinf, myvinf);
  P12 = myS->Value(myuinf, myvsup);
  P21 = myS->Value(myusup, myvinf);
  P22 = myS->Value(myusup, myvsup);
  d11 = P.Distance(P11);
  d12 = P.Distance(P12);
  d21 = P.Distance(P21);
  d22 = P.Distance(P22);
  
  switch(mytype) {
    
  case GeomAbs_Cylinder:
    myExtPElS.Perform(P, myS->Cylinder(), Precision::Confusion());
    break;
  case GeomAbs_Plane:
    myExtPElS.Perform(P, myS->Plane(), Precision::Confusion());
    break;
  case GeomAbs_Cone:
    myExtPElS.Perform(P, myS->Cone(), Precision::Confusion());
    break;
  case GeomAbs_Sphere:
    myExtPElS.Perform(P, myS->Sphere(), Precision::Confusion());
    break;
  case GeomAbs_Torus:
    myExtPElS.Perform(P, myS->Torus(), Precision::Confusion());
    break;

    
  case GeomAbs_SurfaceOfExtrusion: {
    Extrema_ExtPExtS anExtPExtS(P,
				Adaptor3d_SurfaceOfLinearExtrusion(myS->BasisCurve(),
								   myS->Direction()),
				myuinf,myusup, myvinf,myvsup, mytolu,mytolv);
    myDone = anExtPExtS.IsDone();
    if (myDone) 
      for (i = 1; i <= anExtPExtS.NbExt(); i++) {
	TreatSolution (anExtPExtS.Point(i), anExtPExtS.Value(i));
      }
    return;
  }

    
  case GeomAbs_SurfaceOfRevolution: {
    Extrema_ExtPRevS anExtPRevS(P,
				Adaptor3d_SurfaceOfRevolution(myS->BasisCurve(),
							      myS->AxeOfRevolution()),
				myuinf, myusup,myvinf, myvsup,mytolu, mytolv);
    myDone = anExtPRevS.IsDone();
    if (myDone) 
      for (i = 1; i <= anExtPRevS.NbExt(); i++) {
	TreatSolution (anExtPRevS.Point(i), anExtPRevS.Value(i));
      }
    return;
  }

    
  default: 
    myExtPS.Perform(P);
    myDone = myExtPS.IsDone();
    if (myDone) 
      for (i = 1; i <= myExtPS.NbExt(); i++) 
	TreatSolution (myExtPS.Point(i), myExtPS.Value(i));
    return;
  }

  
  myDone = myExtPElS.IsDone();
  if (myDone) 
    for (i = 1; i <= myExtPElS.NbExt(); i++) 
      TreatSolution (myExtPElS.Point(i), myExtPElS.Value(i));
  return;
}



Standard_Boolean Extrema_ExtPS::IsDone() const
{
  return myDone;
}


Standard_Real Extrema_ExtPS::Value(const Standard_Integer N) const
{
  if(!myDone) StdFail_NotDone::Raise();
  if ((N < 1) || (N > myvalue.Length())) Standard_OutOfRange::Raise();
  return myvalue.Value(N);
}


Standard_Integer Extrema_ExtPS::NbExt() const
{
  if(!myDone) StdFail_NotDone::Raise();
  return myvalue.Length();
}



Extrema_POnSurf Extrema_ExtPS::Point(const Standard_Integer N) const
{
  if(!myDone) StdFail_NotDone::Raise();
  return myPoints.Value(N);
}


void Extrema_ExtPS::TrimmedDistances(Standard_Real& dUfVf,
				      Standard_Real& dUfVl,
				      Standard_Real& dUlVf,
				      Standard_Real& dUlVl,
				      gp_Pnt&        PUfVf,
				      gp_Pnt&        PUfVl,
				      gp_Pnt&        PUlVf,
				      gp_Pnt&        PUlVl) const
{
  dUfVf = d11;
  dUfVl = d12;
  dUlVf = d21;
  dUlVl = d22;
  PUfVf = P11;
  PUfVl = P12;
  PUlVf = P21;
  PUlVl = P22;
}
