// Created on: 1995-02-13
// Created by: Mister rmi
// Copyright (c) 1995-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 <SelectMgr_SelectionManager.hxx>

#include <Select3D_SensitiveGroup.hxx>
#include <SelectMgr_SelectableObject.hxx>
#include <SelectMgr_Selection.hxx>
#include <StdSelect_BRepSelectionTool.hxx>
#include <TCollection_AsciiString.hxx>

IMPLEMENT_STANDARD_RTTIEXT(SelectMgr_SelectionManager, Standard_Transient)

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

SelectMgr_SelectionManager::SelectMgr_SelectionManager(
  const occ::handle<SelectMgr_ViewerSelector>& theSelector)
    : mySelector(theSelector)
{
  //
}

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

bool SelectMgr_SelectionManager::Contains(
  const occ::handle<SelectMgr_SelectableObject>& theObject) const
{
  return myGlobal.Contains(theObject);
}

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

void SelectMgr_SelectionManager::Load(const occ::handle<SelectMgr_SelectableObject>& theObject,
                                      const int                                      theMode)
{
  if (myGlobal.Contains(theObject))
    return;

  for (NCollection_List<occ::handle<PrsMgr_PresentableObject>>::Iterator anChildrenIter(
         theObject->Children());
       anChildrenIter.More();
       anChildrenIter.Next())
  {
    Load(occ::down_cast<SelectMgr_SelectableObject>(anChildrenIter.Value()), theMode);
  }

  if (!theObject->HasOwnPresentations())
    return;

  myGlobal.Add(theObject);
  if (!mySelector->Contains(theObject) && theObject->HasOwnPresentations())
  {
    mySelector->AddSelectableObject(theObject);
  }
  if (theMode != -1)
    loadMode(theObject, theMode);
}

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

void SelectMgr_SelectionManager::Remove(const occ::handle<SelectMgr_SelectableObject>& theObject)
{
  for (NCollection_List<occ::handle<PrsMgr_PresentableObject>>::Iterator anChildrenIter(
         theObject->Children());
       anChildrenIter.More();
       anChildrenIter.Next())
  {
    Remove(occ::down_cast<SelectMgr_SelectableObject>(anChildrenIter.Value()));
  }

  if (!theObject->HasOwnPresentations())
    return;

  if (myGlobal.Contains(theObject))
  {
    if (mySelector->Contains(theObject))
    {
      for (NCollection_Sequence<occ::handle<SelectMgr_Selection>>::Iterator aSelIter(
             theObject->Selections());
           aSelIter.More();
           aSelIter.Next())
      {
        mySelector->RemoveSelectionOfObject(theObject, aSelIter.Value());
        aSelIter.Value()->UpdateBVHStatus(SelectMgr_TBU_Remove);
        mySelector->Deactivate(aSelIter.Value());
      }
      mySelector->RemoveSelectableObject(theObject);
    }
    myGlobal.Remove(theObject);
  }

  theObject->ClearSelections();
}

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

