#include <BRepProj_Projection.ixx>

#include <BRepAlgo_BooleanOperations.hxx>
#include <BRepAlgo_DSAccess.hxx>


#include <BRepBndLib.hxx>
#include <BRepTools_TrsfModification.hxx>
#include <BRepTools_Modifier.hxx>
#include <BRepLib_MakeEdge.hxx>
#include <BRepLib_MakeWire.hxx>
#include <BRep_Tool.hxx>
#include <Bnd_Box.hxx>

#include <BRepSweep_Prism.hxx>
#include <BRepFill_Generator.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <TopLoc_Location.hxx>
#include <gp_Dir.hxx>
#include <gp_Pnt.hxx>
#include <gp_Vec.hxx>
#include <gp_Trsf.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Iterator.hxx>
#include <TopoDS_Shape.hxx>
#include <TopTools_ListOfShape.hxx>


#include <Standard_NullObject.hxx>
#include <Standard_ConstructionError.hxx>
#include <Standard_NotImplemented.hxx>

//
//=======================================================================
//function : BRepProj_Projection    
//purpose  : Cylindrical Projection
//=======================================================================
  BRepProj_Projection::BRepProj_Projection(const TopoDS_Shape& Lsh,
					   const TopoDS_Shape& Ssh,
					   const gp_Dir& D,
					   const Standard_Boolean )//FaceBounds)
:
  myIsDone(Standard_False), myLsh(Lsh)
 
{

  // Check the input shapes
  if ((Lsh.ShapeType() != TopAbs_EDGE && 
       Lsh.ShapeType() != TopAbs_WIRE ) ||
      (Ssh.ShapeType() == TopAbs_EDGE ||
       Ssh.ShapeType() == TopAbs_WIRE )) Standard_ConstructionError::Raise("Projection");
  Standard_NullObject_Raise_if((myLsh.IsNull() || Ssh.IsNull()),"Projection");

  // clear the list result
  mySectionEdgeList.Clear();

  // compute the "length" of the cylindrical surface to build
  Standard_Real mdis = DistanceIn(Ssh);

  gp_Vec Vsup(D),Vinf(D);
  Vsup *= 2*mdis; Vinf *= -mdis;

  // move the base of the cylindrical surface by translating it by -mdis
  gp_Trsf T;
  T.SetTranslation(Vinf);
  TopLoc_Location TL(T);
  TopoDS_Shape ShapeBase = Lsh.Moved(TL);

  // Creation of a cylindrical surface
  BRepSweep_Prism CylSurf (ShapeBase, Vsup, Standard_False);

  // Initialization of a Boolean Operation between CylSurf and Ssh
  BRepAlgo_BooleanOperations BOOP;
  BOOP.Shapes(CylSurf.Shape(), Ssh);

  // Compute the section
  myShape = BOOP.Section();


  MakeList(BOOP);

  // Initialize the iterator upon mySectionEdgeList
  Init();

  if (!mySectionEdgeList.IsEmpty())  myIsDone = Standard_True;
}

//=======================================================================
//function : BRepProj_Projection
//purpose  : Conical projection
//=======================================================================
  BRepProj_Projection::BRepProj_Projection(const TopoDS_Shape& Lsh,
					   const TopoDS_Shape& Ssh,
					   const gp_Pnt& P,
					   const Standard_Boolean )//FaceBounds)
