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

#include "vtkCamera.h"
#include "vtkImageData.h"
#include "vtkImageProperty.h"
#include "vtkImageSlice.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkMatrix3x3.h"
#include "vtkMatrix4x4.h"
#include "vtkObjectFactory.h"
#include "vtkPlane.h"
#include "vtkPoints.h"
#include "vtkRenderer.h"
#include "vtkStreamingDemandDrivenPipeline.h"

VTK_ABI_NAMESPACE_BEGIN
vtkCxxSetObjectMacro(vtkImageSliceMapper, Points, vtkPoints);

//------------------------------------------------------------------------------
// Return nullptr if no override is supplied.
vtkObjectFactoryNewMacro(vtkImageSliceMapper);

//------------------------------------------------------------------------------
vtkImageSliceMapper::vtkImageSliceMapper()
{
  this->SliceNumber = 0;
  this->SliceNumberMinValue = 0;
  this->SliceNumberMaxValue = 0;

  this->Orientation = 2;

  this->Cropping = false;

  this->CroppingRegion[0] = 0;
  this->CroppingRegion[1] = 0;
  this->CroppingRegion[2] = 0;
  this->CroppingRegion[3] = 0;
  this->CroppingRegion[4] = 0;
  this->CroppingRegion[5] = 0;

  this->Points = nullptr;
  this->ExactPixelMatch = false;
  this->PassColorData = false;

  // streaming misbehaves if there is no output port
  this->SetNumberOfOutputPorts(1);
}

//------------------------------------------------------------------------------
vtkImageSliceMapper::~vtkImageSliceMapper()
{
  if (this->Points)
  {
    this->Points->Delete();
  }
}

//------------------------------------------------------------------------------
void vtkImageSliceMapper::ReleaseGraphicsResources(vtkWindow*)
{
  // see OpenGL subclass for implementation
}

//------------------------------------------------------------------------------
void vtkImageSliceMapper::Render(vtkRenderer*, vtkImageSlice*)
{
  // see OpenGL subclass for implementation
}

