// File:	ShapeUpgrade_ConvertCurve2dToBezier.cxx
// Created:	Thu May 13 14:06:07 1999
// Author:	data exchange team
//		<det@friendox.nnov.matra-dtv.fr>


#include <ShapeUpgrade_ConvertCurve2dToBezier.ixx>
#include <Precision.hxx>
#include <Geom2d_TrimmedCurve.hxx>
#include <Geom2d_BezierCurve.hxx>
#include <ShapeExtend.hxx>
#include <Geom2d_Line.hxx>
#include <TColgp_Array1OfPnt2d.hxx>
#include <Geom2d_BSplineCurve.hxx>
#include <Geom2d_Conic.hxx>
#include <Geom_Curve.hxx>
#include <Geom2dConvert_ApproxCurve.hxx>
#include <Geom2dConvert.hxx>
#include <Geom2dConvert_BSplineCurveToBezierCurve.hxx>
#include <TColStd_Array1OfReal.hxx>
#include <TColGeom2d_HArray1OfCurve.hxx>
#include <GeomTools.hxx>
#include <ShapeCustom_Curve2d.hxx>

ShapeUpgrade_ConvertCurve2dToBezier::ShapeUpgrade_ConvertCurve2dToBezier()
{
  mySegments = new TColGeom2d_HSequenceOfCurve;
  mySplitParams = new TColStd_HSequenceOfReal;
}

static Handle(Geom2d_BezierCurve) MakeBezier2d(const Handle(Geom2d_Curve)& theCurve2d,
                                               const Standard_Real theFirst,
                                               const Standard_Real theLast)
{
  TColgp_Array1OfPnt2d poles(1,2);
  poles(1) = theCurve2d->Value(theFirst);
  poles(2) = theCurve2d->Value(theLast);
  Handle(Geom2d_BezierCurve) bezier = new Geom2d_BezierCurve(poles);
  return bezier;
}


//=======================================================================
//function : Compute
//purpose  : 
//=======================================================================

