// Created on: 1995-05-30
// Created by: Jacques GOUSSARD
// Copyright (c) 1995-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 <Bnd_Box.hxx>
#include <BRepBndLib.hxx>
#include <BRepFeat_MakeCylindricalHole.hxx>
#include <BRepPrim_Cylinder.hxx>
#include <ElCLib.hxx>
#include <Geom_Curve.hxx>
#include <gp_Ax1.hxx>
#include <LocOpe_CurveShapeIntersector.hxx>
#include <LocOpe_PntFace.hxx>
#include <Precision.hxx>
#include <Standard_ConstructionError.hxx>
#include <TopExp_Explorer.hxx>
#include <TopLoc_Location.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Solid.hxx>
#include <NCollection_List.hxx>
#include <BRepAdaptor_Surface.hxx>
#include <CSLib.hxx>

static void Baryc(const TopoDS_Shape&, gp_Pnt&);

static void BoxParameters(const TopoDS_Shape&, const gp_Ax1&, double&, double&);

static bool GetOffset(const LocOpe_PntFace& PntInfo,
                      const double          Radius,
                      const gp_Ax1&         Axis,
                      double&               outOff);

static void CreateCyl(const LocOpe_PntFace& PntInfoFirst,
                      const LocOpe_PntFace& PntInfoLast,
                      const double          Radius,
                      const gp_Ax1&         Axis,
                      TopoDS_Shell&         Cyl,
                      TopoDS_Face&          CylTopF,
                      TopoDS_Face&          CylBottF);

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

void BRepFeat_MakeCylindricalHole::Perform(const double Radius)
{
  const TopoDS_Shape& aObject = myArguments.First();
  if (aObject.IsNull() || !myAxDef)
  {
    throw Standard_ConstructionError();
  }

  myIsBlind = false;
  myStatus  = BRepFeat_NoError;

  LocOpe_CurveShapeIntersector theASI(myAxis, aObject);
  if (!theASI.IsDone() || theASI.NbPoints() <= 0)
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  // It is not possible to use infinite cylinder for topological operations.
  double PMin, PMax;
  BoxParameters(aObject, myAxis, PMin, PMax);
  double Heigth  = 2. * (PMax - PMin);
  gp_XYZ theOrig = myAxis.Location().XYZ();
  theOrig += ((3. * PMin - PMax) / 2.) * myAxis.Direction().XYZ();
  gp_Pnt            p1_ao1(theOrig);
  gp_Ax2            a1_ao1(p1_ao1, myAxis.Direction());
  BRepPrim_Cylinder theCylinder(a1_ao1, Radius, Heigth);

  // Probably it is better to make cut directly

  BRep_Builder B;
  TopoDS_Solid theTool;
  B.MakeSolid(theTool);
  B.Add(theTool, theCylinder.Shell());

  myTopFace  = theCylinder.TopFace();
  myBotFace  = theCylinder.BottomFace();
  myValidate = false;

  //  BRepTools::Dump(theTool,std::cout);
  bool Fuse = false;
  //
  AddTool(theTool);
  SetOperation(Fuse);
  BOPAlgo_BOP::Perform();
}

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