//------------------------------------------------------------------------------
vtkTypeBool vtkImageSliceMapper::ProcessRequest(
  vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  // compute display extent
  if (request->Has(vtkStreamingDemandDrivenPipeline::REQUEST_INFORMATION()))
  {
    int wholeExtent[6];
    int* extent = this->DataWholeExtent;

    vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
    inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), wholeExtent);

    for (int k = 0; k < 6; k++)
    {
      extent[k] = wholeExtent[k];
    }

    if (this->Cropping)
    {
      for (int i = 0; i < 3; i++)
      {
        extent[2 * i] = std::max(extent[2 * i], this->CroppingRegion[2 * i]);
        extent[2 * i + 1] = std::min(extent[2 * i + 1], this->CroppingRegion[2 * i + 1]);
      }
    }

    double* spacing = this->DataSpacing;
    double* origin = this->DataOrigin;
    double* dir = this->DataDirection;

    inInfo->Get(vtkDataObject::SPACING(), spacing);
    inInfo->Get(vtkDataObject::ORIGIN(), origin);

    vtkMatrix4x4* matrix = this->GetDataToWorldMatrix();
    if (inInfo->Has(vtkDataObject::DIRECTION()))
    {
      inInfo->Get(vtkDataObject::DIRECTION(), dir);
    }
    else
    {
      vtkMatrix3x3::Identity(dir);
    }
    // prepend indextophysical matrix
    double i2p[16];
    for (int i = 0; i < 3; ++i)
    {
      i2p[i * 4] = dir[i * 3] * spacing[0];
      i2p[i * 4 + 1] = dir[i * 3 + 1] * spacing[1];
      i2p[i * 4 + 2] = dir[i * 3 + 2] * spacing[2];
      i2p[i * 4 + 3] = origin[i];
      i2p[12 + i] = 0.0;
    }
    i2p[15] = 1.0;
    if (matrix)
    {
      vtkMatrix4x4::Multiply4x4(matrix->GetData(), i2p, i2p);
    }

    if (this->SliceFacesCamera || this->SliceAtFocalPoint)
    {
      vtkRenderer* ren = this->GetCurrentRenderer();

      if (ren)
      {
        vtkCamera* camera = ren->GetActiveCamera();

        if (this->SliceFacesCamera)
        {
          this->Orientation = this->GetOrientationFromCamera(i2p, camera);
          this->Orientation = this->Orientation % 3;
        }

        if (this->SliceAtFocalPoint)
        {
          this->SliceNumber = this->GetSliceFromCamera(i2p, camera);
        }
      }
    }

    int orientation = this->Orientation % 3;

    this->SliceNumberMinValue = wholeExtent[2 * orientation];
    this->SliceNumberMaxValue = wholeExtent[2 * orientation + 1];

    this->SliceNumber = std::max(this->SliceNumber, extent[2 * orientation]);
    this->SliceNumber = std::min(this->SliceNumber, extent[2 * orientation + 1]);

    // the test is for an empty extent (0, -1, 0, -1, 0, -1) which
    // otherwise would be changed into (0, -1, 0, -1, -1, -1)
    if (extent[2 * orientation] <= extent[2 * orientation + 1])
    {
      extent[2 * orientation] = this->SliceNumber;
      extent[2 * orientation + 1] = this->SliceNumber;
    }

    this->DisplayExtent[0] = extent[0];
    this->DisplayExtent[1] = extent[1];
    this->DisplayExtent[2] = extent[2];
    this->DisplayExtent[3] = extent[3];
    this->DisplayExtent[4] = extent[4];
    this->DisplayExtent[5] = extent[5];

    // Create point and normal of plane
    double point[4];
    point[0] = 0.5 * (extent[0] + extent[1]);
    point[1] = 0.5 * (extent[2] + extent[3]);
    point[2] = 0.5 * (extent[4] + extent[5]);
    point[3] = 1.0;

    double normal[4];
    normal[0] = 0.0;
    normal[1] = 0.0;
    normal[2] = 0.0;
    normal[3] = -point[orientation];
    normal[orientation] = 1.0;

    // Convert point and normal to world coords
    vtkMatrix4x4::MultiplyPoint(i2p, point, point);
    point[0] /= point[3];
    point[1] /= point[3];
    point[2] /= point[3];
    vtkMatrix4x4::Invert(i2p, i2p);
    vtkMatrix4x4::Transpose(i2p, i2p);
    vtkMatrix4x4::MultiplyPoint(i2p, normal, normal);
    vtkMath::Normalize(normal);

    this->SlicePlane->SetOrigin(point);
    this->SlicePlane->SetNormal(normal);

    return 1;
  }

  // set update extent
  if (request->Has(vtkStreamingDemandDrivenPipeline::REQUEST_UPDATE_EXTENT()))
  {
    vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);

    if (this->Streaming)
    {
      // only update the display extent if streaming is on
      inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), this->DisplayExtent, 6);
    }
    else
    {
      int ext[6];
      inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), ext);
      inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), ext, 6);
    }

    return 1;
  }

  // just a dummy, does not do anything
  if (request->Has(vtkStreamingDemandDrivenPipeline::REQUEST_DATA()))
  {
    vtkInformation* outInfo = outputVector->GetInformationObject(0);
    vtkImageData* output = vtkImageData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

    // set output extent to avoid re-execution
    output->GetInformation()->Set(vtkDataObject::DATA_EXTENT(),
      outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT()), 6);

    return 1;
  }

  return this->Superclass::ProcessRequest(request, inputVector, outputVector);
}

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

  os << indent << "SliceNumber: " << this->SliceNumber << "\n";
  os << indent << "SliceNumberMinValue: " << this->SliceNumberMinValue << "\n";
  os << indent << "SliceNumberMaxValue: " << this->SliceNumberMaxValue << "\n";
  os << indent << "Orientation: " << this->Orientation << "\n";
  os << indent << "Cropping: " << (this->Cropping ? "On\n" : "Off\n");
  os << indent << "CroppingRegion: " << this->CroppingRegion[0] << " " << this->CroppingRegion[1]
     << " " << this->CroppingRegion[2] << " " << this->CroppingRegion[3] << " "
     << this->CroppingRegion[4] << " " << this->CroppingRegion[5] << "\n";
  os << indent << "Points: " << this->Points << "\n";
}

