#include <ShapeFix_TransferParametersProj.ixx>
#include <Geom2dAdaptor_HCurve.hxx>
#include <Geom2d_Curve.hxx>
#include <Adaptor3d_CurveOnSurface.hxx>
#include <Geom_Surface.hxx>
#include <GeomAdaptor_HSurface.hxx>
#include <BRep_Tool.hxx>
#include <TColStd_HArray1OfReal.hxx>
#include <TopLoc_Location.hxx>
#include <ShapeAnalysis_Edge.hxx>
#include <TColgp_SequenceOfPnt.hxx>
#include <ShapeAnalysis_Curve.hxx>
#include <BRep_ListIteratorOfListOfCurveRepresentation.hxx>
#include <BRep_TEdge.hxx>
#include <BRep_GCurve.hxx>
#include <BRep_ListOfCurveRepresentation.hxx>
#include <BRep_ListOfCurveRepresentation.hxx>
#include <Precision.hxx>
#include <ShapeBuild_Edge.hxx>
#include <BRep_Builder.hxx>
#include <GeomAdaptor_Curve.hxx>
#include <ShapeAnalysis.hxx>
#include <Geom2d_TrimmedCurve.hxx>
#include <Geom2d_OffsetCurve.hxx>
#include <Geom2d_BSplineCurve.hxx>

//=======================================================================
//function : ShapeFix_TransferParametersProj
//purpose  : 
//=======================================================================

ShapeFix_TransferParametersProj::ShapeFix_TransferParametersProj()
{
  myMaxTolerance = 1; //Precision::Infinite(); ?? pdn
  myForceProj = Standard_False;
  myInitOK = Standard_False;
}

//=======================================================================
//function : ShapeFix_TransferParametersProj
//purpose  : 
//=======================================================================

ShapeFix_TransferParametersProj::ShapeFix_TransferParametersProj(const TopoDS_Edge& E,
								 const TopoDS_Face& F)
{
  myMaxTolerance = 1; //Precision::Infinite(); ?? pdn
  myForceProj = Standard_False;
  Init(E,F);
}

//=======================================================================
//function : Init
//purpose  : 
//=======================================================================

void ShapeFix_TransferParametersProj::Init(const TopoDS_Edge& E,
					   const TopoDS_Face& F)
{
  myInitOK = Standard_False;
  ShapeFix_TransferParameters::Init(E,F);
  myEdge = E;
  myPrecision = BRep_Tool::Tolerance(E); // it is better - skl OCC2851
  //myPrecision = Precision::Confusion();

  myCurve = BRep_Tool::Curve (E, myFirst, myLast);
  if ( myCurve.IsNull() ) { myFirst = 0.; myLast = 1.; return;}
    
  if ( F.IsNull() ) return;
    
  Standard_Real f2d, l2d;
  ShapeAnalysis_Edge sae;
  if(sae.PCurve (E, F, myCurve2d, f2d, l2d, Standard_False)) {
      
    Handle(Geom2dAdaptor_HCurve) AC2d  = new Geom2dAdaptor_HCurve(myCurve2d,f2d,l2d);
    Handle(Geom_Surface) aSurface = BRep_Tool::Surface(F,myLocation);
    Handle(GeomAdaptor_HSurface) AdS = new GeomAdaptor_HSurface(aSurface);
    
    Adaptor3d_CurveOnSurface Ad1(AC2d,AdS);
    myAC3d = Ad1;//new Adaptor3d_CurveOnSurface(AC2d,AdS);
    myInitOK = Standard_True;
  }
}


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

Handle(TColStd_HSequenceOfReal) ShapeFix_TransferParametersProj::Perform(const Handle(TColStd_HSequenceOfReal)& Knots,
									 const Standard_Boolean To2d) 
{
  //pdn
  if( !myInitOK || 
     (! myForceProj && myPrecision < myMaxTolerance && BRep_Tool::SameParameter(myEdge)))
    return ShapeFix_TransferParameters::Perform(Knots,To2d);
  
  Handle(TColStd_HSequenceOfReal) resKnots = new TColStd_HSequenceOfReal;

  Standard_Integer len = Knots->Length();
  Standard_Real preci = 2*Precision::PConfusion();

  Standard_Real first = (To2d ? myAC3d.FirstParameter() : myFirst);
  Standard_Real last  = (To2d ? myAC3d.LastParameter() : myLast);
  Standard_Real maxPar = first;
  Standard_Real lastPar = last;
  Standard_Real prevPar = maxPar;

  Standard_Integer j; // svv Jan 10 2000 : porting on DEC
  for(j = 1; j <= len; j++) {
    Standard_Real par = PreformSegment(Knots->Value(j),To2d,prevPar,lastPar);
    prevPar = par;
    if(prevPar > lastPar)
      prevPar -= preci;
    resKnots->Append(par);
    if(par > maxPar)
      maxPar = par;
  }
  
  //pdn correcting on periodic
  if(myCurve->IsClosed())
    for(j = len; j >=1; j--) 
      if(resKnots->Value(j) < maxPar)
	resKnots->SetValue(j,(To2d ? myAC3d.LastParameter() : myCurve->LastParameter())-(len-j)*preci);
      else
	break;
  //pdn correction on range
  for ( j=1; j <= len; j++ ) {
    if ( resKnots->Value (j) < first ) resKnots->SetValue ( j, first );
    if ( resKnots->Value (j) > last  ) resKnots->SetValue ( j, last );
  }
  
  return resKnots;
}

