/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkAbstractCellLocator.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/

#include "vtkAbstractCellLocator.h"

#include "vtkCellArray.h"
#include "vtkDataArrayRange.h"
#include "vtkDataSet.h"
#include "vtkGenericCell.h"
#include "vtkIdList.h"
#include "vtkIdTypeArray.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkSMPTools.h"
#include "vtkUnstructuredGrid.h"

//------------------------------------------------------------------------------
vtkAbstractCellLocator::vtkAbstractCellLocator()
{
  this->CacheCellBounds = 1;
  this->CellBounds = nullptr;
  this->MaxLevel = 8;
  this->Level = 0;
  this->RetainCellLists = 1;
  this->NumberOfCellsPerNode = 32;
  this->UseExistingSearchStructure = 0;
}

//------------------------------------------------------------------------------
vtkAbstractCellLocator::~vtkAbstractCellLocator() = default;

//------------------------------------------------------------------------------
bool vtkAbstractCellLocator::StoreCellBounds()
{
  if (this->CellBounds)
  {
    return false;
  }
  if (!this->DataSet)
  {
    return false;
  }
  // Allocate space for cell bounds storage, then fill
  vtkIdType numCells = this->DataSet->GetNumberOfCells();
  this->CellBoundsSharedPtr = std::make_shared<std::vector<double>>(numCells * 6);
  this->CellBounds = this->CellBoundsSharedPtr->data();

  // This is done to cause non-thread safe initialization to occur due to
  // side effects from GetCellBounds().
  this->DataSet->GetCellBounds(0, &this->CellBounds[0]);

  vtkSMPTools::For(1, numCells, [&](vtkIdType begin, vtkIdType end) {
    for (vtkIdType cellId = begin; cellId < end; cellId++)
    {
      this->DataSet->GetCellBounds(cellId, &this->CellBounds[cellId * 6]);
    }
  });
  return true;
}

//------------------------------------------------------------------------------
void vtkAbstractCellLocator::FreeCellBounds()
{
  this->CellBoundsSharedPtr.reset();
  this->CellBounds = nullptr;
}

//------------------------------------------------------------------------------
void vtkAbstractCellLocator::ComputeCellBounds()
{
  if (this->CacheCellBounds)
  {
    this->FreeCellBounds();
    this->StoreCellBounds();
  }
}

//------------------------------------------------------------------------------
void vtkAbstractCellLocator::UpdateInternalWeights()
{
  if (this->WeightsTime > this->MTime || !this->DataSet)
  {
    return;
  }

  this->Weights.resize(this->DataSet->GetMaxCellSize());
  this->WeightsTime.Modified();
}

//------------------------------------------------------------------------------
bool vtkAbstractCellLocator::IsInBounds(const double bounds[6], const double x[3], const double tol)
{
  return (bounds[0] - tol) <= x[0] && x[0] <= (bounds[1] + tol) && (bounds[2] - tol) <= x[1] &&
    x[1] <= (bounds[3] + tol) && (bounds[4] - tol) <= x[2] && x[2] <= (bounds[5] + tol);
}

//------------------------------------------------------------------------------
int vtkAbstractCellLocator::IntersectWithLine(const double p1[3], const double p2[3], double tol,
  double& t, double x[3], double pcoords[3], int& subId)
{
  vtkIdType cellId = -1;
  return this->IntersectWithLine(p1, p2, tol, t, x, pcoords, subId, cellId);
}

//------------------------------------------------------------------------------
int vtkAbstractCellLocator::IntersectWithLine(const double p1[3], const double p2[3], double tol,
  double& t, double x[3], double pcoords[3], int& subId, vtkIdType& cellId)
{
  int returnVal;
  returnVal = this->IntersectWithLine(p1, p2, tol, t, x, pcoords, subId, cellId, this->GenericCell);
  return returnVal;
}

//------------------------------------------------------------------------------
int vtkAbstractCellLocator::IntersectWithLine(const double vtkNotUsed(p1)[3],
  const double vtkNotUsed(p2)[3], double vtkNotUsed(tol), double& vtkNotUsed(t),
  double vtkNotUsed(x)[3], double vtkNotUsed(pcoords)[3], int& vtkNotUsed(subId),
  vtkIdType& vtkNotUsed(cellId), vtkGenericCell* vtkNotUsed(cell))
{
  vtkErrorMacro(<< "The locator class - " << this->GetClassName()
                << " does not yet support IntersectWithLine");
  return 0;
}

//------------------------------------------------------------------------------
int vtkAbstractCellLocator::IntersectWithLine(const double vtkNotUsed(p1)[3],
  const double vtkNotUsed(p2)[3], vtkPoints* vtkNotUsed(points), vtkIdList* vtkNotUsed(cellIds))
{
  vtkErrorMacro(<< "The locator class - " << this->GetClassName()
                << " does not yet support this IntersectWithLine interface");
  return 0;
}

//------------------------------------------------------------------------------
int vtkAbstractCellLocator::IntersectWithLine(
  const double p1[3], const double p2[3], const double tol, vtkPoints* points, vtkIdList* cellIds)
{
  return this->IntersectWithLine(p1, p2, tol, points, cellIds, this->GenericCell);
}

//------------------------------------------------------------------------------
int vtkAbstractCellLocator::IntersectWithLine(const double vtkNotUsed(p1)[3],
  const double vtkNotUsed(p2)[3], double vtkNotUsed(tol), vtkPoints* vtkNotUsed(points),
  vtkIdList* vtkNotUsed(cellIds), vtkGenericCell* vtkNotUsed(cell))
{
  vtkErrorMacro(<< "The locator class - " << this->GetClassName()
                << " does not yet support this IntersectWithLine interface");
  return 0;
}