void BRepFeat_MakeCylindricalHole::PerformThruNext(const double Radius, const bool Cont)
{
  //
  const TopoDS_Shape& aObject = myArguments.First();
  if (aObject.IsNull() || !myAxDef)
  {
    throw Standard_ConstructionError();
  }

  myIsBlind  = false;
  myValidate = Cont;
  myStatus   = BRepFeat_NoError;

  LocOpe_CurveShapeIntersector theASI(myAxis, aObject);
  if (!theASI.IsDone())
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  int                IndFrom, IndTo;
  TopAbs_Orientation theOr;
  LocOpe_PntFace     PntInfoFirst, PntInfoLast;
  bool               ok = theASI.LocalizeAfter(0., theOr, IndFrom, IndTo);
  if (ok)
  {
    if (theOr == TopAbs_FORWARD)
    {
      PntInfoFirst = theASI.Point(IndFrom);
      ok           = theASI.LocalizeAfter(IndTo, theOr, IndFrom, IndTo);
      if (ok)
      {
        if (theOr != TopAbs_REVERSED)
        {
          ok = false;
        }
        else
        {
          PntInfoLast = theASI.Point(IndTo);
        }
      }
    }
    else
    { // TopAbs_REVERSED
      PntInfoLast = theASI.Point(IndTo);
      ok          = theASI.LocalizeBefore(IndFrom, theOr, IndFrom, IndTo);
      if (ok)
      {
        if (theOr != TopAbs_FORWARD)
        {
          ok = false;
        }
        else
        {
          PntInfoFirst = theASI.Point(IndFrom);
        }
      }
    }
  }
  if (!ok)
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  TopoDS_Shell Cyl;
  CreateCyl(PntInfoFirst, PntInfoLast, Radius, myAxis, Cyl, myTopFace, myBotFace);

  BRep_Builder B;
  TopoDS_Solid theTool;
  B.MakeSolid(theTool);
  B.Add(theTool, Cyl);

  bool Fuse = false;
  AddTool(theTool);
  SetOperation(Fuse);
  BOPAlgo_BOP::Perform();
  NCollection_List<TopoDS_Shape> parts;
  PartsOfTool(parts);

  int                                      nbparts = 0;
  NCollection_List<TopoDS_Shape>::Iterator its(parts);
  for (; its.More(); its.Next())
  {
    nbparts++;
  }
  if (nbparts == 0)
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  if (nbparts >= 2)
  { // preserve the smallest as parameter
    // along the axis

    double       First = PntInfoFirst.Parameter();
    double       Last  = PntInfoLast.Parameter();
    TopoDS_Shape tokeep;
    double       parbar, parmin = Last;
    gp_Pnt       Barycentre;
    for (its.Initialize(parts); its.More(); its.Next())
    {
      Baryc(its.Value(), Barycentre);
      parbar = ElCLib::LineParameter(myAxis, Barycentre);
      if (parbar >= First && parbar <= Last && parbar <= parmin)
      {
        parmin = parbar;
        tokeep = its.Value();
      }
    }

    if (tokeep.IsNull())
    { // preserve the closest interval

      double dmin = RealLast();
      for (its.Initialize(parts); its.More(); its.Next())
      {
        Baryc(its.Value(), Barycentre);
        parbar = ElCLib::LineParameter(myAxis, Barycentre);
        if (parbar < First)
        {
          if (First - parbar < dmin)
          {
            dmin   = First - parbar;
            tokeep = its.Value();
          }
          else
          { // parbar > Last
            if (parbar - Last < dmin)
            {
              dmin   = parbar - Last;
              tokeep = its.Value();
            }
          }
        }
      }
    }
    for (its.Initialize(parts); its.More(); its.Next())
    {
      if (tokeep.IsSame(its.Value()))
      {
        KeepPart(its.Value());
        break;
      }
    }
  }
}

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

void BRepFeat_MakeCylindricalHole::PerformUntilEnd(const double Radius, const bool Cont)
{
  //
  const TopoDS_Shape& aObject = myArguments.First();
  if (aObject.IsNull() || !myAxDef)
  {
    throw Standard_ConstructionError();
  }

  myIsBlind  = false;
  myValidate = Cont;
  myStatus   = BRepFeat_NoError;

  LocOpe_CurveShapeIntersector theASI(myAxis, aObject);
  if (!theASI.IsDone())
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  int                IndFrom, IndTo;
  TopAbs_Orientation theOr;
  bool               ok = theASI.LocalizeAfter(0., theOr, IndFrom, IndTo);
  LocOpe_PntFace     PntInfoFirst, PntInfoLast;

  if (ok)
  {
    if (theOr == TopAbs_REVERSED)
    {
      ok = theASI.LocalizeBefore(IndFrom, theOr, IndFrom, IndTo); // on reset
      // It is possible to search for the next.
    }
    if (ok && theOr == TopAbs_FORWARD)
    {
      PntInfoFirst = theASI.Point(IndFrom);
      ok           = theASI.LocalizeBefore(theASI.NbPoints() + 1, theOr, IndFrom, IndTo);
      if (ok)
      {
        if (theOr != TopAbs_REVERSED)
        {
          ok = false;
        }
        else
        {
          PntInfoLast = theASI.Point(IndTo);
        }
      }
    }
  }
  if (!ok)
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  TopoDS_Shell Cyl;
  CreateCyl(PntInfoFirst, PntInfoLast, Radius, myAxis, Cyl, myTopFace, myBotFace);

  BRep_Builder B;
  TopoDS_Solid theTool;
  B.MakeSolid(theTool);
  B.Add(theTool, Cyl);

  bool Fuse = false;
  AddTool(theTool);
  SetOperation(Fuse);
  BOPAlgo_BOP::Perform();
  NCollection_List<TopoDS_Shape> parts;
  PartsOfTool(parts);

  int                                      nbparts = 0;
  NCollection_List<TopoDS_Shape>::Iterator its(parts);
  for (; its.More(); its.Next())
  {
    nbparts++;
  }
  if (nbparts == 0)
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  if (nbparts >= 2)
  { // preserve everything above the First
    double parbar;
    gp_Pnt Barycentre;
    for (its.Initialize(parts); its.More(); its.Next())
    {
      Baryc(its.Value(), Barycentre);
      parbar = ElCLib::LineParameter(myAxis, Barycentre);
      if (parbar > PntInfoFirst.Parameter())
      {
        KeepPart(its.Value());
      }
    }
  }
}

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

