// Created on: 2016-08-22
// Copyright (c) 2016 OPEN CASCADE SAS
// Created by: Oleg AGASHIN
//
// 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 <BRepMesh_MeshTool.hxx>
#include <BRepMesh_SelectorOfDataStructureOfDelaun.hxx>
#include <stack>

#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakePolygon.hxx>
#include <BRep_Builder.hxx>
#include <TopoDS_Compound.hxx>
#include <BRepTools.hxx>
#include <gp_Pln.hxx>

IMPLEMENT_STANDARD_RTTIEXT(BRepMesh_MeshTool, Standard_Transient)

namespace
{
//! Returns index of triangle node opposite to the given link.
int findApexIndex(const int (&aNodes)[3], const BRepMesh_Edge& theLink)
{
  int i = 0;
  for (; i < 3; ++i)
  {
    if (aNodes[i] != theLink.FirstNode() && aNodes[i] != theLink.LastNode())
    {
      break;
    }
  }

  return i;
}
} // namespace

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

BRepMesh_MeshTool::BRepMesh_MeshTool(
  const occ::handle<BRepMesh_DataStructureOfDelaun>& theStructure)
    : myStructure(theStructure)
{
}

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

BRepMesh_MeshTool::~BRepMesh_MeshTool() = default;

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

void BRepMesh_MeshTool::Legalize(const int theLinkIndex)
{
  std::stack<int> aStack;
  aStack.push(theLinkIndex);

  IMeshData::MapOfInteger aUsedLinks;
  while (!aStack.empty())
  {
    const int aLinkIndex = aStack.top();
    aStack.pop();

    aUsedLinks.Add(aLinkIndex);
    const BRepMesh_Edge& aLink = myStructure->GetLink(aLinkIndex);
    if (aLink.Movability() != BRepMesh_Frontier)
    {
      const BRepMesh_PairOfIndex& aPair = myStructure->ElementsConnectedTo(aLinkIndex);
      if (aPair.Extent() == 2)
      {
        const BRepMesh_Triangle& aTriangle1 = myStructure->GetElement(aPair.FirstIndex());
        const BRepMesh_Triangle& aTriangle2 = myStructure->GetElement(aPair.LastIndex());

        int aNodes[2][3];
        myStructure->ElementNodes(aTriangle1, aNodes[0]);
        myStructure->ElementNodes(aTriangle2, aNodes[1]);

        const int aApexIndex[2] = {findApexIndex(aNodes[0], aLink),
                                   findApexIndex(aNodes[1], aLink)};

        if (checkCircle(aNodes[0], aNodes[1][aApexIndex[1]])
            || checkCircle(aNodes[1], aNodes[0][aApexIndex[0]]))
        {
          myStructure->RemoveElement(aPair.FirstIndex());
          myStructure->RemoveElement(aPair.LastIndex());
          myStructure->RemoveLink(aLinkIndex);

          addTriangleAndUpdateStack(aNodes[0][(aApexIndex[0])],
                                    aNodes[0][(aApexIndex[0] + 1) % 3],
                                    aNodes[1][(aApexIndex[1])],
                                    aUsedLinks,
                                    aStack);

          addTriangleAndUpdateStack(aNodes[1][(aApexIndex[1])],
                                    aNodes[1][(aApexIndex[1] + 1) % 3],
                                    aNodes[0][(aApexIndex[0])],
                                    aUsedLinks,
                                    aStack);
        }
      }
    }
  }
}

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

void BRepMesh_MeshTool::EraseItemsConnectedTo(const int theNodeIndex)
{
  BRepMesh_SelectorOfDataStructureOfDelaun aSelector(myStructure);
  aSelector.NeighboursOfNode(theNodeIndex);

  IMeshData::MapOfIntegerInteger aLoopEdges(1, new NCollection_IncAllocator);
  EraseTriangles(aSelector.Elements(), aLoopEdges);
  EraseFreeLinks(aLoopEdges);
  myStructure->RemoveNode(theNodeIndex);
}

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

