// Created on: 1996-09-23
// Created by: Philippe MANGIN
// Copyright (c) 1996-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.

// Modified:	Fri Jul 10 11:23:35 1998
//              JCT : Add WithRatio,MinM

#include <Geom_BoundedCurve.hxx>
#include <Geom_BSplineCurve.hxx>
#include <GeomConvert.hxx>
#include <GeomConvert_CompCurveToBSplineCurve.hxx>
#include <gp_Pnt.hxx>
#include <gp_Vec.hxx>
#include <Precision.hxx>
#include <NCollection_Array1.hxx>
#include <Standard_Integer.hxx>

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

GeomConvert_CompCurveToBSplineCurve::GeomConvert_CompCurveToBSplineCurve(
  const Convert_ParameterisationType theParameterisation)
    : myTol(Precision::Confusion()),
      myType(theParameterisation)
{
}

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

GeomConvert_CompCurveToBSplineCurve::GeomConvert_CompCurveToBSplineCurve(
  const occ::handle<Geom_BoundedCurve>& BasisCurve,
  const Convert_ParameterisationType    Parameterisation)
    : myTol(Precision::Confusion()),
      myType(Parameterisation)
{
  occ::handle<Geom_BSplineCurve> Bs = occ::down_cast<Geom_BSplineCurve>(BasisCurve);
  if (!Bs.IsNull())
  {
    myCurve = occ::down_cast<Geom_BSplineCurve>(BasisCurve->Copy());
  }
  else
  {
    myCurve = GeomConvert::CurveToBSplineCurve(BasisCurve, myType);
  }
}

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

bool GeomConvert_CompCurveToBSplineCurve::Add(const occ::handle<Geom_BoundedCurve>& NewCurve,
                                              const double                          Tolerance,
                                              const bool                            After,
                                              const bool                            WithRatio,
                                              const int                             MinM)
{
  // conversion
  occ::handle<Geom_BSplineCurve> Bs = occ::down_cast<Geom_BSplineCurve>(NewCurve);
  if (!Bs.IsNull())
  {
    Bs = occ::down_cast<Geom_BSplineCurve>(NewCurve->Copy());
  }
  else
  {
    Bs = GeomConvert::CurveToBSplineCurve(NewCurve, myType);
  }
  if (myCurve.IsNull())
  {
    myCurve = Bs;
    return true;
  }

  bool avant, apres;
  myTol = Tolerance;

  // Use actual curve endpoints instead of poles for proper G0 continuity check.
  // G0 continuity (positional continuity) requires curves to share endpoints.
  // For non-clamped or periodic B-splines, first/last poles may not coincide with endpoints.
  const gp_Pnt aCurveStart = myCurve->StartPoint();
  const gp_Pnt aCurveEnd   = myCurve->EndPoint();
  const gp_Pnt aBsStart    = Bs->StartPoint();
  const gp_Pnt aBsEnd      = Bs->EndPoint();

  avant = ((aCurveStart.Distance(aBsStart) < myTol) || (aCurveStart.Distance(aBsEnd) < myTol));
  apres = ((aCurveEnd.Distance(aBsStart) < myTol) || (aCurveEnd.Distance(aBsEnd) < myTol));

  // Will myCurve be (or become) closed?
  if (avant && apres)
  { // Resolve the ambiguity
    if (After)
    {
      avant = false;
    }
    else
    {
      apres = false;
    }
  }

  // Append after?
  if (apres)
  {
    if (aCurveEnd.Distance(aBsEnd) < myTol)
    {
      Bs->Reverse();
    }
    Add(myCurve, Bs, true, WithRatio, MinM);
    return true;
  }
  // Prepend before?
  else if (avant)
  {
    if (aCurveStart.Distance(aBsStart) < myTol)
    {
      Bs->Reverse();
    }
    Add(Bs, myCurve, false, WithRatio, MinM);
    return true;
  }

  return false;
}

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