void BRepFeat_MakeCylindricalHole::Perform(const double Radius,
                                           const double PFrom,
                                           const double PTo,
                                           const bool   Cont)
{
  //
  const TopoDS_Shape& aObject = myArguments.First();
  if (aObject.IsNull() || !myAxDef)
  {
    throw Standard_ConstructionError();
  }

  myIsBlind  = false;
  myValidate = Cont;
  myStatus   = BRepFeat_NoError;

  LocOpe_CurveShapeIntersector theASI(myAxis, aObject);
  if (!theASI.IsDone())
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  double thePFrom, thePTo;
  if (PFrom < PTo)
  {
    thePFrom = PFrom;
    thePTo   = PTo;
  }
  else
  {
    thePFrom = PTo;
    thePTo   = PFrom;
  }

  // double First=0,Last=0,prm;
  LocOpe_PntFace     PntInfoFirst, PntInfoLast;
  int                IndFrom, IndTo;
  TopAbs_Orientation theOr;
  bool               ok = theASI.LocalizeAfter(thePFrom, theOr, IndFrom, IndTo);
  if (ok)
  {
    if (theOr == TopAbs_REVERSED)
    {
      ok = theASI.LocalizeBefore(IndFrom, theOr, IndFrom, IndTo); // reset
      // It is possible to find the next.
    }
    if (ok && theOr == TopAbs_FORWARD)
    {
      PntInfoFirst = theASI.Point(IndFrom);
      ok           = theASI.LocalizeBefore(thePTo, theOr, IndFrom, IndTo);
      if (ok)
      {
        if (theOr == TopAbs_FORWARD)
        {
          ok = theASI.LocalizeAfter(IndTo, theOr, IndFrom, IndTo);
        }
        if (ok && theOr == TopAbs_REVERSED)
        {
          PntInfoLast = theASI.Point(IndTo);
        }
      }
    }
  }

  if (!ok)
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  TopoDS_Shell Cyl;
  CreateCyl(PntInfoFirst, PntInfoLast, Radius, myAxis, Cyl, myTopFace, myBotFace);

  BRep_Builder B;
  TopoDS_Solid theTool;
  B.MakeSolid(theTool);
  B.Add(theTool, Cyl);

  bool Fuse = false;
  AddTool(theTool);
  SetOperation(Fuse);
  BOPAlgo_BOP::Perform();
  NCollection_List<TopoDS_Shape> parts;
  PartsOfTool(parts);

  int                                      nbparts = 0;
  NCollection_List<TopoDS_Shape>::Iterator its(parts);
  for (; its.More(); its.Next())
  {
    nbparts++;
  }
  if (nbparts == 0)
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  if (nbparts >= 2)
  { // preserve parts between First and Last

    TopoDS_Shape tokeep;
    double       parbar;
    gp_Pnt       Barycentre;
    for (its.Initialize(parts); its.More(); its.Next())
    {
      Baryc(its.Value(), Barycentre);
      parbar = ElCLib::LineParameter(myAxis, Barycentre);
      if (!(parbar < PntInfoFirst.Parameter() || parbar > PntInfoLast.Parameter()))
      {
        KeepPart(its.Value());
      }
    }
  }
}

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

