// Created on: 1996-09-05
// Created by: Yves FRICAUD
// Copyright (c) 1996-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 <BRep_TVertex.hxx>
#include <BRepAlgo_AsDes.hxx>
#include <BRepAlgo_Loop.hxx>
#include <BRepOffset_Analyse.hxx>
#include <BRepOffset_MakeLoops.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Iterator.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopTools_MapOfShape.hxx>

#include <stdio.h>
#ifdef DRAW
  #include <DBRep.hxx>
Standard_Integer        NbF    = 1;
static Standard_Boolean Affich = Standard_False;
#endif

BRepOffset_MakeLoops::BRepOffset_MakeLoops() {}

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

void BRepOffset_MakeLoops::Build(const TopTools_ListOfShape&   LF,
                                 const Handle(BRepAlgo_AsDes)& AsDes,
                                 BRepAlgo_Image&               Image,
                                 BRepAlgo_Image&               theImageVV,
                                 const Message_ProgressRange&  theRange)
{
  TopTools_ListIteratorOfListOfShape it(LF);
  TopTools_ListIteratorOfListOfShape itl, itLCE;
  BRepAlgo_Loop                      Loops;
  Loops.VerticesForSubstitute(myVerVerMap);
  Loops.SetImageVV(theImageVV);
  Message_ProgressScope aPSOuter(theRange, NULL, 2);
  Message_ProgressScope aPS1(aPSOuter.Next(), "Init loops", LF.Size());
  for (; it.More(); it.Next(), aPS1.Next())
  {
    if (!aPS1.More())
    {
      return;
    }
    const TopoDS_Face& F = TopoDS::Face(it.Value());
    //---------------------------
    // Initialization of Loops.
    //---------------------------
    Loops.Init(F);
    //-----------------------------
    // return edges of F.
    //-----------------------------
    const TopTools_ListOfShape& LE = AsDes->Descendant(F);
    TopTools_ListOfShape        AddedEdges;

    for (itl.Initialize(LE); itl.More(); itl.Next())
    {
      TopoDS_Edge E = TopoDS::Edge(itl.Value());
      if (Image.HasImage(E))
      {
        //-------------------------------------------
        // E was already cut in another face.
        // Return the cut edges reorientate them as E.
        // See pb for the edges that have disappeared?
        //-------------------------------------------
        const TopTools_ListOfShape& LCE = Image.Image(E);
        for (itLCE.Initialize(LCE); itLCE.More(); itLCE.Next())
        {
          TopoDS_Shape CE = itLCE.Value().Oriented(E.Orientation());
          Loops.AddConstEdge(TopoDS::Edge(CE));
        }
      }
      else
      {
        Loops.AddEdge(E, AsDes->Descendant(E));
        AddedEdges.Append(E);
      }
    }
    //------------------------
    // Unwind.
    //------------------------
    Loops.Perform();
    Loops.WiresToFaces();
    //------------------------
    // MAJ SD.
    //------------------------
    const TopTools_ListOfShape& NF = Loops.NewFaces();
    //-----------------------
    // F => New faces;
    //-----------------------
    Image.Bind(F, NF);

    TopTools_ListIteratorOfListOfShape itAdded;
    for (itAdded.Initialize(AddedEdges); itAdded.More(); itAdded.Next())
    {
      const TopoDS_Edge& E = TopoDS::Edge(itAdded.Value());
      //-----------------------
      //  E => New edges;
      //-----------------------
      const TopTools_ListOfShape& LoopNE = Loops.NewEdges(E);
      if (Image.HasImage(E))
      {
        Image.Add(E, LoopNE);
      }
      else
      {
        Image.Bind(E, LoopNE);
      }
    }
  }
  Loops.GetVerticesForSubstitute(myVerVerMap);
  if (myVerVerMap.IsEmpty())
    return;
  BRep_Builder          BB;
  Message_ProgressScope aPS2(aPSOuter.Next(), "Building loops", LF.Size());
  for (it.Initialize(LF); it.More(); it.Next(), aPS2.Next())
  {
    if (!aPS2.More())
    {
      return;
    }
    TopoDS_Shape         F = it.Value();
    TopTools_ListOfShape LIF;
    Image.LastImage(F, LIF);
    for (itl.Initialize(LIF); itl.More(); itl.Next())
    {
      const TopoDS_Shape& IF = itl.Value();
      TopExp_Explorer     EdExp(IF, TopAbs_EDGE);
      for (; EdExp.More(); EdExp.Next())
      {
        TopoDS_Shape         E = EdExp.Current();
        TopTools_ListOfShape VList;
        TopoDS_Iterator      VerExp(E);
        for (; VerExp.More(); VerExp.Next())
          VList.Append(VerExp.Value());
        TopTools_ListIteratorOfListOfShape itlv(VList);
        for (; itlv.More(); itlv.Next())
        {
          const TopoDS_Shape& V = itlv.Value();
          if (myVerVerMap.IsBound(V))
          {
            TopoDS_Shape NewV = myVerVerMap(V);
            E.Free(Standard_True);
            NewV.Orientation(V.Orientation());
            Handle(BRep_TVertex)& TV    = *((Handle(BRep_TVertex)*)&V.TShape());
            Handle(BRep_TVertex)& NewTV = *((Handle(BRep_TVertex)*)&NewV.TShape());
            if (TV->Tolerance() > NewTV->Tolerance())
              NewTV->Tolerance(TV->Tolerance());
            NewTV->ChangePoints().Append(TV->ChangePoints());
            AsDes->Replace(V, NewV);
            BB.Remove(E, V);
            BB.Add(E, NewV);
          }
        }
      }
    }
  }
}

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

