// Created on: 1994-10-07
// Created by: Jean Yves LEBEY
// Copyright (c) 1994-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 <BRep_Tool.hxx>
#include <BRepIntCurveSurface_Inter.hxx>
#include <Geom_Curve.hxx>
#include <gp_Pnt.hxx>
#include <gp_Pnt2d.hxx>
#include <IntCurveSurface_IntersectionPoint.hxx>
#include <IntCurveSurface_TransitionOnCurve.hxx>
#include <Precision.hxx>
#include <Standard_ProgramError.hxx>
#include <TopLoc_Location.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Shape.hxx>
#include <TopOpeBRep_FaceEdgeIntersector.hxx>
#include <TopOpeBRepDS_Transition.hxx>
#include <TopOpeBRepTool_ShapeTool.hxx>

#ifdef OCCT_DEBUG
  #include <TopAbs.hxx>
extern bool TopOpeBRep_GettraceFITOL();
extern bool TopOpeBRep_GettraceSAVFF();
  #include <TCollection_AsciiString.hxx>
  #include <Standard_CString.hxx>
  #include <BRepTools.hxx>

static void SAVFE(const TopoDS_Face& F1, const TopoDS_Edge& E)
{
  TCollection_AsciiString aname_1("FE_face"), aname_2("FE_edge");
  const char *            name_1 = aname_1.ToCString(), name_2 = aname_2.ToCString();
  std::cout << "FaceEdgeIntersector : " << name_1 << "," << name_2 << std::endl;
  BRepTools::Write(F1, name_1);
  BRepTools::Write(E, name_2);
}

extern bool TopOpeBRepTool_GettraceKRO();
  #include <TopOpeBRepTool_KRO.hxx>
Standard_EXPORT TOPKRO KRO_DSFILLER_INTFE("intersection face/edge");
#endif

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

TopOpeBRep_FaceEdgeIntersector::TopOpeBRep_FaceEdgeIntersector()
{
  ResetIntersection();
}

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

void TopOpeBRep_FaceEdgeIntersector::ResetIntersection()
{
  mySequenceOfPnt.Clear();
  mySequenceOfState.Clear();
  myNbPoints         = 0;
  myIntersectionDone = false;
}

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

void TopOpeBRep_FaceEdgeIntersector::Perform(const TopoDS_Shape& SF, const TopoDS_Shape& SE)
{
  ResetIntersection();
  if (!myForceTolerance)
    ShapeTolerances(SF, SE);
  myTol = BRep_Tool::Tolerance(TopoDS::Edge(SE));
#ifdef OCCT_DEBUG
  if (TopOpeBRep_GettraceFITOL())
    std::cout << "Perform : myTol = " << myTol << std::endl;
#endif

  myFace = TopoDS::Face(SF);
  myFace.Orientation(TopAbs_FORWARD);
  myEdge = TopoDS::Edge(SE);
  myEdge.Orientation(TopAbs_FORWARD);

#ifdef OCCT_DEBUG
  if (TopOpeBRep_GettraceSAVFF())
    SAVFE(myFace, myEdge);
#endif

  double                        f, l;
  TopLoc_Location               loc;
  const occ::handle<Geom_Curve> C = BRep_Tool::Curve(myEdge, loc, f, l);

  occ::handle<Geom_Geometry> GGao1  = C->Transformed(loc.Transformation());
  occ::handle<Geom_Curve>*   PGCao1 = (occ::handle<Geom_Curve>*)&GGao1;
  myCurve.Load(*PGCao1, f, l);

#ifdef OCCT_DEBUG
  if (TopOpeBRepTool_GettraceKRO())
    KRO_DSFILLER_INTFE.Start();
#endif

  BRepIntCurveSurface_Inter FEINT;
  FEINT.Init(myFace, myCurve, myTol);

#ifdef OCCT_DEBUG
  if (TopOpeBRepTool_GettraceKRO())
    KRO_DSFILLER_INTFE.Stop();
#endif

  for (FEINT.Init(myFace, myCurve, myTol); FEINT.More(); FEINT.Next())
  {
    mySequenceOfPnt.Append(FEINT.Point());
    int i = (FEINT.State() == TopAbs_IN) ? 0 : 1;
    mySequenceOfState.Append(i);
  }

  myNbPoints         = mySequenceOfPnt.Length();
  myIntersectionDone = true;
}

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

