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

#include "vtkCamera.h"
#include "vtkCommand.h"
#include "vtkGenericCell.h"
#include "vtkLine.h"
#include "vtkMath.h"
#include "vtkMatrix4x4.h"
#include "vtkPlane.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkResliceCursor.h"
#include "vtkResliceCursorPolyDataAlgorithm.h"
#include "vtkSmartPointer.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkResliceCursorPicker);
vtkCxxSetObjectMacro(
  vtkResliceCursorPicker, ResliceCursorAlgorithm, vtkResliceCursorPolyDataAlgorithm);
vtkCxxSetObjectMacro(vtkResliceCursorPicker, TransformMatrix, vtkMatrix4x4);

//------------------------------------------------------------------------------
vtkResliceCursorPicker::vtkResliceCursorPicker()
{
  // For polydata picking
  this->Cell = vtkGenericCell::New();

  // Miscellaneous ivars
  this->Tolerance = 1e-6;

  this->PickedAxis1 = this->PickedAxis2 = 0;

  this->ResliceCursorAlgorithm = nullptr;
  this->TransformMatrix = nullptr;

  // Plane on which the cursor lies. This is the picked plane.
  this->Plane = vtkPlane::New();
}

//------------------------------------------------------------------------------
vtkResliceCursorPicker::~vtkResliceCursorPicker()
{
  this->Cell->Delete();
  this->SetResliceCursorAlgorithm(nullptr);
  this->SetTransformMatrix(nullptr);
  this->Plane->Delete();
}

