// Created by: Peter KURNEV
// Copyright (c) 2010-2014 OPEN CASCADE SAS
// Copyright (c) 2007-2010 CEA/DEN, EDF R&D, OPEN CASCADE
// Copyright (c) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, CEDRAT,
//                         EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
//
// 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 <Bnd_OBB.hxx>
#include <Bnd_Tools.hxx>
#include <BOPDS_DS.hxx>
#include <BOPDS_IndexRange.hxx>
#include <BOPDS_Iterator.hxx>
#include <BOPDS_Pair.hxx>
#include <BOPDS_MapOfPair.hxx>
#include <BOPDS_Tools.hxx>
#include <BOPTools_BoxTree.hxx>
#include <BOPTools_Parallel.hxx>
#include <IntTools_Context.hxx>
#include <NCollection_Vector.hxx>
#include <algorithm>

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

class BOPDS_TSR : public BOPTools_BoxTreeSelector
{
public:
  BOPDS_TSR()
      : BOPTools_BoxTreeSelector(),
        myHasBRep(Standard_False),
        myIndex(-1)
  {
  }

  //
  virtual ~BOPDS_TSR() {}

  //
  void SetHasBRep(const Standard_Boolean bFlag) { myHasBRep = bFlag; }

  //
  void SetIndex(const Standard_Integer theIndex) { myIndex = theIndex; }

  //
  Standard_Integer Index() const { return myIndex; }

  //
  void Perform()
  {
    if (myHasBRep)
    {
      Select();
    }
  }

  //
protected:
  Standard_Boolean myHasBRep;
  Standard_Integer myIndex;
};

//
//=======================================================================
typedef NCollection_Vector<BOPDS_TSR> BOPDS_VectorOfTSR;

/////////////////////////////////////////////////////////////////////////

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

BOPDS_Iterator::BOPDS_Iterator()
    : myAllocator(NCollection_BaseAllocator::CommonBaseAllocator()),
      myRunParallel(Standard_False),
      myUseExt(Standard_False)
{
  Standard_Integer i, aNb;
  //
  myDS     = NULL;
  myLength = 0;
  //
  aNb = BOPDS_DS::NbInterfTypes();
  myLists.SetIncrement(aNb);
  for (i = 0; i < aNb; ++i)
  {
    myLists.Appended();
  }

  const Standard_Integer aNbExt = BOPDS_Iterator::NbExtInterfs();
  myExtLists.SetIncrement(aNbExt);
  for (i = 0; i < aNbExt; ++i)
  {
    myExtLists.Appended();
  }
}

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

BOPDS_Iterator::BOPDS_Iterator(const Handle(NCollection_BaseAllocator)& theAllocator)
    : myAllocator(theAllocator),
      myLists(0, theAllocator),
      myRunParallel(Standard_False),
      myExtLists(0, theAllocator),
      myUseExt(Standard_False)
{
  Standard_Integer i, aNb;
  //
  myDS     = NULL;
  myLength = 0;
  //
  aNb = BOPDS_DS::NbInterfTypes();
  myLists.SetIncrement(aNb);
  for (i = 0; i < aNb; ++i)
  {
    myLists.Appended();
  }

  const Standard_Integer aNbExt = BOPDS_Iterator::NbExtInterfs();
  myExtLists.SetIncrement(aNbExt);
  for (i = 0; i < aNbExt; ++i)
  {
    myExtLists.Appended();
  }
}

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

BOPDS_Iterator::~BOPDS_Iterator() {}

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

void BOPDS_Iterator::SetRunParallel(const Standard_Boolean theFlag)
{
  myRunParallel = theFlag;
}

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

Standard_Boolean BOPDS_Iterator::RunParallel() const
{
  return myRunParallel;
}

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

void BOPDS_Iterator::SetDS(const BOPDS_PDS& aDS)
{
  myDS = aDS;
}

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

const BOPDS_DS& BOPDS_Iterator::DS() const
{
  return *myDS;
}

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

Standard_Integer BOPDS_Iterator::ExpectedLength() const
{
  return myLength;
}

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

Standard_Integer BOPDS_Iterator::BlockLength() const
{
  Standard_Integer aNbIIs;
  Standard_Real    aCfPredict = .5;

  aNbIIs = ExpectedLength();

  if (aNbIIs <= 1)
  {
    return 1;
  }
  //
  aNbIIs = (Standard_Integer)(aCfPredict * (Standard_Real)aNbIIs);
  return aNbIIs;
}

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