void SelectMgr_SelectionManager::Activate(const occ::handle<SelectMgr_SelectableObject>& theObject,
                                          const int                                      theMode)
{
  if (theMode == -1)
    return;

  for (NCollection_List<occ::handle<PrsMgr_PresentableObject>>::Iterator anChildIter(
         theObject->Children());
       anChildIter.More();
       anChildIter.Next())
  {
    occ::handle<SelectMgr_SelectableObject> aChild =
      occ::down_cast<SelectMgr_SelectableObject>(anChildIter.Value());
    if (aChild->DisplayStatus() != PrsMgr_DisplayStatus_Erased)
    {
      Activate(aChild, theMode);
    }
  }
  if (!theObject->HasOwnPresentations())
    return;

  bool isComputed = false;
  if (const occ::handle<SelectMgr_Selection>& aSelOld = theObject->Selection(theMode))
  {
    isComputed = !aSelOld->IsEmpty();
  }
  if (!isComputed)
  {
    loadMode(theObject, theMode);
  }

  const occ::handle<SelectMgr_Selection>& aSelection = theObject->Selection(theMode);
  switch (aSelection->UpdateStatus())
  {
    case SelectMgr_TOU_Full: {
      if (theObject->HasSelection(theMode))
      {
        mySelector->RemoveSelectionOfObject(theObject, aSelection);
      }
      theObject->RecomputePrimitives(theMode);
      // pass through SelectMgr_TOU_Partial
    }
      [[fallthrough]];
    case SelectMgr_TOU_Partial: {
      theObject->UpdateTransformations(aSelection);
      mySelector->RebuildObjectsTree();
      break;
    }
    default:
      break;
  }
  aSelection->UpdateStatus(SelectMgr_TOU_None);

  switch (aSelection->BVHUpdateStatus())
  {
    case SelectMgr_TBU_Add:
    case SelectMgr_TBU_Renew: {
      mySelector->AddSelectionToObject(theObject, aSelection);
      break;
    }
    case SelectMgr_TBU_Remove: {
      if (aSelection->GetSelectionState() == SelectMgr_SOS_Deactivated)
      {
        mySelector->AddSelectionToObject(theObject, aSelection);
      }
      break;
    }
    default:
      break;
  }
  aSelection->UpdateBVHStatus(SelectMgr_TBU_None);

  if (myGlobal.Contains(theObject))
  {
    mySelector->Activate(theObject->Selection(theMode));
  }
}

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

void SelectMgr_SelectionManager::Deactivate(
  const occ::handle<SelectMgr_SelectableObject>& theObject,
  const int                                      theMode)
{
  for (NCollection_List<occ::handle<PrsMgr_PresentableObject>>::Iterator anChildrenIter(
         theObject->Children());
       anChildrenIter.More();
       anChildrenIter.Next())
  {
    Deactivate(occ::down_cast<SelectMgr_SelectableObject>(anChildrenIter.Value()), theMode);
  }
  if (!theObject->HasOwnPresentations())
  {
    return;
  }
  if (!myGlobal.Contains(theObject))
  {
    return;
  }

  const occ::handle<SelectMgr_Selection>& aSel = theObject->Selection(theMode);
  if (theMode == -1)
  {
    for (NCollection_Sequence<occ::handle<SelectMgr_Selection>>::Iterator aSelIter(
           theObject->Selections());
         aSelIter.More();
         aSelIter.Next())
    {
      mySelector->Deactivate(aSelIter.Value());
    }
  }
  else if (!aSel.IsNull())
  {
    mySelector->Deactivate(aSel);
  }
}

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

bool SelectMgr_SelectionManager::IsActivated(
  const occ::handle<SelectMgr_SelectableObject>& theObject,
  const int                                      theMode) const
{
  for (NCollection_List<occ::handle<PrsMgr_PresentableObject>>::Iterator anChildrenIter(
         theObject->Children());
       anChildrenIter.More();
       anChildrenIter.Next())
  {
    if (IsActivated(occ::down_cast<SelectMgr_SelectableObject>(anChildrenIter.Value()), theMode))
      return true;
  }
  if (!theObject->HasOwnPresentations())
  {
    return false;
  }
  if (!myGlobal.Contains(theObject))
  {
    return false;
  }

  if (theMode == -1)
  {
    for (NCollection_Sequence<occ::handle<SelectMgr_Selection>>::Iterator aSelIter(
           theObject->Selections());
         aSelIter.More();
         aSelIter.Next())
    {
      if (mySelector->Status(aSelIter.Value()) == SelectMgr_SOS_Activated)
      {
        return true;
      }
    }
    return false;
  }

  const occ::handle<SelectMgr_Selection>& aSelection = theObject->Selection(theMode);
  if (aSelection.IsNull())
  {
    return false;
  }
  return !aSelection.IsNull() && mySelector->Status(aSelection) == SelectMgr_SOS_Activated;
}

