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

  Program:   Visualization Toolkit
  Module:    vtkImageSliceMapper.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 "vtkImageSliceMapper.h"

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

vtkCxxSetObjectMacro(vtkImageSliceMapper, Points, vtkPoints);

//----------------------------------------------------------------------------
// Return nullptr if no override is supplied.
vtkAbstractObjectFactoryNewMacro(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
}

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

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

    vtkMatrix4x4 *matrix = this->GetDataToWorldMatrix();

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

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

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

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

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

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

    int orientation = this->Orientation % 3;

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

    if (this->SliceNumber < extent[2*orientation])
    {
      this->SliceNumber = extent[2*orientation];
    }
    if (this->SliceNumber > extent[2*orientation + 1])
    {
      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])*spacing[0] + origin[0];
    point[1] = 0.5*(extent[2] + extent[3])*spacing[1] + origin[1];
    point[2] = 0.5*(extent[4] + extent[5])*spacing[2] + origin[2];
    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;

    if (matrix)
    {
      // Convert point and normal to world coords
      double mat[16];
      vtkMatrix4x4::DeepCopy(mat, matrix);
      vtkMatrix4x4::MultiplyPoint(mat, point, point);
      point[0] /= point[3];
      point[1] /= point[3];
      point[2] /= point[3];
      vtkMatrix4x4::Invert(mat, mat);
      vtkMatrix4x4::Transpose(mat, mat);
      vtkMatrix4x4::MultiplyPoint(mat, 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();
      if (mTime2 > mTime)
      {
        mTime = mTime2;
      }
      mTime2 = camera->GetMTime();
      if (mTime2 > mTime)
      {
        mTime = mTime2;
      }
    }
  }

  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;
}

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

  this->UpdateInformation();

  int extent[6];
  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;

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

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

  // swap the extent if the spacing is negative
  int swapX = (spacing[0] < 0);
  int swapY = (spacing[1] < 0);
  int swapZ = (spacing[2] < 0);

  this->Bounds[0+swapX] = origin[0] + (extent[0] - borderX) * spacing[0];
  this->Bounds[2+swapY] = origin[1] + (extent[2] - borderY) * spacing[1];
  this->Bounds[4+swapZ] = origin[2] + (extent[4] - borderZ) * spacing[2];

  this->Bounds[1-swapX] = origin[0] + (extent[1] + borderX) * spacing[0];
  this->Bounds[3-swapY] = origin[1] + (extent[3] + borderY) * spacing[1];
  this->Bounds[5-swapZ] = origin[2] + (extent[5] + borderZ) * spacing[2];

  return this->Bounds;
}

//----------------------------------------------------------------------------
int vtkImageSliceMapper::GetOrientationFromCamera(
  vtkMatrix4x4 *propMatrix, vtkCamera *camera)
{
  int orientation = 2;
  double normal[4] = { 0, 0, -1, 0 };
  double maxval = 0;
  double mat[16];

  camera->GetDirectionOfProjection(normal);
  vtkMatrix4x4::Transpose(*propMatrix->Element, mat);
  vtkMatrix4x4::MultiplyPoint(mat, normal, normal);

  for (int i = 2; i >= 0; --i)
  {
    int j = 0;
    double a = normal[i];
    if (a < 0)
    {
      a = -a;
      j = 3;
    }
    if (a > maxval)
    {
      orientation = i + j;
      maxval = a;
    }
  }

  return orientation;
}

//----------------------------------------------------------------------------
int vtkImageSliceMapper::GetSliceFromCamera(
  vtkMatrix4x4 *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->Element, mat);
  vtkMatrix4x4::MultiplyPoint(mat, p, p);
  double slicepos = p[orientation]/p[3];

  // adjust for origin/spacing
  slicepos -= this->DataOrigin[orientation];
  slicepos /= this->DataSpacing[orientation];

  // 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] = 0.0;
  normal[1] = 0.0;
  normal[2] = 0.0;
  normal[3] = -(slice*this->DataSpacing[orientation] +
                this->DataOrigin[orientation]);
  normal[orientation] = 1.0;
}

//----------------------------------------------------------------------------
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;
    }
  }
}