void ShapeUpgrade_ConvertCurve2dToBezier::Compute()
{
  mySegments->Clear();
  mySplitParams->Clear();
  Standard_Real precision = Precision::PConfusion();
  Standard_Real First =  mySplitValues->Value(1);
  Standard_Real Last = mySplitValues->Value(mySplitValues->Length());
  
  // PTV Try to create line2d from myCurve
  if ( myCurve->IsKind(STANDARD_TYPE(Geom2d_BSplineCurve)) ||
       myCurve->IsKind(STANDARD_TYPE(Geom2d_BezierCurve)) )
  {
    // static function`s code getted from ShapeConvert
    Standard_Real tmpF, tmpL, aDeviation;
    Handle(Geom2d_Line) aTmpLine2d = 
      ShapeCustom_Curve2d::ConvertToLine2d(myCurve, First, Last, Precision::Approximation(),
                                               tmpF, tmpL, aDeviation);
    if (!aTmpLine2d.IsNull() && (aDeviation <= Precision::Approximation()) )
    {
      Handle(Geom2d_BezierCurve) bezier = MakeBezier2d(aTmpLine2d, tmpF, tmpL);
      mySegments->Append(bezier);
      mySplitParams->Append(First);
      mySplitParams->Append(Last);
      myNbCurves = mySplitValues->Length()-1;
      myStatus = ShapeExtend::EncodeStatus (ShapeExtend_DONE1);
      return;
    }
  }
  
  if(myCurve->IsKind(STANDARD_TYPE(Geom2d_TrimmedCurve))) {
    Handle(Geom2d_TrimmedCurve) tmp = Handle(Geom2d_TrimmedCurve)::DownCast (myCurve);
    Handle(Geom2d_Curve) BasCurve = tmp->BasisCurve();
    ShapeUpgrade_ConvertCurve2dToBezier converter;
    //converter.Init(BasCurve,Max(First,BasCurve->FirstParameter()),Min(Last,BasCurve->LastParameter())); //???
    converter.Init(BasCurve,First,Last);
    converter.SetSplitValues(mySplitValues);
    converter.Compute();
    mySplitValues->Clear();
    mySplitValues->ChangeSequence() = converter.SplitValues()->Sequence();
    myNbCurves = mySplitValues->Length()-1;
    myStatus |= converter.myStatus;
    mySegments->ChangeSequence() = converter.Segments()->Sequence();
    mySplitParams->ChangeSequence() = converter.SplitParams()->Sequence();
    return;
  }
  else if(myCurve->IsKind(STANDARD_TYPE(Geom2d_BezierCurve))) {
    Handle(Geom2d_BezierCurve) bezier = Handle(Geom2d_BezierCurve)::DownCast (myCurve);
    myNbCurves = mySplitValues->Length()-1;
    mySplitParams->Append(First);
    mySplitParams->Append(Last);
    if(First < precision && Last > 1 - precision) {
      mySegments->Append(bezier);
      myStatus = ShapeExtend::EncodeStatus (ShapeExtend_OK);
    } else {
      Handle(Geom2d_BezierCurve) besNew = Handle(Geom2d_BezierCurve)::DownCast(bezier->Copy());
      besNew->Segment(First,Last);
      mySegments->Append(besNew);
      myStatus = ShapeExtend::EncodeStatus (ShapeExtend_DONE2);
    }
    return;
  }
  else if(myCurve->IsKind(STANDARD_TYPE(Geom2d_Line))) {
    Handle(Geom2d_Line) aLine2d = Handle(Geom2d_Line)::DownCast(myCurve);
    Handle(Geom2d_BezierCurve) bezier = MakeBezier2d(aLine2d, First, Last);    
    mySegments->Append(bezier);
    mySplitParams->Append(First);
    mySplitParams->Append(Last);
    myNbCurves = mySplitValues->Length()-1;
    myStatus = ShapeExtend::EncodeStatus (ShapeExtend_DONE1);
    return;
  }
  else {
    Handle(Geom2d_BSplineCurve) aBSpline2d;
    Standard_Real Shift = 0.;
    if(myCurve->IsKind(STANDARD_TYPE(Geom2d_Conic))) {
      Handle(Geom2d_TrimmedCurve) tcurve = new Geom2d_TrimmedCurve(myCurve,First,Last); //protection agains parabols ets
      Geom2dConvert_ApproxCurve approx (tcurve, Precision::Approximation(), 
					GeomAbs_C1, 100, 6 );
      if ( approx.HasResult() )
	aBSpline2d = Handle(Geom2d_BSplineCurve)::DownCast(approx.Curve());
      else 
	aBSpline2d = Geom2dConvert::CurveToBSplineCurve(tcurve,Convert_QuasiAngular);
      
      Shift = First - aBSpline2d->FirstParameter();
      First = aBSpline2d->FirstParameter();
      Last = aBSpline2d->LastParameter();
    }
    else if(!myCurve->IsKind(STANDARD_TYPE(Geom2d_BSplineCurve))) {
      aBSpline2d = Geom2dConvert::CurveToBSplineCurve(myCurve,Convert_QuasiAngular);
    }
    else
      aBSpline2d = Handle(Geom2d_BSplineCurve)::DownCast(myCurve);
    
    Standard_Real bf = aBSpline2d->FirstParameter();
    Standard_Real bl = aBSpline2d->LastParameter(); 
    if(Abs(First-bf) < precision)
      First = bf;
    if(Abs(Last-bl) < precision)
      Last = bl;
    if(First < bf){
#ifdef DEB
      cout <<"Warning: The range of the edge exceeds the pcurve domain" <<endl;
#endif
      First = bf;
      mySplitValues->SetValue(1,First);
    }
    if(Last > bl){
#ifdef DEB
      cout <<"Warning: The range of the edge exceeds the pcurve domain" <<endl;
#endif
      Last = bl;
      mySplitValues->SetValue(mySplitValues->Length(),Last);
    }

    // PTV 20.12.2001 Try to simpify BSpline Curve
    ShapeCustom_Curve2d::SimplifyBSpline2d (aBSpline2d, Precision::Approximation());
    
    Geom2dConvert_BSplineCurveToBezierCurve tool(aBSpline2d,First,Last,precision);
    Standard_Integer nbArcs = tool.NbArcs();
    TColStd_Array1OfReal knots(1,nbArcs+1);
    tool.Knots(knots);
    mySplitParams->Append(First+Shift);
    Standard_Integer j; // svv Jan 10 2000 : porting on DEC
    
    Standard_Real newFirst = First+Shift;
    Standard_Real newLast = First+Shift;
    
    for(j = 1; j <=nbArcs; j++) {
      Standard_Real nextKnot = knots(j+1)+Shift;
      if(nextKnot - mySplitParams->Value(mySplitParams->Length()) > precision) {
        Handle(Geom2d_Curve) aCrv2d = tool.Arc(j);
        if ( aCrv2d->IsKind(STANDARD_TYPE(Geom2d_BezierCurve)) )
        {
          newFirst = newLast;
          newLast = nextKnot;
          Standard_Real tmpF, tmpL, aDeviation;
          Handle(Geom2d_Line) aTmpLine2d = 
	    ShapeCustom_Curve2d::ConvertToLine2d(aCrv2d, newFirst, newLast, Precision::Approximation(),
                                                     tmpF, tmpL, aDeviation);
          if (!aTmpLine2d.IsNull() && (aDeviation <= Precision::Approximation()) )
          {
            Handle(Geom2d_BezierCurve) bezier = MakeBezier2d(aBSpline2d, newFirst, newLast);
            mySegments->Append(bezier);
            mySplitParams->Append(newLast);
            continue;
          }
        }
        mySegments->Append(aCrv2d);
        mySplitParams->Append(nextKnot);
      }
    }
    
    First = mySplitValues->Value(1);
    for(j = 2; j <= mySplitValues->Length(); j++) {
      Last =  mySplitValues->Value(j);
      for(Standard_Integer i = 2; i <= nbArcs+1; i++) {
	Standard_Real valknot = knots(i)+Shift;
	if(valknot <= First + precision) continue;
	if(valknot >= Last - precision) break;
	mySplitValues->InsertBefore(j++,valknot);
      }
      First = Last;
    }
    myNbCurves = mySplitValues->Length()-1;
  }
  myStatus = ShapeExtend::EncodeStatus (ShapeExtend_DONE1);
}

