// Edge/face diagnostics for the OCCT3D edges, coedges, and seams article.
// Build with OCCT headers and link TKPrim, TKTopAlgo, TKBRep, TKGeomBase,
// TKG3d, TKG2d, TKMath, and TKernel. This example uses OCCT 8.x APIs.
#include <BRepAdaptor_Surface.hxx>
#include <BRepBuilderAPI_Copy.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <BRep_Builder.hxx>
#include <BRep_Tool.hxx>
#include <Geom2d_BezierCurve.hxx>
#include <Geom_Plane.hxx>
#include <NCollection_Array1.hxx>
#include <Precision.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <gp_Trsf.hxx>

#include <cmath>
#include <iomanip>
#include <iostream>
#include <stdexcept>

struct EdgeFaceSamples
{
  const char* Status = "Unchecked";
  int Count = 0;
  double First3d = 0.0, Last3d = 0.0;
  double First2d = 0.0, Last2d = 0.0;
  double MaxGap = 0.0, ParameterAtMax = 0.0;
  gp_Pnt2d UVStart, UVEnd;
};

// Compare an oriented edge's 3D curve with its PCurve on one face.
// This measures same-parameter agreement at samples. It does not certify
// agreement between samples, face validity, or connected wire traversal.
EdgeFaceSamples SampleEdgeOnFace(const TopoDS_Edge& theEdge,
                                const TopoDS_Face& theFace,
                                const int theIntervals = 32,
                                const double theParameterTolerance = Precision::PConfusion())
{
  EdgeFaceSamples aResult;
  if (theEdge.IsNull() || theFace.IsNull() || theIntervals < 1
      || !Precision::IsFinite(theParameterTolerance) || theParameterTolerance < 0.0)
  {
    aResult.Status = "Invalid input";
    return aResult;
  }
  if (theEdge.Orientation() != TopAbs_FORWARD && theEdge.Orientation() != TopAbs_REVERSED)
  {
    aResult.Status = "Internal or external use requires separate handling";
    return aResult;
  }

  TopLoc_Location aCurveLocation, aSurfaceLocation;
  const auto aCurve = BRep_Tool::Curve(theEdge, aCurveLocation,
                                      aResult.First3d, aResult.Last3d);
  if (aCurve.IsNull())
  {
    aResult.Status = "No stored 3D curve to compare";
    return aResult;
  }
  const auto aSurface = BRep_Tool::Surface(theFace, aSurfaceLocation);
  if (aSurface.IsNull())
  {
    aResult.Status = "Missing supporting surface";
    return aResult;
  }
  const auto aPCurve = BRep_Tool::CurveOnSurface(theEdge, theFace,
                                               aResult.First2d, aResult.Last2d);
  if (aPCurve.IsNull())
  {
    aResult.Status = "Missing PCurve";
    return aResult;
  }
  if (!Precision::IsFinite(aResult.First3d) || !Precision::IsFinite(aResult.Last3d)
      || !Precision::IsFinite(aResult.First2d) || !Precision::IsFinite(aResult.Last2d)
      || aResult.Last3d <= aResult.First3d || aResult.Last2d <= aResult.First2d)
  {
    aResult.Status = "Range requires separate handling";
    return aResult;
  }
  if (std::abs(aResult.First3d - aResult.First2d) > theParameterTolerance
      || std::abs(aResult.Last3d - aResult.Last2d) > theParameterTolerance)
  {
    aResult.Status = "Different parameter ranges";
    return aResult;
  }

  const bool isReversed = theEdge.Orientation() == TopAbs_REVERSED;
  aResult.UVStart = aPCurve->Value(isReversed ? aResult.Last2d : aResult.First2d);
  aResult.UVEnd = aPCurve->Value(isReversed ? aResult.First2d : aResult.Last2d);
  aResult.ParameterAtMax = aResult.First3d;
  for (int i = 0; i <= theIntervals; ++i)
  {
    const double aRatio = double(i) / double(theIntervals);
    const double aParameter = aResult.First3d * (1.0 - aRatio) + aResult.Last3d * aRatio;
    const gp_Pnt2d aUV = aPCurve->Value(aParameter);
    const gp_Pnt aCurvePoint =
      aCurve->Value(aParameter).Transformed(aCurveLocation.Transformation());
    const gp_Pnt aSurfacePoint =
      aSurface->Value(aUV.X(), aUV.Y()).Transformed(aSurfaceLocation.Transformation());
    const double aGap = aCurvePoint.Distance(aSurfacePoint);
    if (!Precision::IsFinite(aGap))
    {
      aResult.Status = "Non-finite evaluated distance";
      return aResult;
    }
    if (aGap > aResult.MaxGap)
    {
      aResult.MaxGap = aGap;
      aResult.ParameterAtMax = aParameter;
    }
    ++aResult.Count;
  }
  aResult.Status = "Sampled";
  return aResult;
}

