// Created on: 1996-06-07
// 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.

#include <Geom_BezierSurface.hxx>
#include <Geom_BSplineSurface.hxx>
#include <Geom_Curve.hxx>
#include <GeomConvert_CompBezierSurfacesToBSplineSurface.hxx>
#include <gp_Pnt.hxx>
#include <gp_Vec.hxx>
#include <gp_XYZ.hxx>
#include <Precision.hxx>
#include <Standard_ConstructionError.hxx>
#include <Standard_NotImplemented.hxx>
#include <TColGeom_Array2OfBezierSurface.hxx>
#include <TColgp_HArray2OfPnt.hxx>
#include <TColStd_HArray1OfReal.hxx>

// ============================================================================
GeomConvert_CompBezierSurfacesToBSplineSurface::GeomConvert_CompBezierSurfacesToBSplineSurface(
  const TColGeom_Array2OfBezierSurface& Beziers)
// ============================================================================
{
  Standard_Integer ii;
  myDone = Standard_True;
  // Choix des noeuds
  myUKnots = new (TColStd_HArray1OfReal)(1, Beziers.ColLength() + 1);
  for (ii = 0; ii < myUKnots->Length(); ii++)
  {
    myUKnots->SetValue(ii + 1, ii);
  }

  myVKnots = new (TColStd_HArray1OfReal)(1, Beziers.RowLength() + 1);
  for (ii = 0; ii < myVKnots->Length(); ii++)
  {
    myVKnots->SetValue(ii + 1, ii);
  }

  // Calcul des Poles
  Perform(Beziers);
}

// ============================================================================
GeomConvert_CompBezierSurfacesToBSplineSurface::GeomConvert_CompBezierSurfacesToBSplineSurface(
  const TColGeom_Array2OfBezierSurface& Beziers,
  const Standard_Real                   Tolerance,
  const Standard_Boolean                RemoveKnots)
