// Created on: 1994-12-23
// Created by: Remi LEQUETTE
// 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_Builder.hxx>
#include <BRep_Tool.hxx>
#include <BRepTools_Quilt.hxx>
#include <Geom2d_Curve.hxx>
#include <Standard_NoSuchObject.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Compound.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Iterator.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Shell.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopoDS_Wire.hxx>
#include <TopTools_ShapeMapHasher.hxx>
#include <NCollection_DataMap.hxx>
#include <NCollection_IndexedDataMap.hxx>
#include <NCollection_Map.hxx>

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

BRepTools_Quilt::BRepTools_Quilt()
    : hasCopy(false)
{
}

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

static bool NeedCopied(
  const TopoDS_Shape&                                                                    theShape,
  const NCollection_IndexedDataMap<TopoDS_Shape, TopoDS_Shape, TopTools_ShapeMapHasher>& myBounds)
{
  // test if the shape must be copied
  // i.e. it contains a bound subshape
  bool            IsCopied = false;
  TopoDS_Iterator itv(theShape);
  for (; itv.More(); itv.Next())
  {
    if (myBounds.Contains(itv.Value()))
    {
      IsCopied = true;
      break;
    }
  }
  return IsCopied;
}

static void CopyShape(
  const TopoDS_Edge&                                                               E,
  NCollection_IndexedDataMap<TopoDS_Shape, TopoDS_Shape, TopTools_ShapeMapHasher>& myBounds)
{
  TopoDS_Edge NE = E;
  NE.EmptyCopy();
  NE.Orientation(TopAbs_FORWARD);
  BRep_Builder B;
  // add the edges
  TopoDS_Iterator itv;
  itv.Initialize(E, false); // TCollection_DataMap
  for (; itv.More(); itv.Next())
  {
    const TopoDS_Shape& V = itv.Value();
    if (myBounds.Contains(V))
    {
      B.Add(NE, myBounds.FindFromKey(V).Oriented(V.Orientation()));
    }
    else
    {
      B.Add(NE, V);
    }
  }
  // set the 3d range
  double f, l;
  BRep_Tool::Range(E, f, l);
  B.Range(NE, f, l);
  myBounds.Add(E, NE.Oriented(TopAbs_FORWARD));
}

