// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkPolygonalSurfacePointPlacer.h"

#include "vtkAssemblyNode.h"
#include "vtkAssemblyPath.h"
#include "vtkCellData.h"
#include "vtkCellPicker.h"
#include "vtkDataArray.h"
#include "vtkIdList.h"
#include "vtkInteractorObserver.h"
#include "vtkMapper.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPolyData.h"
#include "vtkPolyDataCollection.h"
#include "vtkProp.h"
#include "vtkPropCollection.h"
#include "vtkRenderer.h"

#include <vector>

VTK_ABI_NAMESPACE_BEGIN
class vtkPolygonalSurfacePointPlacerInternals
{

public:
  typedef std::vector<vtkPolygonalSurfacePointPlacerNode*> NodesContainerType;

  NodesContainerType Nodes;

  ~vtkPolygonalSurfacePointPlacerInternals()
  {
    for (unsigned int i = 0; i < this->Nodes.size(); i++)
    {
      delete this->Nodes[i];
    }
    this->Nodes.clear();
  }

  vtkPolygonalSurfacePointPlacerNode* GetNodeAtSurfaceWorldPosition(double worldPos[3])
  {
    constexpr double tolerance = 0.0005;
    for (unsigned int i = 0; i < this->Nodes.size(); i++)
    {
      if (vtkMath::Distance2BetweenPoints(this->Nodes[i]->SurfaceWorldPosition, worldPos) <
        tolerance)
      {
        return this->Nodes[i];
      }
    }
    return nullptr;
  }

  vtkPolygonalSurfacePointPlacerNode* GetNodeAtWorldPosition(double worldPos[3])
  {
    constexpr double tolerance = 0.0005;
    for (unsigned int i = 0; i < this->Nodes.size(); i++)
    {
      if (vtkMath::Distance2BetweenPoints(this->Nodes[i]->WorldPosition, worldPos) < tolerance)
      {
        return this->Nodes[i];
      }
    }
    return nullptr;
  }

  vtkPolygonalSurfacePointPlacerNode* InsertNodeAtCurrentPickPosition(
    vtkCellPicker* picker, const double distanceOffset, int snapToClosestPoint)
  {
    double worldPos[3];
    picker->GetPickPosition(worldPos);

    // Get a node at this position if one exists and overwrite it
    // with the current pick position. If one doesn't exist, add
    // a new node.
    vtkPolygonalSurfacePointPlacerNode* node = this->GetNodeAtSurfaceWorldPosition(worldPos);
    if (!node)
    {
      node = new vtkPolygonalSurfacePointPlacerNode;
      this->Nodes.push_back(node);
    }

    vtkMapper* mapper = vtkMapper::SafeDownCast(picker->GetMapper());
    if (!mapper)
    {
      return nullptr;
    }

    // Get the underlying dataset
    vtkPolyData* pd = vtkPolyData::SafeDownCast(mapper->GetInput());
    if (!pd)
    {
      return nullptr;
    }

    node->CellId = picker->GetCellId();
    picker->GetPCoords(node->ParametricCoords);

    // translate to the closest point on that cell, if requested

    if (snapToClosestPoint)
    {
      vtkIdList* ids = vtkIdList::New();
      pd->GetCellPoints(picker->GetCellId(), ids);
      double p[3], minDistance = VTK_DOUBLE_MAX;
      for (vtkIdType i = 0; i < ids->GetNumberOfIds(); ++i)
      {
        pd->GetPoints()->GetPoint(ids->GetId(i), p);
        const double dist2 =
          vtkMath::Distance2BetweenPoints(worldPos, pd->GetPoints()->GetPoint(ids->GetId(i)));
        if (dist2 < minDistance)
        {
          minDistance = dist2;
          worldPos[0] = p[0];
          worldPos[1] = p[1];
          worldPos[2] = p[2];
        }
      }
      ids->Delete();
    }

    node->SurfaceWorldPosition[0] = worldPos[0];
    node->SurfaceWorldPosition[1] = worldPos[1];
    node->SurfaceWorldPosition[2] = worldPos[2];
    node->PolyData = pd;
    double cellNormal[3];

    if (distanceOffset != 0.0)
    {
      pd->GetCellData()->GetNormals()->GetTuple(node->CellId, cellNormal);

      // Polyline can be drawn on polydata at a height offset.
      for (unsigned int i = 0; i < 3; i++)
      {
        node->WorldPosition[i] = node->SurfaceWorldPosition[i] + cellNormal[i] * distanceOffset;
      }
    }
    else
    {
      for (unsigned int i = 0; i < 3; i++)
      {
        node->WorldPosition[i] = node->SurfaceWorldPosition[i];
      }
    }
    return node;
  }

