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

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

#include "vtkBoundedPlanePointPlacer.h"
#include "vtkCamera.h"
#include "vtkCommand.h"
#include "vtkImageActor.h"
#include "vtkImageData.h"
#include "vtkImageMapToWindowLevelColors.h"
#include "vtkImageReslice.h"
#include "vtkInteractorStyleImage.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPlane.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkResliceCursor.h"
#include "vtkResliceCursorActor.h"
#include "vtkResliceCursorLineRepresentation.h"
#include "vtkResliceCursorPolyDataAlgorithm.h"
#include "vtkResliceCursorThickLineRepresentation.h"
#include "vtkResliceCursorWidget.h"
#include "vtkResliceImageViewerMeasurements.h"
#include "vtkScalarsToColors.h"
#include "vtkSmartPointer.h"

vtkStandardNewMacro(vtkResliceImageViewer);

//----------------------------------------------------------------------------
// This class is used to scroll slices with the scroll bar. In the case of MPR
// view, it moves one "normalized spacing" in the direction of the normal to
// the resliced plane, provided the new center will continue to lie within the
// volume.
class vtkResliceImageViewerScrollCallback : public vtkCommand
{
public:
  static vtkResliceImageViewerScrollCallback *New()
    { return new vtkResliceImageViewerScrollCallback; }

  void Execute(vtkObject *, unsigned long ev, void*) override
  {
    if (!this->Viewer->GetSliceScrollOnMouseWheel())
    {
      return;
    }

    // Do not process if any modifiers are ON
    if (this->Viewer->GetInteractor()->GetShiftKey() ||
        this->Viewer->GetInteractor()->GetControlKey() ||
        this->Viewer->GetInteractor()->GetAltKey())
    {
      return;
    }

    // forwards or backwards
    int sign = (ev == vtkCommand::MouseWheelForwardEvent) ? 1 : -1;
    this->Viewer->IncrementSlice(sign);

    // Abort further event processing for the scroll.
    this->SetAbortFlag(1);
  }

  vtkResliceImageViewerScrollCallback():Viewer(nullptr) {}
  vtkResliceImageViewer *Viewer;
};

//----------------------------------------------------------------------------
vtkResliceImageViewer::vtkResliceImageViewer()
{
  // Default is to not use the reslice cursor widget, ie use fast
  // 3D texture mapping to display slices.
  this->ResliceMode = vtkResliceImageViewer::RESLICE_AXIS_ALIGNED;

  // Set up the reslice cursor widget, should it be used.

  this->ResliceCursorWidget = vtkResliceCursorWidget::New();

  vtkSmartPointer< vtkResliceCursor > resliceCursor =
    vtkSmartPointer< vtkResliceCursor >::New();
  resliceCursor->SetThickMode(0);
  resliceCursor->SetThickness(10, 10, 10);

  vtkSmartPointer< vtkResliceCursorLineRepresentation >
    resliceCursorRep = vtkSmartPointer<
      vtkResliceCursorLineRepresentation >::New();
  resliceCursorRep->GetResliceCursorActor()->
      GetCursorAlgorithm()->SetResliceCursor(resliceCursor);
  resliceCursorRep->GetResliceCursorActor()->
      GetCursorAlgorithm()->SetReslicePlaneNormal(this->SliceOrientation);
  this->ResliceCursorWidget->SetRepresentation(resliceCursorRep);

  this->PointPlacer = vtkBoundedPlanePointPlacer::New();

  this->Measurements = vtkResliceImageViewerMeasurements::New();
  this->Measurements->SetResliceImageViewer(this);

  this->ScrollCallback = vtkResliceImageViewerScrollCallback::New();
  this->ScrollCallback->Viewer = this;
  this->SliceScrollOnMouseWheel = 1;

  this->InstallPipeline();
}