//------------------------------------------------------------------------------
vtkMTimeType vtkImageSliceMapper::GetMTime()
{
  vtkMTimeType mTime = this->Superclass::GetMTime();

  if (this->SliceFacesCamera || this->SliceAtFocalPoint)
  {
    vtkImageSlice* prop = this->GetCurrentProp();
    vtkRenderer* ren = this->GetCurrentRenderer();

    if (prop && ren)
    {
      vtkCamera* camera = ren->GetActiveCamera();
      vtkMTimeType mTime2 = prop->GetMTime();
      mTime = std::max(mTime2, mTime);
      mTime2 = camera->GetMTime();
      mTime = std::max(mTime2, mTime);
    }
  }

  return mTime;
}

//------------------------------------------------------------------------------
void vtkImageSliceMapper::SetSliceNumber(int i)
{
  if (i != this->SliceNumber)
  {
    this->SliceNumber = i;
    this->Modified();
  }
}

//------------------------------------------------------------------------------
int vtkImageSliceMapper::GetSliceNumber()
{
  return this->SliceNumber;
}

//------------------------------------------------------------------------------
int vtkImageSliceMapper::GetSliceNumberMinValue()
{
  this->UpdateInformation();
  return this->SliceNumberMinValue;
}

//------------------------------------------------------------------------------
int vtkImageSliceMapper::GetSliceNumberMaxValue()
{
  this->UpdateInformation();
  return this->SliceNumberMaxValue;
}

//------------------------------------------------------------------------------
void vtkImageSliceMapper::GetIndexBounds(double extent[6])
{
  if (!this->GetInput())
  {
    return;
  }

  this->UpdateInformation();

  extent[0] = this->DisplayExtent[0];
  extent[1] = this->DisplayExtent[1];
  extent[2] = this->DisplayExtent[2];
  extent[3] = this->DisplayExtent[3];
  extent[4] = this->DisplayExtent[4];
  extent[5] = this->DisplayExtent[5];

  int orientation = this->Orientation % 3;
  extent[2 * orientation] = this->SliceNumberMinValue;
  extent[2 * orientation + 1] = this->SliceNumberMaxValue;

  // expand by half a pixel if border is on, except in slice direction
  double border = 0.5 * (this->Border != 0);
  double borders[3];
  borders[0] = border * (orientation != 0);
  borders[1] = border * (orientation != 1);
  borders[2] = border * (orientation != 2);

  extent[0] -= borders[0];
  extent[1] += borders[0];
  extent[2] -= borders[1];
  extent[3] += borders[1];
  extent[4] -= borders[2];
  extent[5] += borders[2];
}