  vtkPolygonalSurfacePointPlacerNode* InsertNodeAtCurrentPickPosition(vtkPolyData* pd,
    double worldPos[3], vtkIdType cellId, vtkIdType pointId,
    const double vtkNotUsed(distanceOffset), int vtkNotUsed(snapToClosestPoint))
  {

    // Get a node at this position if one exists and overwrite it
    // with the current pick position. If one doesn't exist, add
    // a new node.
    vtkPolygonalSurfacePointPlacerNode* node = this->GetNodeAtSurfaceWorldPosition(worldPos);
    if (!node)
    {
      node = new vtkPolygonalSurfacePointPlacerNode;
      this->Nodes.push_back(node);
    }

    node->CellId = cellId;
    node->PointId = pointId;

    node->SurfaceWorldPosition[0] = worldPos[0];
    node->SurfaceWorldPosition[1] = worldPos[1];
    node->SurfaceWorldPosition[2] = worldPos[2];
    node->PolyData = pd;

    for (unsigned int i = 0; i < 3; i++)
    {
      node->WorldPosition[i] = node->SurfaceWorldPosition[i];
    }
    return node;
  }
  // ashish
};

vtkStandardNewMacro(vtkPolygonalSurfacePointPlacer);

//------------------------------------------------------------------------------
vtkPolygonalSurfacePointPlacer::vtkPolygonalSurfacePointPlacer()
{
  this->Polys = vtkPolyDataCollection::New();
  this->CellPicker = vtkCellPicker::New();
  this->CellPicker->PickFromListOn();
  this->CellPicker->SetTolerance(0.005); // need some fluff

  this->Internals = new vtkPolygonalSurfacePointPlacerInternals;
  this->DistanceOffset = 0.0;
  this->SnapToClosestPoint = 0;
}

//------------------------------------------------------------------------------
vtkPolygonalSurfacePointPlacer::~vtkPolygonalSurfacePointPlacer()
{
  this->CellPicker->Delete();
  this->Polys->Delete();
  delete this->Internals;
}

//------------------------------------------------------------------------------
void vtkPolygonalSurfacePointPlacer::AddProp(vtkProp* prop)
{
  this->SurfaceProps->AddItem(prop);
  this->CellPicker->AddPickList(prop);
}

//------------------------------------------------------------------------------
void vtkPolygonalSurfacePointPlacer::RemoveViewProp(vtkProp* prop)
{
  this->Superclass::RemoveViewProp(prop);
  this->CellPicker->DeletePickList(prop);
}

//------------------------------------------------------------------------------
void vtkPolygonalSurfacePointPlacer::RemoveAllProps()
{
  this->Superclass::RemoveAllProps();
  this->CellPicker->InitializePickList();
}

//------------------------------------------------------------------------------
int vtkPolygonalSurfacePointPlacer::ComputeWorldPosition(vtkRenderer* ren, double displayPos[2],
  double* vtkNotUsed(refWorldPos), double worldPos[3], double worldOrient[9])
{
  return this->ComputeWorldPosition(ren, displayPos, worldPos, worldOrient);
}

