// 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_Builder.hxx>
#include <BRep_TEdge.hxx>
#include <BRep_Tool.hxx>
#include <Geom2d_Curve.hxx>
#include <Geom_Curve.hxx>
#include <Geom_Geometry.hxx>
#include <Geom_Plane.hxx>
#include <Geom_Surface.hxx>
#include <gp_Pnt.hxx>
#include <ShapeUpgrade_RemoveLocations.hxx>
#include <Standard_Type.hxx>
#include <TopAbs_ShapeEnum.hxx>
#include <TopExp.hxx>
#include <TopLoc_Location.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Iterator.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Vertex.hxx>

IMPLEMENT_STANDARD_RTTIEXT(ShapeUpgrade_RemoveLocations, Standard_Transient)

// #include <ShapeUpgrade_DataMapOfShapeListOfTransient.hxx>
//=================================================================================================

ShapeUpgrade_RemoveLocations::ShapeUpgrade_RemoveLocations()
{
  myLevelRemoving = TopAbs_SHAPE;
}

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

bool ShapeUpgrade_RemoveLocations::Remove(const TopoDS_Shape& theShape)
{
  const TopoDS_Shape& aShape   = theShape;
  myShape                      = aShape;
  TopAbs_ShapeEnum shtype      = theShape.ShapeType();
  bool             isRemoveLoc = ((shtype != TopAbs_COMPOUND && myLevelRemoving == TopAbs_SHAPE)
                      || ((int)myLevelRemoving <= ((int)shtype)));
  TopoDS_Shape     S;
  bool             isDone = MakeNewShape(theShape, S, myShape, isRemoveLoc);

  return isDone;
}

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

static bool RebuildShape(const TopoDS_Face& theFace, TopoDS_Face& theNewFace)
{
  BRep_Builder              aB;
  TopLoc_Location           aLoc;
  occ::handle<Geom_Surface> aSurf     = BRep_Tool::Surface(theFace, aLoc);
  bool                      isRebuild = false;
  if (!aLoc.IsIdentity())
  {
    occ::handle<Geom_Surface> anewSurf =
      occ::down_cast<Geom_Surface>(aSurf->Transformed(aLoc.Transformation()));
    aB.MakeFace(theNewFace, anewSurf, BRep_Tool::Tolerance(theFace));
    isRebuild = true;
  }
  return isRebuild;
}

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

static bool RebuildShape(const TopoDS_Edge& theEdge,
                         TopoDS_Edge&       theNewEdge,
                         const TopoDS_Face& theFace,
                         TopoDS_Face&       theNewFace,
                         bool               isBound)
{
  bool         isRebuild = false;
  BRep_Builder aB;
  if (!isBound)
  {
    occ::handle<Geom_Curve> C3d;
    TopLoc_Location         aLoc;
    double                  First3d, Last3d;
    C3d = BRep_Tool::Curve(theEdge, aLoc, First3d, Last3d);
    aB.MakeEdge(theNewEdge);
    if (!C3d.IsNull())
    {
      if (!aLoc.IsIdentity())
      {
        occ::handle<Geom_Curve> anewC3d =
          occ::down_cast<Geom_Curve>(C3d->Transformed(aLoc.Transformation()));
        C3d = anewC3d;
      }

      aB.UpdateEdge(theNewEdge, C3d, BRep_Tool::Tolerance(theEdge));
      aB.Range(theNewEdge, First3d, Last3d);
    }
    theNewEdge.Orientation(theEdge.Orientation());
    if (BRep_Tool::Degenerated(theEdge))
    {
      aB.Degenerated(theNewEdge, true);
    }
    isRebuild = true;
  }
  if (!theFace.IsNull())
  {
    occ::handle<Geom_Surface> aSurf = BRep_Tool::Surface(theFace);
    if (!aSurf->IsKind(STANDARD_TYPE(Geom_Plane)))
    {
      occ::handle<Geom2d_Curve> c2d, c2d1;
      double                    First2d, Last2d;

      c2d = BRep_Tool::CurveOnSurface(theEdge, theFace, First2d, Last2d);
      if (BRep_Tool::IsClosed(theEdge, theFace))
      {
        if (!BRep_Tool::IsClosed(theNewEdge, theNewFace))
        {
          TopoDS_Edge tmpE          = TopoDS::Edge(theEdge.Reversed());
          c2d1                      = BRep_Tool::CurveOnSurface(tmpE, theFace, First2d, Last2d);
          TopAbs_Orientation OrEdge = theNewEdge.Orientation();

          if (theFace.Orientation() == TopAbs_REVERSED)
          {
            OrEdge = (OrEdge == TopAbs_FORWARD ? TopAbs_REVERSED : TopAbs_FORWARD);
          }

          if (OrEdge == TopAbs_FORWARD)
          {
            aB.UpdateEdge(theNewEdge, c2d, c2d1, theNewFace, 0);
          }
          else
          {
            aB.UpdateEdge(theNewEdge, c2d1, c2d, theNewFace, 0);
          }
        }
      }
      else
      {
        aB.UpdateEdge(theNewEdge, c2d, theNewFace, 0);
      }

      if (!c2d.IsNull() || !c2d1.IsNull())
      {
        aB.Range(theNewEdge, theNewFace, First2d, Last2d);
      }
    }
  }
  return isRebuild;
}

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