//=======================================================================
// function : ClearSelectionStructures
// purpose  : Removes sensitive entities from all viewer selectors
//           after method Clear() was called to the selection they belonged to
//           or it was recomputed somehow
//=======================================================================
void SelectMgr_SelectionManager::ClearSelectionStructures(
  const occ::handle<SelectMgr_SelectableObject>& theObj,
  const int                                      theMode)
{
  for (NCollection_List<occ::handle<PrsMgr_PresentableObject>>::Iterator anChildrenIter(
         theObj->Children());
       anChildrenIter.More();
       anChildrenIter.Next())
  {
    ClearSelectionStructures(occ::down_cast<SelectMgr_SelectableObject>(anChildrenIter.Value()),
                             theMode);
  }

  if (!theObj->HasOwnPresentations())
  {
    return;
  }
  if (!myGlobal.Contains(theObj))
  {
    return;
  }

  if (theMode != -1)
  {
    if (const occ::handle<SelectMgr_Selection>& aSelection = theObj->Selection(theMode))
    {
      mySelector->RemoveSelectionOfObject(theObj, aSelection);
      aSelection->UpdateBVHStatus(SelectMgr_TBU_Add);
    }
  }
  else
  {
    for (NCollection_Sequence<occ::handle<SelectMgr_Selection>>::Iterator aSelIter(
           theObj->Selections());
         aSelIter.More();
         aSelIter.Next())
    {
      const occ::handle<SelectMgr_Selection>& aSelection = aSelIter.Value();
      mySelector->RemoveSelectionOfObject(theObj, aSelection);
      aSelection->UpdateBVHStatus(SelectMgr_TBU_Add);
    }
  }
  mySelector->RebuildObjectsTree();
}

//=======================================================================
// function : RestoreSelectionStructuress
// purpose  : Re-adds newely calculated sensitive  entities of recomputed selection
//           defined by mode theMode to all viewer selectors contained that selection.
//=======================================================================
void SelectMgr_SelectionManager::RestoreSelectionStructures(
  const occ::handle<SelectMgr_SelectableObject>& theObj,
  const int                                      theMode)
{
  for (NCollection_List<occ::handle<PrsMgr_PresentableObject>>::Iterator anChildrenIter(
         theObj->Children());
       anChildrenIter.More();
       anChildrenIter.Next())
  {
    RestoreSelectionStructures(occ::down_cast<SelectMgr_SelectableObject>(anChildrenIter.Value()),
                               theMode);
  }
  if (!theObj->HasOwnPresentations())
  {
    return;
  }
  if (!myGlobal.Contains(theObj))
  {
    return;
  }

  if (theMode != -1)
  {
    if (const occ::handle<SelectMgr_Selection>& aSelection = theObj->Selection(theMode))
    {
      mySelector->AddSelectionToObject(theObj, aSelection);
      aSelection->UpdateBVHStatus(SelectMgr_TBU_None);
    }
  }
  else
  {
    for (NCollection_Sequence<occ::handle<SelectMgr_Selection>>::Iterator aSelIter(
           theObj->Selections());
         aSelIter.More();
         aSelIter.Next())
    {
      const occ::handle<SelectMgr_Selection>& aSelection = aSelIter.Value();
      mySelector->AddSelectionToObject(theObj, aSelection);
      aSelection->UpdateBVHStatus(SelectMgr_TBU_None);
    }
  }
  mySelector->RebuildObjectsTree();
}

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

void SelectMgr_SelectionManager::recomputeSelectionMode(
  const occ::handle<SelectMgr_SelectableObject>& theObject,
  const occ::handle<SelectMgr_Selection>&        theSelection,
  const int                                      theMode)
{
  theSelection->UpdateStatus(SelectMgr_TOU_Full);

  ClearSelectionStructures(theObject, theMode);
  theObject->RecomputePrimitives(theMode);
  RestoreSelectionStructures(theObject, theMode);
  theSelection->UpdateStatus(SelectMgr_TOU_None);
  theSelection->UpdateBVHStatus(SelectMgr_TBU_None);
}

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

