// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-FileCopyrightText: Copyright (c) Sandia Corporation
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkOctreePointLocatorNode.h"

#include "vtkBox.h"
#include "vtkCell.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPlanesIntersection.h"
#include "vtkPoints.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkOctreePointLocatorNode);

//------------------------------------------------------------------------------
vtkOctreePointLocatorNode::vtkOctreePointLocatorNode()
  : NumberOfPoints(0)
  , Children(nullptr)
  , ID(-1)
  , MinID(-1)
{
  // set the min and max data value and bounds since we won't know it
  // for a while
  for (int i = 0; i < 3; i++)
  {
    this->MinDataBounds[i] = VTK_DOUBLE_MAX;
    this->MaxDataBounds[i] = VTK_DOUBLE_MIN;
    this->MinBounds[i] = VTK_DOUBLE_MAX;
    this->MaxBounds[i] = VTK_DOUBLE_MIN;
  }
}

//------------------------------------------------------------------------------
vtkOctreePointLocatorNode::~vtkOctreePointLocatorNode()
{
  this->DeleteChildNodes();
}

//------------------------------------------------------------------------------
void vtkOctreePointLocatorNode::CreateChildNodes()
{
  if (!this->Children)
  {
    int i;
    double midpoint[3];
    for (i = 0; i < 3; i++)
    {
      midpoint[i] = (this->MinBounds[i] + this->MaxBounds[i]) * .5;
    }
    this->Children = new vtkOctreePointLocatorNode*[8];
    for (i = 0; i < 8; i++)
    {
      this->Children[i] = vtkOctreePointLocatorNode::New();
      double NewMin[3], NewMax[3];
      for (int j = 0; j < 3; j++)
      {
        if (!((i >> j) & 1))
        {
          NewMin[j] = this->MinBounds[j];
          NewMax[j] = midpoint[j];
        }
        else
        {
          NewMin[j] = midpoint[j];
          NewMax[j] = this->MaxBounds[j];
        }
      }
      this->Children[i]->SetMinBounds(NewMin);
      this->Children[i]->SetMaxBounds(NewMax);
    }
  }
}

//------------------------------------------------------------------------------
void vtkOctreePointLocatorNode::DeleteChildNodes()
{
  if (this->Children)
  {
    for (int i = 0; i < 8; i++)
    {
      this->Children[i]->Delete();
    }
    delete[] this->Children;
    this->Children = nullptr;
  }
}

//------------------------------------------------------------------------------
vtkOctreePointLocatorNode* vtkOctreePointLocatorNode::GetChild(int i)
{
  if (this->Children)
  {
    return this->Children[i];
  }
  return nullptr;
}

//------------------------------------------------------------------------------
int vtkOctreePointLocatorNode::GetSubOctantIndex(double* point, int CheckContainment)
{
  int i, index = 0;
  if (CheckContainment)
  {
    for (i = 0; i < 3; i++)
    {
      if (point[i] <= this->MinBounds[i] || point[i] > this->MaxBounds[i])
      {
        return -1;
      }
    }
  }

  for (i = 0; i < 3; i++)
  {
    if (point[i] > (this->MinBounds[i] + this->MaxBounds[i]) * .5)
    {
      index += 1 << i;
    }
  }
  return index;
}

//------------------------------------------------------------------------------
void vtkOctreePointLocatorNode::ComputeOctreeNodeInformation(
  vtkOctreePointLocatorNode* Parent, int& NextLeafId, int& NextMinId, float* coordinates)
{
  this->MinID = NextMinId;
  if (this->Children)
  {
    int i;
    for (i = 0; i < 8; i++)
    {
      this->Children[i]->ComputeOctreeNodeInformation(this, NextLeafId, NextMinId, coordinates);
    }
    // a non-leaf region can get its data bounds from its children...
    this->SetMinDataBounds(this->Children[0]->GetMinDataBounds());
    this->SetMaxDataBounds(this->Children[0]->GetMaxDataBounds());

    for (i = 1; i < 8; i++)
    {
      const double* min = this->Children[i]->GetMinDataBounds();
      const double* max = this->Children[i]->GetMaxDataBounds();
      for (int j = 0; j < 3; j++)
      {
        if (min[j] < this->MinDataBounds[j])
        {
          this->MinDataBounds[j] = min[j];
        }
        if (max[j] > this->MaxDataBounds[j])
        {
          this->MaxDataBounds[j] = max[j];
        }
      }
    }
  }
  else
  {
    this->ID = NextLeafId;
    NextLeafId++;
    NextMinId = this->MinID + this->NumberOfPoints;
    if (this->NumberOfPoints == 0)
    {
      // since there are no points in this, set the data bounds
      // such that they won't affect anything else
      this->SetMinDataBounds(Parent->GetMaxBounds());
      this->SetMaxDataBounds(Parent->GetMinBounds());
    }
    else
    {
      int i;
      float* coordptr = coordinates + 3 * this->MinID;
      for (i = 0; i < 3; i++)
      {
        this->MinDataBounds[i] = this->MaxDataBounds[i] = coordptr[i];
      }
      for (i = 1; i < this->NumberOfPoints; i++)
      {
        coordptr += 3;
        for (int j = 0; j < 3; j++)
        {
          if (coordptr[j] < this->MinDataBounds[j])
          {
            this->MinDataBounds[j] = coordptr[j];
          }
          else if (coordptr[j] > this->MaxDataBounds[j])
          {
            this->MaxDataBounds[j] = coordptr[j];
          }
        }
      }
    }
  }
}