static bool RebuildShape(const TopoDS_Vertex& theVertex, TopoDS_Vertex& theNewVertex)
{
  BRep_Builder aB;
  aB.MakeVertex(theNewVertex);
  theNewVertex.Orientation(theVertex.Orientation());
  gp_Pnt p1 = BRep_Tool::Pnt(theVertex);
  aB.UpdateVertex(theNewVertex, p1, BRep_Tool::Tolerance(theVertex));
  return true;
}

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

bool ShapeUpgrade_RemoveLocations::MakeNewShape(const TopoDS_Shape& theShape,
                                                const TopoDS_Shape& theAncShape,
                                                TopoDS_Shape&       theNewShape,
                                                const bool          theRemoveLoc)
{
  bool             isDone = false;
  TopoDS_Shape     aNewShape;
  TopAbs_ShapeEnum shtype = theShape.ShapeType();
  BRep_Builder     aB;
  TopoDS_Shape     aShape = theShape;
  if (!theRemoveLoc && !theShape.Location().IsIdentity())
  {
    TopLoc_Location nulLoc;
    aShape.Location(nulLoc);
  }
  bool isBound = myMapNewShapes.IsBound(aShape);
  if (isBound)
  {
    aNewShape = myMapNewShapes.Find(aShape);
    aNewShape.Orientation(theShape.Orientation());
    if (!theRemoveLoc && !theShape.Location().IsIdentity())
    {
      const TopLoc_Location& aL = theShape.Location();
      aNewShape.Location(aL);
    }
    if (shtype != TopAbs_EDGE)
    {
      theNewShape = aNewShape;
      return true;
    }
  }

  bool isRemoveLoc = theRemoveLoc;
  if (!theRemoveLoc)
  {
    isRemoveLoc = ((shtype != TopAbs_COMPOUND && myLevelRemoving == TopAbs_SHAPE)
                   || ((int)myLevelRemoving <= ((int)shtype)));
  }

  bool         aRebuild   = false;
  TopoDS_Shape anAncShape = theAncShape;
  if (shtype == TopAbs_FACE)
  {
    anAncShape = aShape;
  }
  if (isRemoveLoc
      && (!aShape.Location().IsIdentity() || shtype == TopAbs_EDGE || shtype == TopAbs_FACE))
  {

    // Rebuild geometry for shape with location.
    if (shtype == TopAbs_FACE)
    {
      TopoDS_Face anewFace;
      TopoDS_Face oldFace = TopoDS::Face(aShape);
      aRebuild            = RebuildShape(oldFace, anewFace);
      if (aRebuild)
      {
        aNewShape = anewFace;
        myMapNewShapes.Bind(oldFace, aNewShape);
      }
    }
    else if (shtype == TopAbs_EDGE)
    {
      TopoDS_Edge oldEdge = TopoDS::Edge(aShape);
      TopoDS_Edge anewEdge;
      TopoDS_Face F, newFace;

      if (!anAncShape.IsNull())
      {
        F       = TopoDS::Face(anAncShape);
        newFace = F;
        if (myMapNewShapes.IsBound(F))
        {
          newFace = TopoDS::Face(myMapNewShapes.Find(F));
        }
      }
      if (isBound)
      {
        anewEdge = TopoDS::Edge(aNewShape);
      }
      aRebuild  = RebuildShape(oldEdge, anewEdge, F, newFace, isBound);
      aNewShape = anewEdge;
    }
    else if (shtype == TopAbs_VERTEX)
    {
      TopoDS_Vertex aVnew;
      TopoDS_Vertex aV = TopoDS::Vertex(aShape);
      aRebuild         = RebuildShape(aV, aVnew);
      if (aRebuild)
      {
        aNewShape = aVnew;
      }
    }
  }
  isDone = aRebuild;

  // Removing location from sub-shapes in dependence of LevelRemoving and re-building shape.

  if (!isBound)
  {
    if (!aRebuild)
    {
      aNewShape = theShape.EmptyCopied();
      // it is safe to simply copy Closed flag since this operation does not change topology
      aNewShape.Closed(theShape.Closed());
    }
    TopLoc_Location oldLoc, nullloc;
    oldLoc = theShape.Location();
    if (!oldLoc.IsIdentity())
    {
      aNewShape.Location(nullloc);
    }
    TopAbs_Orientation orient = theShape.Orientation();
    aNewShape.Orientation(TopAbs_FORWARD);
    TopoDS_Iterator aIt(aShape, false, isRemoveLoc);
    for (; aIt.More(); aIt.Next())
    {
      const TopoDS_Shape& subshape = aIt.Value();
      TopoDS_Shape        anewsubshape;
      bool isDoneSubShape = MakeNewShape(subshape, anAncShape, anewsubshape, isRemoveLoc);
      isDone              = (isDone || isDoneSubShape);
      aB.Add(aNewShape, anewsubshape);
    }
    if (isDone)
    {
      aNewShape.Orientation(orient);
    }
    else
    {
      aNewShape = aShape;
    }
    myMapNewShapes.Bind(aShape, aNewShape);
    if (!theRemoveLoc && !oldLoc.IsIdentity())
    {
      aNewShape.Location(oldLoc);
    }
  }
  theNewShape = aNewShape;

  return (isDone || isBound);
}