//------------------------------------------------------------------------------
int vtkResliceCursorPicker::Pick(
  double selectionX, double selectionY, double selectionZ, vtkRenderer* renderer)
{
  int i;
  vtkCamera* camera;
  double p1World[4], p2World[4];
  int winSize[2] = { 1, 1 };
  double x, y;
  double* viewport;
  double cameraPos[4], cameraFP[4];
  double *displayCoords, *worldCoords;
  double* clipRange;
  double ray[3], rayLength;
  double windowLowerLeft[4], windowUpperRight[4];
  double tF, tB;
  double cameraDOP[3];

  //  Initialize picking process
  this->Initialize();
  this->Renderer = renderer;
  this->SelectionPoint[0] = selectionX;
  this->SelectionPoint[1] = selectionY;
  this->SelectionPoint[2] = selectionZ;

  // Invoke start pick method if defined
  this->InvokeEvent(vtkCommand::StartPickEvent, nullptr);

  if (renderer == nullptr)
  {
    vtkErrorMacro(<< "Must specify renderer!");
    return 0;
  }

  // Get camera focal point and position. Convert to display (screen)
  // coordinates. We need a depth value for z-buffer.
  //
  camera = renderer->GetActiveCamera();
  camera->GetPosition(cameraPos);
  cameraPos[3] = 1.0;
  camera->GetFocalPoint(cameraFP);
  cameraFP[3] = 1.0;

  renderer->SetWorldPoint(cameraFP[0], cameraFP[1], cameraFP[2], cameraFP[3]);
  renderer->WorldToDisplay();
  displayCoords = renderer->GetDisplayPoint();
  selectionZ = displayCoords[2];

  // Convert the selection point into world coordinates.
  //
  renderer->SetDisplayPoint(selectionX, selectionY, selectionZ);
  renderer->DisplayToWorld();
  worldCoords = renderer->GetWorldPoint();
  if (worldCoords[3] == 0.0)
  {
    vtkErrorMacro(<< "Bad homogeneous coordinates");
    return 0;
  }
  for (i = 0; i < 3; i++)
  {
    this->PickPosition[i] = worldCoords[i] / worldCoords[3];
  }

  //  Compute the ray endpoints.  The ray is along the line running from
  //  the camera position to the selection point, starting where this line
  //  intersects the front clipping plane, and terminating where this
  //  line intersects the back clipping plane.
  for (i = 0; i < 3; i++)
  {
    ray[i] = this->PickPosition[i] - cameraPos[i];
  }
  for (i = 0; i < 3; i++)
  {
    cameraDOP[i] = cameraFP[i] - cameraPos[i];
  }

  vtkMath::Normalize(cameraDOP);

  if ((rayLength = vtkMath::Dot(cameraDOP, ray)) == 0.0)
  {
    vtkWarningMacro("Cannot process points");
    return 0;
  }

  clipRange = camera->GetClippingRange();

  if (camera->GetParallelProjection())
  {
    tF = clipRange[0] - rayLength;
    tB = clipRange[1] - rayLength;
    for (i = 0; i < 3; i++)
    {
      p1World[i] = this->PickPosition[i] + tF * cameraDOP[i];
      p2World[i] = this->PickPosition[i] + tB * cameraDOP[i];
    }
  }
  else
  {
    tF = clipRange[0] / rayLength;
    tB = clipRange[1] / rayLength;
    for (i = 0; i < 3; i++)
    {
      p1World[i] = cameraPos[i] + tF * ray[i];
      p2World[i] = cameraPos[i] + tB * ray[i];
    }
  }
  p1World[3] = p2World[3] = 1.0;

  // Compute the tolerance in world coordinates.  Do this by
  // determining the world coordinates of the diagonal points of the
  // window, computing the width of the window in world coordinates, and
  // multiplying by the tolerance.
  //
  viewport = renderer->GetViewport();
  if (renderer->GetRenderWindow())
  {
    const int* winSizePtr = renderer->GetRenderWindow()->GetSize();
    if (winSizePtr)
    {
      winSize[0] = winSizePtr[0];
      winSize[1] = winSizePtr[1];
    }
  }
  x = winSize[0] * viewport[0];
  y = winSize[1] * viewport[1];
  renderer->SetDisplayPoint(x, y, selectionZ);
  renderer->DisplayToWorld();
  renderer->GetWorldPoint(windowLowerLeft);

  x = winSize[0] * viewport[2];
  y = winSize[1] * viewport[3];
  renderer->SetDisplayPoint(x, y, selectionZ);
  renderer->DisplayToWorld();
  renderer->GetWorldPoint(windowUpperRight);

  double tol;
  for (tol = 0.0, i = 0; i < 3; i++)
  {
    tol += (windowUpperRight[i] - windowLowerLeft[i]) * (windowUpperRight[i] - windowLowerLeft[i]);
  }

  tol = sqrt(tol) * this->Tolerance;

  vtkResliceCursor* rc = this->ResliceCursorAlgorithm->GetResliceCursor();
  const int axis1 = this->ResliceCursorAlgorithm->GetAxis1();
  const int axis2 = this->ResliceCursorAlgorithm->GetAxis2();
  // const int axis3 = this->ResliceCursorAlgorithm->GetReslicePlaneNormal();

  double center[3];
  rc->GetCenter(center);
  this->PickedCenter = this->IntersectPointWithLine(p1World, p2World, center, tol);
  this->PickedAxis1 =
    this->IntersectPolyDataWithLine(p1World, p2World, rc->GetCenterlineAxisPolyData(axis1), tol);
  this->PickedAxis2 =
    this->IntersectPolyDataWithLine(p1World, p2World, rc->GetCenterlineAxisPolyData(axis2), tol);

  if (this->PickedAxis1 || this->PickedAxis2 || this->PickedCenter)
  {

    // Pick on transformed plane coords and get the real coords back after
    // inverse transformation.

    double t, pickPosT[4], pickPos[4];
    this->TransformPlane();
    this->Plane->IntersectWithLine(p1World, p2World, t, pickPosT);
    pickPosT[3] = 1.0;
    this->InverseTransformPoint(pickPosT, pickPos);

    this->PickPosition[0] = pickPos[0];
    this->PickPosition[1] = pickPos[1];
    this->PickPosition[2] = pickPos[2];
  }

  return this->PickedAxis1 + this->PickedAxis2 + this->PickedCenter;
}