//----------------------------------------------------------------------------
vtkResliceImageViewer::~vtkResliceImageViewer()
{
  this->Measurements->Delete();

  if (this->ResliceCursorWidget)
  {
    this->ResliceCursorWidget->Delete();
    this->ResliceCursorWidget = nullptr;
  }

  this->PointPlacer->Delete();
  this->ScrollCallback->Delete();
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::SetThickMode( int t )
{
  vtkSmartPointer< vtkResliceCursor > rc = this->GetResliceCursor();

  if (t == this->GetThickMode())
  {
    return;
  }

  vtkSmartPointer< vtkResliceCursorLineRepresentation >
    resliceCursorRepOld = vtkResliceCursorLineRepresentation::SafeDownCast(
                          this->ResliceCursorWidget->GetRepresentation());
  vtkSmartPointer< vtkResliceCursorLineRepresentation > resliceCursorRepNew;

  this->GetResliceCursor()->SetThickMode(t);

  if (t)
  {
    resliceCursorRepNew = vtkSmartPointer<
        vtkResliceCursorThickLineRepresentation >::New();
  }
  else
  {
    resliceCursorRepNew = vtkSmartPointer<
        vtkResliceCursorLineRepresentation >::New();
  }

  int e = this->ResliceCursorWidget->GetEnabled();
  this->ResliceCursorWidget->SetEnabled(0);

  resliceCursorRepNew->GetResliceCursorActor()->
      GetCursorAlgorithm()->SetResliceCursor(rc);
  resliceCursorRepNew->GetResliceCursorActor()->
      GetCursorAlgorithm()->SetReslicePlaneNormal(this->SliceOrientation);
  this->ResliceCursorWidget->SetRepresentation(resliceCursorRepNew);
  resliceCursorRepNew->SetLookupTable(resliceCursorRepOld->GetLookupTable());

  resliceCursorRepNew->SetWindowLevel(
      resliceCursorRepOld->GetWindow(),
      resliceCursorRepOld->GetLevel(), 1);

  this->ResliceCursorWidget->SetEnabled(e);
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::SetResliceCursor( vtkResliceCursor * rc )
{
  vtkResliceCursorRepresentation *rep =
    vtkResliceCursorRepresentation::SafeDownCast(
          this->GetResliceCursorWidget()->GetRepresentation());
  rep->GetCursorAlgorithm()->SetResliceCursor(rc);

  // Rehook the observer to this reslice cursor.
  this->Measurements->SetResliceImageViewer(this);
}

//----------------------------------------------------------------------------
int vtkResliceImageViewer::GetThickMode()
{
  return (vtkResliceCursorThickLineRepresentation::
    SafeDownCast(this->ResliceCursorWidget->GetRepresentation())) ? 1 : 0;
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::SetLookupTable( vtkScalarsToColors * l )
{
  if (vtkResliceCursorRepresentation *rep =
        vtkResliceCursorRepresentation::SafeDownCast(
          this->ResliceCursorWidget->GetRepresentation()))
  {
    rep->SetLookupTable(l);
  }

  if (this->WindowLevel)
  {
    this->WindowLevel->SetLookupTable(l);
    this->WindowLevel->SetOutputFormatToRGBA();
    this->WindowLevel->PassAlphaToOutputOn();
  }
}

//----------------------------------------------------------------------------
vtkScalarsToColors * vtkResliceImageViewer::GetLookupTable()
{
  if (vtkResliceCursorRepresentation *rep =
        vtkResliceCursorRepresentation::SafeDownCast(
          this->ResliceCursorWidget->GetRepresentation()))
  {
    return rep->GetLookupTable();
  }

  return nullptr;
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::UpdateOrientation()
{
    // Set the camera position

    vtkCamera *cam = this->Renderer ? this->Renderer->GetActiveCamera() : nullptr;
    if (cam)
    {
      switch (this->SliceOrientation)
      {
        case vtkImageViewer2::SLICE_ORIENTATION_XY:
          cam->SetFocalPoint(0,0,0);
          cam->SetPosition(0,0,1); // -1 if medical ?
          cam->SetViewUp(0,1,0);
          break;

        case vtkImageViewer2::SLICE_ORIENTATION_XZ:
          cam->SetFocalPoint(0,0,0);
          cam->SetPosition(0,-1,0); // 1 if medical ?
          cam->SetViewUp(0,0,1);
          break;

        case vtkImageViewer2::SLICE_ORIENTATION_YZ:
          cam->SetFocalPoint(0,0,0);
          cam->SetPosition(1,0,0); // -1 if medical ?
          cam->SetViewUp(0,0,1);
          break;
      }
    }
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::UpdateDisplayExtent()
{
  // Only update the display extent in axis aligned mode

  if (this->ResliceMode == RESLICE_AXIS_ALIGNED)
  {
    this->Superclass::UpdateDisplayExtent();
  }
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::InstallPipeline()
{
  this->Superclass::InstallPipeline();

  if (this->Interactor)
  {
    this->ResliceCursorWidget->SetInteractor(this->Interactor);

    // Observe the scroll for slice manipulation at a higher priority
    // than the interactor style.
    this->Interactor->RemoveObserver(this->ScrollCallback);
    this->Interactor->AddObserver(vtkCommand::MouseWheelForwardEvent,
        this->ScrollCallback, 0.55 );
    this->Interactor->AddObserver(vtkCommand::MouseWheelBackwardEvent,
        this->ScrollCallback, 0.55 );
  }

  if (this->Renderer)
  {
    this->ResliceCursorWidget->SetDefaultRenderer(this->Renderer);
    vtkCamera *cam = this->Renderer->GetActiveCamera();
    cam->ParallelProjectionOn();
  }

  if (this->ResliceMode == RESLICE_OBLIQUE)
  {
    this->ResliceCursorWidget->SetEnabled(1);
    this->ImageActor->SetVisibility(0);
    this->UpdateOrientation();

    double bounds[6] = {0, 1, 0, 1, 0, 1};

    vtkCamera *cam = this->Renderer->GetActiveCamera();
    double onespacing[3] = {1, 1, 1};
    double *spacing = onespacing;
    if (this->GetResliceCursor()->GetImage())
    {
      this->GetResliceCursor()->GetImage()->GetBounds(bounds);
      spacing = this->GetResliceCursor()->GetImage()->GetSpacing();
    }
    double avg_spacing =
      (spacing[0] + spacing[1] + spacing[2]) / 3.0;
    cam->SetClippingRange(
      bounds[this->SliceOrientation * 2] - 100 * avg_spacing,
      bounds[this->SliceOrientation * 2 + 1] + 100 * avg_spacing);
  }
  else
  {
    this->ResliceCursorWidget->SetEnabled(0);
    this->ImageActor->SetVisibility(1);
    this->UpdateOrientation();
  }

  if (this->WindowLevel)
  {
    this->WindowLevel->SetLookupTable(this->GetLookupTable());
  }
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::UnInstallPipeline()
{
  this->ResliceCursorWidget->SetEnabled(0);

  if (this->Interactor)
  {
    this->Interactor->RemoveObserver(this->ScrollCallback);
  }

  this->Superclass::UnInstallPipeline();
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::UpdatePointPlacer()
{
  if (this->ResliceMode == RESLICE_OBLIQUE)
  {
    this->PointPlacer->SetProjectionNormalToOblique();
    if (vtkResliceCursorRepresentation *rep =
        vtkResliceCursorRepresentation::SafeDownCast(
          this->ResliceCursorWidget->GetRepresentation()))
    {
      const int planeOrientation =
        rep->GetCursorAlgorithm()->GetReslicePlaneNormal();
      vtkPlane *plane = this->GetResliceCursor()->GetPlane(planeOrientation);
      this->PointPlacer->SetObliquePlane(plane);
    }
  }
  else
  {

    if (!this->WindowLevel->GetInput())
    {
      return;
    }

    vtkImageData *input = this->ImageActor->GetInput();
    if ( !input )
    {
      return;
    }

    double spacing[3];
    input->GetSpacing(spacing);

    double origin[3];
    input->GetOrigin(origin);

    double bounds[6];
    this->ImageActor->GetBounds(bounds);

    int displayExtent[6];
    this->ImageActor->GetDisplayExtent(displayExtent);

    int axis = vtkBoundedPlanePointPlacer::XAxis;
    double position = 0.0;
    if ( displayExtent[0] == displayExtent[1] )
    {
      axis = vtkBoundedPlanePointPlacer::XAxis;
      position = origin[0] + displayExtent[0]*spacing[0];
    }
    else if ( displayExtent[2] == displayExtent[3] )
    {
      axis = vtkBoundedPlanePointPlacer::YAxis;
      position = origin[1] + displayExtent[2]*spacing[1];
    }
    else if ( displayExtent[4] == displayExtent[5] )
    {
      axis = vtkBoundedPlanePointPlacer::ZAxis;
      position = origin[2] + displayExtent[4]*spacing[2];
    }

    this->PointPlacer->SetProjectionNormal(axis);
    this->PointPlacer->SetProjectionPosition(position);
  }

}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::Render()
{
  if (!this->WindowLevel->GetInput())
  {
    return;
  }

  this->UpdatePointPlacer();

  this->Superclass::Render();
}

//----------------------------------------------------------------------------
vtkResliceCursor * vtkResliceImageViewer::GetResliceCursor()
{
  if (vtkResliceCursorRepresentation *rep =
        vtkResliceCursorRepresentation::SafeDownCast(
          this->ResliceCursorWidget->GetRepresentation()))
  {
    return rep->GetResliceCursor();
  }

  return nullptr;
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::SetInputData(vtkImageData *in)
{
  if(!in)
  {
    return;
  }

  this->WindowLevel->SetInputData(in);
  this->GetResliceCursor()->SetImage(in);
  this->GetResliceCursor()->SetCenter(in->GetCenter());
  this->UpdateDisplayExtent();

  double range[2];
  in->GetScalarRange(range);
  if (vtkResliceCursorRepresentation *rep =
        vtkResliceCursorRepresentation::SafeDownCast(
          this->ResliceCursorWidget->GetRepresentation()))
  {
    if (vtkImageReslice *reslice =
        vtkImageReslice::SafeDownCast(rep->GetReslice()))
    {
      // default background color is the min value of the image scalar range
      reslice->SetBackgroundColor(range[0],range[0],range[0],range[0]);
      this->SetColorWindow(range[1]-range[0]);
      this->SetColorLevel((range[0]+range[1])/2.0);
    }
  }
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::SetInputConnection(vtkAlgorithmOutput* input)
{
  vtkErrorMacro( << "Use SetInputData instead. " );
  this->WindowLevel->SetInputConnection(input);
  this->UpdateDisplayExtent();
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::SetResliceMode( int r )
{
  if (r == this->ResliceMode)
  {
    return;
  }

  this->ResliceMode = r;
  this->Modified();

  this->InstallPipeline();
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::SetColorWindow( double w )
{
  double rmin = this->GetColorLevel() - 0.5*fabs( w );
  double rmax = rmin + fabs( w );
  this->GetLookupTable()->SetRange( rmin, rmax );

  this->WindowLevel->SetWindow(w);
  if (vtkResliceCursorRepresentation *rep =
        vtkResliceCursorRepresentation::SafeDownCast(
          this->ResliceCursorWidget->GetRepresentation()))
  {
    rep->SetWindowLevel(w, rep->GetLevel(), 1);
  }
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::SetColorLevel( double w )
{
  double rmin = w - 0.5*fabs( this->GetColorWindow() );
  double rmax = rmin + fabs( this->GetColorWindow() );
  this->GetLookupTable()->SetRange( rmin, rmax );

  this->WindowLevel->SetLevel(w);
  if (vtkResliceCursorRepresentation *rep =
        vtkResliceCursorRepresentation::SafeDownCast(
          this->ResliceCursorWidget->GetRepresentation()))
  {
    rep->SetWindowLevel(rep->GetWindow(), w, 1);
  }
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::Reset()
{
  this->ResliceCursorWidget->ResetResliceCursor();
}

//----------------------------------------------------------------------------
vtkPlane * vtkResliceImageViewer::GetReslicePlane()
{
  // Get the reslice plane
  if (vtkResliceCursorRepresentation *rep =
      vtkResliceCursorRepresentation::SafeDownCast(
        this->ResliceCursorWidget->GetRepresentation()))
  {
    const int planeOrientation =
      rep->GetCursorAlgorithm()->GetReslicePlaneNormal();
    vtkPlane *plane = this->GetResliceCursor()->GetPlane(planeOrientation);
    return plane;
  }

  return nullptr;
}

//----------------------------------------------------------------------------
double vtkResliceImageViewer::GetInterSliceSpacingInResliceMode()
{
  double n[3], imageSpacing[3], resliceSpacing = 0;

  if (vtkPlane *plane = this->GetReslicePlane())
  {
    plane->GetNormal(n);
    this->GetResliceCursor()->GetImage()->GetSpacing(imageSpacing);
    resliceSpacing = fabs(vtkMath::Dot(n, imageSpacing));
  }

  return resliceSpacing;
}

//----------------------------------------------------------------------------
void vtkResliceImageViewer::IncrementSlice( int inc )
{
  if (this->GetResliceMode() ==
      vtkResliceImageViewer::RESLICE_AXIS_ALIGNED)
  {
    int oldSlice = this->GetSlice();
    this->SetSlice(this->GetSlice() + inc);
    if (this->GetSlice() != oldSlice)
    {
      this->InvokeEvent( vtkResliceImageViewer::SliceChangedEvent, nullptr );
      this->InvokeEvent( vtkCommand::InteractionEvent, nullptr );
    }
  }
  else
  {
    if (vtkPlane *p = this->GetReslicePlane())
    {
      double n[3], c[3], bounds[6];
      p->GetNormal(n);
      const double spacing =
          this->GetInterSliceSpacingInResliceMode() * inc;
      this->GetResliceCursor()->GetCenter(c);
      vtkMath::MultiplyScalar(n, spacing);
      c[0] += n[0];
      c[1] += n[1];
      c[2] += n[2];

      // If the new center is inside, put it there...
      if (vtkImageData *image = this->GetResliceCursor()->GetImage())
      {
        image->GetBounds(bounds);
        if (c[0] >= bounds[0] && c[0] <= bounds[1] &&
            c[1] >= bounds[2] && c[1] <= bounds[3] &&
            c[2] >= bounds[4] && c[2] <= bounds[5])
        {
          this->GetResliceCursor()->SetCenter(c);

          this->InvokeEvent( vtkResliceImageViewer::SliceChangedEvent, nullptr );
          this->InvokeEvent( vtkCommand::InteractionEvent, nullptr );
        }
      }
    }
  }
}

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

  os << indent << "ResliceCursorWidget:\n";
  this->ResliceCursorWidget->PrintSelf(os,indent.GetNextIndent());
  os << indent << "ResliceMode: " << this->ResliceMode << endl;
  os << indent << "SliceScrollOnMouseWheel: " << this->SliceScrollOnMouseWheel << endl;
  os << indent << "Point Placer: ";
  this->PointPlacer->PrintSelf(os,indent.GetNextIndent());
  os << indent << "Measurements: ";
  this->Measurements->PrintSelf(os,indent.GetNextIndent());
  os << indent << "Interactor: " << this->Interactor << "\n";
  if (this->Interactor)
  {
    this->Interactor->PrintSelf(os,indent.GetNextIndent());
  }
}