void BRepFeat_MakeCylindricalHole::PerformBlind(const double Radius,
                                                const double Length,
                                                const bool   Cont)
{
  //
  const TopoDS_Shape& aObject = myArguments.First();
  if (aObject.IsNull() || !myAxDef || Length <= 0.)
  {
    throw Standard_ConstructionError();
  }

  myIsBlind  = true;
  myValidate = Cont;
  myStatus   = BRepFeat_NoError;

  LocOpe_CurveShapeIntersector theASI(myAxis, aObject);
  if (!theASI.IsDone())
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  double             First;
  int                IndFrom, IndTo;
  TopAbs_Orientation theOr;
  bool               ok = theASI.LocalizeAfter(0., theOr, IndFrom, IndTo);

  if (ok)
  {
    if (theOr == TopAbs_REVERSED)
    {
      ok = theASI.LocalizeBefore(IndFrom, theOr, IndFrom, IndTo); // reset
      // it is possible to find the next
    }
    ok = ok && theOr == TopAbs_FORWARD;
  }
  if (!ok)
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  // check a priori the length of the hole
  int IFNext, ITNext;
  ok = theASI.LocalizeAfter(IndTo, theOr, IFNext, ITNext);
  if (!ok)
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }
  if (theASI.Point(IFNext).Parameter() <= Length)
  {
    myStatus = BRepFeat_HoleTooLong;
    return;
  }

  NCollection_List<TopoDS_Shape> theList;

  // version for advanced control
  for (int i = IndFrom; i <= ITNext; i++)
  {
    theList.Append(theASI.Point(i).Face());
  }

  First = theASI.Point(IndFrom).Parameter();

  //// It is not possible to use infinite cylinder for topological operations.
  double PMin, PMax;
  BoxParameters(aObject, myAxis, PMin, PMax);
  if (PMin > Length)
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  double Heigth  = 3. * (Length - PMin) / 2.;
  gp_XYZ theOrig = myAxis.Location().XYZ();
  theOrig += ((3. * PMin - Length) / 2.) * myAxis.Direction().XYZ();
  gp_Pnt            p5_ao1(theOrig);
  gp_Ax2            a5_ao1(p5_ao1, myAxis.Direction());
  BRepPrim_Cylinder theCylinder(a5_ao1, Radius, Heigth);

  BRep_Builder B;
  TopoDS_Solid theTool;
  B.MakeSolid(theTool);
  B.Add(theTool, theCylinder.Shell());

  myTopFace = theCylinder.TopFace();
  myBotFace.Nullify();

  //  BRepTools::Dump(theTool,std::cout);
  bool Fuse = false;
  // myBuilder.Perform(theTool,theList,Fuse);
  // myBuilder.BuildPartsOfTool();
  AddTool(theTool);
  SetOperation(Fuse);
  BOPAlgo_BOP::Perform();
  NCollection_List<TopoDS_Shape> parts;
  PartsOfTool(parts);

  int                                      nbparts = 0;
  NCollection_List<TopoDS_Shape>::Iterator its(parts);
  for (; its.More(); its.Next())
  {
    nbparts++;
  }
  if (nbparts == 0)
  {
    myStatus = BRepFeat_InvalidPlacement;
    return;
  }

  if (nbparts >= 2)
  { // preserve the smallest as parameter along the axis
    TopoDS_Shape tokeep;
    double       parbar, parmin = RealLast();
    gp_Pnt       Barycentre;
    for (its.Initialize(parts); its.More(); its.Next())
    {
      Baryc(its.Value(), Barycentre);
      parbar = ElCLib::LineParameter(myAxis, Barycentre);
      if (parbar >= First && parbar <= parmin)
      {
        parmin = parbar;
        tokeep = its.Value();
      }
    }

    if (tokeep.IsNull())
    { // preserve the closest interval

      double dmin = RealLast();
      for (its.Initialize(parts); its.More(); its.Next())
      {
        Baryc(its.Value(), Barycentre);
        parbar = ElCLib::LineParameter(myAxis, Barycentre);
        if (std::abs(First - parbar) < dmin)
        {
          dmin   = std::abs(First - parbar);
          tokeep = its.Value();
        }
      }
    }
    for (its.Initialize(parts); its.More(); its.Next())
    {
      if (tokeep.IsSame(its.Value()))
      {
        KeepPart(its.Value());
        break;
      }
    }
  }
}

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

void BRepFeat_MakeCylindricalHole::Build()
{
  if (myStatus == BRepFeat_NoError)
  {
    PerformResult();
    if (!HasErrors())
    {
      myStatus = (myValidate) ? Validate() : BRepFeat_NoError;
      if (myStatus == BRepFeat_NoError)
      {
        myShape = Shape();
      }
    }
    else
    {
      myStatus = BRepFeat_InvalidPlacement; // why not
    }
  }
}

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

BRepFeat_Status BRepFeat_MakeCylindricalHole::Validate()
{
  BRepFeat_Status thestat = BRepFeat_NoError;
  TopExp_Explorer ex(Shape(), TopAbs_FACE);
  if (myIsBlind)
  { // limit of the hole
    for (; ex.More(); ex.Next())
    {
      if (ex.Current().IsSame(myTopFace))
      {
        break;
      }
    }
    if (!ex.More())
    {
      thestat = BRepFeat_HoleTooLong;
    }
  }
  else
  {
    for (; ex.More(); ex.Next())
    {
      if (ex.Current().IsSame(myTopFace))
      {
        return BRepFeat_InvalidPlacement;
      }
    }
    for (ex.ReInit(); ex.More(); ex.Next())
    {
      if (ex.Current().IsSame(myBotFace))
      {
        return BRepFeat_InvalidPlacement;
      }
    }
  }
  return thestat;
}