//------------------------------------------------------------------------------
// Pick a display coordinate and return the picked world coordinates
//
void vtkResliceCursorPicker::Pick(double displayPos[2], double world[3], vtkRenderer* ren)
{
  // First compute the equivalent of this display point on the focal plane

  double fp[4], tmp1[4], camPos[4], eventFPpos[4];
  ren->GetActiveCamera()->GetFocalPoint(fp);
  ren->GetActiveCamera()->GetPosition(camPos);
  fp[3] = 1.0;
  ren->SetWorldPoint(fp);
  ren->WorldToDisplay();
  ren->GetDisplayPoint(tmp1);

  tmp1[0] = displayPos[0];
  tmp1[1] = displayPos[1];

  this->Renderer->SetDisplayPoint(tmp1);
  this->Renderer->DisplayToWorld();

  // This is the world coordinates of the point on the focal plane.

  this->Renderer->GetWorldPoint(eventFPpos);

  // Now construct the pick ray

  double cameraDOP[3];
  for (int i = 0; i < 3; i++)
  {
    cameraDOP[i] = fp[i] - camPos[i];
  }

  double otherPoint[3] = { eventFPpos[0] + cameraDOP[0], eventFPpos[1] + cameraDOP[1],
    eventFPpos[2] + cameraDOP[2] };

  double t, pickPosT[4], pickPos[4];

  // Transform the plane into one that lies on the resliced plane
  this->TransformPlane();

  // Pick it
  this->Plane->IntersectWithLine(eventFPpos, otherPoint, t, pickPosT);

  // Transform it back from the resliced plane coords to actual world coords
  pickPosT[3] = 1.0;
  this->InverseTransformPoint(pickPosT, pickPos);

  // Copy the result
  for (int i = 0; i < 3; i++)
  {
    world[i] = pickPos[i];
  }
}

//------------------------------------------------------------------------------
// Helper function for sanity check - to see if one point is different from
// another.
//
static bool vtkResliceCursorPickerIsDifferentSanityCheck(const double a[3], const double b[3])
{
  // Tolerance of 0.0001 needs some fluff..

  return (fabs(a[0] - b[0]) > 0.0001 || fabs(a[1] - b[1]) > 0.0001 || fabs(a[2] - b[2]) > 0.0001);
}

//------------------------------------------------------------------------------
// Transform the reslice plane onto the coordinate system its displayed in.
//
void vtkResliceCursorPicker::TransformPlane()
{
  vtkResliceCursor* rc = this->ResliceCursorAlgorithm->GetResliceCursor();
  const int axis3 = this->ResliceCursorAlgorithm->GetReslicePlaneNormal();

  double origin[4] = { 0, 0, 0, 1 }, originT[4];
  double normal[3];
  rc->GetPlane(axis3)->GetOrigin(origin);
  rc->GetPlane(axis3)->GetNormal(normal);

  if (!this->TransformMatrix)
  {
    this->Plane->SetOrigin(origin);
    this->Plane->SetNormal(normal);

    // The origin of the reslice cursor will remain untransformed.
    double center[3];
    rc->GetCenter(center);

    // Sanity check
    if (vtkResliceCursorPickerIsDifferentSanityCheck(origin, center))
    {
      vtkErrorMacro("Reslice cursor center of ("
        << center[0] << "," << center[1] << "," << center[2] << ") is not equal to plane "
        << "origin along axis " << axis3 << " of (" << origin[0] << "," << origin[1] << ","
        << origin[2] << ").");
    }

    return;
  }

  double normalPoint[4] = { origin[0] + normal[0], origin[1] + normal[1], origin[2] + normal[2],
    1.0 };
  double normalPointT[4];

  this->TransformPoint(origin, originT);

  // Sanity check
  if (vtkResliceCursorPickerIsDifferentSanityCheck(origin, originT))
  {
    vtkErrorMacro("Reslice cursor after transformation ("
      << originT[0] << "," << originT[1] << "," << originT[2] << ") is not equal to before "
      << "transformation along axis " << axis3 << " of (" << origin[0] << "," << origin[1] << ","
      << origin[2] << ").");
  }

  this->TransformPoint(normalPoint, normalPointT);

  double normalT[3];
  vtkMath::Subtract(normalPointT, originT, normalT);
  vtkMath::Normalize(normalT);

  // The origin of the reslice cursor will remain untransformed.
  double center[3];
  rc->GetCenter(center);

  // Sanity check
  if (vtkResliceCursorPickerIsDifferentSanityCheck(origin, center))
  {
    vtkErrorMacro("Reslice cursor center of ("
      << center[0] << "," << center[1] << "," << center[2] << ") is not equal to plane "
      << "origin along axis " << axis3 << " of (" << origin[0] << "," << origin[1] << ","
      << origin[2] << ").");
  }

  this->Plane->SetOrigin(originT);
  this->Plane->SetNormal(normalT);
}

//------------------------------------------------------------------------------
void vtkResliceCursorPicker::TransformPoint(double pIn[4], double pOut[4])
{
  this->TransformMatrix->MultiplyPoint(pIn, pOut);
}