//------------------------------------------------------------------------------
void vtkOctreePointLocatorNode::SetBounds(
  double xMin, double xMax, double yMin, double yMax, double zMin, double zMax)
{
  this->MinBounds[0] = xMin;
  this->MaxBounds[0] = xMax;
  this->MinBounds[1] = yMin;
  this->MaxBounds[1] = yMax;
  this->MinBounds[2] = zMin;
  this->MaxBounds[2] = zMax;
}

//------------------------------------------------------------------------------
void vtkOctreePointLocatorNode::GetBounds(double* b) const
{
  b[0] = this->MinBounds[0];
  b[1] = this->MaxBounds[0];
  b[2] = this->MinBounds[1];
  b[3] = this->MaxBounds[1];
  b[4] = this->MinBounds[2];
  b[5] = this->MaxBounds[2];
}

//------------------------------------------------------------------------------
void vtkOctreePointLocatorNode::SetDataBounds(
  double xMin, double xMax, double yMin, double yMax, double zMin, double zMax)
{
  this->MinDataBounds[0] = xMin;
  this->MaxDataBounds[0] = xMax;
  this->MinDataBounds[1] = yMin;
  this->MaxDataBounds[1] = yMax;
  this->MinDataBounds[2] = zMin;
  this->MaxDataBounds[2] = zMax;
}

//------------------------------------------------------------------------------
void vtkOctreePointLocatorNode::GetDataBounds(double* b) const
{
  b[0] = this->MinDataBounds[0];
  b[1] = this->MaxDataBounds[0];
  b[2] = this->MinDataBounds[1];
  b[3] = this->MaxDataBounds[1];
  b[4] = this->MinDataBounds[2];
  b[5] = this->MaxDataBounds[2];
}

//------------------------------------------------------------------------------
// Squared distance from any point anywhere to the boundary of spatial region
//
double vtkOctreePointLocatorNode::GetDistance2ToBoundary(
  double x, double y, double z, vtkOctreePointLocatorNode* top, int useDataBounds = 0)
{
  return this->GetDistance2ToBoundaryPrivate(x, y, z, nullptr, 0, top, useDataBounds);
}

//------------------------------------------------------------------------------
// Squared distance from any point anywhere to the boundary of spatial region,
// and give me the point on the boundary closest to this point.
//
double vtkOctreePointLocatorNode::GetDistance2ToBoundary(
  double x, double y, double z, double* p, vtkOctreePointLocatorNode* top, int useDataBounds = 0)
{
  return this->GetDistance2ToBoundaryPrivate(x, y, z, p, 0, top, useDataBounds);
}

//------------------------------------------------------------------------------
// The point is inside the region, and I want the distance (squared)
// to the closest "interior" wall, one that is not an outer boundary of
// the entire space.
//
double vtkOctreePointLocatorNode::GetDistance2ToInnerBoundary(
  double x, double y, double z, vtkOctreePointLocatorNode* top)
{
  return this->GetDistance2ToBoundaryPrivate(x, y, z, nullptr, 1, top, 0);
}