bool TopOpeBRep_FaceEdgeIntersector::IsEmpty()
{
  bool b = myNbPoints == 0;
  return b;
}

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

const TopoDS_Shape& TopOpeBRep_FaceEdgeIntersector::Shape(const int Index) const
{
  if (Index == 1)
    return myFace;
  else if (Index == 2)
    return myEdge;
  else
    throw Standard_ProgramError("TopOpeBRep_FaceEdgeIntersector::Shape");
}

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

void TopOpeBRep_FaceEdgeIntersector::ForceTolerance(const double Tol)
{
  myTol            = Tol;
  myForceTolerance = true;

#ifdef OCCT_DEBUG
  if (TopOpeBRep_GettraceFITOL())
    std::cout << "ForceTolerance : myTol = " << myTol << std::endl;
#endif
}

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

double TopOpeBRep_FaceEdgeIntersector::Tolerance() const
{
  return myTol;
}

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

int TopOpeBRep_FaceEdgeIntersector::NbPoints() const
{
  int n = myNbPoints;
  return n;
}

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

void TopOpeBRep_FaceEdgeIntersector::InitPoint()
{
  myPointIndex = 1;
}

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

bool TopOpeBRep_FaceEdgeIntersector::MorePoint() const
{
  bool b = myPointIndex <= myNbPoints;
  return b;
}

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

void TopOpeBRep_FaceEdgeIntersector::NextPoint()
{
  myPointIndex++;
}

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

gp_Pnt TopOpeBRep_FaceEdgeIntersector::Value() const
{
  const IntCurveSurface_IntersectionPoint& IP = mySequenceOfPnt(myPointIndex);
  const gp_Pnt&                            P  = IP.Pnt();
  return P;
}

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

double TopOpeBRep_FaceEdgeIntersector::Parameter() const
{
  const IntCurveSurface_IntersectionPoint& IP = mySequenceOfPnt(myPointIndex);
  double                                   p  = IP.W();
  return p;
}

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

void TopOpeBRep_FaceEdgeIntersector::UVPoint(gp_Pnt2d& P2d) const
{
  const IntCurveSurface_IntersectionPoint& IP = mySequenceOfPnt(myPointIndex);
  double                                   u  = IP.U();
  double                                   v  = IP.V();
  P2d.SetCoord(u, v);
}

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

TopAbs_State TopOpeBRep_FaceEdgeIntersector::State() const
{
  int          i = mySequenceOfState(myPointIndex);
  TopAbs_State s = (i == 0) ? TopAbs_IN : TopAbs_ON;
  return s;
}

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

