// 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.

// abv 06.01.99 fix of misprint
//: p6 abv 26.02.99: make ConvertToPeriodic() return Null if nothing done

#include <ShapeCustom_Surface.hxx>

#include <ElSLib.hxx>
#include <Geom_BezierSurface.hxx>
#include <Geom_BSplineSurface.hxx>
#include <Geom_ConicalSurface.hxx>
#include <Geom_Curve.hxx>
#include <Geom_CylindricalSurface.hxx>
#include <Geom_Plane.hxx>
#include <Geom_SphericalSurface.hxx>
#include <Geom_Surface.hxx>
#include <Geom_ToroidalSurface.hxx>
#include <gp_Ax3.hxx>
#include <gp_Cylinder.hxx>
#include <gp_Pln.hxx>
#include <gp_Pnt.hxx>
#include <gp_Vec.hxx>
#include <ShapeAnalysis_Geom.hxx>
#include <ShapeAnalysis_Surface.hxx>
#include <NCollection_Array1.hxx>
#include <NCollection_Array2.hxx>
#include <Standard_Integer.hxx>

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

ShapeCustom_Surface::ShapeCustom_Surface()
    : myGap(0)
{
}

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

ShapeCustom_Surface::ShapeCustom_Surface(const occ::handle<Geom_Surface>& S)
    : myGap(0)
{
  Init(S);
}

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

void ShapeCustom_Surface::Init(const occ::handle<Geom_Surface>& S)
{
  mySurf = S;
}

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