//------------------------------------------------------------------------------
int vtkPolygonalSurfacePointPlacer::ComputeWorldPosition(
  vtkRenderer* ren, double displayPos[2], double worldPos[3], double vtkNotUsed(worldOrient)[9])
{
  if (this->CellPicker->Pick(displayPos[0], displayPos[1], 0.0, ren))
  {

    vtkMapper* mapper = vtkMapper::SafeDownCast(this->CellPicker->GetMapper());
    if (!mapper)
    {
      return 0;
    }

    // Get the underlying dataset
    vtkPolyData* pd = vtkPolyData::SafeDownCast(mapper->GetInput());
    if (!pd)
    {
      return 0;
    }

    if (vtkAssemblyPath* path = this->CellPicker->GetPath())
    {

      // We are checking if the prop present in the path is present
      // in the list supplied to us.. If it is, that prop will be picked.
      // If not, no prop will be picked.

      bool found = false;
      vtkAssemblyNode* node = nullptr;
      vtkCollectionSimpleIterator sit;
      this->SurfaceProps->InitTraversal(sit);

      while (vtkProp* p = this->SurfaceProps->GetNextProp(sit))
      {
        vtkCollectionSimpleIterator psit;
        path->InitTraversal(psit);

        for (int i = 0; i < path->GetNumberOfItems() && !found; ++i)
        {
          node = path->GetNextNode(psit);
          found = (node->GetViewProp() == p);
        }

        if (found)
        {
          vtkPolygonalSurfacePointPlacer::Node* contourNode =
            this->Internals->InsertNodeAtCurrentPickPosition(
              this->CellPicker, this->DistanceOffset, this->SnapToClosestPoint);
          if (contourNode)
          {
            worldPos[0] = contourNode->WorldPosition[0];
            worldPos[1] = contourNode->WorldPosition[1];
            worldPos[2] = contourNode->WorldPosition[2];
            return 1;
          }
        }
      }
    }
  }

  return 0;
}

//------------------------------------------------------------------------------
int vtkPolygonalSurfacePointPlacer::ValidateWorldPosition(
  double worldPos[3], double* vtkNotUsed(worldOrient))
{
  return this->ValidateWorldPosition(worldPos);
}

//------------------------------------------------------------------------------
int vtkPolygonalSurfacePointPlacer::ValidateWorldPosition(double vtkNotUsed(worldPos)[3])
{
  return 1;
}

//------------------------------------------------------------------------------
int vtkPolygonalSurfacePointPlacer::ValidateDisplayPosition(
  vtkRenderer*, double vtkNotUsed(displayPos)[2])
{
  // We could check here to ensure that the display point picks one of the
  // terrain props, but the contour representation always calls
  // ComputeWorldPosition followed by
  // ValidateDisplayPosition/ValidateWorldPosition when it needs to
  // update a node...
  //
  // So that would be wasting CPU cycles to perform
  // the same check twice..  Just return 1 here.

  return 1;
}

//------------------------------------------------------------------------------
vtkPolygonalSurfacePointPlacer::Node* vtkPolygonalSurfacePointPlacer ::GetNodeAtWorldPosition(
  double worldPos[3])
{
  return this->Internals->GetNodeAtWorldPosition(worldPos);
}

//------------------------------------------------------------------------------
int vtkPolygonalSurfacePointPlacer::UpdateNodeWorldPosition(
  double worldPos[3], vtkIdType nodePointId)
{
  if (this->Polys->GetNumberOfItems() != 0)
  {
    vtkPolyData* pd = vtkPolyData::SafeDownCast(this->Polys->GetItemAsObject(0));
    this->Internals->InsertNodeAtCurrentPickPosition(
      pd, worldPos, -1, nodePointId, this->DistanceOffset, this->SnapToClosestPoint);
    return 1;
  }
  else
  {
    vtkErrorMacro("PolyDataCollection has no items.");
    return 0;
  }
}

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

  os << indent << "Cell Picker: " << this->CellPicker << endl;
  if (this->CellPicker)
  {
    this->CellPicker->PrintSelf(os, indent.GetNextIndent());
  }

  os << indent << "Surface Props: " << this->SurfaceProps << endl;
  if (this->SurfaceProps)
  {
    this->SurfaceProps->PrintSelf(os, indent.GetNextIndent());
  }

  os << indent << "Surface polygons: " << this->Polys << endl;
  if (this->Polys)
  {
    this->Polys->PrintSelf(os, indent.GetNextIndent());
  }

  os << indent << "Distance Offset: " << this->DistanceOffset << "\n";
  os << indent << "SnapToClosestPoint: " << this->SnapToClosestPoint << endl;
}
VTK_ABI_NAMESPACE_END