TopOpeBRepDS_Transition TopOpeBRep_FaceEdgeIntersector::Transition(
  const int                Index,
  const TopAbs_Orientation FaceOrientation) const
{
  //  TopAbs_ShapeEnum onB = TopAbs_FACE, onA = TopAbs_FACE; // bidon
  //  if ((FaceOrientation == TopAbs_INTERNAL) ||
  //      (FaceOrientation == TopAbs_EXTERNAL)) {
  //    TopOpeBRepDS_Transition TR(TopAbs_IN,TopAbs_IN,onB,onA); // IN bidon
  //    TR.Set(FaceOrientation);
  //    return TR;
  //  }

  TopAbs_State stB, stA;

  const IntCurveSurface_IntersectionPoint& IP = mySequenceOfPnt(myPointIndex);

  if (Index == 2)
  { //--   Edge In <=>   Rentre ds la matiere face
    switch (IP.Transition())
    {
      case IntCurveSurface_In:
        stB = TopAbs_OUT;
        stA = TopAbs_IN;
        break;
      case IntCurveSurface_Out:
        stB = TopAbs_IN;
        stA = TopAbs_OUT;
        break;
      default:
        stB = TopAbs_IN;
        stA = TopAbs_IN;
        break;
    }

    TopOpeBRepDS_Transition TR;
    TopAbs_ShapeEnum        onB = TopAbs_FACE, onA = TopAbs_FACE;
    if (FaceOrientation == TopAbs_FORWARD)
      TR.Set(stB, stA, onB, onA);
    else if (FaceOrientation == TopAbs_REVERSED)
      TR.Set(stA, stB, onA, onB);
    else if (FaceOrientation == TopAbs_EXTERNAL)
      TR.Set(TopAbs_OUT, TopAbs_OUT, onA, onB);
    else if (FaceOrientation == TopAbs_INTERNAL)
      TR.Set(TopAbs_IN, TopAbs_IN, onA, onB);
    return TR;
  }

  else if (Index == 1)
  { //-- Face On est toujours ds la face .
    switch (IP.Transition())
    {
      case IntCurveSurface_In:
        stB = stA = TopAbs_IN;
        break;
      case IntCurveSurface_Out:
        stB = stA = TopAbs_IN;
        break;
      default:
        stB = stA = TopAbs_IN;
        break;
    }
    TopAbs_ShapeEnum        onB = TopAbs_FACE, onA = TopAbs_FACE;
    TopOpeBRepDS_Transition TR;
    TR.Set(stB, stA, onB, onA);
    return TR;
  }

  else
    throw Standard_ProgramError("FEINT Transition Index");
}

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

bool TopOpeBRep_FaceEdgeIntersector::IsVertex(const TopoDS_Shape& S,
                                              const gp_Pnt&       P,
                                              const double        Tol,
                                              TopoDS_Vertex&      VR)
{
  bool isv = false;
  VR       = myNullVertex;

  double Tol2 = Tol * Tol;
  for (myVertexExplorer.Init(S, TopAbs_VERTEX); myVertexExplorer.More(); myVertexExplorer.Next())
  {
    const TopoDS_Shape&  SS = myVertexExplorer.Current();
    const TopoDS_Vertex& VV = TopoDS::Vertex(SS);
    gp_Pnt               PV = BRep_Tool::Pnt(VV);
    isv                     = P.SquareDistance(PV) < Tol2;
    if (isv)
    {
      VR = VV;
    }
  }

  return isv;
}

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

bool TopOpeBRep_FaceEdgeIntersector::IsVertex(const int I, TopoDS_Vertex& VR)
{
  bool   isv = false;
  gp_Pnt P   = Value();
  if (I == 1)
    isv = IsVertex(myFace, P, myTol, VR);
  else if (I == 2)
    isv = IsVertex(myEdge, P, myTol, VR);
  return isv;
}

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

int TopOpeBRep_FaceEdgeIntersector::Index() const
{
#ifdef OCCT_DEBUG
  return myPointIndex;
#else
  return 0;
#endif
}

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

void TopOpeBRep_FaceEdgeIntersector::ShapeTolerances(const TopoDS_Shape& S1, const TopoDS_Shape& S2)
{
  myTol            = std::max(ToleranceMax(S1, TopAbs_EDGE), ToleranceMax(S2, TopAbs_EDGE));
  myForceTolerance = false;

#ifdef OCCT_DEBUG
  if (TopOpeBRep_GettraceFITOL())
  {
    std::cout << "ShapeTolerances on S1 = ";
    TopAbs::Print(S1.ShapeType(), std::cout);
    std::cout << " S2 = ";
    TopAbs::Print(S2.ShapeType(), std::cout);
    std::cout << " : myTol = " << myTol << std::endl;
  }
#endif
}

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

double TopOpeBRep_FaceEdgeIntersector::ToleranceMax(const TopoDS_Shape&    S,
                                                    const TopAbs_ShapeEnum T) const
{
  TopExp_Explorer e(S, T);
  if (!e.More())
    return Precision::Intersection();
  else
  {
    double tol = RealFirst();
    for (; e.More(); e.Next())
      tol = std::max(tol, TopOpeBRepTool_ShapeTool::Tolerance(e.Current()));
    return tol;
  }
}