void SelectMgr_SelectionManager::RecomputeSelection(
  const occ::handle<SelectMgr_SelectableObject>& theObject,
  const bool                                     theIsForce,
  const int                                      theMode)
{
  if (theIsForce)
  {
    if (theMode == -1)
    {
      ClearSelectionStructures(theObject);
      theObject->RecomputePrimitives();
      theObject->UpdateTransformation();
      RestoreSelectionStructures(theObject);
    }
    else if (theObject->HasSelection(theMode))
    {
      ClearSelectionStructures(theObject, theMode);
      theObject->RecomputePrimitives(theMode);
      theObject->UpdateTransformation();
      RestoreSelectionStructures(theObject, theMode);
    }
    return;
  }

  for (NCollection_List<occ::handle<PrsMgr_PresentableObject>>::Iterator anChildrenIter(
         theObject->Children());
       anChildrenIter.More();
       anChildrenIter.Next())
  {
    RecomputeSelection(occ::down_cast<SelectMgr_SelectableObject>(anChildrenIter.Value()),
                       theIsForce,
                       theMode);
  }
  if (!theObject->HasOwnPresentations())
  {
    return;
  }
  if (!myGlobal.Contains(theObject))
  {
    return;
  }

  if (theMode == -1)
  {
    for (NCollection_Sequence<occ::handle<SelectMgr_Selection>>::Iterator aSelIter(
           theObject->Selections());
         aSelIter.More();
         aSelIter.Next())
    {
      const occ::handle<SelectMgr_Selection>& aSelection = aSelIter.Value();
      const int                               aSelMode   = aSelection->Mode();
      recomputeSelectionMode(theObject, aSelection, aSelMode);
    }
  }
  else
  {
    if (const occ::handle<SelectMgr_Selection>& aSelection = theObject->Selection(theMode))
    {
      recomputeSelectionMode(theObject, aSelection, theMode);
    }
  }
}

//=======================================================================
// function : Update
// purpose  : Selections are recalculated if they are flagged
//           "TO RECALCULATE" and activated in one of selectors.
//           If ForceUpdate = True, and they are "TO RECALCULATE"
//           This is done without caring for the state of activation.
//=======================================================================
void SelectMgr_SelectionManager::Update(const occ::handle<SelectMgr_SelectableObject>& theObject,
                                        const bool                                     theIsForce)
{
  for (NCollection_List<occ::handle<PrsMgr_PresentableObject>>::Iterator aChildIter(
         theObject->Children());
       aChildIter.More();
       aChildIter.Next())
  {
    Update(occ::down_cast<SelectMgr_SelectableObject>(aChildIter.Value()), theIsForce);
  }
  if (!theObject->HasOwnPresentations())
  {
    return;
  }

  for (NCollection_Sequence<occ::handle<SelectMgr_Selection>>::Iterator aSelIter(
         theObject->Selections());
       aSelIter.More();
       aSelIter.Next())
  {
    const occ::handle<SelectMgr_Selection>& aSelection = aSelIter.Value();
    if (theIsForce || mySelector->Status(aSelection) == SelectMgr_SOS_Activated)
    {
      switch (aSelection->UpdateStatus())
      {
        case SelectMgr_TOU_Full: {
          ClearSelectionStructures(theObject, aSelection->Mode());
          theObject->RecomputePrimitives(aSelection->Mode()); // no break on purpose...
          RestoreSelectionStructures(theObject, aSelection->Mode());
          // pass through SelectMgr_TOU_Partial
        }
          [[fallthrough]];
        case SelectMgr_TOU_Partial: {
          theObject->UpdateTransformations(aSelection);
          mySelector->RebuildObjectsTree();
          break;
        }
        default:
          break;
      }
      aSelection->UpdateStatus(SelectMgr_TOU_None);
      aSelection->UpdateBVHStatus(SelectMgr_TBU_None);
    }
  }
}

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

void SelectMgr_SelectionManager::loadMode(const occ::handle<SelectMgr_SelectableObject>& theObject,
                                          const int                                      theMode)
{
  if (theMode == -1)
  {
    return;
  }

  if (const occ::handle<SelectMgr_Selection>& aSelOld = theObject->Selection(theMode))
  {
    if (aSelOld->IsEmpty())
    {
      if (aSelOld->BVHUpdateStatus() == SelectMgr_TBU_Remove)
      {
        occ::handle<SelectMgr_Selection> aNewSel = new SelectMgr_Selection(theMode);
        theObject->AddSelection(aNewSel, theMode);
        aNewSel->UpdateBVHStatus(SelectMgr_TBU_Remove);
        aNewSel->SetSelectionState(SelectMgr_SOS_Deactivated);

        buildBVH(aNewSel);
      }
    }
    return;
  }

  occ::handle<SelectMgr_Selection> aNewSel = new SelectMgr_Selection(theMode);
  theObject->AddSelection(aNewSel, theMode);
  if (myGlobal.Contains(theObject))
  {
    mySelector->AddSelectionToObject(theObject, aNewSel);
    aNewSel->UpdateBVHStatus(SelectMgr_TBU_None);
  }

  buildBVH(aNewSel);
}

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