void Baryc(const TopoDS_Shape& S, gp_Pnt& B)
{
  TopExp_Explorer         exp(S, TopAbs_EDGE);
  gp_XYZ                  Bar(0., 0., 0.);
  TopLoc_Location         L;
  occ::handle<Geom_Curve> C;
  double                  prm, First, Last;

  int i, nbp = 0;
  for (; exp.More(); exp.Next())
  {
    // Calculate points by non-degenerated edges
    const TopoDS_Edge& E = TopoDS::Edge(exp.Current());
    if (!BRep_Tool::Degenerated(E))
    {
      C = BRep_Tool::Curve(E, L, First, Last);
      C = occ::down_cast<Geom_Curve>(C->Transformed(L.Transformation()));
      for (i = 1; i <= 11; i++)
      {
        prm = ((11 - i) * First + (i - 1) * Last) / 10.;
        Bar += C->Value(prm).XYZ();
        nbp++;
      }
    }
  }
  Bar.Divide((double)nbp);
  B.SetXYZ(Bar);
}

void BoxParameters(const TopoDS_Shape& S, const gp_Ax1& Axis, double& parmin, double& parmax)
{

  // calculate the parameters of a bounding box in the direction of the axis of the hole
  Bnd_Box B;
  BRepBndLib::Add(S, B);
  double c[6];
  B.Get(c[0], c[2], c[4], c[1], c[3], c[5]);
  gp_Pnt P;
  int    i, j, k;
  parmin = RealLast();
  parmax = RealFirst();
  double param;
  for (i = 0; i <= 1; i++)
  {
    P.SetX(c[i]);
    for (j = 2; j <= 3; j++)
    {
      P.SetY(c[j]);
      for (k = 4; k <= 5; k++)
      {
        P.SetZ(c[k]);
        param  = ElCLib::LineParameter(Axis, P);
        parmin = std::min(param, parmin);
        parmax = std::max(param, parmax);
      }
    }
  }
}

bool GetOffset(const LocOpe_PntFace& PntInfo,
               const double          Radius,
               const gp_Ax1&         Axis,
               double&               outOff)
{
  const TopoDS_Face&  FF = PntInfo.Face();
  BRepAdaptor_Surface FFA(FF);

  double Up = PntInfo.UParameter();
  double Vp = PntInfo.VParameter();
  gp_Pnt PP;
  gp_Vec D1U, D1V;
  FFA.D1(Up, Vp, PP, D1U, D1V);
  gp_Dir             NormF;
  CSLib_NormalStatus stat;
  CSLib::Normal(D1U, D1V, Precision::Angular(), stat, NormF);
  if (stat != CSLib_Defined)
    return false;
  double angle = Axis.Direction().Angle(NormF);
  if (std::abs(M_PI / 2. - angle) < Precision::Angular())
    return false;
  outOff = Radius * std::abs(tan(angle));
  return true;
}

void CreateCyl(const LocOpe_PntFace& PntInfoFirst,
               const LocOpe_PntFace& PntInfoLast,
               const double          Radius,
               const gp_Ax1&         Axis,
               TopoDS_Shell&         Cyl,
               TopoDS_Face&          CylTopF,
               TopoDS_Face&          CylBottF)
{
  double First = 0, Last = 0;
  double offF = 0., offL = 0.;
  Last          = PntInfoLast.Parameter();
  First         = PntInfoFirst.Parameter();
  double Heigth = Last - First;

  if (!GetOffset(PntInfoFirst, Radius, Axis, offF))
    offF = Radius;
  if (!GetOffset(PntInfoLast, Radius, Axis, offL))
    offL = Radius;

  // create cylinder along the axis (myAxis);
  // from 'First - offF' to 'Last + offL' params
  gp_XYZ            theOrig = PntInfoFirst.Pnt().XYZ() - offF * Axis.Direction().XYZ();
  gp_Pnt            p2_ao1(theOrig);
  gp_Ax2            a2_ao1(p2_ao1, Axis.Direction());
  BRepPrim_Cylinder theCylinder(a2_ao1, Radius, Heigth + offF + offL);
  Cyl      = theCylinder.Shell();
  CylTopF  = theCylinder.TopFace();
  CylBottF = theCylinder.BottomFace();
}