//=======================================================================
//function : PreformSegment
//purpose  : 
//=======================================================================

Standard_Real ShapeFix_TransferParametersProj::PreformSegment(const Standard_Real Param,
							      const Standard_Boolean To2d,
							      const Standard_Real First,
							      const Standard_Real Last)
{
  Standard_Real linPar = ShapeFix_TransferParameters::Perform(Param, To2d);
  if( !myInitOK || 
     (! myForceProj && myPrecision < myMaxTolerance && BRep_Tool::SameParameter(myEdge)))
    return linPar;
  
  Standard_Real linDev, projDev;
  
  ShapeAnalysis_Curve sac;
  gp_Pnt pproj;
  Standard_Real ppar;
  if(To2d) {
    gp_Pnt p1 = myCurve->Value(Param).Transformed(myLocation.Inverted());
    Handle(Adaptor3d_HSurface) AdS = myAC3d.GetSurface();
    Handle(Geom2dAdaptor_HCurve) AC2d  = new Geom2dAdaptor_HCurve(myCurve2d,First,Last);
    Adaptor3d_CurveOnSurface Ad1(AC2d,AdS);
    projDev = sac.Project(Ad1,p1,myPrecision,pproj,ppar);//pdn
    linDev = p1.Distance(Ad1.Value(linPar));
  }
  else {
    gp_Pnt p1 = myAC3d.Value(Param).Transformed(myLocation);
    projDev = sac.Project(myCurve,p1,myPrecision,pproj,ppar,First,Last,Standard_False);
    linDev = p1.Distance(myCurve->Value(linPar));
  }
  
  if ( linDev <= projDev ||  (linDev < myPrecision && linDev <= 2 * projDev ) ) 
    ppar = linPar;
  return ppar;
}

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

Standard_Real ShapeFix_TransferParametersProj::Perform(const Standard_Real Knot, const Standard_Boolean To2d) 
{
  if( !myInitOK || 
     (! myForceProj && myPrecision < myMaxTolerance && BRep_Tool::SameParameter(myEdge)))
    return ShapeFix_TransferParameters::Perform(Knot, To2d);
  
  //gp_Pnt pproj; //SK
  Standard_Real res;
  if(To2d) 
    res = PreformSegment(Knot,To2d,myAC3d.FirstParameter(),myAC3d.LastParameter());
  else 
    res = PreformSegment(Knot,To2d,myFirst,myLast);

  //pdn correction on range
  Standard_Real first = (To2d ? myAC3d.FirstParameter() : myFirst);
  Standard_Real last  = (To2d ? myAC3d.LastParameter() : myLast);
  if ( res < first ) res = first;
  if ( res > last  ) res = last;
  return res;
}

//=======================================================================
//function : TransferRange
//purpose  : 
//=======================================================================

static Standard_Real CorrectParameter(const Handle(Geom2d_Curve) crv,
				      const Standard_Real param)
{
  if(crv->IsKind(STANDARD_TYPE(Geom2d_TrimmedCurve))) {
    Handle(Geom2d_TrimmedCurve) tmp = Handle(Geom2d_TrimmedCurve)::DownCast (crv);
    return CorrectParameter(tmp->BasisCurve(),param);
  } 
  else if(crv->IsKind(STANDARD_TYPE(Geom2d_OffsetCurve))) {
    Handle(Geom2d_OffsetCurve) tmp = Handle(Geom2d_OffsetCurve)::DownCast (crv);
    return CorrectParameter(tmp->BasisCurve(),param);
  } 
  else if(crv->IsKind(STANDARD_TYPE(Geom2d_BSplineCurve))) {
    Handle(Geom2d_BSplineCurve) bspline = Handle(Geom2d_BSplineCurve)::DownCast (crv);
    //Standard_Integer FirstInd = bspline->FirstUKnotIndex(); //SK
    //Standard_Integer LastInd = bspline->LastUKnotIndex(); //SK
    for(Standard_Integer j = bspline->FirstUKnotIndex(); j <= bspline->LastUKnotIndex(); j++) {
      Standard_Real valknot = bspline->Knot(j);
      if( Abs(valknot-param)<Precision::PConfusion() )
	return valknot;
    }
  }
  return param;
}
    
  