occ::handle<Geom_Surface> ShapeCustom_Surface::ConvertToAnalytical(const double tol,
                                                                   const bool   substitute)
{
  occ::handle<Geom_Surface> newSurf;

  int                     nUP, nVP, nCP, i, j, UDeg, VDeg;
  double                  U1, U2, V1, V2, C1, C2, DU, DV, U = 0, V = 0;
  occ::handle<Geom_Curve> iso;
  bool                    uClosed = true;

  // seuls cas traites : BSpline et Bezier
  occ::handle<Geom_BSplineSurface> theBSplneS = occ::down_cast<Geom_BSplineSurface>(mySurf);
  if (theBSplneS.IsNull())
  {
    occ::handle<Geom_BezierSurface> theBezierS = occ::down_cast<Geom_BezierSurface>(mySurf);
    if (!theBezierS.IsNull())
    { // Bezier :
      nUP  = theBezierS->NbUPoles();
      nVP  = theBezierS->NbVPoles();
      UDeg = theBezierS->UDegree();
      VDeg = theBezierS->VDegree();
    }
    else
    {
      return newSurf; // non reconnu : terminus
    }
  }
  else
  { // BSpline :
    nUP  = theBSplneS->NbUPoles();
    nVP  = theBSplneS->NbVPoles();
    UDeg = theBSplneS->UDegree();
    VDeg = theBSplneS->VDegree();
  }

  mySurf->Bounds(U1, U2, V1, V2);
  //  mySurf->Bounds(U1, U2, V1, V2);
  NCollection_Array1<gp_Pnt> p1(1, 3), p2(1, 3), p3(1, 3);
  NCollection_Array1<double> R(1, 3);
  gp_Pnt                     origPnt, resPnt;
  gp_Vec                     origD1U, resD1U, resD1V;

  bool aCySpCo = false;
  bool aToroid = false;
  bool aPlanar = false;

  if (nUP == 2 && nVP == 2)
  {
    if (UDeg == 1 && VDeg == 1)
    {
      aPlanar = true;
    }
  }
  else if (mySurf->IsUClosed())
  { // VRAI IsUClosed
    if (mySurf->IsVClosed())
    {
      aToroid = true;
    }
    else
    {
      aCySpCo = true;
    }
  }
  else
  {
    if (mySurf->IsVClosed())
    { // VRAI IsVClosed
      aCySpCo = true;
      uClosed = false;
    }
  }

  if (aPlanar)
  {
    //    NearestPlane ...
    NCollection_Array1<gp_Pnt> Pnts(1, 4);
    Pnts.SetValue(1, mySurf->Value(U1, V1));
    Pnts.SetValue(2, mySurf->Value(U2, V1));
    Pnts.SetValue(3, mySurf->Value(U1, V2));
    Pnts.SetValue(4, mySurf->Value(U2, V2));
    gp_Pln aPln; // double Dmax;
    int    It = ShapeAnalysis_Geom::NearestPlane(Pnts, aPln, myGap /*Dmax*/);

    //  ICI, on fabrique le plan, et zou
    if (It == 0 || myGap /*Dmax*/ > tol)
    {
      return newSurf; //  pas un plan
    }

    //    IL RESTE a verifier l orientation ...
    //    On regarde sur chaque surface les vecteurs P(U0->U1),P(V0->V1)
    //    On prend la normale : les deux normales doivent etre dans le meme sens
    //    Sinon, inverser la normale (pas le Pln entier !) et refaire la Plane
    newSurf = new Geom_Plane(aPln);
    gp_Vec uold(Pnts(1), Pnts(2));
    gp_Vec vold(Pnts(1), Pnts(3));
    gp_Vec nold = uold.Crossed(vold);
    gp_Vec unew(newSurf->Value(U1, V1), newSurf->Value(U2, V1));
    gp_Vec vnew(newSurf->Value(U1, V1), newSurf->Value(U1, V2));
    gp_Vec nnew = unew.Crossed(vnew);
    if (nold.Dot(nnew) < 0.0)
    {
      gp_Ax3 ax3 = aPln.Position();
      ax3.ZReverse();
      ax3.XReverse();
      aPln    = gp_Pln(ax3);
      newSurf = new Geom_Plane(aPln);
    }

    if (substitute)
    {
      Init(newSurf);
    }
    return newSurf;
  }
  else if (aCySpCo)
  {
    if (!uClosed)
    {
      C1  = U1;
      C2  = U2;
      U1  = V1;
      U2  = V2;
      V1  = C1;
      V2  = C2;
      nCP = nUP;
      nUP = nVP;
      nVP = nCP;
    }

    for (i = 1; i <= 3; i++)
    {
      if (i == 1)
      {
        V = V1;
      }
      else if (i == 2)
      {
        V = V2;
      }
      else if (i == 3)
      {
        V = 0.5 * (V1 + V2);
      }

      if (uClosed)
      {
        iso = mySurf->VIso(V);
      }
      else
      {
        iso = mySurf->UIso(V);
      }

      iso->D0(U1, p1(i));
      iso->D0(0.5 * (U1 + U2), p2(i));
      p3(i).SetCoord(0.5 * (p1(i).X() + p2(i).X()),
                     0.5 * (p1(i).Y() + p2(i).Y()),
                     0.5 * (p1(i).Z() + p2(i).Z()));
      R(i) = p3(i).Distance(p1(i));
      //	std::cout<<"sphere, i="<<i<<" V="<<V<<" R="<<R(i)<<"
      // p1="<<p1(i).X()<<","<<p1(i).Y()<<","<<p1(i).Z()<<"
      // p2="<<p2(i).X()<<","<<p2(i).Y()<<","<<p2(i).Z()<<"
      // p3="<<p3(i).X()<<","<<p3(i).Y()<<","<<p3(i).Z()<<std::endl;
    }

    iso->D1(0., origPnt, origD1U);
    gp_Vec xVec(p3(3), p1(3));
    gp_Vec aVec(p3(1), p3(2));
    //      gp_Dir xDir(xVec);  ne sert pas. Null si R3 = 0
    gp_Dir aDir(aVec);
    gp_Ax3 aAx3(p3(1), aDir, xVec);
    //  CKY  3-FEV-1997 : verification du sens de description
    // gp_Dir AXY = aAx3.YDirection(); // AXY not used (skl)
    if (aAx3.YDirection().Dot(origD1U) < 0)
    {
#ifdef OCCT_DEBUG
      std::cout << " Surface Analytique : sens a inverser" << std::endl;
#endif
      aAx3.YReverse(); //  mais X reste !
    }

    if (nVP > 2)
    {
      if ((std::abs(R(1)) < tol) && (std::abs(R(2)) < tol) && (std::abs(R(3)) > tol))
      {
        // deja fait	  gp_Ax3 aAx3(p3(1), aDir, xVec);
        // gp_Ax3 aAx3(p3(3), aDir);
        occ::handle<Geom_SphericalSurface> anObject = new Geom_SphericalSurface(aAx3, R(3));
        if (!uClosed)
        {
          anObject->UReverse();
        }
        newSurf = anObject;
      }
    }
    else if (nVP == 2)
    {

      // deja fait	gp_Ax3 aAx3(p3(1), aDir, xVec);
      // gp_Ax3 aAx3(p3(1), aDir);

      if (std::abs(R(2) - R(1)) < tol)
      {
        occ::handle<Geom_CylindricalSurface> anObject = new Geom_CylindricalSurface(aAx3, R(1));
        if (!uClosed)
        {
          anObject->UReverse();
        }
        newSurf = anObject;
      }
      else
      {
        gp_Vec aVec2(p1(1), p1(2));
        double angle = aVec.Angle(aVec2);
        if (R(1) < R(2))
        {
          occ::handle<Geom_ConicalSurface> anObject = new Geom_ConicalSurface(aAx3, angle, R(1));
          // if (!uClosed) anObject->UReverse();
          anObject->UReverse();
          newSurf = anObject;
        }
        else
        {
          aDir.Reverse();
          gp_Vec                           anotherXVec(p3(2), p1(2));
          gp_Dir                           anotherXDir(anotherXVec);
          gp_Ax3                           anotherAx3(p3(2), aDir, anotherXDir);
          occ::handle<Geom_ConicalSurface> anObject =
            new Geom_ConicalSurface(anotherAx3, angle, R(2));
          // if (!uClosed) anObject->UReverse();
          anObject->UReverse();
          newSurf = anObject;
        }
      }
    }
  }
  else if (aToroid)
  {
    // test by iso U and isoV
    bool isFound = false;
    for (j = 1; (j <= 2) && !isFound; j++)
    {
      if (j == 2)
      {
        C1 = U1;
        C2 = U2;
        U1 = V1;
        U2 = V2;
        V1 = C1;
        V2 = C2;
      }
      for (i = 1; i <= 3; i++)
      {
        if (i == 1)
        {
          U = U1;
        }
        else if (i == 2)
        {
          U = 0.5 * (U1 + U2);
        }
        else if (i == 3)
        {
          U = 0.25 * (U1 + U2);
        }

        iso = mySurf->UIso(U);

        iso->D0(V1, p1(i));
        iso->D0(0.5 * (V1 + V2), p2(i));
        p3(i).SetCoord(0.5 * (p1(i).X() + p2(i).X()),
                       0.5 * (p1(i).Y() + p2(i).Y()),
                       0.5 * (p1(i).Z() + p2(i).Z()));
        R(i) = p3(i).Distance(p1(i));
      }
      if ((std::abs(R(1) - R(2)) < tol) && (std::abs(R(1) - R(3)) < tol))
      {
        gp_Pnt p10(0.5 * (p3(1).X() + p3(2).X()),
                   0.5 * (p3(1).Y() + p3(2).Y()),
                   0.5 * (p3(1).Z() + p3(2).Z()));
        gp_Vec aVec(p10, p3(1));
        gp_Vec aVec2(p10, p3(3));
        double RR1 = R(1), RR2 = R(2), RR3;
        aVec ^= aVec2;

        if (aVec.Magnitude() <= gp::Resolution())
        {
          aVec.SetCoord(0., 0., 1.);
        }

        gp_Dir aDir(aVec);

        gp_Ax3 aAx3(p10, aDir);
        RR1 = p10.Distance(p3(1));
        //          modif empirique (pourtant NON DEMONTREE) : inverser roles RR1,RR2
        //          CKY, 24-JAN-1997
        if (RR1 < RR2)
        {
          RR3 = RR1;
          RR1 = RR2;
          RR2 = RR3;
        }
        occ::handle<Geom_ToroidalSurface> anObject = new Geom_ToroidalSurface(aAx3, RR1, RR2);
        if (j == 2)
        {
          anObject->UReverse();
        }
        anObject->D1(0., 0., resPnt, resD1U, resD1V);
#ifdef OCCT_DEBUG
        if (resD1U.Dot(origD1U) < 0 && j != 2)
          std::cout << " Tore a inverser !" << std::endl;
#endif
        newSurf = anObject;
        isFound = true;
      }
    }
  }
  if (newSurf.IsNull())
  {
    return newSurf;
  }

  //---------------------------------------------------------------------
  //                 verification
  //---------------------------------------------------------------------

  occ::handle<GeomAdaptor_Surface> NHS       = new GeomAdaptor_Surface(newSurf);
  GeomAdaptor_Surface&             SurfAdapt = *NHS;

  const int NP = 21;
  double    S = 0., T = 0.; // U,V deja fait
  gp_Pnt    P3d, P3d2;
  bool      onSurface = true;

  double dis;
  myGap = 0.;

  DU = (U2 - U1) / (NP - 1);
  DV = (V2 - V1) / (NP - 1);
  for (j = 1; (j <= NP) && onSurface; j++)
  {
    V = V1 + DV * (j - 1);

    if (uClosed)
    {
      iso = mySurf->VIso(V);
    }
    else
    {
      iso = mySurf->UIso(V);
    }

    for (i = 1; i <= NP; i++)
    {
      U = U1 + DU * (i - 1);
      iso->D0(U, P3d);
      switch (SurfAdapt.GetType())
      {

        case GeomAbs_Cylinder: {
          gp_Cylinder Cylinder = SurfAdapt.Cylinder();
          ElSLib::Parameters(Cylinder, P3d, S, T);
          break;
        }
        case GeomAbs_Cone: {
          gp_Cone Cone = SurfAdapt.Cone();
          ElSLib::Parameters(Cone, P3d, S, T);
          break;
        }
        case GeomAbs_Sphere: {
          gp_Sphere Sphere = SurfAdapt.Sphere();
          ElSLib::Parameters(Sphere, P3d, S, T);
          break;
        }
        case GeomAbs_Torus: {
          gp_Torus Torus = SurfAdapt.Torus();
          ElSLib::Parameters(Torus, P3d, S, T);
          break;
        }
        default:
          break;
      }

      newSurf->D0(S, T, P3d2);

      dis = P3d.Distance(P3d2);
      if (dis > myGap)
      {
        myGap = dis;
      }

      if (dis > tol)
      {
        onSurface = false;
        newSurf.Nullify();
        // The presumption is rejected
        break;
      }
    }
  }
  if (substitute && !NHS.IsNull())
  {
    Init(newSurf);
  }
  return newSurf;
}