void SelectMgr_SelectionManager::buildBVH(const occ::handle<SelectMgr_Selection>& theSelection)
{
  if (mySelector->ToPrebuildBVH())
  {
    for (NCollection_Vector<occ::handle<SelectMgr_SensitiveEntity>>::Iterator anIter(
           theSelection->Entities());
         anIter.More();
         anIter.Next())
    {
      const occ::handle<Select3D_SensitiveEntity>& anEntity = anIter.Value()->BaseSensitive();
      mySelector->QueueBVHBuild(anEntity);

      if (occ::handle<Select3D_SensitiveGroup> aGroup =
            occ::down_cast<Select3D_SensitiveGroup>(anEntity))
      {
        for (NCollection_IndexedMap<occ::handle<Select3D_SensitiveEntity>>::Iterator
               aSubEntitiesIter(aGroup->Entities());
             aSubEntitiesIter.More();
             aSubEntitiesIter.Next())
        {
          const occ::handle<Select3D_SensitiveEntity>& aSubEntity = aSubEntitiesIter.Value();
          mySelector->QueueBVHBuild(aSubEntity);
        }
      }
    }
  }
  else
  {
    StdSelect_BRepSelectionTool::PreBuildBVH(theSelection);
  }
}

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

void SelectMgr_SelectionManager::SetUpdateMode(
  const occ::handle<SelectMgr_SelectableObject>& theObject,
  const SelectMgr_TypeOfUpdate                   theType)
{
  for (NCollection_Sequence<occ::handle<SelectMgr_Selection>>::Iterator aSelIter(
         theObject->Selections());
       aSelIter.More();
       aSelIter.Next())
  {
    aSelIter.Value()->UpdateStatus(theType);
  }
}

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

void SelectMgr_SelectionManager::SetUpdateMode(
  const occ::handle<SelectMgr_SelectableObject>& theObject,
  const int                                      theMode,
  const SelectMgr_TypeOfUpdate                   theType)
{
  if (const occ::handle<SelectMgr_Selection>& aSel = theObject->Selection(theMode))
  {
    aSel->UpdateStatus(theType);
  }
}

//=======================================================================
// function : SetSelectionSensitivity
// purpose  : Allows to manage sensitivity of a particular selection of interactive object theObject
// and
//           changes previous sensitivity value of all sensitive entities in selection with theMode
//           to the given theNewSensitivity.
//=======================================================================
void SelectMgr_SelectionManager::SetSelectionSensitivity(
  const occ::handle<SelectMgr_SelectableObject>& theObject,
  const int                                      theMode,
  const int                                      theNewSens)
{
  Standard_ASSERT_RAISE(theNewSens >= 0,
                        "Error! Selection sensitivity should not be negative value.");
  if (theObject.IsNull())
  {
    return;
  }

  const occ::handle<SelectMgr_Selection>& aSel = theObject->Selection(theMode);
  if (aSel.IsNull())
  {
    return;
  }

  const int aPrevSens = aSel->Sensitivity();
  aSel->SetSensitivity(theNewSens);
  if (myGlobal.Contains(theObject) && mySelector->Contains(theObject))
  {
    mySelector->myTolerances.Decrement(aPrevSens);
    mySelector->myTolerances.Add(theNewSens);
  }
}

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

void SelectMgr_SelectionManager::UpdateSelection(
  const occ::handle<SelectMgr_SelectableObject>& theObject)
{
  if (myGlobal.Contains(theObject) && mySelector->Contains(theObject))
  {
    mySelector->MoveSelectableObject(theObject);
  }
}
