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

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

#include "vtkCamera.h"
#include "vtkDataArray.h"
#include "vtkImageData.h"
#include "vtkImageMapper3D.h"
#include "vtkImageProperty.h"
#include "vtkLinearTransform.h"
#include "vtkLookupTable.h"
#include "vtkMatrix4x4.h"
#include "vtkObjectFactory.h"
#include "vtkRenderer.h"
#include "vtkTransform.h"

#include <cmath>

vtkStandardNewMacro(vtkImageSlice);

//----------------------------------------------------------------------------
class vtkImageToImageMapper3DFriendship
{
public:
  static void SetCurrentProp(vtkImageMapper3D* mapper, vtkImageSlice* prop)
  {
    mapper->CurrentProp = prop;
  }
  static void SetCurrentRenderer(vtkImageMapper3D* mapper, vtkRenderer* ren)
  {
    mapper->CurrentRenderer = ren;
  }
  static void SetStackedImagePass(vtkImageMapper3D* mapper, int pass)
  {
    switch (pass)
    {
      case 0:
        mapper->MatteEnable = true;
        mapper->ColorEnable = false;
        mapper->DepthEnable = false;
        break;
      case 1:
        mapper->MatteEnable = false;
        mapper->ColorEnable = true;
        mapper->DepthEnable = false;
        break;
      case 2:
        mapper->MatteEnable = false;
        mapper->ColorEnable = false;
        mapper->DepthEnable = true;
        break;
      default:
        mapper->MatteEnable = true;
        mapper->ColorEnable = true;
        mapper->DepthEnable = true;
        break;
    }
  }
};

//----------------------------------------------------------------------------
vtkImageSlice::vtkImageSlice()
{
  this->Mapper = nullptr;
  this->Property = nullptr;

  this->ForceTranslucent = false;
}

//----------------------------------------------------------------------------
vtkImageSlice::~vtkImageSlice()
{
  if (this->Property)
  {
    this->Property->UnRegister(this);
  }

  this->SetMapper(nullptr);
}

//----------------------------------------------------------------------------
void vtkImageSlice::GetImages(vtkPropCollection* vc)
{
  vc->AddItem(this);
}

//----------------------------------------------------------------------------
void vtkImageSlice::ShallowCopy(vtkProp* prop)
{
  vtkImageSlice* v = vtkImageSlice::SafeDownCast(prop);

  if (v != nullptr)
  {
    this->SetMapper(v->GetMapper());
    this->SetProperty(v->GetProperty());
  }

  // Now do superclass
  this->vtkProp3D::ShallowCopy(prop);
}

//----------------------------------------------------------------------------
void vtkImageSlice::SetMapper(vtkImageMapper3D* mapper)
{
  if (this->Mapper != mapper)
  {
    if (this->Mapper != nullptr)
    {
      vtkImageToImageMapper3DFriendship::SetCurrentProp(this->Mapper, nullptr);
      this->Mapper->UnRegister(this);
    }
    this->Mapper = mapper;
    if (this->Mapper != nullptr)
    {
      this->Mapper->Register(this);
      vtkImageToImageMapper3DFriendship::SetCurrentProp(this->Mapper, this);
    }
    this->Modified();
  }
}