namespace
{
void Require(const bool theCondition, const char* theMessage)
{
  if (!theCondition)
  {
    throw std::runtime_error(theMessage);
  }
}

void Print(const char* theName, const EdgeFaceSamples& theResult)
{
  std::cout << theName << ": " << theResult.Status;
  if (theResult.Count > 0)
  {
    std::cout << ", samples=" << theResult.Count
              << ", max gap=" << theResult.MaxGap
              << ", parameter=" << theResult.ParameterAtMax;
  }
  std::cout << '\n';
}

TopoDS_Edge MakePlanarExample(const TopoDS_Face& theFace, const bool theNonlinear)
{
  TopoDS_Edge anEdge = BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(1, 0, 0));
  NCollection_Array1<gp_Pnt2d> aPoles(3);
  aPoles.ChangeAt(0) = gp_Pnt2d(0, 0);
  aPoles.ChangeAt(1) = gp_Pnt2d(theNonlinear ? 0.0 : 0.5, 0);
  aPoles.ChangeAt(2) = gp_Pnt2d(1, 0);
  const occ::handle<Geom2d_Curve> aPCurve = new Geom2d_BezierCurve(aPoles);
  BRep_Builder aBuilder;
  aBuilder.UpdateEdge(anEdge, aPCurve, theFace, 1.e-7);
  aBuilder.Range(anEdge, 0.0, 1.0);
  aBuilder.SameRange(anEdge, true);
  aBuilder.SameParameter(anEdge, !theNonlinear);
  return anEdge;
}
} // namespace

int main()
{
  try
  {
    std::cout << std::setprecision(8);
    BRep_Builder aBuilder;
    TopoDS_Face aPlaneFace;
    aBuilder.MakeFace(aPlaneFace, new Geom_Plane(gp_Ax3()), 1.e-7);
    const TopoDS_Edge aLinear = MakePlanarExample(aPlaneFace, false);
    const TopoDS_Edge aNonlinear = MakePlanarExample(aPlaneFace, true);
    const auto aMatch = SampleEdgeOnFace(aLinear, aPlaneFace);
    const auto aMismatch = SampleEdgeOnFace(aNonlinear, aPlaneFace);
    Require(aMatch.Count == 33 && aMatch.MaxGap < 1.e-12, "Linear example failed");
    Require(aMismatch.Count == 33 && std::abs(aMismatch.MaxGap - 0.25) < 1.e-12,
            "Nonlinear example failed");
    Print("Matching curves", aMatch);
    Print("Nonlinear PCurve", aMismatch);

    gp_Trsf aTranslation;
    aTranslation.SetTranslation(gp_Vec(10, 20, 30));
    const TopLoc_Location aLocation(aTranslation);
    const auto aMoved = SampleEdgeOnFace(TopoDS::Edge(aLinear.Moved(aLocation)),
                                         TopoDS::Face(aPlaneFace.Moved(aLocation)));
    Require(aMoved.Count == 33 && aMoved.MaxGap < 1.e-12, "Located example failed");
    Print("Located matching curves", aMoved);

    const TopoDS_Shape aCylinder = BRepPrimAPI_MakeCylinder(2.0, 3.0).Shape();
    int aSeamUses = 0;
    for (TopExp_Explorer aFaces(aCylinder, TopAbs_FACE); aFaces.More(); aFaces.Next())
    {
      const TopoDS_Face aFace = TopoDS::Face(aFaces.Current().Oriented(TopAbs_FORWARD));
      if (BRepAdaptor_Surface(aFace).GetType() != GeomAbs_Cylinder)
      {
        continue;
      }
      for (TopExp_Explorer anEdges(aFace, TopAbs_EDGE); anEdges.More(); anEdges.Next())
      {
        const TopoDS_Edge anEdge = TopoDS::Edge(anEdges.Current());
        if (!BRep_Tool::IsClosed(anEdge, aFace))
        {
          continue;
        }
        const auto aSamples = SampleEdgeOnFace(anEdge, aFace);
        Require(aSamples.Count == 33 && aSamples.MaxGap < 1.e-12, "Cylinder seam failed");
        Print("Cylinder seam use", aSamples);
        std::cout << "  UV start=(" << aSamples.UVStart.X() << ',' << aSamples.UVStart.Y()
                  << "), end=(" << aSamples.UVEnd.X() << ',' << aSamples.UVEnd.Y() << ")\n";
        ++aSeamUses;
      }
    }
    Require(aSeamUses == 2, "Expected both seam uses");

    BRepBuilderAPI_Copy aCopy(aLinear, true, false);
    const TopoDS_Edge anIndependentEdge = TopoDS::Edge(aCopy.Shape());
    TopoDS_Edge anAlias = aLinear;
    aBuilder.SameParameter(anAlias, false);
    Require(!BRep_Tool::SameParameter(aLinear), "Assignment should share edge data");
    Require(BRep_Tool::SameParameter(anIndependentEdge), "Copied edge should be independent");
    Require(!aLinear.IsPartner(anIndependentEdge), "Copied topology should have new identity");
    std::cout << "Assignment shares edge data; explicit copy keeps independent flags.\n";
    return 0;
  }
  catch (const std::exception& anError)
  {
    std::cerr << anError.what() << '\n';
    return 1;
  }
}
