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

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.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 "vtkKWProbeImageWidget.h"

#include "vtkCamera.h"
#include "vtkCornerAnnotation.h"
#include "vtkImageActor.h"
#include "vtkImageData.h"
#include "vtkImageReslice.h"
#include "vtkKWCursorWidget.h"
#include "vtkKWEvent.h"
#include "vtkKWMenu.h"
#include "vtkKWIcon.h"
#include "vtkKWImageMapToWindowLevelColors.h"
#include "vtkKWInternationalization.h"
#include "vtkKWWindow.h"
#include "vtkKWEventMap.h"
#include "vtkMath.h"
#include "vtkMatrix4x4.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkPolyDataAlgorithm.h"
#include "vtkAlgorithmOutput.h"
#include "vtkCutter.h"
#include "vtkPlane.h"
#include "vtkTransform.h"
#include "vtkKWOrientationWidget.h"
#include "vtkKWInteractorStyleImageView.h"

#include <vtksys/stl/string>

vtkStandardNewMacro(vtkKWProbeImageWidget);
vtkCxxRevisionMacro(vtkKWProbeImageWidget, "$Revision: 1.60 $");

#ifdef KWCommonPro_USE_XML_RW
#include "XML/vtkXMLKWProbeImageWidgetReader.h"
#include "XML/vtkXMLKWProbeImageWidgetWriter.h"
#endif
vtkKWGetXMLReaderWriterObjectsImplementationMacro(vtkKWProbeImageWidget, vtkXMLKWProbeImageWidgetReader, vtkXMLKWProbeImageWidgetWriter);

//----------------------------------------------------------------------------
vtkKWProbeImageWidget::vtkKWProbeImageWidget()
{
  this->CornerAnnotation->ShowSliceAndImageOff();
  this->HasSliceControl = 0;
  this->ProbeInputAlgorithm = 0;
  this->ImageReslice = vtkImageReslice::New();
  this->ImageReslice->TransformInputSamplingOff();
  this->ImageReslice->SetInterpolationModeToLinear();
  this->ImageReslice->SetOutputDimensionality(2);
  this->ImageReslice->AutoCropOutputOn();
  // ImplicitPlaneWdg default are changed, need to update ImageReslice for Axial view
  this->ImageReslice->SetResliceAxesDirectionCosines(1,0,0,0,-1,0,0,0,1);
  this->ImageReslice->OptimizationOn ();
  this->Image->SetVisibility(1);
  this->Transform = vtkTransform::New();
}