/*static void CopyShape(const TopoDS_Wire& W,NCollection_DataMap<TopoDS_Shape, TopoDS_Shape,
TopTools_ShapeMapHasher>& myBounds)
{
  TopoDS_Wire NW;
  B.MakeWire(NW);
  TopoDS_Iterator ite(W,false);
  for ( ; ite.More(); ite.Next()){
    const TopoDS_Edge& E = TopoDS::Edge(ite.Value());
    TopAbs_Orientation OE = E.Orientation();
    if (myBounds.IsBound(E)) {
      TopoDS_Edge& NE = TopoDS::Edge(myBounds(E));
      B.Add(NW,NE.Oriented(OE));
    }
    else
      B.Add(NW,E);
  }
  NW.Orientation(W.Orientation());
  myBounds.Bind(W,NW);
}*/
void BRepTools_Quilt::Add(const TopoDS_Shape& S)
{
  // std::cout <<" Version of sewing with free edges"<<std::endl;
  //  Binds all the faces of S
  //   - to the face itself if it is not copied
  //   - to the copy if it is copied
  if (myBounds.Contains(S))
  {
    return;
  }

  BRep_Builder B;
  for (TopExp_Explorer wex(S, TopAbs_WIRE, TopAbs_FACE); wex.More(); wex.Next())
  {
    myBounds.Add(wex.Current(), wex.Current());
  }

  for (TopExp_Explorer eex(S, TopAbs_EDGE, TopAbs_WIRE); eex.More(); eex.Next())
  {
    myBounds.Add(eex.Current(), eex.Current());
  }

  for (TopExp_Explorer vex(S, TopAbs_VERTEX, TopAbs_EDGE); vex.More(); vex.Next())
  {
    myBounds.Add(vex.Current(), vex.Current());
  }

  // explore the faces
  for (TopExp_Explorer fex(S, TopAbs_FACE); fex.More(); fex.Next())
  {

    // explore the edges of the face and try to copy them
    // if one edge is bound the face must be copied

    bool               copyFace = false;
    const TopoDS_Face& F        = TopoDS::Face(fex.Current());

    if (hasCopy)
    { // if their is no binding, do not test for copy

      for (TopExp_Explorer fed(F, TopAbs_EDGE); fed.More(); fed.Next())
      {

        if (myBounds.Contains(fed.Current()))
        {
          copyFace = true;
        }
        else
        {
          // test if the edge must be copied
          // i.e. it contains a bound vertex

          bool copyEdge = NeedCopied(fed.Current(), myBounds);
          // bool copyEdge = false;
          const TopoDS_Edge& E = TopoDS::Edge(fed.Current());

          // TopoDS_Iterator itv(E) ;
          // for ( ; itv.More(); itv.Next()) {
          // if (myBounds.IsBound(itv.Value())) {
          //	copyEdge = true;
          // break;
          //     }
          //  }

          if (copyEdge)
          {

            // copy of an edge

            copyFace = true;
            CopyShape(E, myBounds);
            // TopoDS_Edge NE = E; //gka version for free edges
            // NE.EmptyCopy();

            // NE.Orientation(TopAbs_FORWARD);
            //  add the edges
            // itv.Initialize(E,false) ;
            // for ( ; itv.More(); itv.Next()) {
            // const TopoDS_Shape& V = itv.Value();
            // if (myBounds.IsBound(V)) {
            //  B.Add(NE,myBounds(V).Oriented(V.Orientation()));
            //}
            // else {
            // B.Add(NE,V);
            //}
            //}
            // set the 3d range
            // double f,l;
            // BRep_Tool::Range(E,f,l);
            // B.Range(NE,f,l);

            // myBounds.Bind(E,NE.Oriented(TopAbs_FORWARD));
          }
        }
      }
    }

    // NF will be the copy of F or F itself
    TopoDS_Face NF = F;

    if (copyFace)
    {

      // copy of a face

      NF.EmptyCopy();
      NF.Orientation(TopAbs_FORWARD);

      for (TopoDS_Iterator itw(F, false); itw.More(); itw.Next())
      {
        const TopoDS_Wire& W = TopoDS::Wire(itw.Value());

        TopoDS_Wire NW;
        B.MakeWire(NW);
        TopoDS_Iterator ite(W, false);
        double          UFirst, ULast;

        // Reconstruction of wires.

        for (; ite.More(); ite.Next())
        {
          const TopoDS_Edge& E  = TopoDS::Edge(ite.Value());
          TopAbs_Orientation OE = E.Orientation();
          if (myBounds.Contains(E))
          {
            const TopoDS_Edge& NE = TopoDS::Edge(myBounds.FindFromKey(E));
            // pcurve.
            if (NE.Orientation() == TopAbs_FORWARD)
            {
              B.UpdateEdge(NE,
                           BRep_Tool::CurveOnSurface(E, F, UFirst, ULast),
                           F,
                           BRep_Tool::Tolerance(E));
            }
            else
            {
              // If NE is REVERSED
              // => the 3D curves do not have the same orientation.
              // (This is a convention, cf. BRepTools_Quilt.cdl and the Bind
              // method.)
              // => the PCurve of E on F must be reversed.

              OE                            = TopAbs::Reverse(OE);
              occ::handle<Geom2d_Curve> CE  = BRep_Tool::CurveOnSurface(E, F, UFirst, ULast);
              occ::handle<Geom2d_Curve> NCE = CE->Reversed();
              B.UpdateEdge(NE, NCE, F, BRep_Tool::Tolerance(E));
              double tmp = UFirst;
              UFirst     = CE->ReversedParameter(ULast);
              ULast      = CE->ReversedParameter(tmp);
            }
            // pcurve range
            B.Range(NE, F, UFirst, ULast);

            B.Add(NW, NE.Oriented(OE));
          }
          else
          {
            B.Add(NW, E);
          }
        }
        NW.Orientation(W.Orientation());
        B.Add(NF, NW);
      }
      NF.Orientation(F.Orientation());
    }

    // binds the face to itself or its copy
    myBounds.Add(F, NF);
  }
}

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

void BRepTools_Quilt::Bind(const TopoDS_Vertex& Vold, const TopoDS_Vertex& Vnew)
{
  if (!myBounds.Contains(Vold))
  {
    myBounds.Add(Vold, Vnew);
  }
}

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

