// Created on: 2018-03-29
// Created by: Eugeny MALTCHIKOV
// Copyright (c) 2018 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 <BOPAlgo_MakeConnected.hxx>

#include <BOPAlgo_Alerts.hxx>
#include <BOPAlgo_Builder.hxx>
#include <BOPAlgo_Tools.hxx>

#include <BOPTools_AlgoTools.hxx>

#include <BRep_Builder.hxx>

#include <TopExp_Explorer.hxx>

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

void BOPAlgo_MakeConnected::Perform()
{
  // Check the input data
  CheckData();
  if (HasErrors())
  {
    return;
  }

  if (myHistory.IsNull())
  {
    myHistory = new BRepTools_History;
  }

  // Glue the arguments
  MakeConnected();
  if (HasErrors())
  {
    return;
  }

  // Perform material associations for the faces
  AssociateMaterials();
  if (HasErrors())
  {
    return;
  }
}

//=======================================================================
// function : CheckData
// purpose  : Check the validity of input data
//=======================================================================
void BOPAlgo_MakeConnected::CheckData()
{
  // Check the number of arguments
  if (myArguments.IsEmpty())
  {
    // Not enough arguments
    AddError(new BOPAlgo_AlertTooFewArguments());
    return;
  }

  // Check that all shapes in arguments are of the same type

  // Extract the shapes from the compound arguments
  NCollection_List<TopoDS_Shape> aLA;
  // Fence map
  NCollection_Map<TopoDS_Shape, TopTools_ShapeMapHasher> aMFence;

  NCollection_List<TopoDS_Shape>::Iterator itLA(myArguments);
  for (; itLA.More(); itLA.Next())
  {
    BOPTools_AlgoTools::TreatCompound(itLA.Value(), aLA, &aMFence);
  }

  if (aLA.IsEmpty())
  {
    // It seems that all argument shapes are empty compounds
    AddError(new BOPAlgo_AlertTooFewArguments());
    return;
  }

  // Check dimensions of the extracted non-compound shapes
  itLA.Initialize(aLA);
  int iDim = BOPTools_AlgoTools::Dimension(itLA.Value());
  for (itLA.Next(); itLA.More(); itLA.Next())
  {
    if (iDim != BOPTools_AlgoTools::Dimension(itLA.Value()))
    {
      // The arguments are of different type
      AddError(new BOPAlgo_AlertMultiDimensionalArguments());
      return;
    }
  }
}

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

void BOPAlgo_MakeConnected::MakeConnected()
{
  // Initialize the history
  if (myGlueHistory.IsNull())
  {
    myGlueHistory = new BRepTools_History;
  }

  if (myArguments.Extent() == 1)
  {
    // No need to glue the single shape
    myShape = myArguments.First();
  }
  else
  {
    // Glue the shapes
    BOPAlgo_Builder aGluer;
    aGluer.SetArguments(myArguments);
    aGluer.SetGlue(BOPAlgo_GlueShift);
    aGluer.SetRunParallel(myRunParallel);
    aGluer.SetNonDestructive(true);
    aGluer.Perform();
    if (aGluer.HasErrors())
    {
      // Unable to glue the shapes
      TopoDS_Compound aCW;
      BRep_Builder().MakeCompound(aCW);
      for (NCollection_List<TopoDS_Shape>::Iterator it(myArguments); it.More(); it.Next())
      {
        BRep_Builder().Add(aCW, it.Value());
      }
      AddError(new BOPAlgo_AlertUnableToGlue(aCW));
      return;
    }
    myShape = aGluer.Shape();
    // Save the gluing history
    myGlueHistory->Merge(aGluer.Arguments(), aGluer);
    myHistory->Merge(myGlueHistory);
  }

  // Keep the glued shape
  myGlued = myShape;

  // Fill the map of origins
  FillOrigins();
}

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

void BOPAlgo_MakeConnected::FillOrigins()
{
  myOrigins.Clear();

  // Map the history shapes of the arguments
  if (myAllInputsMap.IsEmpty())
  {
    NCollection_List<TopoDS_Shape>::Iterator itLA(myArguments);
    for (; itLA.More(); itLA.Next())
    {
      TopExp::MapShapes(itLA.Value(), myAllInputsMap);
    }
  }

  const int aNbS = myAllInputsMap.Extent();
  for (int i = 1; i <= aNbS; ++i)
  {
    const TopoDS_Shape& aS = myAllInputsMap(i);
    if (!BRepTools_History::IsSupportedType(aS))
    {
      continue;
    }

    // Get Modified & Generated shapes
    for (int j = 0; j < 2; ++j)
    {
      const NCollection_List<TopoDS_Shape>& aLH =
        !j ? myHistory->Modified(aS) : myHistory->Generated(aS);
      NCollection_List<TopoDS_Shape>::Iterator itLH(aLH);
      for (; itLH.More(); itLH.Next())
      {
        const TopoDS_Shape&             aHS  = itLH.Value();
        NCollection_List<TopoDS_Shape>* pLOr = myOrigins.ChangeSeek(aHS);
        if (!pLOr)
        {
          pLOr = myOrigins.Bound(aHS, NCollection_List<TopoDS_Shape>());
        }
        if (!pLOr->Contains(aS))
        {
          pLOr->Append(aS);
        }
      }
    }
  }
}