void BOPDS_Iterator::Initialize(const TopAbs_ShapeEnum aType1, const TopAbs_ShapeEnum aType2)
{
  Standard_Integer iX;
  //
  myLength = 0;
  iX       = BOPDS_Tools::TypeToInteger(aType1, aType2);
  if (iX >= 0)
  {
    BOPDS_VectorOfPair& aPairs =
      (myUseExt && iX < BOPDS_Iterator::NbExtInterfs()) ? myExtLists(iX) : myLists(iX);
    // sort interfering pairs for constant order of intersection
    std::stable_sort(aPairs.begin(), aPairs.end());
    // initialize iterator to access the pairs
    myIterator.Init(aPairs);
    myLength = aPairs.Length();
  }
}

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

Standard_Boolean BOPDS_Iterator::More() const
{
  return myIterator.More();
}

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

void BOPDS_Iterator::Next()
{
  myIterator.Next();
}

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

void BOPDS_Iterator::Value(Standard_Integer& theI1, Standard_Integer& theI2) const
{
  Standard_Integer iT1, iT2, n1, n2;
  //
  const BOPDS_Pair& aPair = myIterator.Value();
  aPair.Indices(n1, n2);
  //
  iT1 = (Standard_Integer)(myDS->ShapeInfo(n1).ShapeType());
  iT2 = (Standard_Integer)(myDS->ShapeInfo(n2).ShapeType());
  //
  theI1 = n1;
  theI2 = n2;
  if (iT1 < iT2)
  {
    theI1 = n2;
    theI2 = n1;
  }
}

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

void BOPDS_Iterator::Prepare(const Handle(IntTools_Context)& theCtx,
                             const Standard_Boolean          theCheckOBB,
                             const Standard_Real             theFuzzyValue)
{
  Standard_Integer i, aNbInterfTypes;
  //
  aNbInterfTypes = BOPDS_DS::NbInterfTypes();
  myLength       = 0;
  for (i = 0; i < aNbInterfTypes; ++i)
  {
    myLists(i).Clear();
  }
  //
  if (myDS == NULL)
  {
    return;
  }
  Intersect(theCtx, theCheckOBB, theFuzzyValue);
}

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

void BOPDS_Iterator::Intersect(const Handle(IntTools_Context)& theCtx,
                               const Standard_Boolean          theCheckOBB,
                               const Standard_Real             theFuzzyValue)
{
  const Standard_Integer aNb = myDS->NbSourceShapes();

  // Prepare BVH
  BOPTools_BoxTree aBoxTree;
  aBoxTree.SetSize(aNb);
  for (Standard_Integer i = 0; i < aNb; ++i)
  {
    const BOPDS_ShapeInfo& aSI = myDS->ShapeInfo(i);
    if (!aSI.HasBRep())
      continue;
    const Bnd_Box& aBox = aSI.Box();
    aBoxTree.Add(i, Bnd_Tools::Bnd2BVH(aBox));
  }

  // Build BVH
  aBoxTree.Build();

  // Select pairs of shapes with interfering bounding boxes
  BOPTools_BoxPairSelector aPairSelector;
  aPairSelector.SetBVHSets(&aBoxTree, &aBoxTree);
  aPairSelector.SetSame(Standard_True);
  aPairSelector.Select();
  aPairSelector.Sort();

  // Treat the selected pairs
  const std::vector<BOPTools_BoxPairSelector::PairIDs>& aPairs = aPairSelector.Pairs();
  const Standard_Integer aNbPairs = static_cast<Standard_Integer>(aPairs.size());

  Standard_Integer iPair = 0;

  const Standard_Integer aNbR = myDS->NbRanges();
  for (Standard_Integer iR = 0; iR < aNbR; ++iR)
  {
    const BOPDS_IndexRange& aRange = myDS->Range(iR);

    for (; iPair < aNbPairs; ++iPair)
    {
      const BOPTools_BoxPairSelector::PairIDs& aPair = aPairs[iPair];
      if (!aRange.Contains(aPair.ID1))
        // Go to the next range
        break;

      if (aRange.Contains(aPair.ID2))
        // Go to the next pair
        continue;

      const BOPDS_ShapeInfo& aSI1 = myDS->ShapeInfo(aPair.ID1);
      const BOPDS_ShapeInfo& aSI2 = myDS->ShapeInfo(aPair.ID2);

      const TopAbs_ShapeEnum aType1 = aSI1.ShapeType();
      const TopAbs_ShapeEnum aType2 = aSI2.ShapeType();

      Standard_Integer iType1 = BOPDS_Tools::TypeToInteger(aType1);
      Standard_Integer iType2 = BOPDS_Tools::TypeToInteger(aType2);

      // avoid interfering of the shape with its sub-shapes
      if (((iType1 < iType2) && aSI1.HasSubShape(aPair.ID2))
          || ((iType1 > iType2) && aSI2.HasSubShape(aPair.ID1)))
        continue;

      if (theCheckOBB)
      {
        // Check intersection of Oriented bounding boxes of the shapes
        const Bnd_OBB& anOBB1 = theCtx->OBB(aSI1.Shape(), theFuzzyValue);
        const Bnd_OBB& anOBB2 = theCtx->OBB(aSI2.Shape(), theFuzzyValue);

        if (anOBB1.IsOut(anOBB2))
          continue;
      }

      Standard_Integer iX = BOPDS_Tools::TypeToInteger(aType1, aType2);
      myLists(iX).Append(BOPDS_Pair(Min(aPair.ID1, aPair.ID2), Max(aPair.ID1, aPair.ID2)));
    }
  }
}

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