void ShapeFix_TransferParametersProj::TransferRange(TopoDS_Edge& newEdge,
						    const Standard_Real prevPar,
						    const Standard_Real currPar,
						    const Standard_Boolean Is2d) 
{
  if( !myInitOK || 
     (! myForceProj && myPrecision < myMaxTolerance && BRep_Tool::SameParameter(myEdge))) {
    ShapeFix_TransferParameters::TransferRange(newEdge,prevPar,currPar,Is2d);
    return;
  }

  BRep_Builder B;
  Standard_Boolean samerange = Standard_True;
  ShapeBuild_Edge sbe;
  sbe.CopyRanges(newEdge,myEdge);
  gp_Pnt p1;
  gp_Pnt p2;
  Standard_Real alpha = 0, beta = 1;
  Standard_Real preci = Precision::PConfusion();
  Standard_Real firstPar, lastPar;
  if(prevPar < currPar) {
    firstPar = prevPar;
    lastPar  = currPar;
  }
  else {
    firstPar = currPar;
    lastPar  = prevPar;
  }
  if(Is2d) {
    p1 = myAC3d.Value(firstPar).Transformed(myLocation);
    p2 = myAC3d.Value(lastPar).Transformed(myLocation);
    Standard_Real fact = myAC3d.LastParameter() - myAC3d.FirstParameter();
    if( fact > Epsilon(myAC3d.LastParameter()) ) {
      alpha = ( firstPar - myAC3d.FirstParameter() ) / fact;
      beta  = ( lastPar - myAC3d.FirstParameter() ) / fact;
    }
  }
  else {
    p1 = myCurve->Value(firstPar);
    p2 = myCurve->Value(lastPar);
    Standard_Real fact = myLast - myFirst;
    if( fact > Epsilon(myLast) ) {
      alpha = ( firstPar - myFirst ) / fact;
      beta  = ( lastPar - myFirst ) / fact;
    }
  }
  const Standard_Boolean useLinearFirst = (alpha < preci);
  const Standard_Boolean useLinearLast = (1-beta < preci);
  TopLoc_Location EdgeLoc = myEdge.Location();
  ShapeAnalysis_Curve sac;
  gp_Pnt pproj;
  Standard_Real ppar1,ppar2;
  BRep_ListOfCurveRepresentation& tolist = (*((Handle(BRep_TEdge)*)&newEdge.TShape()))->ChangeCurves();
  Handle(BRep_GCurve) toGC;
  for (BRep_ListIteratorOfListOfCurveRepresentation toitcr (tolist); toitcr.More(); toitcr.Next()) {
    toGC = Handle(BRep_GCurve)::DownCast(toitcr.Value());
    if ( toGC.IsNull() ) continue;
    TopLoc_Location loc = ( EdgeLoc * toGC->Location() ).Inverted();
    if ( toGC->IsCurve3D() ) {
      if (!Is2d) {
	ppar1 = prevPar;
	ppar2 = currPar;
      } 
      else {
	Handle(Geom_Curve) C3d = toGC->Curve3D();
	if (C3d.IsNull()) continue;
	Standard_Real first = toGC->First();
	Standard_Real last  = toGC->Last();
	Standard_Real len = last -first;
	gp_Pnt ploc1 = p1.Transformed(loc);
	gp_Pnt ploc2 = p2.Transformed(loc);
	GeomAdaptor_Curve GAC(C3d,first,last);
	// CATIA bplseitli.model FAC1155 - Copy: protection for degenerated edges(3d case for symmetry)
	Standard_Real linFirst = first+alpha*len;
	Standard_Real linLast  = first+beta*len;
	Standard_Real dist1 = sac.NextProject(linFirst,GAC,ploc1,myPrecision,pproj,ppar1);
	Standard_Real dist2 = sac.NextProject(linLast,GAC,ploc2,myPrecision,pproj,ppar2);
	Standard_Boolean useLinear = Abs(ppar1-ppar2) < preci;
	
	gp_Pnt pos1 = C3d->Value ( linFirst );
	gp_Pnt pos2 = C3d->Value ( linLast );
	Standard_Real d01 = pos1.Distance ( ploc1 );
	Standard_Real d02 = pos2.Distance ( ploc2 );
	if ( useLinearFirst || useLinear || d01 <= dist1 || ( d01 < myPrecision && d01 <= 2 * dist1 ) ) 
	  ppar1 = linFirst;
	if ( useLinearLast || useLinear || d02 <= dist2 || ( d02 < myPrecision && d02 <= 2 * dist2 ) ) 
	  ppar2 = linLast;
      }
      if(ppar1 > ppar2) {
	Standard_Real tmpP = ppar2; ppar2 = ppar1; ppar1 = tmpP;
      }
      if(ppar2-ppar1 < preci) {
	if(ppar1-toGC->First() < preci)
	  ppar2+=2*preci;
	else if(toGC->Last()-ppar2 < preci)
	  ppar1-=2*preci;
	else {
	  ppar1 -= preci;
	  ppar2 += preci;
	}
      }
      toGC->SetRange ( ppar1, ppar2);
      if(ppar1!=prevPar || ppar1!=currPar)
	samerange = Standard_False;
      
    }
    else if (toGC->IsCurveOnSurface()) { //continue;  || 

      Standard_Boolean localLinearFirst = useLinearFirst;
      Standard_Boolean localLinearLast  = useLinearLast;
      Handle(Geom2d_Curve) C2d = toGC->PCurve();
      Standard_Real first = toGC->First();
      Standard_Real last  = toGC->Last();
      Standard_Real len = last -first;
      Handle(Geom2dAdaptor_HCurve) AC2d  = new Geom2dAdaptor_HCurve(toGC->PCurve(),first,last);
      Handle(GeomAdaptor_HSurface) AdS = new GeomAdaptor_HSurface( toGC->Surface());
      Adaptor3d_CurveOnSurface Ad1(AC2d,AdS);
      ShapeAnalysis_Curve sac1;
      
//      gp_Pnt p1 = Ad1.Value(prevPar);
//      gp_Pnt p2 = Ad1.Value(currPar);
      gp_Pnt ploc1 = p1.Transformed(loc);
      gp_Pnt ploc2 = p2.Transformed(loc);
      // CATIA bplseitli.model FAC1155 - Copy: protection for degenerated edges
      Standard_Real linFirst = first+alpha*len;
      Standard_Real linLast  = first+beta*len;
      Standard_Real dist1 = sac1.NextProject(linFirst, Ad1, ploc1, myPrecision,pproj,ppar1);
      Standard_Real dist2 = sac1.NextProject(linLast, Ad1, ploc2, myPrecision,pproj,ppar2);

      Standard_Boolean isFirstOnEnd = (ppar1-first)/len < Precision::PConfusion();
      Standard_Boolean isLastOnEnd = (last-ppar2)/len < Precision::PConfusion();
      Standard_Boolean useLinear = Abs(ppar1-ppar2) < Precision::PConfusion();
      if(isFirstOnEnd && ! localLinearFirst)
	localLinearFirst = Standard_True;
      if(isLastOnEnd && ! localLinearLast)
	localLinearLast  = Standard_True;

      gp_Pnt pos1 = Ad1.Value ( linFirst );
      gp_Pnt pos2 = Ad1.Value ( linLast );
      Standard_Real d01 = pos1.Distance ( ploc1 );
      Standard_Real d02 = pos2.Distance ( ploc2 );
      if ( localLinearFirst || useLinear || d01 <= dist1 || ( d01 < myPrecision && d01 <= 2 * dist1 ) ) 
	ppar1 = linFirst;
      if ( localLinearLast  || useLinear || d02 <= dist2 || ( d02 < myPrecision && d02 <= 2 * dist2 ) ) 
	ppar2 = linLast;

      if(ppar1 > ppar2) {
	Standard_Real tmpP = ppar2; ppar2 = ppar1; ppar1 = tmpP;
      }
      ppar1 = CorrectParameter(C2d,ppar1);
      ppar2 = CorrectParameter(C2d,ppar2);
      if(ppar2-ppar1 < preci) {
	if(ppar1-toGC->First() < preci)
	  ppar2+=2*preci;
	else if(toGC->Last()-ppar2 < preci)
	  ppar1-=2*preci;
	else {
	  ppar1 -= preci;
	  ppar2 += preci;
	}
      }
      toGC->SetRange ( ppar1, ppar2);
      if(ppar1!=prevPar || ppar1!=currPar)
	samerange = Standard_False;
    }
  }
  B.SameRange(newEdge, samerange);
}

//=======================================================================
//function : IsSameRange
//purpose  : 
//=======================================================================

Standard_Boolean ShapeFix_TransferParametersProj::IsSameRange() const
{

  if( !myInitOK || 
     (! myForceProj && myPrecision < myMaxTolerance && BRep_Tool::SameParameter(myEdge)))
    return ShapeFix_TransferParameters::IsSameRange();
  else
    return Standard_False;
}

//=======================================================================
//function : ForceProjection
//purpose  : 
//=======================================================================

Standard_Boolean& ShapeFix_TransferParametersProj::ForceProjection() 
{
  return myForceProj;
}