static Standard_Boolean IsBetweenCorks(const TopoDS_Shape&           E,
                                       const Handle(BRepAlgo_AsDes)& AsDes,
                                       const TopTools_ListOfShape&   LContext)
{
  if (!AsDes->HasAscendant(E))
    return 1;
  const TopTools_ListOfShape&        LF = AsDes->Ascendant(E);
  TopTools_ListIteratorOfListOfShape it;
  for (it.Initialize(LF); it.More(); it.Next())
  {
    const TopoDS_Shape&                S     = it.Value();
    Standard_Boolean                   found = 0;
    TopTools_ListIteratorOfListOfShape it2;
    for (it2.Initialize(LContext); it2.More(); it2.Next())
    {
      if (S.IsSame(it2.Value()))
      {
        found = 1;
        break;
      }
    }
    if (!found)
      return 0;
  }
  return 1;
}

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

void BRepOffset_MakeLoops::BuildOnContext(const TopTools_ListOfShape&   LContext,
                                          const BRepOffset_Analyse&     Analyse,
                                          const Handle(BRepAlgo_AsDes)& AsDes,
                                          BRepAlgo_Image&               Image,
                                          const Standard_Boolean        InSide,
                                          const Message_ProgressRange&  theRange)
{
  //-----------------------------------------
  // unwinding of caps.
  //-----------------------------------------
  TopTools_ListIteratorOfListOfShape it(LContext);
  TopTools_ListIteratorOfListOfShape itl, itLCE;
  BRepAlgo_Loop                      Loops;
  Loops.VerticesForSubstitute(myVerVerMap);
  TopExp_Explorer     exp;
  TopTools_MapOfShape MapExtent;

  Message_ProgressScope aPS(theRange, "Building deepening faces", LContext.Extent());
  for (; it.More(); it.Next(), aPS.Next())
  {
    if (!aPS.More())
    {
      return;
    }
    const TopoDS_Face&  F = TopoDS::Face(it.Value());
    TopTools_MapOfShape MBound;
    //-----------------------------------------------
    // Initialisation of Loops.
    // F is reversed it will be added in myOffC.
    // and myOffC will be reversed in the final result.
    //-----------------------------------------------
    TopoDS_Shape aReversedF = F.Reversed();
    if (InSide)
      Loops.Init(TopoDS::Face(aReversedF));
    //    if (InSide) Loops.Init(TopoDS::Face(F.Reversed()));
    else
      Loops.Init(F);
    //--------------------------------------------------------
    // return edges of F not modified by definition.
    //--------------------------------------------------------
    for (exp.Init(F.Oriented(TopAbs_FORWARD), TopAbs_EDGE); exp.More(); exp.Next())
    {
      TopoDS_Edge CE = TopoDS::Edge(exp.Current());
      MBound.Add(CE);
      if (Analyse.HasAncestor(CE))
      {
        // the stop of cups except for the connectivity stops between caps.
        //      if (!AsDes->HasAscendant(CE)) {
        TopoDS_Shape aReversedE = CE.Reversed();
        if (InSide)
          Loops.AddConstEdge(CE);
        else
          Loops.AddConstEdge(TopoDS::Edge(aReversedE));
        //	else        Loops.AddConstEdge(TopoDS::Edge(CE.Reversed()));
      }
    }
    //------------------------------------------------------
    // Trace of offsets + connectivity edge between caps.
    //------------------------------------------------------
    const TopTools_ListOfShape& LE = AsDes->Descendant(F);
    TopTools_ListOfShape        AddedEdges;

    for (itl.Initialize(LE); itl.More(); itl.Next())
    {
      TopoDS_Edge E = TopoDS::Edge(itl.Value());
      if (Image.HasImage(E))
      {
        //-------------------------------------------
        // E was already cut in another face.
        // Return cut edges and orientate them as E.
        // See pb for the edges that have disappeared?
        //-------------------------------------------
        const TopTools_ListOfShape& LCE = Image.Image(E);
        for (itLCE.Initialize(LCE); itLCE.More(); itLCE.Next())
        {
          TopoDS_Shape CE = itLCE.Value().Oriented(E.Orientation());
          if (MapExtent.Contains(E))
          {
            Loops.AddConstEdge(TopoDS::Edge(CE));
            continue;
          }
          if (!MBound.Contains(E))
            CE.Reverse();
          if (InSide)
            Loops.AddConstEdge(TopoDS::Edge(CE));
          else
          {
            TopoDS_Shape aReversedE = CE.Reversed();
            Loops.AddConstEdge(TopoDS::Edge(aReversedE));
          }
          //	  else        Loops.AddConstEdge(TopoDS::Edge(CE.Reversed()));
        }
      }
      else
      {
        if (IsBetweenCorks(E, AsDes, LContext) && AsDes->HasDescendant(E))
        {
          // connection between 2 caps
          MapExtent.Add(E);
          TopTools_ListOfShape LV;
          if (InSide)
          {
            for (itLCE.Initialize(AsDes->Descendant(E)); itLCE.More(); itLCE.Next())
            {
              LV.Append(itLCE.Value().Reversed());
            }
            Loops.AddEdge(E, LV);
          }
          else
          {
            Loops.AddEdge(E, AsDes->Descendant(E));
          }
          AddedEdges.Append(E);
        }
        else if (IsBetweenCorks(E, AsDes, LContext))
        {
          TopoDS_Shape aLocalShape = E.Reversed();
          if (InSide)
            Loops.AddConstEdge(E);
          else
            Loops.AddConstEdge(TopoDS::Edge(aLocalShape));
          //	  if (InSide) Loops.AddConstEdge(TopoDS::Edge(E));
          //	  else        Loops.AddConstEdge(TopoDS::Edge(E.Reversed()));
        }
        else
        {
          TopoDS_Shape aLocalShape = E.Reversed();
          if (InSide)
            Loops.AddConstEdge(TopoDS::Edge(aLocalShape));
          else
            Loops.AddConstEdge(E);
          //	  if (InSide) Loops.AddConstEdge(TopoDS::Edge(E.Reversed()));
          //	  else        Loops.AddConstEdge(TopoDS::Edge(E));
        }
      }
    }
    //------------------------
    // Unwind.
    //------------------------
    Loops.Perform();
    Loops.WiresToFaces();
    //------------------------
    // MAJ SD.
    //------------------------
    const TopTools_ListOfShape& NF = Loops.NewFaces();
    //-----------------------
    // F => New faces;
    //-----------------------
    Image.Bind(F, NF);

    TopTools_ListIteratorOfListOfShape itAdded;
    for (itAdded.Initialize(AddedEdges); itAdded.More(); itAdded.Next())
    {
      const TopoDS_Edge& E = TopoDS::Edge(itAdded.Value());
      //-----------------------
      //  E => New edges;
      //-----------------------
      if (Image.HasImage(E))
      {
        Image.Add(E, Loops.NewEdges(E));
      }
      else
      {
        Image.Bind(E, Loops.NewEdges(E));
      }
    }
  }
  Loops.GetVerticesForSubstitute(myVerVerMap);
  if (myVerVerMap.IsEmpty())
    return;
  BRep_Builder BB;
  for (it.Initialize(LContext); it.More(); it.Next())
  {
    TopoDS_Shape         F = it.Value();
    TopTools_ListOfShape LIF;
    Image.LastImage(F, LIF);
    for (itl.Initialize(LIF); itl.More(); itl.Next())
    {
      const TopoDS_Shape& IF = itl.Value();
      TopExp_Explorer     EdExp(IF, TopAbs_EDGE);
      for (; EdExp.More(); EdExp.Next())
      {
        TopoDS_Shape         E = EdExp.Current();
        TopTools_ListOfShape VList;
        TopoDS_Iterator      VerExp(E);
        for (; VerExp.More(); VerExp.Next())
          VList.Append(VerExp.Value());
        TopTools_ListIteratorOfListOfShape itlv(VList);
        for (; itlv.More(); itlv.Next())
        {
          const TopoDS_Shape& V = itlv.Value();
          if (myVerVerMap.IsBound(V))
          {
            TopoDS_Shape NewV = myVerVerMap(V);
            E.Free(Standard_True);
            NewV.Orientation(V.Orientation());
            Handle(BRep_TVertex)& TV    = *((Handle(BRep_TVertex)*)&V.TShape());
            Handle(BRep_TVertex)& NewTV = *((Handle(BRep_TVertex)*)&NewV.TShape());
            if (TV->Tolerance() > NewTV->Tolerance())
              NewTV->Tolerance(TV->Tolerance());
            NewTV->ChangePoints().Append(TV->ChangePoints());
            AsDes->Replace(V, NewV);
            BB.Remove(E, V);
            BB.Add(E, NewV);
          }
        }
      }
    }
  }
}

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