:
       myIsDone(Standard_False),
       myLsh(Lsh)
{
  // Check the input shapes
  if ((Lsh.ShapeType() != TopAbs_EDGE && 
       Lsh.ShapeType() != TopAbs_WIRE ) ||
      (Ssh.ShapeType() == TopAbs_EDGE ||
       Ssh.ShapeType() == TopAbs_WIRE )) Standard_ConstructionError::Raise("Projection");
  Standard_NullObject_Raise_if((myLsh.IsNull() || Ssh.IsNull()),"Projection");

  //if Lsh is only an edge, transform it into a Wire
  BRep_Builder BB;
  TopoDS_Wire WireLsh;

  if (Lsh.ShapeType() == TopAbs_EDGE) {
    BB.MakeWire(WireLsh);
//    BB.MakeWire(TopoDS::Wire(WireLsh));
    BB.Add(WireLsh, Lsh);
  }
  else {
    WireLsh = TopoDS::Wire(Lsh);
  }

  // clear the list result
  mySectionEdgeList.Clear();

  // compute the "length" of the conical surface to build
  Standard_Real mdis = DistanceIn(Ssh);

  // Initialize iterator to get first sub-shape of Lsh
  TopExp_Explorer ExpLsh; 
  ExpLsh.Init(WireLsh,TopAbs_VERTEX);
  
  // get the first Point of the first sub-shape os Lsh
  gp_Pnt PC = BRep_Tool::Pnt(TopoDS::Vertex(ExpLsh.Current()));
  
  // compute the ratio of the scale transformation
  Standard_Real Scale = PC.Distance(P);
  if (Scale==RealLast()) Standard_ConstructionError::Raise("Projection");
  Scale = 1. + mdis/Scale;
  
  // move the base of the conical surface by scaling it with ratio Scale
  // then we do a symmetric relative to a point. So we have two generators
  // for building a "semi-infinite" conic surface
  gp_Trsf T;
  T.SetScale(P,Scale);
  Handle(BRepTools_TrsfModification) Tsca = new BRepTools_TrsfModification(T);
  BRepTools_Modifier ModifScale(WireLsh,Tsca);
  TopoDS_Shape ShapeGen1 = TopoDS::Wire(ModifScale.ModifiedShape(WireLsh));

  T.SetMirror(P);
  Handle(BRepTools_TrsfModification) Tmir = new BRepTools_TrsfModification(T);
//  TopLoc_Location TLmir(T);
  BRepTools_Modifier ModifMirror(ShapeGen1,Tmir);
  TopoDS_Shape ShapeGen2 = TopoDS::Wire(ModifMirror.ModifiedShape(ShapeGen1));
//  TopoDS_Shape ShapeGen2 = ShapeGen1.Moved(TLmir);



  //Build the Ruled surface based shape
  BRepFill_Generator RuledSurf;
  RuledSurf.AddWire(TopoDS::Wire(ShapeGen1));
  RuledSurf.AddWire(TopoDS::Wire(ShapeGen2));
  RuledSurf.Perform();


 // Initialization of a Boolean Operation between RuledSurf and Ssh
  BRepAlgo_BooleanOperations BOOP;
  TopoDS_Shell SurfShell = RuledSurf.Shell();

  BOOP.Shapes(SurfShell, Ssh);

  // Compute the section
  myShape = BOOP.Section();


  MakeList(BOOP);

  // Initialize the iterator upon mySectionEdgeList
  Init();

  if (!mySectionEdgeList.IsEmpty())  myIsDone = Standard_True;

}

//=======================================================================
//function : DistanceOut
//purpose  : Compute the minimum distance between input shapes 
//           (using Bounding Boxes of each Shape)
//=======================================================================
  Standard_Real BRepProj_Projection::DistanceOut(const TopoDS_Shape& DS) 
{
  Bnd_Box BBox1,
          BBox2;
  BRepBndLib::Add(DS,BBox1);
  BRepBndLib::Add(myLsh,BBox2);
  return BBox2.Distance(BBox1);
}

  
//=======================================================================
//function : DistanceIn
//purpose  : Compute the maximum distance between input Shapes
//           we compute the maximum dimension of each Bounding Box and then
//           add each other with the minimum distance of shapes.
//=======================================================================
  Standard_Real BRepProj_Projection::DistanceIn(const TopoDS_Shape& DS) 
{
  Standard_Real md1,
                md2,
                mdis = 0;
  Standard_Real LXmin, LYmin, LZmin, LXmax, LYmax, LZmax, 
                SXmin, SYmin, SZmin, SXmax, SYmax, SZmax; 
  Bnd_Box LBBox,SBBox;
  BRepBndLib::Add(DS,SBBox);
  BRepBndLib::Add(myLsh,LBBox);
  SBBox.Get(SXmin, SYmin, SZmin, 
            SXmax, SYmax, SZmax);
  LBBox.Get(LXmin, LYmin, LZmin, 
            LXmax, LYmax, LZmax);

  //Compute the max distance between input shapes------------//
  gp_XYZ Lmin(LXmin, LYmin, LZmin), 
         Lmax(LXmax, LYmax, LZmax);
  gp_XYZ Smin(SXmin, SYmin, SZmin), 
         Smax(SXmax, SYmax, SZmax);
  Lmax.Subtract(Lmin);
  md1 = Lmax.Modulus();
  Smax.Subtract(Smin);
  md2 = Smax.Modulus();
  mdis = md1 + md2 + DistanceOut(DS);
  return mdis;
}


//=======================================================================
//function : MakeList
//purpose  : Build the list of result wire (or compound of edges if we cannot
//           build a correct wire from the Section)
//=======================================================================
  void BRepProj_Projection::MakeList(BRepAlgo_BooleanOperations& Bop)
{

  TopoDS_Shape Wire;
  BRepAlgo_DSAccess& DSA =Bop.DataStructureAccess();

  // get all the compound of connex edges from section
  TopTools_ListOfShape SecEdgeSet;

  SecEdgeSet = DSA.GetSectionEdgeSet();

  TopTools_ListIteratorOfListOfShape it;
  for (it.Initialize(SecEdgeSet); it.More(); it.Next()) {
    if (DSA.IsWire(it.Value())) {
      Wire = DSA.Wire(it.Value());
      BRepLib_MakeWire MW(TopoDS::Wire(Wire));
      mySectionEdgeList.Append(MW.Wire());
    }
    else {
      mySectionEdgeList.Append(it.Value());
    }
  }
}

  