//------------------------------------------------------------------------------
double* vtkImageSliceMapper::GetBounds()
{
  if (!this->GetInput())
  {
    vtkMath::UninitializeBounds(this->Bounds);
    return this->Bounds;
  }

  double extent[6];
  this->GetIndexBounds(extent);

  double* spacing = this->DataSpacing;
  double* origin = this->DataOrigin;
  double* direction = this->DataDirection;

  // compute bounds
  for (int k = 0; k < 2; ++k)
  {
    double kval = extent[k + 4];
    for (int j = 0; j < 2; ++j)
    {
      double jval = extent[j + 2];
      for (int i = 0; i < 2; ++i)
      {
        double ival = extent[i];
        double point[3];
        vtkImageData::TransformContinuousIndexToPhysicalPoint(
          ival, jval, kval, origin, spacing, direction, point);
        if (i + j + k == 0)
        {
          this->Bounds[0] = point[0];
          this->Bounds[1] = point[0];
          this->Bounds[2] = point[1];
          this->Bounds[3] = point[1];
          this->Bounds[4] = point[2];
          this->Bounds[5] = point[2];
        }
        else
        {
          for (int c = 0; c < 3; ++c)
          {
            this->Bounds[c * 2] = point[c] < this->Bounds[c * 2] ? point[c] : this->Bounds[c * 2];
            this->Bounds[c * 2 + 1] =
              point[c] > this->Bounds[c * 2 + 1] ? point[c] : this->Bounds[c * 2 + 1];
          }
        }
      }
    }
  }

  return this->Bounds;
}

//------------------------------------------------------------------------------
int vtkImageSliceMapper::GetOrientationFromCamera(double const* propMatrix, vtkCamera* camera)
{
  double normal[4] = { 0, 0, -1, 0 };
  camera->GetDirectionOfProjection(normal);

  double mat[16];
  vtkMatrix4x4::Invert(propMatrix, mat);
  vtkMatrix4x4::Transpose(mat, mat);

  int maxIdx = 0;
  double maxDot = 0.0;
  for (int c = 0; c < 3; ++c)
  {
    double vec[3];
    vec[0] = mat[c];
    vec[1] = mat[c + 4];
    vec[2] = mat[c + 8];
    vtkMath::Normalize(vec);
    double dot = vtkMath::Dot(vec, normal);
    // code is written this way (with the continue) to work around
    // what seems to be a bug in Apple clang version 17.0.0
    if (fabs(dot) <= fabs(maxDot))
    {
      continue;
    }
    maxIdx = c;
    maxDot = dot;
  }

  return maxIdx + (maxDot < 0.0 ? 3 : 0);
}

//------------------------------------------------------------------------------
int vtkImageSliceMapper::GetSliceFromCamera(double const* propMatrix, vtkCamera* camera)
{
  int orientation = this->Orientation;

  double p[4] = { 0, 0, 0, 1 };
  camera->GetFocalPoint(p);

  // convert world coords to data coords
  double mat[16];
  vtkMatrix4x4::Invert(propMatrix, mat);
  vtkMatrix4x4::MultiplyPoint(mat, p, p);
  double slicepos = p[orientation] / p[3];

  // round to get integer, add a tolerance to prefer rounding up
  return vtkMath::Floor(slicepos + (0.5 + 7.62939453125e-06));
}

//------------------------------------------------------------------------------
void vtkImageSliceMapper::GetSlicePlaneInDataCoords(
  vtkMatrix4x4* vtkNotUsed(propMatrix), double normal[4])
{
  int orientation = this->Orientation % 3;
  int slice = this->SliceNumber;

  normal[0] = this->DataDirection[orientation];
  normal[1] = this->DataDirection[3 + orientation];
  normal[2] = this->DataDirection[6 + orientation];
  double scale = vtkMath::Normalize(normal);

  // in this context data coordinates is physical coordinates
  // in that spacing and origin and direction are still applied
  // so it is basically index -> data (aka physical) -> world
  normal[3] = -(slice * this->DataSpacing[orientation] + this->DataOrigin[0] * normal[0] +
                this->DataOrigin[1] * normal[1] + this->DataOrigin[2] * normal[2]) /
    scale;
}

//------------------------------------------------------------------------------
void vtkImageSliceMapper::GetDimensionIndices(int orientation, int& xdim, int& ydim)
{
  orientation = orientation % 3;
  xdim = 1;
  ydim = 2;
  if (orientation != 0)
  {
    xdim = 0;
    if (orientation != 1)
    {
      ydim = 1;
    }
  }
}
VTK_ABI_NAMESPACE_END