//----------------------------------------------------------------------------
// Get the bounds for this Volume as (Xmin,Xmax,Ymin,Ymax,Zmin,Zmax).
double* vtkImageSlice::GetBounds()
{
  int i, n;
  double bbox[24], *fptr;

  // get the bounds of the Mapper if we have one
  if (!this->Mapper)
  {
    return this->Bounds;
  }

  const double* bounds = this->Mapper->GetBounds();
  // Check for the special case when the mapper's bounds are unknown
  if (!bounds)
  {
    return nullptr;
  }

  // fill out vertices of a bounding box
  bbox[0] = bounds[1];
  bbox[1] = bounds[3];
  bbox[2] = bounds[5];
  bbox[3] = bounds[1];
  bbox[4] = bounds[2];
  bbox[5] = bounds[5];
  bbox[6] = bounds[0];
  bbox[7] = bounds[2];
  bbox[8] = bounds[5];
  bbox[9] = bounds[0];
  bbox[10] = bounds[3];
  bbox[11] = bounds[5];
  bbox[12] = bounds[1];
  bbox[13] = bounds[3];
  bbox[14] = bounds[4];
  bbox[15] = bounds[1];
  bbox[16] = bounds[2];
  bbox[17] = bounds[4];
  bbox[18] = bounds[0];
  bbox[19] = bounds[2];
  bbox[20] = bounds[4];
  bbox[21] = bounds[0];
  bbox[22] = bounds[3];
  bbox[23] = bounds[4];

  // make sure matrix (transform) is up-to-date
  this->ComputeMatrix();

  // and transform into actors coordinates
  fptr = bbox;
  for (n = 0; n < 8; n++)
  {
    double homogeneousPt[4] = { fptr[0], fptr[1], fptr[2], 1.0 };
    this->Matrix->MultiplyPoint(homogeneousPt, homogeneousPt);
    fptr[0] = homogeneousPt[0] / homogeneousPt[3];
    fptr[1] = homogeneousPt[1] / homogeneousPt[3];
    fptr[2] = homogeneousPt[2] / homogeneousPt[3];
    fptr += 3;
  }

  // now calc the new bounds
  this->Bounds[0] = this->Bounds[2] = this->Bounds[4] = VTK_DOUBLE_MAX;
  this->Bounds[1] = this->Bounds[3] = this->Bounds[5] = -VTK_DOUBLE_MAX;
  for (i = 0; i < 8; i++)
  {
    for (n = 0; n < 3; n++)
    {
      if (bbox[i * 3 + n] < this->Bounds[n * 2])
      {
        this->Bounds[n * 2] = bbox[i * 3 + n];
      }
      if (bbox[i * 3 + n] > this->Bounds[n * 2 + 1])
      {
        this->Bounds[n * 2 + 1] = bbox[i * 3 + n];
      }
    }
  }

  return this->Bounds;
}

//----------------------------------------------------------------------------
// Get the minimum X bound
double vtkImageSlice::GetMinXBound()
{
  this->GetBounds();
  return this->Bounds[0];
}

// Get the maximum X bound
double vtkImageSlice::GetMaxXBound()
{
  this->GetBounds();
  return this->Bounds[1];
}

// Get the minimum Y bound
double vtkImageSlice::GetMinYBound()
{
  this->GetBounds();
  return this->Bounds[2];
}

// Get the maximum Y bound
double vtkImageSlice::GetMaxYBound()
{
  this->GetBounds();
  return this->Bounds[3];
}

// Get the minimum Z bound
double vtkImageSlice::GetMinZBound()
{
  this->GetBounds();
  return this->Bounds[4];
}

// Get the maximum Z bound
double vtkImageSlice::GetMaxZBound()
{
  this->GetBounds();
  return this->Bounds[5];
}

//----------------------------------------------------------------------------
// Does this prop have some translucent polygonal geometry?
vtkTypeBool vtkImageSlice::HasTranslucentPolygonalGeometry()
{
  if (this->ForceTranslucent)
  {
    return 1;
  }

  // Unless forced to translucent, always render during opaque pass,
  // to keep the behavior predictable and because depth-peeling kills
  // alpha-blending.
  return 0;
}

//----------------------------------------------------------------------------
int vtkImageSlice::RenderTranslucentPolygonalGeometry(vtkViewport* viewport)
{
  vtkDebugMacro(<< "vtkImageSlice::RenderTranslucentPolygonalGeometry");

  if (this->HasTranslucentPolygonalGeometry())
  {
    this->Render(vtkRenderer::SafeDownCast(viewport));
    return 1;
  }

  return 0;
}

//----------------------------------------------------------------------------
int vtkImageSlice::RenderOpaqueGeometry(vtkViewport* viewport)
{
  vtkDebugMacro(<< "vtkImageSlice::RenderOpaqueGeometry");

  if (!this->HasTranslucentPolygonalGeometry())
  {
    this->Render(vtkRenderer::SafeDownCast(viewport));
    return 1;
  }

  return 0;
}

//----------------------------------------------------------------------------
int vtkImageSlice::RenderOverlay(vtkViewport* vtkNotUsed(viewport))
{
  vtkDebugMacro(<< "vtkImageSlice::RenderOverlay");

  // Render the image as an underlay

  return 0;
}