void BOPDS_Iterator::IntersectExt(const TColStd_MapOfInteger& theIndices)
{
  if (!myDS)
    return;

  const Standard_Integer aNb = myDS->NbSourceShapes();

  BOPTools_BoxTree aBoxTree;
  aBoxTree.SetSize(aNb);
  BOPDS_VectorOfTSR aVTSR(theIndices.Extent());
  //
  for (Standard_Integer i = 0; i < aNb; ++i)
  {
    const BOPDS_ShapeInfo& aSI = myDS->ShapeInfo(i);
    if (!aSI.IsInterfering() || (aSI.ShapeType() == TopAbs_SOLID))
      continue;
    //
    if (theIndices.Contains(i))
    {
      Standard_Integer nVSD = i;
      myDS->HasShapeSD(i, nVSD);
      const BOPDS_ShapeInfo& aSISD = myDS->ShapeInfo(nVSD);
      const Bnd_Box&         aBox  = aSISD.Box();
      aBoxTree.Add(i, Bnd_Tools::Bnd2BVH(aBox));

      BOPDS_TSR& aTSR = aVTSR.Appended();
      aTSR.SetHasBRep(Standard_True);
      aTSR.SetBVHSet(&aBoxTree);
      aTSR.SetBox(Bnd_Tools::Bnd2BVH(aBox));
      aTSR.SetIndex(i);
    }
    else
    {
      aBoxTree.Add(i, Bnd_Tools::Bnd2BVH(aSI.Box()));
    }
  }

  aBoxTree.Build();

  // Perform selection
  BOPTools_Parallel::Perform(myRunParallel, aVTSR);

  // Treat selections

  // Fence map to avoid duplicating pairs
  BOPDS_MapOfPair aMPFence;

  const Standard_Integer aNbV = aVTSR.Length();
  for (Standard_Integer k = 0; k < aNbV; ++k)
  {
    BOPDS_TSR&                   aTSRi = aVTSR(k);
    const TColStd_ListOfInteger& aLI   = aTSRi.Indices();
    if (aLI.IsEmpty())
      continue;

    const Standard_Integer i      = aTSRi.Index();
    const BOPDS_ShapeInfo& aSI    = myDS->ShapeInfo(i);
    const Standard_Integer iRankI = myDS->Rank(i);
    const TopAbs_ShapeEnum aTI    = aSI.ShapeType();
    const Standard_Integer iTI    = BOPDS_Tools::TypeToInteger(aTI);

    TColStd_ListIteratorOfListOfInteger itLI(aLI);
    for (; itLI.More(); itLI.Next())
    {
      const Standard_Integer j      = itLI.Value(); // Index in DS
      const Standard_Integer iRankJ = myDS->Rank(j);
      if (iRankI == iRankJ)
        continue;

      const BOPDS_ShapeInfo& aSJ = myDS->ShapeInfo(j);
      const TopAbs_ShapeEnum aTJ = aSJ.ShapeType();
      const Standard_Integer iTJ = BOPDS_Tools::TypeToInteger(aTJ);

      // avoid interfering of the shape with its sub-shapes
      if (((iTI < iTJ) && aSI.HasSubShape(j)) || ((iTI > iTJ) && aSJ.HasSubShape(i)))
        continue;

      BOPDS_Pair aPair(i, j);
      if (aMPFence.Add(aPair))
      {
        const Standard_Integer iX = BOPDS_Tools::TypeToInteger(aTI, aTJ);
        if (iX < BOPDS_Iterator::NbExtInterfs())
          myExtLists(iX).Append(aPair);
      }
    }
  }

  myUseExt = Standard_True;
}