// ============================================================================
{
  Standard_Integer   ii, jj, multU = 0, multV, minus;
  Standard_Boolean   Ok;
  gp_Vec             vec;
  Standard_Real      V1, V2, V3, Ratio, L1, L2, Tol, val;
  gp_Pnt             P1, P2, P3;
  Handle(Geom_Curve) FirstCurve, SecondCurve;

  myDone = Standard_True;

  // Choix des noeuds

  myUKnots = new (TColStd_HArray1OfReal)(1, Beziers.ColLength() + 1);
  myVKnots = new (TColStd_HArray1OfReal)(1, Beziers.RowLength() + 1);

  // --> en U
  myUKnots->SetValue(1, 0);
  jj         = myVKnots->Length() / 2;
  FirstCurve = Beziers(1, jj)->VIso(0.3);
  FirstCurve->D0(0, P1);
  FirstCurve->D0(0.5, P2);
  FirstCurve->D1(1, P3, vec);

  L1 = P1.Distance(P2) + P2.Distance(P3);
  myUKnots->SetValue(2, L1);

  V1 = vec.Magnitude();
  // si la Parametrisation est trop bizzare on garde la pseudo-longueur
  if ((V1 > 1000 * L1) || (V1 < L1 * 1.e-3))
    V1 = L1;

  for (ii = 2; ii < myUKnots->Length(); ii++)
  {

    SecondCurve = Beziers(ii, jj)->VIso(0.3);
    SecondCurve->D1(0, P1, vec);
    V2 = vec.Magnitude();
    SecondCurve->D0(0.5, P2);
    SecondCurve->D1(1, P3, vec);
    V3 = vec.Magnitude();
    L2 = P1.Distance(P2) + P2.Distance(P3);

    // On calcul le ratio, en evitant les cas tordus...
    if ((V2 > 1000 * L2) || (V2 < L2 * 1.e-3))
      V2 = L2;
    if ((V3 > 1000 * L2) || (V3 < L2 * 1.e-3))
      V3 = L2;

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

    // On en deduit un  nouveau noeud
    val = myUKnots->Value(ii);
    myUKnots->SetValue(ii + 1, val + Ratio * (val - myUKnots->Value(ii - 1)));

    // Et c'est reparti, pour un tour
    L1         = L2;
    V1         = V3;
    FirstCurve = SecondCurve;
  }

  // --> en V
  myVKnots->SetValue(1, 0);
  ii         = myUKnots->Length() / 2;
  FirstCurve = Beziers(ii, 1)->UIso(0.3);
  FirstCurve->D0(0, P1);
  FirstCurve->D0(0.5, P2);
  FirstCurve->D1(1, P3, vec);

  L1 = P1.Distance(P2) + P2.Distance(P3);
  myVKnots->SetValue(2, L1);

  V1 = vec.Magnitude();
  // si la Parametrisation est trop bizzare on garde la pseudo-longueur
  if ((V1 > 1000 * L1) || (V1 < L1 * 1.e-3))
    V1 = L1;

  for (jj = 2; jj < myVKnots->Length(); jj++)
  {

    SecondCurve = Beziers(ii, jj)->UIso(0.3);
    SecondCurve->D1(0, P1, vec);
    V2 = vec.Magnitude();
    SecondCurve->D0(0.5, P2);
    SecondCurve->D1(1, P3, vec);
    V3 = vec.Magnitude();
    L2 = P1.Distance(P2) + P2.Distance(P3);

    // On calcul le ratio, en evitant les cas tordus...
    if ((V2 > 1000 * L2) || (V2 < L2 * 1.e-3))
      V2 = L2;
    if ((V3 > 1000 * L2) || (V3 < L2 * 1.e-3))
      V3 = L2;

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

    // On en deduit un  nouveau noeud
    val = myVKnots->Value(jj);
    myVKnots->SetValue(jj + 1, val + Ratio * (val - myVKnots->Value(jj - 1)));

    // Et c'est reparti, pour un tour
    L1         = L2;
    V1         = V3;
    FirstCurve = SecondCurve;
  }

  // Calcul des Poles
  Perform(Beziers);

  // Reduction de la multiplicite
  Handle(Geom_BSplineSurface) Surface = new (Geom_BSplineSurface)(myPoles->Array2(),
                                                                  myUKnots->Array1(),
                                                                  myVKnots->Array1(),
                                                                  myUMults->Array1(),
                                                                  myVMults->Array1(),
                                                                  myUDegree,
                                                                  myVDegree);

  if (RemoveKnots)
    minus = 0;
  else
    minus = 1;

  for (ii = myUKnots->Length() - 1; ii > 1; ii--)
  {
    Ok    = Standard_True;
    Tol   = Tolerance / 2;
    multU = myUMults->Value(ii) - 1;
    for (; Ok && multU > minus; multU--, Tol /= 2)
    {
      Ok = Surface->RemoveUKnot(ii, multU, Tol);
    }
  }

  for (ii = myVKnots->Length() - 1; ii > 1; ii--)
  {
    Ok    = Standard_True;
    Tol   = Tolerance / 2;
    multV = myVMults->Value(ii) - 1;
    for (; Ok && multU > minus; multV--, Tol /= 2)
    {
      Ok = Surface->RemoveVKnot(ii, multV, Tol);
    }
  }

  // Les nouveaux champs sont arrivees ....
  myPoles = new (TColgp_HArray2OfPnt)(1, Surface->NbUPoles(), 1, Surface->NbVPoles());
  Surface->Poles(myPoles->ChangeArray2());

  myUMults = new (TColStd_HArray1OfInteger)(1, Surface->NbUKnots());
  myVMults = new (TColStd_HArray1OfInteger)(1, Surface->NbVKnots());
  myUKnots = new (TColStd_HArray1OfReal)(1, Surface->NbUKnots());
  myVKnots = new (TColStd_HArray1OfReal)(1, Surface->NbVKnots());

  Surface->UMultiplicities(myUMults->ChangeArray1());
  Surface->VMultiplicities(myVMults->ChangeArray1());
  Surface->UKnots(myUKnots->ChangeArray1());
  Surface->VKnots(myVKnots->ChangeArray1());
}

// ============================================================================
GeomConvert_CompBezierSurfacesToBSplineSurface::GeomConvert_CompBezierSurfacesToBSplineSurface(
  const TColGeom_Array2OfBezierSurface& Beziers,
  const TColStd_Array1OfReal&           UKnots,
  const TColStd_Array1OfReal&           VKnots,
  const GeomAbs_Shape                   UContinuity,
  const GeomAbs_Shape                   VContinuity,
  const Standard_Real                   Tolerance)
