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

  Program:   Visualization Toolkit
  Module:    vtkResliceCursorPicker.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 "vtkResliceCursorPicker.h"
#include "vtkObjectFactory.h"

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

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())
  {
    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 co-ordinate 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(
                       const_cast<double *>(p1), const_cast<double *>(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;
}