//=======================================================================
// function : AssociateMaterials
// purpose  : Associates the materials for the border elements
//=======================================================================
void BOPAlgo_MakeConnected::AssociateMaterials()
{
  myMaterials.Clear();

  // Extract all non-compound shapes from the result
  NCollection_List<TopoDS_Shape>                         aLShapes;
  NCollection_Map<TopoDS_Shape, TopTools_ShapeMapHasher> aMFence;
  BOPTools_AlgoTools::TreatCompound(myShape, aLShapes, &aMFence);

  if (aLShapes.IsEmpty())
  {
    return;
  }

  // Define the element type and the material type
  TopAbs_ShapeEnum       anElemType;
  const TopAbs_ShapeEnum aMaterialType = aLShapes.First().ShapeType();
  if (aMaterialType == TopAbs_SOLID || aMaterialType == TopAbs_COMPSOLID)
  {
    anElemType = TopAbs_FACE;
  }
  else if (aMaterialType == TopAbs_FACE || aMaterialType == TopAbs_SHELL)
  {
    anElemType = TopAbs_EDGE;
  }
  else if (aMaterialType == TopAbs_EDGE || aMaterialType == TopAbs_WIRE)
  {
    anElemType = TopAbs_VERTEX;
  }
  else
  {
    return;
  }

  NCollection_List<TopoDS_Shape>::Iterator itLS(aLShapes);
  for (; itLS.More(); itLS.Next())
  {
    const TopoDS_Shape&                   aS   = itLS.Value();
    const NCollection_List<TopoDS_Shape>& aLOr = GetOrigins(aS);
    const TopoDS_Shape&                   aSOr = aLOr.IsEmpty() ? aS : aLOr.First();

    TopExp_Explorer anExp(aS, anElemType);
    for (; anExp.More(); anExp.Next())
    {
      const TopoDS_Shape&             anElement = anExp.Current();
      NCollection_List<TopoDS_Shape>* pLM       = myMaterials.ChangeSeek(anElement);
      if (!pLM)
      {
        pLM = myMaterials.Bound(anElement, NCollection_List<TopoDS_Shape>());
      }
      pLM->Append(aSOr);
    }
  }
}

//=======================================================================
// function : Update
// purpose  : Updates the history, material associations and origins map
//           after periodicity operations
//=======================================================================
void BOPAlgo_MakeConnected::Update()
{
  // Update history
  myHistory->Clear();
  if (!myGlueHistory.IsNull())
  {
    myHistory->Merge(myGlueHistory);
  }
  if (!myPeriodicityMaker.History().IsNull())
  {
    myHistory->Merge(myPeriodicityMaker.History());
  }

  // Fill the map of origins
  FillOrigins();

  // Update the material associations after making the shape periodic
  AssociateMaterials();
}

//=======================================================================
// function : MakePeriodic
// purpose  : Makes the shape periodic according to the given parameters
//=======================================================================
void BOPAlgo_MakeConnected::MakePeriodic(const BOPAlgo_MakePeriodic::PeriodicityParams& theParams)
{
  if (HasErrors())
  {
    return;
  }

  // Make the shape periodic
  myPeriodicityMaker.Clear();
  myPeriodicityMaker.SetShape(myGlued);
  myPeriodicityMaker.SetPeriodicityParameters(theParams);
  myPeriodicityMaker.SetRunParallel(myRunParallel);
  myPeriodicityMaker.Perform();
  if (myPeriodicityMaker.HasErrors())
  {
    // Add warning informing the user that periodicity with
    // given parameters is not possible
    AddWarning(new BOPAlgo_AlertUnableToMakePeriodic(myShape));
    return;
  }

  myShape = myPeriodicityMaker.Shape();

  // Update history, materials, origins
  Update();
}

//=======================================================================
// function : RepeatShape
// purpose  : Repeats the shape in the given direction given number of times
//=======================================================================
void BOPAlgo_MakeConnected::RepeatShape(const int theDirectionID, const int theTimes)
{
  if (HasErrors())
  {
    return;
  }

  if (myPeriodicityMaker.Shape().IsNull() || myPeriodicityMaker.HasErrors())
  {
    // The shape has not been made periodic yet
    AddWarning(new BOPAlgo_AlertShapeIsNotPeriodic(myShape));
    return;
  }

  // Repeat the shape
  myShape = myPeriodicityMaker.RepeatShape(theDirectionID, theTimes);

  // Update history, materials, origins
  Update();
}

//=======================================================================
// function : ClearRepetitions
// purpose  : Clears the repetitions performed on the periodic shape
//           keeping the shape periodic
//=======================================================================
void BOPAlgo_MakeConnected::ClearRepetitions()
{
  if (HasErrors())
  {
    return;
  }

  if (myPeriodicityMaker.Shape().IsNull() || myPeriodicityMaker.HasErrors())
  {
    // The shape has not been made periodic yet
    AddWarning(new BOPAlgo_AlertShapeIsNotPeriodic(myShape));
    return;
  }

  // Clear repetitions
  myPeriodicityMaker.ClearRepetitions();
  myShape = myPeriodicityMaker.Shape();

  // Update history, materials, origins
  Update();
}