//------------------------------------------------------------------------------
void vtkAbstractCellLocator::FindClosestPoint(
  const double x[3], double closestPoint[3], vtkIdType& cellId, int& subId, double& dist2)
{
  this->FindClosestPoint(x, closestPoint, this->GenericCell, cellId, subId, dist2);
}

//------------------------------------------------------------------------------
void vtkAbstractCellLocator::FindClosestPoint(const double x[3], double closestPoint[3],
  vtkGenericCell* cell, vtkIdType& cellId, int& subId, double& dist2)
{
  int inside;
  double radius = vtkMath::Inf();
  double point[3] = { x[0], x[1], x[2] };
  this->FindClosestPointWithinRadius(
    point, radius, closestPoint, cell, cellId, subId, dist2, inside);
}

//------------------------------------------------------------------------------
vtkIdType vtkAbstractCellLocator::FindClosestPointWithinRadius(double x[3], double radius,
  double closestPoint[3], vtkGenericCell* cell, vtkIdType& cellId, int& subId, double& dist2)
{
  int inside;
  return this->FindClosestPointWithinRadius(
    x, radius, closestPoint, cell, cellId, subId, dist2, inside);
}

//------------------------------------------------------------------------------
vtkIdType vtkAbstractCellLocator::FindClosestPointWithinRadius(
  double x[3], double radius, double closestPoint[3], vtkIdType& cellId, int& subId, double& dist2)
{
  int inside;
  return this->FindClosestPointWithinRadius(
    x, radius, closestPoint, this->GenericCell, cellId, subId, dist2, inside);
}

//------------------------------------------------------------------------------
vtkIdType vtkAbstractCellLocator::FindClosestPointWithinRadius(double vtkNotUsed(x)[3],
  double vtkNotUsed(radius), double vtkNotUsed(closestPoint)[3], vtkGenericCell* vtkNotUsed(cell),
  vtkIdType& vtkNotUsed(cellId), int& vtkNotUsed(subId), double& vtkNotUsed(dist2),
  int& vtkNotUsed(inside))
{
  vtkErrorMacro(<< "The locator class - " << this->GetClassName()
                << " does not yet support FindClosestPointWithinRadius");
  return 0;
}

//------------------------------------------------------------------------------
void vtkAbstractCellLocator::FindCellsWithinBounds(
  double* vtkNotUsed(bbox), vtkIdList* vtkNotUsed(cells))
{
  vtkErrorMacro(<< "The locator class - " << this->GetClassName()
                << " does not yet support FindCellsWithinBounds");
}

//------------------------------------------------------------------------------
void vtkAbstractCellLocator::FindCellsAlongLine(
  const double p1[3], const double p2[3], double tolerance, vtkIdList* cells)
{
  this->IntersectWithLine(p1, p2, tolerance, nullptr, cells, nullptr);
}

//------------------------------------------------------------------------------
void vtkAbstractCellLocator::FindCellsAlongPlane(const double vtkNotUsed(o)[3],
  const double vtkNotUsed(n)[3], double vtkNotUsed(tolerance), vtkIdList* vtkNotUsed(cells))
{
  vtkErrorMacro(<< "The locator " << this->GetClassName()
                << " does not yet support FindCellsAlongPlane");
}

//------------------------------------------------------------------------------
vtkIdType vtkAbstractCellLocator::FindCell(double x[3])
{
  this->UpdateInternalWeights();
  double dist2 = 0, pcoords[3];
  return this->FindCell(x, dist2, this->GenericCell, pcoords, this->Weights.data());
}

//------------------------------------------------------------------------------
vtkIdType vtkAbstractCellLocator::FindCell(
  double x[3], double tol2, vtkGenericCell* GenCell, double pcoords[3], double* weights)
{
  int subId;
  return this->FindCell(x, tol2, GenCell, subId, pcoords, weights);
}

//------------------------------------------------------------------------------
vtkIdType vtkAbstractCellLocator::FindCell(
  double x[3], double tol2, vtkGenericCell* GenCell, int& subId, double pcoords[3], double* weights)
{
  vtkIdType returnVal = -1;
  //
  static bool warning_shown = false;
  if (!warning_shown)
  {
    vtkWarningMacro(<< this->GetClassName() << " Does not implement FindCell"
                    << " Reverting to slow DataSet implementation");
    warning_shown = true;
  }
  //
  if (this->DataSet)
  {
    returnVal = this->DataSet->FindCell(x, nullptr, GenCell, 0, tol2, subId, pcoords, weights);
  }
  return returnVal;
}

//------------------------------------------------------------------------------
bool vtkAbstractCellLocator::InsideCellBounds(double x[3], vtkIdType cell_ID)
{
  if (this->CacheCellBounds)
  {
    return vtkAbstractCellLocator::IsInBounds(&this->CellBounds[cell_ID * 6], x);
  }
  else
  {
    double cellBounds[6];
    this->DataSet->GetCellBounds(cell_ID, cellBounds);
    return vtkAbstractCellLocator::IsInBounds(cellBounds, x);
  }
}

//------------------------------------------------------------------------------
void vtkAbstractCellLocator::GetCellBounds(vtkIdType cellId, double*& cellBoundsPtr)
{
  if (this->CacheCellBounds)
  {
    cellBoundsPtr = &this->CellBounds[cellId * 6];
  }
  else
  {
    this->DataSet->GetCellBounds(cellId, cellBoundsPtr);
  }
}

//------------------------------------------------------------------------------
void vtkAbstractCellLocator::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Cache Cell Bounds: " << this->CacheCellBounds << "\n";
  os << indent << "Retain Cell Lists: " << (this->RetainCellLists ? "On\n" : "Off\n");
  os << indent << "Number of Cells Per Bucket: " << this->NumberOfCellsPerNode << "\n";
}