//------------------------------------------------------------------------------
void vtkResliceCursorPicker::InverseTransformPoint(double pIn[4], double pOut[4])
{
  if (!this->TransformMatrix)
  {
    for (int i = 0; i < 3; i++)
    {
      pOut[i] = pIn[i];
    }
    return;
  }

  // Maintain a copy of the existing elements.
  double elements[4][4];
  for (int i = 0; i < 4; i++)
  {
    for (int j = 0; j < 4; j++)
    {
      elements[i][j] = this->TransformMatrix->Element[i][j];
    }
  }

  // Invert matrix
  this->TransformMatrix->Invert();

  // Transform point
  this->TransformPoint(pIn, pOut);

  // Copy back the elements.
  for (int i = 0; i < 4; i++)
  {
    for (int j = 0; j < 4; j++)
    {
      this->TransformMatrix->Element[i][j] = elements[i][j];
    }
  }
}

//------------------------------------------------------------------------------
int vtkResliceCursorPicker::IntersectPolyDataWithLine(
  double p1[3], double p2[3], vtkPolyData* data, double tol)
{
  vtkIdType numCells = data->GetNumberOfCells();

  for (vtkIdType cellId = 0; cellId < numCells; cellId++)
  {
    double t;
    double x[3];
    double pcoords[3];
    pcoords[0] = pcoords[1] = pcoords[2] = 0;
    int newSubId = -1;
    int numSubIds = 1;

    // This will only loop once unless we need to deal with a strip
    for (int subId = 0; subId < numSubIds; subId++)
    {
      data->GetCell(cellId, this->Cell);

      // Transform the points using any transform matrix that may be set.

      for (int i = 0; i < this->Cell->GetPoints()->GetNumberOfPoints(); i++)
      {
        if (this->TransformMatrix)
        {
          double pIn[4] = { 0, 0, 0, 1 }, pOut[4];
          this->Cell->GetPoints()->GetPoint(i, pIn);
          this->TransformMatrix->MultiplyPoint(pIn, pOut);
          this->Cell->GetPoints()->SetPoint(i, pOut);
        }
      }

      int cellPicked = 0;
      cellPicked = this->Cell->IntersectWithLine(p1, p2, tol, t, x, pcoords, newSubId);

      if (cellPicked)
      {
        return cellPicked;
      }

    } // if a close cell
  }   // for all cells

  return 0;
}

//------------------------------------------------------------------------------
int vtkResliceCursorPicker::IntersectPointWithLine(
  double p1[3], double p2[3], double x[3], double tol)
{

  double X[4] = { x[0], x[1], x[2], 1 };
  if (this->TransformMatrix)
  {
    double pIn[4] = { x[0], x[1], x[2], 1 };
    this->TransformMatrix->MultiplyPoint(pIn, X);
  }

  int i;
  double ray[3], rayFactor, projXYZ[3];

  for (i = 0; i < 3; i++)
  {
    ray[i] = p2[i] - p1[i];
  }
  if ((rayFactor = vtkMath::Dot(ray, ray)) == 0.0)
  {
    return 0;
  }
  //
  //  Project each point onto ray. Determine if point is within tolerance.
  //
  const double t =
    (ray[0] * (X[0] - p1[0]) + ray[1] * (X[1] - p1[1]) + ray[2] * (X[2] - p1[2])) / rayFactor;

  if (t >= 0.0 && t <= 1.0)
  {
    for (i = 0; i < 3; i++)
    {
      projXYZ[i] = p1[i] + t * ray[i];
      if (fabs(X[i] - projXYZ[i]) > tol)
      {
        break;
      }
    }

    if (i > 2) // within tolerance
    {
      return 1;
    }
  }

  return 0;
}

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

  os << indent << "PickedAxis1: " << this->PickedAxis1 << endl;
  os << indent << "PickedAxis2: " << this->PickedAxis2 << endl;
  os << indent << "PickedCenter: " << this->PickedCenter << endl;
  os << indent << "ResliceCursorAlgorithm: " << this->ResliceCursorAlgorithm << "\n";
  if (this->ResliceCursorAlgorithm)
  {
    this->ResliceCursorAlgorithm->PrintSelf(os, indent);
  }
  os << indent << "TransformMatrix: " << this->TransformMatrix << "\n";
  if (this->TransformMatrix)
  {
    this->TransformMatrix->PrintSelf(os, indent);
  }
  // this->PointIds;
  // this->Plane;
  // this->Cell;
}
VTK_ABI_NAMESPACE_END