// ============================================================================
{
  Standard_Integer decu = 0, decv = 0;
  Standard_Boolean Ok;

  myDone = Standard_True;

  // Recuperation des noeuds
  myUKnots                 = new (TColStd_HArray1OfReal)(1, Beziers.ColLength() + 1);
  myUKnots->ChangeArray1() = UKnots;

  myVKnots                 = new (TColStd_HArray1OfReal)(1, Beziers.RowLength() + 1);
  myVKnots->ChangeArray1() = VKnots;

  // Calcul des Poles
  Perform(Beziers);

  // Obtention des bonne continuitee

  switch (UContinuity)
  {
    case GeomAbs_C0:
      decu = 0;
      break;
    case GeomAbs_C1:
      decu = 1;
      break;
    case GeomAbs_C2:
      decu = 2;
      break;
    case GeomAbs_C3:
      decu = 3;
      break;
    default:
      throw Standard_ConstructionError("GeomConvert_CompBezierSurfacesToBSpl:: UContinuity error");
  }

  switch (VContinuity)
  {
    case GeomAbs_C0:
      decv = 0;
      break;
    case GeomAbs_C1:
      decv = 1;
      break;
    case GeomAbs_C2:
      decv = 2;
      break;
    case GeomAbs_C3:
      decv = 3;
      break;
    default:
      throw Standard_ConstructionError("GeomConvert_CompBezierSurfacesToBSpl:: VContinuity error");
  }

  if ((decu > 0) || (decv > 0))
  {

    Standard_Integer ii;
    Standard_Integer multU = myUDegree - decu;
    Standard_ConstructionError_Raise_if(
      ((multU <= 0) && (myUKnots->Length() > 2)),
      "GeomConvert_CompBezierSurfacesToBSpl:: UContinuity or Udeg error");

    Standard_Integer multV = myVDegree - decv;
    Standard_ConstructionError_Raise_if(
      ((multV <= 0) && (myVKnots->Length() > 2)),
      "GeomConvert_CompBezierSurfacesToBSpl:: VContinuity or Vdeg error");

    Handle(Geom_BSplineSurface) Surface = new (Geom_BSplineSurface)(myPoles->Array2(),
                                                                    myUKnots->Array1(),
                                                                    myVKnots->Array1(),
                                                                    myUMults->Array1(),
                                                                    myVMults->Array1(),
                                                                    myUDegree,
                                                                    myVDegree);

    if (decu > 0)
    {
      for (ii = 2; ii < myUKnots->Length(); ii++)
      {
        Ok = Surface->RemoveUKnot(ii, multU, Tolerance);
        if (!Ok)
        {
          myDone = Ok;
        }
      }
    }

    if (decv > 0)
    {
      for (ii = 2; ii < myVKnots->Length(); ii++)
      {
        Ok = Surface->RemoveVKnot(ii, multV, Tolerance);
        if (!Ok)
        {
          myDone = Ok;
        }
      }
    }

    // Les nouveaux champs sont arrivees ....
    myPoles = new (TColgp_HArray2OfPnt)(1, Surface->NbUPoles(), 1, Surface->NbVPoles());
    Surface->Poles(myPoles->ChangeArray2());
    Surface->UMultiplicities(myUMults->ChangeArray1());
    Surface->VMultiplicities(myVMults->ChangeArray1());
  }
}

// ================================================================================
void GeomConvert_CompBezierSurfacesToBSplineSurface::Perform(
  const TColGeom_Array2OfBezierSurface& Beziers)