void GeomConvert_CompCurveToBSplineCurve::Add(occ::handle<Geom_BSplineCurve>& FirstCurve,
                                              occ::handle<Geom_BSplineCurve>& SecondCurve,
                                              const bool                      After,
                                              const bool                      WithRatio,
                                              const int                       MinM)
{
  // Harmonize the degrees.
  int Deg = std::max(FirstCurve->Degree(), SecondCurve->Degree());
  if (FirstCurve->Degree() < Deg)
  {
    FirstCurve->IncreaseDegree(Deg);
  }
  if (SecondCurve->Degree() < Deg)
  {
    SecondCurve->IncreaseDegree(Deg);
  }

  // Declarations
  double                     L1, L2;
  int                        ii, jj;
  double                     Ratio = 1, Ratio1, Ratio2, Delta1, Delta2;
  int                        NbP1 = FirstCurve->NbPoles(), NbP2 = SecondCurve->NbPoles();
  int                        NbK1 = FirstCurve->NbKnots(), NbK2 = SecondCurve->NbKnots();
  NCollection_Array1<double> Noeuds(1, NbK1 + NbK2 - 1);
  NCollection_Array1<gp_Pnt> Poles(1, NbP1 + NbP2 - 1);
  NCollection_Array1<double> Poids(1, NbP1 + NbP2 - 1);
  NCollection_Array1<int>    Mults(1, NbK1 + NbK2 - 1);

  // Reparameterization ratio (C1 if possible)
  if (WithRatio)
  {
    L1 = FirstCurve->DN(FirstCurve->LastParameter(), 1).Magnitude();
    L2 = SecondCurve->DN(SecondCurve->FirstParameter(), 1).Magnitude();

    if ((L1 > Precision::Confusion()) && (L2 > Precision::Confusion()))
    {
      Ratio = L1 / L2;
    }
    if ((Ratio < Precision::Confusion()) || (Ratio > 1 / Precision::Confusion()))
    {
      Ratio = 1;
    }
  }

  if (After)
  {
    // Do not move the first curve
    Ratio1 = 1;
    Delta1 = 0;
    Ratio2 = 1 / Ratio;
    Delta2 = Ratio2 * SecondCurve->Knot(1) - FirstCurve->Knot(NbK1);
  }
  else
  {
    // Do not move the second curve
    Ratio1 = Ratio;
    Delta1 = Ratio1 * FirstCurve->Knot(NbK1) - SecondCurve->Knot(1);
    Ratio2 = 1;
    Delta2 = 0;
  }

  // The Knots
  double eps;
  for (ii = 1; ii <= NbK1; ii++)
  {
    Noeuds(ii) = Ratio1 * FirstCurve->Knot(ii) - Delta1;
    if (ii > 1)
    {
      eps = Epsilon(std::abs(Noeuds(ii - 1)));
      if (eps < 5.e-10)
      {
        eps = 5.e-10;
      }
      if (Noeuds(ii) - Noeuds(ii - 1) <= eps)
      {
        Noeuds(ii) += eps;
      }
    }
    Mults(ii) = FirstCurve->Multiplicity(ii);
  }
  Mults(NbK1) = FirstCurve->Degree();
  for (ii = 2, jj = NbK1 + 1; ii <= NbK2; ii++, jj++)
  {
    Noeuds(jj) = Ratio2 * SecondCurve->Knot(ii) - Delta2;
    eps        = Epsilon(std::abs(Noeuds(jj - 1)));
    if (eps < 5.e-10)
    {
      eps = 5.e-10;
    }
    if (Noeuds(jj) - Noeuds(jj - 1) <= eps)
    {
      Noeuds(jj) += eps;
    }
    Mults(jj) = SecondCurve->Multiplicity(ii);
  }

  Ratio = FirstCurve->Weight(NbP1);
  Ratio /= SecondCurve->Weight(1);
  // The Poles and Weights
  for (ii = 1; ii < NbP1; ii++)
  {
    Poles(ii) = FirstCurve->Pole(ii);
    Poids(ii) = FirstCurve->Weight(ii);
  }
  for (ii = 1, jj = NbP1; ii <= NbP2; ii++, jj++)
  {
    Poles(jj) = SecondCurve->Pole(ii);
    //
    // Note: the weights may not necessarily connect with C0 continuity,
    // hence the use of Ratio
    //
    Poids(jj) = Ratio * SecondCurve->Weight(ii);
  }

  // Create the BSpline
  myCurve = new (Geom_BSplineCurve)(Poles, Poids, Noeuds, Mults, Deg);

  // Optionally reduce multiplicity down to MinM
  bool Ok = true;
  int  M  = Mults(NbK1);
  while ((M > MinM) && Ok)
  {
    M--;
    Ok = myCurve->RemoveKnot(NbK1, M, myTol);
  }
}

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

occ::handle<Geom_BSplineCurve> GeomConvert_CompCurveToBSplineCurve::BSplineCurve() const
{
  return myCurve;
}

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

void GeomConvert_CompCurveToBSplineCurve::Clear()
{
  myCurve.Nullify();
}