void BRepMesh_MeshTool::CleanFrontierLinks()
{
  occ::handle<NCollection_IncAllocator> aAlloc = new NCollection_IncAllocator;
  IMeshData::MapOfInteger               aTrianglesToErase;
  IMeshData::MapOfIntegerInteger        aLoopEdges(1, aAlloc);

  Handle(IMeshData::MapOfInteger)   aFrontier = GetEdgesByType(BRepMesh_Frontier);
  IMeshData::IteratorOfMapOfInteger aFrontierIt(*aFrontier);
  for (; aFrontierIt.More(); aFrontierIt.Next())
  {
    int                  aFrontierId = aFrontierIt.Key();
    const BRepMesh_Edge& aLink       = myStructure->GetLink(aFrontierId);

    bool                        isTriangleFound = false;
    const BRepMesh_PairOfIndex& aPair           = myStructure->ElementsConnectedTo(aFrontierId);
    for (int aElemIt = 1; aElemIt <= aPair.Extent() && !isTriangleFound; ++aElemIt)
    {
      const int                aPriorElemId = aPair.Index(aElemIt);
      const BRepMesh_Triangle& aElement     = myStructure->GetElement(aPriorElemId);
      const int (&e)[3]                     = aElement.myEdges;
      const bool (&o)[3]                    = aElement.myOrientations;

      for (int n = 0; n < 3 && !isTriangleFound; ++n)
      {
        if (aFrontierId == e[n] && !o[n])
        {
          // Destruction of external triangles on boundary edges
          isTriangleFound = true;
          aTrianglesToErase.Add(aPriorElemId);

          collectTrianglesOnFreeLinksAroundNodesOf(aLink, e[(n + 1) % 3], aTrianglesToErase);
          collectTrianglesOnFreeLinksAroundNodesOf(aLink, e[(n + 2) % 3], aTrianglesToErase);
        }
      }
    }
  }

  EraseTriangles(aTrianglesToErase, aLoopEdges);
  EraseFreeLinks(aLoopEdges);
}

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

void BRepMesh_MeshTool::EraseTriangles(const IMeshData::MapOfInteger&  theTriangles,
                                       IMeshData::MapOfIntegerInteger& theLoopEdges)
{
  IMeshData::IteratorOfMapOfInteger aFreeTriangles(theTriangles);
  for (; aFreeTriangles.More(); aFreeTriangles.Next())
  {
    EraseTriangle(aFreeTriangles.Key(), theLoopEdges);
  }
}

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

void BRepMesh_MeshTool::EraseTriangle(const int                       theTriangleIndex,
                                      IMeshData::MapOfIntegerInteger& theLoopEdges)
{
  const BRepMesh_Triangle& aElement = myStructure->GetElement(theTriangleIndex);
  const int (&e)[3]                 = aElement.myEdges;
  const bool (&o)[3]                = aElement.myOrientations;

  myStructure->RemoveElement(theTriangleIndex);

  for (int i = 0; i < 3; ++i)
  {
    if (!theLoopEdges.Bind(e[i], o[i]))
    {
      theLoopEdges.UnBind(e[i]);
      myStructure->RemoveLink(e[i]);
    }
  }
}

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

void BRepMesh_MeshTool::EraseFreeLinks()
{
  for (int i = 1; i <= myStructure->NbLinks(); i++)
  {
    if (myStructure->ElementsConnectedTo(i).IsEmpty())
    {
      BRepMesh_Edge& anEdge = (BRepMesh_Edge&)myStructure->GetLink(i);
      if (anEdge.Movability() == BRepMesh_Deleted)
      {
        continue;
      }

      anEdge.SetMovability(BRepMesh_Free);
      myStructure->RemoveLink(i);
    }
  }
}

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