//%pdn 30 Nov 98: converting bspline surfaces with degree+1 at ends to periodic
// UKI60591, entity 48720
occ::handle<Geom_Surface> ShapeCustom_Surface::ConvertToPeriodic(const bool   substitute,
                                                                 const double preci)
{
  occ::handle<Geom_Surface>        newSurf;
  occ::handle<Geom_BSplineSurface> BSpl = occ::down_cast<Geom_BSplineSurface>(mySurf);
  if (BSpl.IsNull())
  {
    return newSurf;
  }

  ShapeAnalysis_Surface sas(mySurf);
  bool                  uclosed = sas.IsUClosed(preci);
  bool                  vclosed = sas.IsVClosed(preci);

  if (!uclosed && !vclosed)
  {
    return newSurf;
  }

  bool converted = false; //: p6

  if (uclosed && !BSpl->IsUPeriodic() && BSpl->NbUPoles() > 3)
  {
    bool set = true;
    // if degree+1 at ends, first change it to 1 by rearranging knots
    if (BSpl->UMultiplicity(1) == BSpl->UDegree() + 1
        && BSpl->UMultiplicity(BSpl->NbUKnots()) == BSpl->UDegree() + 1)
    {
      const NCollection_Array2<gp_Pnt>& oldPoles   = BSpl->Poles();
      const NCollection_Array2<double>& oldWeights = BSpl->WeightsArray();
      int                               nbUKnots   = BSpl->NbUKnots();
      const NCollection_Array1<double>& oldUKnots  = BSpl->UKnots();
      const NCollection_Array1<double>& oldVKnots  = BSpl->VKnots();
      const NCollection_Array1<int>&    oldUMults  = BSpl->UMultiplicities();
      const NCollection_Array1<int>&    oldVMults  = BSpl->VMultiplicities();

      NCollection_Array1<double> newUKnots(1, nbUKnots + 2);
      NCollection_Array1<int>    newUMults(1, nbUKnots + 2);
      double                     a =
        0.5 * (BSpl->UKnot(2) - BSpl->UKnot(1) + BSpl->UKnot(nbUKnots) - BSpl->UKnot(nbUKnots - 1));

      newUKnots(1)            = oldUKnots(1) - a;
      newUKnots(nbUKnots + 2) = oldUKnots(nbUKnots) + a;
      newUMults(1) = newUMults(nbUKnots + 2) = 1;
      for (int i = 2; i <= nbUKnots + 1; i++)
      {
        newUKnots(i) = oldUKnots(i - 1);
        newUMults(i) = oldUMults(i - 1);
      }
      newUMults(2) = newUMults(nbUKnots + 1) = BSpl->UDegree();
      occ::handle<Geom_BSplineSurface> res   = new Geom_BSplineSurface(oldPoles,
                                                                     oldWeights,
                                                                     newUKnots,
                                                                     oldVKnots,
                                                                     newUMults,
                                                                     oldVMults,
                                                                     BSpl->UDegree(),
                                                                     BSpl->VDegree(),
                                                                     BSpl->IsUPeriodic(),
                                                                     BSpl->IsVPeriodic());
      BSpl                                   = res;
    }
    else if (BSpl->UMultiplicity(1) > BSpl->UDegree()
             || BSpl->UMultiplicity(BSpl->NbUKnots()) > BSpl->UDegree() + 1)
    {
      set = false;
    }
    if (set)
    {
      BSpl->SetUPeriodic(); // make periodic
      converted = true;
    }
  }

  if (vclosed && !BSpl->IsVPeriodic() && BSpl->NbVPoles() > 3)
  {
    bool set = true;
    // if degree+1 at ends, first change it to 1 by rearranging knots
    if (BSpl->VMultiplicity(1) == BSpl->VDegree() + 1
        && BSpl->VMultiplicity(BSpl->NbVKnots()) == BSpl->VDegree() + 1)
    {
      const NCollection_Array2<gp_Pnt>& oldPoles   = BSpl->Poles();
      const NCollection_Array2<double>& oldWeights = BSpl->WeightsArray();
      int                               nbVKnots   = BSpl->NbVKnots();
      const NCollection_Array1<double>& oldUKnots  = BSpl->UKnots();
      const NCollection_Array1<double>& oldVKnots  = BSpl->VKnots();
      const NCollection_Array1<int>&    oldUMults  = BSpl->UMultiplicities();
      const NCollection_Array1<int>&    oldVMults  = BSpl->VMultiplicities();

      NCollection_Array1<double> newVKnots(1, nbVKnots + 2);
      NCollection_Array1<int>    newVMults(1, nbVKnots + 2);
      double                     a =
        0.5 * (BSpl->VKnot(2) - BSpl->VKnot(1) + BSpl->VKnot(nbVKnots) - BSpl->VKnot(nbVKnots - 1));

      newVKnots(1)            = oldVKnots(1) - a;
      newVKnots(nbVKnots + 2) = oldVKnots(nbVKnots) + a;
      newVMults(1) = newVMults(nbVKnots + 2) = 1;
      for (int i = 2; i <= nbVKnots + 1; i++)
      {
        newVKnots(i) = oldVKnots(i - 1);
        newVMults(i) = oldVMults(i - 1);
      }
      newVMults(2) = newVMults(nbVKnots + 1) = BSpl->VDegree();
      occ::handle<Geom_BSplineSurface> res   = new Geom_BSplineSurface(oldPoles,
                                                                     oldWeights,
                                                                     oldUKnots,
                                                                     newVKnots,
                                                                     oldUMults,
                                                                     newVMults,
                                                                     BSpl->UDegree(),
                                                                     BSpl->VDegree(),
                                                                     BSpl->IsUPeriodic(),
                                                                     BSpl->IsVPeriodic());
      BSpl                                   = res;
    }
    else if (BSpl->VMultiplicity(1) > BSpl->VDegree()
             || BSpl->VMultiplicity(BSpl->NbVKnots()) > BSpl->VDegree() + 1)
    {
      set = false;
    }
    if (set)
    {
      BSpl->SetVPeriodic(); // make periodic
      converted = true;
    }
  }

#ifdef OCCT_DEBUG
  std::cout << "Warning: ShapeCustom_Surface: Closed BSplineSurface is caused to be periodic"
            << std::endl;
#endif
  if (!converted)
  {
    return newSurf;
  }
  newSurf = BSpl;
  if (substitute)
  {
    mySurf = newSurf;
  }
  return newSurf;
}