//----------------------------------------------------------------------------
vtkKWProbeImageWidget::~vtkKWProbeImageWidget()
{
  this->SetProbeInputAlgorithm(NULL);
  this->ImageReslice->Delete();
  this->Transform->Delete();
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::SetProbeInputAlgorithm(vtkPolyDataAlgorithm *arg)
{
  if (this->ProbeInputAlgorithm == arg)
    {
    return;
    }

  if (this->ProbeInputAlgorithm)
    {
    this->ProbeInputAlgorithm->UnRegister(this);
    }
    
  this->ProbeInputAlgorithm = arg;

  if (this->ProbeInputAlgorithm)
    {
    this->ProbeInputAlgorithm->Register(this);
    }

  this->ConnectInternalPipeline();

  this->Modified();
}


//----------------------------------------------------------------------------
int vtkKWProbeImageWidget::ConnectInternalPipeline()
{
  // Have a look at the documentation for this method (see .h).

  if (!this->Superclass::ConnectInternalPipeline())
    {
    return 0;
    }

  if (this->ProbeInputAlgorithm)
    {
    this->ImageReslice->SetInput(this->Input);
    vtkKWImageMapToWindowLevelColors *map = this->GetImageMapToRGBA();
    if (map)
      {
      map->SetInput(this->ImageReslice->GetOutput());
      this->Image->SetInput(map->GetOutput());
      }
    }

  return 1;
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::ConnectImageMapToRGBA()
{
  if (!this->ProbeInputAlgorithm)
    {
    this->Superclass::ConnectImageMapToRGBA();
    return;
    }
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::CreateWidget()
{
  if (this->IsCreated())
    {
    vtkErrorMacro("widget already created " << this->GetClassName());
    return;
    }

  this->Superclass::CreateWidget();
  
  // The axis labels don't make sense for off-axis slices through the volume.

  this->SetSideAnnotationVisibility(0);

  this->SetOrientationMarkerVisibility(1);
  this->GetOrientationWidget()->SynchronizeRenderersOff();
  const double xAxis[3] = {1.0, 0.0, 0.0};
  const double yAxis[3] = {0.0, 1.0, 0.0};
  
  this->GetOrientationWidget()->UpdateCamera(180.0, xAxis);
  this->GetOrientationWidget()->UpdateCamera(0.0, yAxis);
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::ConfigureEventMap()
{
  // Get the render-widget common event map settings

  this->Superclass::ConfigureEventMap();

  // Configure the 2d-widget specific settings

  switch (this->InteractionMode)
    {
    case vtkKWProbeImageWidget::INTERACTION_MODE_ROLL:
      this->EventMap->SetMouseEvent(
        vtkKWEventMap::LeftButton,vtkKWEventMap::NoModifier, "Roll");
      this->EventMap->SetMouseEvent(
        vtkKWEventMap::LeftButton,vtkKWEventMap::ShiftModifier, "WindowLevel");
      this->EventMap->SetMouseEvent(
        vtkKWEventMap::LeftButton,vtkKWEventMap::ControlModifier, "Zoom");

      /*
      this->EventMap->SetMouseEvent(
        vtkKWEventMap::MiddleButton, vtkKWEventMap::NoModifier, "Zoom");
      */
      break;

    case vtkKWProbeImageWidget::INTERACTION_MODE_RESLICE:
      this->EventMap->SetMouseEvent(
        vtkKWEventMap::LeftButton,vtkKWEventMap::NoModifier, "Reslice");
      this->EventMap->SetMouseEvent(
        vtkKWEventMap::LeftButton,vtkKWEventMap::ShiftModifier, "WindowLevel");
      this->EventMap->SetMouseEvent(
        vtkKWEventMap::LeftButton,vtkKWEventMap::ControlModifier, "Zoom");

      /*
      this->EventMap->SetMouseEvent(
        vtkKWEventMap::MiddleButton, vtkKWEventMap::NoModifier, "Zoom");
      */
      break;

    case vtkKWProbeImageWidget::INTERACTION_MODE_TRANSLATE:
      this->EventMap->SetMouseEvent(
        vtkKWEventMap::LeftButton,vtkKWEventMap::NoModifier, "Translate");
      this->EventMap->SetMouseEvent(
        vtkKWEventMap::LeftButton,vtkKWEventMap::ShiftModifier, "WindowLevel");
      this->EventMap->SetMouseEvent(
        vtkKWEventMap::LeftButton,vtkKWEventMap::ControlModifier, "Zoom");

      /*
      this->EventMap->SetMouseEvent(
        vtkKWEventMap::MiddleButton, vtkKWEventMap::NoModifier, "Zoom");
      */
      break;
    }
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::PopulateContextMenuWithInteractionEntries(vtkKWMenu *menu)
{
  this->Superclass::PopulateContextMenuWithInteractionEntries(menu);

  if (!menu)
    {
    return;
    }

  const char *group_name = "InteractionMode";

  int tcl_major, tcl_minor, tcl_patch_level;
  Tcl_GetVersion(&tcl_major, &tcl_minor, &tcl_patch_level, NULL);
  int show_icons = (tcl_major > 8 || (tcl_major == 8 && tcl_minor >= 5));

  int index;

  index = menu->AddRadioButton(
    ks_("Interaction Mode|Rotate"), 
    this, "SetInteractionModeToRoll");
  menu->SetItemSelectedValueAsInt(
    index, vtkKWProbeImageWidget::INTERACTION_MODE_ROLL);
  menu->SetItemGroupName(index, group_name);
  if (show_icons)
    {
    menu->SetItemImageToPredefinedIcon(
      index, vtkKWIcon::IconCrystalProject16x16ActionsRotate);
    menu->SetItemCompoundModeToLeft(index);
    }
  
  index = menu->AddRadioButton(
    ks_("Interaction Mode|Reslice"), 
    this, "SetInteractionModeToReslice");
  menu->SetItemSelectedValueAsInt(
    index, vtkKWProbeImageWidget::INTERACTION_MODE_RESLICE);
  menu->SetItemGroupName(index, group_name);
  if (show_icons)
    {
    menu->SetItemImageToPredefinedIcon(index, vtkKWIcon::IconObliqueProbe);
    menu->SetItemCompoundModeToLeft(index);
    }

  index = menu->AddRadioButton(
    ks_("Interaction Mode|Translate"), 
    this, "SetInteractionModeToTranslate");
  menu->SetItemSelectedValueAsInt(
    index, vtkKWProbeImageWidget::INTERACTION_MODE_TRANSLATE);
  menu->SetItemGroupName(index, group_name);
  if (show_icons)
    {
    menu->SetItemImageToPredefinedIcon(index, vtkKWIcon::IconMoveV);
    menu->SetItemCompoundModeToLeft(index);
    }

  menu->SelectItemInGroupWithSelectedValueAsInt(
    "InteractionMode", this->InteractionMode);
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::UpdateDisplayExtent()
{
  if (!this->Image || !this->Image->GetInput() || !this->ProbeInputAlgorithm)
    {
    this->Superclass::UpdateDisplayExtent();
    return;
    }

  this->Image->GetInput()->UpdateInformation();
  this->Image->SetDisplayExtent(this->Image->GetInput()->GetWholeExtent());

  // Note the renderer won't add it twice it is already there

  vtkRenderer *ren = this->GetRenderer();
  ren->AddViewProp(this->Image);
    
  double bounds[6];
  this->Image->GetBounds(bounds);
    
  // Adjust the 3D widgets

  this->Cursor3DWidget->PlaceWidget(bounds);
  this->Cursor3DWidget->SetSliceOrientation(this->SliceOrientation);

  // Figure out the correct clipping range?
  // first get it in world coords
  // This is particularly important when origin is different from 0,0,0
   
  double spos = (double)bounds[this->SliceOrientation * 2];
  double cpos = (double)
    ren->GetActiveCamera()->GetPosition()[this->SliceOrientation];
  double range = fabs(spos - cpos);
  double *spacing = this->Image->GetInput()->GetSpacing();
  double avgSpacing = 
    ((double)spacing[0] + (double)spacing[1] + (double)spacing[2]) / 3.0;
  ren->GetActiveCamera()->SetClippingRange(
    range - avgSpacing * 3.0, range + avgSpacing * 3.0);

  this->UpdateImageMap();
}

//---------------------------------------------------------------------------
int vtkKWProbeImageWidget::ComputeWorldCoordinate(
  int x, int y, double *result, int *id)
{
  if (this->Image->GetVisibility() == 0)
    {
    return 0;
    }

  if (!this->ProbeInputAlgorithm)
    {
    return this->Superclass::ComputeWorldCoordinate(x,y,result,id);
    }

  // What is the focal depth of the origin?

  double point[4];
  point[0] = 0.0;
  point[1] = 0.0;
  point[2] = 0.0;
  point[3] = 1.0;
  
  // Convert 0,0,0 into display coordinates to get the depth

  vtkRenderer *ren = this->GetRenderer();
  ren->SetWorldPoint(point);
  ren->WorldToDisplay();
  double *DPoint = ren->GetDisplayPoint();
  double focalDepth = DPoint[2];
  
  // Convert the x,y probe location to world coordinates using the
  // computed depth

  ren->SetDisplayPoint(x, y, focalDepth);
  ren->DisplayToWorld();
  double *RPoint = ren->GetWorldPoint();
  if (RPoint[3] != 0.0)
    {
    RPoint[0] = (double)((double)RPoint[0] / (double)RPoint[3]);
    RPoint[1] = (double)((double)RPoint[1] / (double)RPoint[3]);
    RPoint[2] = (double)((double)RPoint[2] / (double)RPoint[3]);
    RPoint[3] = 1.0;
    }
  
  // Now we have the world coordinate, now we can multiply that point 
  // by the reslice matrix to get the real world coordinate

  vtkImageReslice *reslice = this->ImageReslice;
  double range[2];
  this->Input->GetScalarRange(range);
  this->ImageReslice->SetBackgroundLevel ( range[0] );
  if (reslice)
    {
    vtkMatrix4x4 *resliceAxes = this->ImageReslice->GetResliceAxes();
    resliceAxes->MultiplyPoint(RPoint,point);
    if (point[3] != 0.0)
      {
      point[0] = (double)((double)point[0] / (double)point[3]);
      point[1] = (double)((double)point[1] / (double)point[3]);
      point[2] = (double)((double)point[2] / (double)point[3]);
      point[3] = 1.0;
      }

    double *origin = this->Input->GetOrigin();
    double *spacing = this->Input->GetSpacing();

    int loc[3];    
    loc[0] = (int)
      (floor(((double)point[0]-(double)origin[0]) / (double)spacing[0] +0.5));
    loc[1] = (int)
      (floor(((double)point[1]-(double)origin[1]) / (double)spacing[1] +0.5));
    loc[2] = (int)
      (floor(((double)point[2]-(double)origin[2]) / (double)spacing[2] +0.5));
    
    int dim[3];
    this->Input->GetDimensions(dim);
    
    // If we are outside the volume return 0

    if (loc[0] < 0 || loc[0] >= dim[0] ||
        loc[1] < 0 || loc[1] >= dim[1] ||
        loc[2] < 0 || loc[2] >= dim[2])
      {
      return 0;
      }
  
    result[0] = point[0];
    result[1] = point[1];
    result[2] = point[2];
    }
  
  if (id)
    {
    *id = this->GetRendererIndex(ren);
    }
  
  return 1;
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::SetImageVisibility(int state)
{
  if (this->GetImageVisibility() == state)
    {
    return;
    }
 
  this->Image->SetVisibility(state);
  this->Render();
}

//----------------------------------------------------------------------------
int vtkKWProbeImageWidget::GetImageVisibility()
{
  return this->Image->GetVisibility();
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::TiltPlane(double rxf, double ryf)
{
  vtkCutter *cutter = vtkCutter::SafeDownCast(this->ProbeInputAlgorithm);
  vtkPlane * plane = vtkPlane::SafeDownCast(cutter->GetCutFunction ());
  double z[3]; // Z axis
  plane->GetNormal(z);
  double origin[3];
  plane->GetOrigin(origin);

  double oldx[3], oldy[3], oldz[3];
  this->ImageReslice->GetResliceAxesDirectionCosines(oldx,oldy,oldz);

  this->Transform->Identity();
  this->Transform->RotateWXYZ(rxf,oldy);
  this->Transform->RotateWXYZ(ryf,oldx);

  //Set the new x
  double x[3];
  this->Transform->TransformNormal(oldx,x);
  //Set the new y
  double y[3];
  this->Transform->TransformNormal(oldy,y);
  //Set the new y
  this->Transform->TransformNormal(oldz,z);
  this->GetOrientationWidget()->UpdateCamera(rxf, oldy);
  this->GetOrientationWidget()->UpdateCamera(ryf, oldx);

  this->ImageReslice->SetResliceAxesDirectionCosines(x,y,z);
  this->ImageReslice->GetResliceAxesDirectionCosines(oldx,oldy,oldz);
  this->ImageReslice->SetResliceAxesOrigin(origin);

  plane->SetNormal(z);
  cutter->Modified();
  
  this->Render();
  this->InvokeEvent(vtkKWEvent::ProbeImageTiltPlaneEvent, 0);
}

//----------------------------------------------------------------------------
// This method moves the relices plane along the normal by 
// a factor passed to it by the interactor that is based on
// the y motion of the mouse
//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::TranslatePlane(double factor)
{
  vtkCutter *cutter = vtkCutter::SafeDownCast(this->ProbeInputAlgorithm);
  vtkPlane * plane = vtkPlane::SafeDownCast(cutter->GetCutFunction ());
  
  // This is the reslice normal
  double z[3];
  plane->GetNormal(z);
  
  // This is the reslice origin
  double origin[3];
  plane->GetOrigin(origin);

  // Find out the x, y bounds of the image - 
  // use this as a scale factor so that the
  // factor input parameter is linked to the size
  // of the data
  double bounds[6];
  this->Image->GetBounds(bounds);
  double avg = 
    ( (bounds[1] - bounds[0]) + 
      (bounds[3] - bounds[2]) ) * 0.5;
  factor *= avg;
  
  // Move the origin along the normal
  vtkMath::Normalize(z);
  origin[0] += factor * z[0];
  origin[1] += factor * z[1];
  origin[2] += factor * z[2];
  
  // No need to check bounds before setting - the plane widget
  // will do it. But that means we need to get it to find out what
  // it really is after we set it.
  //
  // TODO: there are a couple of problems here - first, this
  // doesn't really work in practice - there is some bug that
  // causes the origin to move off the reslice plane as drawn
  // by the widget. Shouldn't happen but it does. Second, we
  // should really be keeping the origin in the center of the 
  // plane - it looks better that way. Perhaps when we fix that
  // issue the first one will go away.
  plane->SetOrigin(origin);
  plane->GetOrigin(origin);
  cutter->Modified();  
  this->ImageReslice->SetResliceAxesOrigin(origin);
  this->Render();
  this->InvokeEvent(vtkKWEvent::ProbeImageTranslatePlaneEvent, 0);
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::RollPlane(double theta)
{
  vtkCutter *cutter = vtkCutter::SafeDownCast(this->ProbeInputAlgorithm);
  vtkPlane * plane = vtkPlane::SafeDownCast(cutter->GetCutFunction ());
  double z[3]; // Z axis
  plane->GetNormal(z);
  double origin[3];
  plane->GetOrigin(origin);

  double oldx[3], oldy[3], oldz[3];
  this->ImageReslice->GetResliceAxesDirectionCosines(oldx,oldy,oldz);
  double axis[3];
  vtkMath::Cross(oldz,z,axis);
  if ( vtkMath::Normalize(axis) != 0.0 )
    {
    abort();
    return;
    }
  this->Transform->Identity();
  //this->Transform->Translate(origin[0],origin[1],origin[2]);
  this->Transform->RotateWXYZ(theta,z);
  //this->Transform->Translate(-origin[0],-origin[1],-origin[2]);

  //Set the new x
  double x[3];
  this->Transform->TransformNormal(oldx,x);
  //Set the new y
  double y[3];
  this->Transform->TransformNormal(oldy,y);
  this->GetOrientationWidget()->UpdateCamera( theta, z);

  this->ImageReslice->SetResliceAxesDirectionCosines(x,y,z);
  this->ImageReslice->SetResliceAxesOrigin(origin);
  this->Render();
 
  this->InvokeEvent(vtkKWEvent::ProbeImageRollPlaneEvent, 0);
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::UpdatePlane()
{
  vtkCutter *cutter = vtkCutter::SafeDownCast(this->ProbeInputAlgorithm);
  vtkPlane * plane = vtkPlane::SafeDownCast(cutter->GetCutFunction ());
  double z[3]; // Z axis
  plane->GetNormal(z);
  double origin[3];
  plane->GetOrigin(origin);

  double oldx[3], oldy[3], oldz[3];
  this->ImageReslice->GetResliceAxesDirectionCosines(oldx,oldy,oldz);
  double axis[3];
  vtkMath::Cross(oldz,z,axis);
  if ( vtkMath::Normalize(axis) < 1.0e-15 )
    {
    this->ImageReslice->SetResliceAxesOrigin(origin);
    return;
    }
  double cos_theta = vtkMath::Dot(oldz, z);
  double theta = acos( cos_theta )*vtkMath::DoubleRadiansToDegrees();
  this->Transform->Identity();
  this->Transform->Translate(origin[0],origin[1],origin[2]);
  this->Transform->RotateWXYZ(theta,axis);
  this->Transform->Translate(-origin[0],-origin[1],-origin[2]);

  //Set the new x
  double x[3];
  this->Transform->TransformNormal(oldx,x);
  //Set the new y
  double y[3];
  this->Transform->TransformNormal(oldy,y);
  this->GetOrientationWidget()->UpdateCamera( theta, axis);

  this->ImageReslice->SetResliceAxesDirectionCosines(x,y,z);
  this->ImageReslice->SetResliceAxesOrigin(origin);
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::Render()
{
  if (this->Image && this->Image->GetInput() && this->ProbeInputAlgorithm)
    {
    this->UpdatePlane();
    this->UpdateDisplayExtent();
    }

  this->Superclass::Render();
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::ResetCamera()
{
  this->vtkKWRenderWidget::ResetCamera();
}

//----------------------------------------------------------------------------
void vtkKWProbeImageWidget::PrintSelf(ostream &os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  
  os << indent << "ProbeInputAlgorithm: " << this->ProbeInputAlgorithm << endl;
  os << indent << "ImageReslice: " << this->ImageReslice << endl;
}