//------------------------------------------------------------------------------
double vtkOctreePointLocatorNode::GetDistance2ToBoundaryPrivate(double x, double y,
  double z,                       // from this point
  double* p,                      // set to point on boundary that is closest
  int innerBoundaryOnly,          // ignore boundaries on "outside"
  vtkOctreePointLocatorNode* top, // the top of the octree
  int useDataBounds = 0)          // use bounds of data within region instead
{
  double minDistance, dist;
  double edgePt[3];
  double cornerPt[3];
  double pt3[3];

  double *min, *max;

  if (useDataBounds)
  {
    min = this->MinDataBounds;
    max = this->MaxDataBounds; // data inside region
  }
  else
  {
    min = this->MinBounds;
    max = this->MaxBounds; // region itself
  }

  double* outerBoundaryMin = nullptr;
  double* outerBoundaryMax = nullptr;

  if (innerBoundaryOnly)
  {
    // We want the distance to the nearest inner boundary, because we
    // are only interested in boundaries such that there may be points on
    // the other side.  This option only makes sense when the point supplied
    // is inside this node (region).

    outerBoundaryMin = (useDataBounds ? top->MinDataBounds : top->MinBounds);
    outerBoundaryMax = (useDataBounds ? top->MaxDataBounds : top->MaxBounds);
  }

  double xmax = max[0];
  double ymax = max[1];
  double zmax = max[2];
  double xmin = min[0];
  double ymin = min[1];
  double zmin = min[2];

  int xless = (x < xmin);
  int xmore = (x > xmax);
  int yless = (y < ymin);
  int ymore = (y > ymax);
  int zless = (z < zmin);
  int zmore = (z > zmax);

  int withinX = !xless && !xmore;
  int withinY = !yless && !ymore;
  int withinZ = !zless && !zmore;

  int mindim = 0;

  if (withinX && withinY && withinZ) // point is inside the box
  {
    if (!innerBoundaryOnly)
    {
      minDistance = x - xmin;
      mindim = 0;

      if ((dist = xmax - x) < minDistance)
      {
        mindim = 1;
        minDistance = dist;
      }
      if ((dist = y - ymin) < minDistance)
      {
        mindim = 2;
        minDistance = dist;
      }
      if ((dist = ymax - y) < minDistance)
      {
        mindim = 3;
        minDistance = dist;
      }
      if ((dist = z - zmin) < minDistance)
      {
        mindim = 4;
        minDistance = dist;
      }
      if ((dist = zmax - z) < minDistance)
      {
        mindim = 5;
        minDistance = dist;
      }
    }
    else
    {
      int first = 1;
      minDistance = VTK_FLOAT_MAX; // Suppresses warning message.

      dist = x - xmin;
      if ((xmin != outerBoundaryMin[0]) && ((dist < minDistance) || first))
      {
        mindim = 0;
        minDistance = dist;
        first = 0;
      }
      dist = xmax - x;
      if ((xmax != outerBoundaryMax[0]) && ((dist < minDistance) || first))
      {
        mindim = 1;
        minDistance = dist;
        first = 0;
      }
      dist = y - ymin;
      if ((ymin != outerBoundaryMin[1]) && ((dist < minDistance) || first))
      {
        mindim = 2;
        minDistance = dist;
        first = 0;
      }
      dist = ymax - y;
      if ((ymax != outerBoundaryMax[1]) && ((dist < minDistance) || first))
      {
        mindim = 3;
        minDistance = dist;
        first = 0;
      }
      dist = z - zmin;
      if ((zmin != outerBoundaryMin[2]) && ((dist < minDistance) || first))
      {
        mindim = 4;
        minDistance = dist;
        first = 0;
      }
      dist = zmax - z;
      if ((zmax != outerBoundaryMax[2]) && ((dist < minDistance) || first))
      {
        mindim = 5;
        minDistance = dist;
      }
    }

    // if there are no inner boundaries we don't want to square.
    if (minDistance != VTK_FLOAT_MAX)
    {
      minDistance *= minDistance;
    }

    if (p)
    {
      p[0] = x;
      p[1] = y;
      p[2] = z;

      if (mindim == 0)
      {
        p[0] = xmin;
      }
      else if (mindim == 1)
      {
        p[0] = xmax;
      }
      else if (mindim == 2)
      {
        p[1] = ymin;
      }
      else if (mindim == 3)
      {
        p[1] = ymax;
      }
      else if (mindim == 4)
      {
        p[2] = zmin;
      }
      else if (mindim == 5)
      {
        p[2] = zmax;
      }
    }
  }
  else if (withinX && withinY) // point projects orthogonally to a face
  {
    minDistance = (zless ? zmin - z : z - zmax);
    minDistance *= minDistance;

    if (p)
    {
      p[0] = x;
      p[1] = y;
      p[2] = (zless ? zmin : zmax);
    }
  }
  else if (withinX && withinZ)
  {
    minDistance = (yless ? ymin - y : y - ymax);
    minDistance *= minDistance;

    if (p)
    {
      p[0] = x;
      p[2] = z;
      p[1] = (yless ? ymin : ymax);
    }
  }
  else if (withinY && withinZ)
  {
    minDistance = (xless ? xmin - x : x - xmax);
    minDistance *= minDistance;

    if (p)
    {
      p[1] = y;
      p[2] = z;
      p[0] = (xless ? xmin : xmax);
    }
  }
  else if (withinX || withinY || withinZ) // point is closest to an edge
  {
    edgePt[0] = (withinX ? x : (xless ? xmin : xmax));
    edgePt[1] = (withinY ? y : (yless ? ymin : ymax));
    edgePt[2] = (withinZ ? z : (zless ? zmin : zmax));

    pt3[0] = x;
    pt3[1] = y;
    pt3[2] = z;

    minDistance = vtkMath::Distance2BetweenPoints(pt3, edgePt);

    if (p)
    {
      p[0] = edgePt[0];
      p[1] = edgePt[1];
      p[2] = edgePt[2];
    }
  }
  else // point is closest to a corner
  {
    cornerPt[0] = (xless ? xmin : xmax);
    cornerPt[1] = (yless ? ymin : ymax);
    cornerPt[2] = (zless ? zmin : zmax);

    pt3[0] = x;
    pt3[1] = y;
    pt3[2] = z;

    minDistance = vtkMath::Distance2BetweenPoints(pt3, cornerPt);

    if (p)
    {
      p[0] = cornerPt[0];
      p[1] = cornerPt[1];
      p[2] = cornerPt[2];
    }
  }

  return minDistance;
}