//----------------------------------------------------------------------------
void vtkImageSlice::Render(vtkRenderer* ren)
{
  // Force the creation of a property
  if (!this->Property)
  {
    this->GetProperty();
  }

  if (!this->Property)
  {
    vtkErrorMacro(<< "Error generating a property!\n");
    return;
  }

  if (!this->Mapper)
  {
    vtkErrorMacro(<< "You must specify a mapper!\n");
    return;
  }

  vtkImageToImageMapper3DFriendship::SetCurrentRenderer(this->Mapper, ren);

  this->Update();

  // only call the mapper if it has an input
  vtkImageData* input = this->Mapper->GetInput();
  int* extent = input->GetExtent();
  if (input && extent[0] <= extent[1] && extent[2] <= extent[3] && extent[4] <= extent[5])
  {
    this->Mapper->Render(ren, this);
    this->EstimatedRenderTime += this->Mapper->GetTimeToDraw();
  }

  vtkImageToImageMapper3DFriendship::SetCurrentRenderer(this->Mapper, nullptr);
}

//----------------------------------------------------------------------------
void vtkImageSlice::ReleaseGraphicsResources(vtkWindow* win)
{
  // pass this information onto the mapper
  if (this->Mapper)
  {
    this->Mapper->ReleaseGraphicsResources(win);
  }
}

//----------------------------------------------------------------------------
void vtkImageSlice::Update()
{
  if (this->Mapper)
  {
    vtkImageToImageMapper3DFriendship::SetCurrentProp(this->Mapper, this);
    this->Mapper->Update();
  }
}

//----------------------------------------------------------------------------
void vtkImageSlice::SetProperty(vtkImageProperty* property)
{
  if (this->Property != property)
  {
    if (this->Property != nullptr)
    {
      this->Property->UnRegister(this);
    }
    this->Property = property;
    if (this->Property != nullptr)
    {
      this->Property->Register(this);
    }
    this->Modified();
  }
}

//----------------------------------------------------------------------------
vtkImageProperty* vtkImageSlice::GetProperty()
{
  if (this->Property == nullptr)
  {
    this->Property = vtkImageProperty::New();
    this->Property->Register(this);
    this->Property->Delete();
  }
  return this->Property;
}

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

  if (this->Property != nullptr)
  {
    time = this->Property->GetMTime();
    mTime = (time > mTime ? time : mTime);
  }

  if (this->UserMatrix != nullptr)
  {
    time = this->UserMatrix->GetMTime();
    mTime = (time > mTime ? time : mTime);
  }

  if (this->UserTransform != nullptr)
  {
    time = this->UserTransform->GetMTime();
    mTime = (time > mTime ? time : mTime);
  }

  return mTime;
}

//----------------------------------------------------------------------------
vtkMTimeType vtkImageSlice::GetRedrawMTime()
{
  vtkMTimeType mTime = this->GetMTime();
  vtkMTimeType time;

  if (this->Mapper != nullptr)
  {
    time = this->Mapper->GetMTime();
    mTime = (time > mTime ? time : mTime);
    if (this->GetMapper()->GetInputAlgorithm() != nullptr)
    {
      this->GetMapper()->GetInputAlgorithm()->Update();
      time = this->Mapper->GetInput()->GetMTime();
      mTime = (time > mTime ? time : mTime);
    }
  }

  if (this->Property != nullptr)
  {
    time = this->Property->GetMTime();
    mTime = (time > mTime ? time : mTime);

    if (this->Property->GetLookupTable() != nullptr)
    {
      // check the lookup table mtime
      time = this->Property->GetLookupTable()->GetMTime();
      mTime = (time > mTime ? time : mTime);
    }
  }

  return mTime;
}

//----------------------------------------------------------------------------
void vtkImageSlice::SetStackedImagePass(int pass)
{
  if (this->Mapper)
  {
    vtkImageToImageMapper3DFriendship::SetStackedImagePass(this->Mapper, pass);
  }
}

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

  if (this->Property)
  {
    os << indent << "Property:\n";
    this->Property->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << indent << "Property: (not defined)\n";
  }

  if (this->Mapper)
  {
    os << indent << "Mapper:\n";
    this->Mapper->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << indent << "Mapper: (not defined)\n";
  }

  // make sure our bounds are up to date
  if (this->Mapper)
  {
    this->GetBounds();
    os << indent << "Bounds: (" << this->Bounds[0] << ", " << this->Bounds[1] << ") ("
       << this->Bounds[2] << ") (" << this->Bounds[3] << ") (" << this->Bounds[4] << ") ("
       << this->Bounds[5] << ")\n";
  }
  else
  {
    os << indent << "Bounds: (not defined)\n";
  }

  os << indent << "ForceTranslucent: " << (this->ForceTranslucent ? "On\n" : "Off\n");
}