void BRepOffset_MakeLoops::BuildFaces(const TopTools_ListOfShape&   LF,
                                      const Handle(BRepAlgo_AsDes)& AsDes,
                                      BRepAlgo_Image&               Image,
                                      const Message_ProgressRange&  theRange)
{
  TopTools_ListIteratorOfListOfShape itr, itl, itLCE;
  Standard_Boolean                   ToRebuild;
  BRepAlgo_Loop                      Loops;
  Loops.VerticesForSubstitute(myVerVerMap);
  BRep_Builder B;

  //----------------------------------
  // Loop on all faces //.
  //----------------------------------
  Message_ProgressScope aPS(theRange, "Building faces", LF.Size());
  for (itr.Initialize(LF); itr.More(); itr.Next(), aPS.Next())
  {
    if (!aPS.More())
    {
      return;
    }
    TopoDS_Face F = TopoDS::Face(itr.Value());
    Loops.Init(F);
    ToRebuild = Standard_False;
    TopTools_ListOfShape AddedEdges;

    if (!Image.HasImage(F))
    {
      //----------------------------------
      // Face F not yet reconstructed.
      //----------------------------------
      const TopTools_ListOfShape& LE = AsDes->Descendant(F);
      //----------------------------------------------------------------
      // first loop to find if the edges of the face were reconstructed.
      // - maj on map MONV. Some vertices on reconstructed edges
      // coincide geometrically with old but are not IsSame.
      //----------------------------------------------------------------
      TopTools_DataMapOfShapeShape MONV;
      TopoDS_Vertex                OV1, OV2, NV1, NV2;

      for (itl.Initialize(LE); itl.More(); itl.Next())
      {
        TopoDS_Edge E = TopoDS::Edge(itl.Value());
        if (Image.HasImage(E))
        {
          const TopTools_ListOfShape& LCE = Image.Image(E);
          if (LCE.Extent() == 1 && LCE.First().IsSame(E))
          {
            TopoDS_Shape aLocalShape = LCE.First().Oriented(E.Orientation());
            TopoDS_Edge  CE          = TopoDS::Edge(aLocalShape);
            //	    TopoDS_Edge CE = TopoDS::Edge(LCE.First().Oriented(E.Orientation()));
            Loops.AddConstEdge(CE);
            continue;
          }
          //----------------------------------
          // F should be reconstructed.
          //----------------------------------
          ToRebuild = Standard_True;
          for (itLCE.Initialize(LCE); itLCE.More(); itLCE.Next())
          {
            TopoDS_Shape aLocalShape = itLCE.Value().Oriented(E.Orientation());
            TopoDS_Edge  CE          = TopoDS::Edge(aLocalShape);
            //	    TopoDS_Edge CE = TopoDS::Edge(itLCE.Value().Oriented(E.Orientation()));
            TopExp::Vertices(E, OV1, OV2);
            TopExp::Vertices(CE, NV1, NV2);
            if (!OV1.IsSame(NV1))
              MONV.Bind(OV1, NV1);
            if (!OV2.IsSame(NV2))
              MONV.Bind(OV2, NV2);
            Loops.AddConstEdge(CE);
          }
        }
      }
      if (ToRebuild)
      {
#ifdef DRAW
        if (Affich)
        {
          char name[256];
          sprintf(name, "CF_%d", NbF++);
          DBRep::Set(name, F);
        }
#endif

        //-----------------------------------------------------------
        // Non-reconstructed edges on other faces are added.
        // If their vertices were reconstructed they are reconstructed.
        //-----------------------------------------------------------
        for (itl.Initialize(LE); itl.More(); itl.Next())
        {
          Standard_Real f, l;
          TopoDS_Edge   E = TopoDS::Edge(itl.Value());
          BRep_Tool::Range(E, f, l);
          if (!Image.HasImage(E))
          {
            TopExp::Vertices(E, OV1, OV2);
            TopTools_ListOfShape LV;
            if (MONV.IsBound(OV1))
            {
              TopoDS_Vertex VV = TopoDS::Vertex(MONV(OV1));
              VV.Orientation(TopAbs_FORWARD);
              LV.Append(VV);
              TopoDS_Shape aLocalShape = VV.Oriented(TopAbs_INTERNAL);
              B.UpdateVertex(TopoDS::Vertex(aLocalShape), f, E, BRep_Tool::Tolerance(VV));
            }
            if (MONV.IsBound(OV2))
            {
              TopoDS_Vertex VV = TopoDS::Vertex(MONV(OV2));
              VV.Orientation(TopAbs_REVERSED);
              LV.Append(VV);
              TopoDS_Shape aLocalShape = VV.Oriented(TopAbs_INTERNAL);
              B.UpdateVertex(TopoDS::Vertex(aLocalShape), l, E, BRep_Tool::Tolerance(VV));
              //	      B.UpdateVertex(TopoDS::Vertex(VV.Oriented(TopAbs_INTERNAL)),
              //			     l,E,BRep_Tool::Tolerance(VV));
            }
            if (LV.IsEmpty())
              Loops.AddConstEdge(E);
            else
            {
              Loops.AddEdge(E, LV);
              AddedEdges.Append(E);
            }
          }
        }
      }
    }
    if (ToRebuild)
    {
      //------------------------
      // Reconstruction.
      //------------------------
      Loops.Perform();
      Loops.WiresToFaces();
      //------------------------
      // MAJ SD.
      //------------------------
      const TopTools_ListOfShape& NF = Loops.NewFaces();
      //-----------------------
      // F => New faces;
      //-----------------------
      Image.Bind(F, NF);

      TopTools_ListIteratorOfListOfShape itAdded;
      for (itAdded.Initialize(AddedEdges); itAdded.More(); itAdded.Next())
      {
        const TopoDS_Edge& E = TopoDS::Edge(itAdded.Value());
        //-----------------------
        //  E => New edges;
        //-----------------------
        if (Image.HasImage(E))
        {
          Image.Add(E, Loops.NewEdges(E));
        }
        else
        {
          Image.Bind(E, Loops.NewEdges(E));
        }
      }
    }
  }
  Loops.GetVerticesForSubstitute(myVerVerMap);
  if (myVerVerMap.IsEmpty())
    return;
  BRep_Builder BB;
  for (itr.Initialize(LF); itr.More(); itr.Next())
  {
    TopoDS_Shape         F = itr.Value();
    TopTools_ListOfShape LIF;
    Image.LastImage(F, LIF);
    for (itl.Initialize(LIF); itl.More(); itl.Next())
    {
      const TopoDS_Shape& IF = itl.Value();
      TopExp_Explorer     EdExp(IF, TopAbs_EDGE);
      for (; EdExp.More(); EdExp.Next())
      {
        TopoDS_Shape         E = EdExp.Current();
        TopTools_ListOfShape VList;
        TopoDS_Iterator      VerExp(E);
        for (; VerExp.More(); VerExp.Next())
          VList.Append(VerExp.Value());
        TopTools_ListIteratorOfListOfShape itlv(VList);
        for (; itlv.More(); itlv.Next())
        {
          const TopoDS_Shape& V = itlv.Value();
          if (myVerVerMap.IsBound(V))
          {
            TopoDS_Shape NewV = myVerVerMap(V);
            E.Free(Standard_True);
            NewV.Orientation(V.Orientation());
            Handle(BRep_TVertex)& TV    = *((Handle(BRep_TVertex)*)&V.TShape());
            Handle(BRep_TVertex)& NewTV = *((Handle(BRep_TVertex)*)&NewV.TShape());
            if (TV->Tolerance() > NewTV->Tolerance())
              NewTV->Tolerance(TV->Tolerance());
            NewTV->ChangePoints().Append(TV->ChangePoints());
            AsDes->Replace(V, NewV);
            BB.Remove(E, V);
            BB.Add(E, NewV);
          }
        }
      }
    }
  }
}