//------------------------------------------------------------------------------
vtkTypeBool vtkOctreePointLocatorNode::ContainsPoint(
  double x, double y, double z, int useDataBounds = 0)
{
  double *min, *max;

  if (useDataBounds)
  {
    min = this->MinDataBounds;
    max = this->MaxDataBounds;
  }
  else
  {
    min = this->MinBounds;
    max = this->MaxBounds;
  }

  if ((min[0] >= x) || (max[0] < x) || (min[1] >= y) || (max[1] < y) || (min[2] >= z) ||
    (max[2] < z))
  {
    return 0;
  }
  else
  {
    return 1;
  }
}

//------------------------------------------------------------------------------
int vtkOctreePointLocatorNode::IntersectsRegion(vtkPlanesIntersection* pi, int useDataBounds)
{
  double xMin, xMax, yMin, yMax, zMin, zMax;
  vtkPoints* box = vtkPoints::New();

  box->SetNumberOfPoints(8);

  double *min, *max;

  if (useDataBounds)
  {
    min = this->MinDataBounds;
    max = this->MaxDataBounds;
  }
  else
  {
    min = this->MinBounds;
    max = this->MaxBounds;
  }

  xMin = min[0];
  xMax = max[0];
  yMin = min[1];
  yMax = max[1];
  zMin = min[2];
  zMax = max[2];

  box->SetPoint(0, xMax, yMin, zMax);
  box->SetPoint(1, xMax, yMin, zMin);
  box->SetPoint(2, xMax, yMax, zMin);
  box->SetPoint(3, xMax, yMax, zMax);
  box->SetPoint(4, xMin, yMin, zMax);
  box->SetPoint(5, xMin, yMin, zMin);
  box->SetPoint(6, xMin, yMax, zMin);
  box->SetPoint(7, xMin, yMax, zMax);

  int intersects = pi->IntersectsRegion(box);

  box->Delete();

  return intersects;
}

//------------------------------------------------------------------------------
void vtkOctreePointLocatorNode::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "NumberOfPoints: " << this->NumberOfPoints << endl;
  os << indent << "Children: " << this->Children << endl;
  os << indent << "ID: " << this->ID << endl;
  os << indent << "MinID: " << this->MinID << endl;
  os << indent << "MinBounds: " << this->MinBounds[0] << " " << this->MinBounds[1] << " "
     << this->MinBounds[2] << endl;
  os << indent << "MaxBounds: " << this->MaxBounds[0] << " " << this->MaxBounds[1] << " "
     << this->MaxBounds[2] << endl;
  os << indent << "MinDataBounds: " << this->MinDataBounds[0] << " " << this->MinDataBounds[1]
     << " " << this->MinDataBounds[2] << endl;
  os << indent << "MaxDataBounds: " << this->MaxDataBounds[0] << " " << this->MaxDataBounds[1]
     << " " << this->MaxDataBounds[2] << endl;
}
VTK_ABI_NAMESPACE_END