void BRepTools_Quilt::Bind(const TopoDS_Edge& Eold, const TopoDS_Edge& Enew)
{
  if (!myBounds.Contains(Eold))
  {
    TopoDS_Edge ENew = Enew;
    if (IsCopied(Enew))
    {
      ENew = TopoDS::Edge(Copy(Enew));
      ENew.Orientation(Enew.Orientation());
    }

    if (Eold.Orientation() != ENew.Orientation())
    {
      myBounds.Add(Eold.Oriented(TopAbs_FORWARD), ENew.Oriented(TopAbs_REVERSED));
    }
    else
    {
      myBounds.Add(Eold.Oriented(TopAbs_FORWARD), ENew.Oriented(TopAbs_FORWARD));
    }
    // if new binding bind also the vertices
    TopoDS_Iterator itold(Eold);
    while (itold.More())
    {
      if (!myBounds.Contains(itold.Value()))
      {
        // find the vertex of Enew with same orientation
        TopAbs_Orientation anOrien = itold.Value().Orientation();
        TopoDS_Iterator    itnew(ENew);
        while (itnew.More())
        {
          if (itnew.Value().Orientation() == anOrien)
          {
            TopoDS_Vertex VNew = TopoDS::Vertex(itnew.Value());
            if (IsCopied(VNew))
            {
              // if VNew has been copied take the copy
              VNew = TopoDS::Vertex(Copy(VNew));
            }
            myBounds.Add(itold.Value(), VNew);
            break;
          }
          itnew.Next();
        }
      }
      itold.Next();
    }
    hasCopy = true;
  }
}

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

bool BRepTools_Quilt::IsCopied(const TopoDS_Shape& S) const
{
  if (myBounds.Contains(S))
  {
    return !S.IsSame(myBounds.FindFromKey(S));
  }
  else
  {
    return false;
  }
}

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

const TopoDS_Shape& BRepTools_Quilt::Copy(const TopoDS_Shape& S) const
{
  Standard_NoSuchObject_Raise_if(!IsCopied(S), "BRepTools_Quilt::Copy");
  return myBounds.FindFromKey(S);
}

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