// ================================================================================
{
  Standard_Integer IU, IV;

  // (1) Determination des degrees et si le resultat est rationnel.
  isrational = Standard_False;
  myUDegree  = 1;
  myVDegree  = 1;

  for (IU = Beziers.LowerRow(); IU <= Beziers.UpperRow(); IU++)
  {
    for (IV = Beziers.LowerCol(); IV <= Beziers.UpperCol(); IV++)
    {
      if (Beziers(IU, IV)->IsURational() || Beziers(IU, IV)->IsVRational())
      {
        isrational = Standard_True;
      }

      myUDegree = (Beziers(IU, IV)->UDegree() > myUDegree) ? Beziers(IU, IV)->UDegree() : myUDegree;

      myVDegree = (Beziers(IU, IV)->VDegree() > myVDegree) ? Beziers(IU, IV)->VDegree() : myVDegree;
    }
  }

  Standard_NotImplemented_Raise_if(isrational, "CompBezierSurfacesToBSpl : rational !");

  // (2) Boucle sur les carreaux  -----------------------------

  Handle(Geom_BezierSurface) Patch;
  Standard_Integer           UIndex, VIndex, uindex, vindex, udeb, vdeb;
  Standard_Integer           upol, vpol, ii;

  myPoles =
    new (TColgp_HArray2OfPnt)(1,
                              (myUDegree + 1) * Beziers.ColLength() - myUKnots->Length() + 2,
                              1,
                              (myVDegree + 1) * Beziers.RowLength() - myVKnots->Length() + 2);

  for (IU = Beziers.LowerRow(); IU <= Beziers.UpperRow(); IU++)
  {
    UIndex = (IU - 1) * myUDegree + 1;
    for (IV = Beziers.LowerCol(); IV <= Beziers.UpperCol(); IV++)
    {

      Patch  = Beziers(IU, IV);
      VIndex = (IV - 1) * myVDegree + 1;

      // (2.1) Augmentation du degree
      Patch->Increase(myUDegree, myVDegree);

      // (2.2) Poles a recopier
      if (IU == 1)
      {
        udeb = 1;
      }
      else
      {
        udeb = 2;
      }
      if (IV == 1)
      {
        vdeb = 1;
      }
      else
      {
        vdeb = 2;
      }

      uindex = UIndex + udeb - 1;

      for (upol = udeb; upol <= myUDegree + 1; upol++, uindex++)
      {
        vindex = VIndex + vdeb - 1;
        for (vpol = vdeb; vpol <= myVDegree + 1; vpol++, vindex++)
        {
          myPoles->ChangeValue(uindex, vindex) = Patch->Pole(upol, vpol);
        }
      }

      // (2.3) Poles a sommer
      if (udeb == 2)
      {
        vindex = VIndex + vdeb - 1;
        for (vpol = vdeb; vpol <= myVDegree + 1; vpol++, vindex++)
        {
          myPoles->ChangeValue(UIndex, vindex).ChangeCoord() += Patch->Pole(1, vpol).Coord();
        }
      }

      if (vdeb == 2)
      {
        uindex = UIndex + udeb - 1;
        for (upol = udeb; upol <= myUDegree + 1; upol++, uindex++)
        {
          myPoles->ChangeValue(uindex, VIndex).ChangeCoord() += Patch->Pole(upol, 1).Coord();
        }
      }

      if (udeb == 2 && vdeb == 2)
      {
        myPoles->ChangeValue(UIndex, VIndex).ChangeCoord() += Patch->Pole(1, 1).Coord();
      }
    }
  }

  // (3) Elimination des redondances
  // car dans la boucle precedente on compte :
  // - 2 fois les poles associes aux noeuds simples
  // - 4 fois les poles associes aux doubles noeuds (en U et V)

  // (3.1) Elimination en U
  for (UIndex = myUDegree + 1, ii = 2; ii < myUKnots->Length(); ii++, UIndex += myUDegree)
  {
    for (vpol = 1; vpol <= myPoles->UpperCol(); vpol++)
    {
      myPoles->ChangeValue(UIndex, vpol).ChangeCoord() *= 0.5;
    }
  }

  // (3.2) Elimination en V
  for (VIndex = myVDegree + 1, ii = 2; ii < myVKnots->Length(); ii++, VIndex += myVDegree)
  {
    for (upol = 1; upol <= myPoles->UpperRow(); upol++)
    {
      myPoles->ChangeValue(upol, VIndex).ChangeCoord() *= 0.5;
    }
  }

  // (4) Init des multiplicites
  myUMults = new (TColStd_HArray1OfInteger)(1, myUKnots->Length());
  myUMults->Init(myUDegree);
  myUMults->SetValue(1, myUDegree + 1);
  myUMults->SetValue(myUMults->Upper(), myUDegree + 1);

  myVMults = new (TColStd_HArray1OfInteger)(1, myVKnots->Length());
  myVMults->Init(myVDegree);
  myVMults->SetValue(1, myVDegree + 1);
  myVMults->SetValue(myVMults->Upper(), myVDegree + 1);
}

// ========================================================================
Standard_Boolean GeomConvert_CompBezierSurfacesToBSplineSurface::IsDone() const
// ========================================================================
{
  return myDone;
}