//=======================================================================
//function : Build
//purpose  : 
//=======================================================================

void ShapeUpgrade_ConvertCurve2dToBezier::Build(const Standard_Boolean /*Segment*/)
{
  Standard_Real prec = Precision::PConfusion();
  Standard_Integer nb = mySplitValues->Length();
  myResultingCurves =  new TColGeom2d_HArray1OfCurve (1,nb-1);
  Standard_Real prevPar =0.;
  Standard_Integer j=2;
  for(Standard_Integer i = 2; i <= nb; i++) {
    Standard_Real par = mySplitValues->Value(i);
    for(; j<= mySplitParams->Length(); j++) 
      if(mySplitParams->Value(j)+prec > par)
	break;
      else
	prevPar = 0.;

    Handle(Geom2d_BezierCurve) bes = Handle(Geom2d_BezierCurve)
      ::DownCast(mySegments->Value(j-1)->Copy());
    Standard_Real uFact = mySplitParams->Value(j) - mySplitParams->Value(j-1);
    Standard_Real pp = mySplitValues->Value(i-1);
    Standard_Real length = (par - pp)/uFact;
    bes->Segment(prevPar, prevPar+length);
    prevPar += length;
    myResultingCurves->SetValue(i-1,bes);
  }
}

//=======================================================================
//function : Segments
//purpose  : 
//=======================================================================

Handle(TColGeom2d_HSequenceOfCurve) ShapeUpgrade_ConvertCurve2dToBezier::Segments() const
{
  return mySegments;
}

//=======================================================================
//function : SplitParams
//purpose  : 
//=======================================================================

Handle(TColStd_HSequenceOfReal) ShapeUpgrade_ConvertCurve2dToBezier::SplitParams() const
{
  return mySplitParams;
}