TopoDS_Shape BRepTools_Quilt::Shells() const
{
  // Outline of the algorithm
  //
  // In the map M we bind the free edges to their shells
  // We explore all the faces in myBounds
  // For each one we search the edges in the map and either :
  //
  // - Start a new shell if no edge is a free edge.
  // - Add the face to an existing shell
  // - Connect other shells if the face touch more than one shell

  // In the Map M the Shell is bound with the relative orientation of E
  // in the shell
  // In the map MF we binb the face to its shell.
  // In the Map MF the Shell is bound with the relative orientation of F
  // in the shell

  NCollection_DataMap<TopoDS_Shape, TopoDS_Shape, TopTools_ShapeMapHasher> M, MF;
  BRep_Builder                                                             B;
  TopoDS_Compound                                                          result;

  B.MakeCompound(result);

  NCollection_Map<TopoDS_Shape, TopTools_ShapeMapHasher> MapOtherShape; // gka
  NCollection_Map<TopoDS_Shape, TopTools_ShapeMapHasher> EdgesFaces;

  // loop on the face in myBounds
  // NCollection_DataMap<TopoDS_Shape, TopoDS_Shape, TopTools_ShapeMapHasher>::Iterator
  // it(myBounds);

  // while (it.More())
  for (int ii = 1; ii <= myBounds.Extent(); ii++)
  {
    const TopoDS_Shape& Shape = myBounds.FindFromIndex(ii); // it.Value();
    if (Shape.ShapeType() == TopAbs_FACE)
    {
      for (TopExp_Explorer aExpEdg(Shape, TopAbs_EDGE); aExpEdg.More(); aExpEdg.Next())
      { // gka
        EdgesFaces.Add(aExpEdg.Current());
      }

      TopoDS_Shell       SH;
      TopAbs_Orientation NewO;

      TopExp_Explorer itf1(Shape, TopAbs_EDGE);
      for (; itf1.More(); itf1.Next())
      {
        const TopoDS_Shape& E = itf1.Current();
        if (M.IsBound(E))
        {
          SH = TopoDS::Shell(M(E));
          if (SH.Orientation() == E.Orientation())
          {
            NewO = TopAbs::Reverse(Shape.Orientation());
          }
          else
          {
            NewO = Shape.Orientation();
          }

          MF.Bind(Shape, SH.Oriented(NewO));
          break;
        }
      }

      if (SH.IsNull())
      {
        // Create a new shell, closed. Add it to the result.
        B.MakeShell(SH);
        SH.Closed(true);
        B.Add(result, SH);
        MF.Bind(Shape, SH.Oriented(Shape.Orientation()));
      }

      // Add the face to the shell
      SH.Free(true);
      //      B.Add(SH.Oriented(TopAbs_FORWARD), F .Oriented(MF(F).Orientation()));
      TopoDS_Shape arefShape = SH.Oriented(TopAbs_FORWARD);
      B.Add(arefShape, Shape.Oriented(MF(Shape).Orientation()));

      TopExp_Explorer itf(Shape.Oriented(TopAbs_FORWARD), TopAbs_EDGE);

      for (; itf.More(); itf.Next())
      {
        const TopoDS_Edge& E = TopoDS::Edge(itf.Current());

        if (M.IsBound(E))
        {
          const TopoDS_Shape oldShell = M(E);
          if (!oldShell.IsSame(SH))
          {
            // Fuse the old shell with the new one
            // Compare the orientation of E in SH and in oldshell.
            TopAbs_Orientation anOrien = E.Orientation();
            if (MF(Shape).Orientation() == TopAbs_REVERSED)
            {
              anOrien = TopAbs::Reverse(anOrien);
            }

            bool Rev = (anOrien == oldShell.Orientation());
            // if rev = True oldShell has to be reversed.

            // Add the faces of oldShell in SH.
            for (TopoDS_Iterator its(oldShell); its.More(); its.Next())
            {
              const TopoDS_Face  Fo = TopoDS::Face(its.Value());
              TopAbs_Orientation NewOFo;
              // update the orientation of Fo in SH.
              if (Rev)
              {
                NewOFo = TopAbs::Reverse(MF(Fo).Orientation());
              }
              else
              {
                NewOFo = MF(Fo).Orientation();
              }

              MF.Bind(Fo, SH.Oriented(NewOFo));
              //	      B.Add  (SH.Oriented(TopAbs_FORWARD),Fo.Oriented(NewO));
              TopoDS_Shape arefShapeFo = SH.Oriented(TopAbs_FORWARD);
              B.Add(arefShapeFo, Fo.Oriented(NewOFo));
            }
            // Rebind the free edges of the old shell to the new shell
            // gka BUG 6491
            TopExp_Explorer aexp(SH, TopAbs_EDGE);
            for (; aexp.More(); aexp.Next())
            {
              // for (NCollection_DataMap<TopoDS_Shape, TopoDS_Shape,
              // TopTools_ShapeMapHasher>::Iterator itm(M);
              //		 itm.More(); ) {
              if (!M.IsBound(aexp.Current()))
              {
                continue;
              }
              const TopoDS_Shape& ae = aexp.Current();
              TopoDS_Shape        as = M.Find(ae);
              if (as.IsSame(oldShell))
              {
                // update the orientation of free edges in SH.
                if (Rev)
                {
                  NewO = TopAbs::Reverse(as.Orientation());
                }
                else
                {
                  NewO = as.Orientation();
                }

                M.Bind(ae, SH.Oriented(NewO));
              }
            }
            // remove the old shell from the result
            B.Remove(result, oldShell.Oriented(TopAbs_FORWARD));
          }
          // Test if SH is always orientable.
          TopAbs_Orientation anOrien = E.Orientation();
          if (MF(Shape).Orientation() == TopAbs_REVERSED)
          {
            anOrien = TopAbs::Reverse(anOrien);
          }

          if (M(E).Orientation() == anOrien)
          {
            SH.Orientable(false);
          }

          // remove the edge from M (no more a free edge)
          M.UnBind(E);
        }
        else
        {
          NewO = E.Orientation();
          if (MF(Shape).Orientation() == TopAbs_REVERSED)
          {
            NewO = TopAbs::Reverse(NewO);
          }
          if (!E.IsNull())
          {
            M.Bind(E, SH.Oriented(NewO));
          }
          else
          {
            continue;
          }
        }
      }

      // freeze the shell
      SH.Free(false);
    }
    else
    {
      MapOtherShape.Add(Shape);
    }

    // it.Next();
  }

  // Unclose all shells having free edges
  for (NCollection_DataMap<TopoDS_Shape, TopoDS_Shape, TopTools_ShapeMapHasher>::Iterator it(M);
       it.More();
       it.Next())
  {
    TopoDS_Shape S = it.Value();
    S.Closed(false);
  }

  NCollection_Map<TopoDS_Shape, TopTools_ShapeMapHasher>::Iterator itother(
    MapOtherShape); // gka version for free edges
  for (; itother.More(); itother.Next())
  {
    if (!EdgesFaces.Contains(itother.Key()) && myBounds.Contains(itother.Key()))
    {
      TopoDS_Shape aSh = myBounds.FindFromKey(itother.Key());
      B.Add(result, aSh);
    }
  }
  return result;
}