void BRepMesh_MeshTool::collectTrianglesOnFreeLinksAroundNodesOf(
  const BRepMesh_Edge&     theConstraint,
  const int                theStartLink,
  IMeshData::MapOfInteger& theTriangles)
{
  IMeshData::MapOfInteger aUsedLinks;
  std::stack<int>         aStack;
  aStack.push(theStartLink);
  aUsedLinks.Add(theStartLink);

  while (!aStack.empty())
  {
    const int aLinkIndex = aStack.top();
    aStack.pop();

    const BRepMesh_Edge& aLink = myStructure->GetLink(aLinkIndex);
    if (aLink.Movability() == BRepMesh_Free
        && (aLink.FirstNode() == theConstraint.FirstNode()
            || aLink.LastNode() == theConstraint.FirstNode()
            || aLink.FirstNode() == theConstraint.LastNode()
            || aLink.LastNode() == theConstraint.LastNode()))
    {
      const BRepMesh_PairOfIndex& aPair = myStructure->ElementsConnectedTo(aLinkIndex);
      for (int aElemIt = 1; aElemIt <= aPair.Extent(); ++aElemIt)
      {
        const int aIndex = aPair.Index(aElemIt);
        theTriangles.Add(aIndex);

        const BRepMesh_Triangle& aElement = myStructure->GetElement(aIndex);
        const int (&aEdges)[3]            = aElement.myEdges;

        for (int i = 0; i < 3; ++i)
        {
          if (aEdges[i] != aLinkIndex && !aUsedLinks.Contains(aEdges[i]))
          {
            aUsedLinks.Add(aEdges[i]);
            aStack.push(aEdges[i]);
          }
        }
      }
    }
  }
}

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

void BRepMesh_MeshTool::EraseFreeLinks(const IMeshData::MapOfIntegerInteger& theLinks)
{
  IMeshData::MapOfIntegerInteger::Iterator aFreeEdges(theLinks);
  for (; aFreeEdges.More(); aFreeEdges.Next())
  {
    if (myStructure->ElementsConnectedTo(aFreeEdges.Key()).IsEmpty())
    {
      myStructure->RemoveLink(aFreeEdges.Key());
    }
  }
}

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

Handle(IMeshData::MapOfInteger) BRepMesh_MeshTool::GetEdgesByType(
  const BRepMesh_DegreeOfFreedom theEdgeType) const
{
  Handle(IMeshData::MapOfInteger)   aResult = new IMeshData::MapOfInteger;
  IMeshData::IteratorOfMapOfInteger aEdgeIt(myStructure->LinksOfDomain());

  for (; aEdgeIt.More(); aEdgeIt.Next())
  {
    const BRepMesh_Edge& aEdge = myStructure->GetLink(aEdgeIt.Key());
    if (aEdge.Movability() == theEdgeType)
    {
      aResult->Add(aEdgeIt.Key());
    }
  }

  return aResult;
}

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

void BRepMesh_MeshTool::DumpTriangles(const char* const        theFileName,
                                      IMeshData::MapOfInteger* theTriangles)
{
  BRep_Builder    aBuilder;
  TopoDS_Compound aResult;
  aBuilder.MakeCompound(aResult);

  const IMeshData::MapOfInteger& aTriangles = myStructure->ElementsOfDomain();
  for (IMeshData::IteratorOfMapOfInteger aIt(aTriangles); aIt.More(); aIt.Next())
  {
    if (theTriangles != nullptr && !theTriangles->Contains(aIt.Key()))
    {
      continue;
    }

    int                      aNodes[3];
    const BRepMesh_Triangle& aTri = myStructure->GetElement(aIt.Key());
    myStructure->ElementNodes(aTri, aNodes);

    const gp_XY& aV1 = myStructure->GetNode(aNodes[0]).Coord();
    const gp_XY& aV2 = myStructure->GetNode(aNodes[1]).Coord();
    const gp_XY& aV3 = myStructure->GetNode(aNodes[2]).Coord();

    BRepBuilderAPI_MakePolygon aPoly(gp_Pnt(aV1.X(), aV1.Y(), 0.),
                                     gp_Pnt(aV2.X(), aV2.Y(), 0.),
                                     gp_Pnt(aV3.X(), aV3.Y(), 0.),
                                     true);

    BRepBuilderAPI_MakeFace aFaceBuilder(gp_Pln(gp::XOY()), aPoly.Wire());
    aBuilder.Add(aResult, aFaceBuilder.Shape());
  }

  BRepTools::Write(aResult, theFileName);
}
