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

  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 "vtkKWInteractorStyle2DView.h"

#include "vtkActor2D.h"
#include "vtkCamera.h"
#include "vtkCoordinate.h"
#include "vtkImageActor.h"
#include "vtkImageData.h"
#include "vtkKW2DRenderWidget.h"
#include "vtkKWEvent.h"
#include "vtkKWFrame.h"
#include "vtkKWImageMapToWindowLevelColors.h"
#include "vtkKWInternationalization.h"
#include "vtkKWWindowBase.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPolyDataMapper2D.h"
#include "vtkProperty2D.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"

#define PROBE_DISPLAY_LENGTH 110

vtkCxxRevisionMacro(vtkKWInteractorStyle2DView, "$Revision: 1.46 $");
vtkStandardNewMacro(vtkKWInteractorStyle2DView);

vtkCxxSetObjectMacro(vtkKWInteractorStyle2DView,
                     ImageMapToRGBA,
                     vtkKWImageMapToWindowLevelColors);

//----------------------------------------------------------------------------
vtkKWInteractorStyle2DView::vtkKWInteractorStyle2DView()
{
  this->InCallback = 0;
  this->ImageMapToRGBA = NULL;
}

//----------------------------------------------------------------------------
vtkKWInteractorStyle2DView::~vtkKWInteractorStyle2DView()
{
  this->SetImageMapToRGBA(NULL);
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::Reset()
{
  vtkKWRenderWidget *widget = this->GetRenderWidget();
  if (widget)
    {
    widget->Reset();
    double arg = this->EventIdentifier;
    this->InvokeEvent(vtkKWEvent::ImageCameraResetEvent, &arg);

    // Let's not reset the W/L anymore. Resetting the view is something
    // useful, but if you have worked on a specific W/L for a while, it
    // is annoying to lose that settings just because you wanted a better
    // view of the image. Most of the time, the UI will provide a
    // "Reset W/L" button

    /*
    vtkKW2DRenderWidget *w2d = vtkKW2DRenderWidget::SafeDownCast(widget);
    if (w2d)
      {
      w2d->ResetWindowLevel();
      this->InvokeEvent(vtkKWEvent::WindowLevelResetEvent, &farg);
      }
    */
    }
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::SetWindowLevel(double window, double level)
{
  if (!this->ImageMapToRGBA)
    {
    vtkErrorMacro("Trying to set window / level before setting ImageMapToRGBA");
    return;
    }
  
  if (this->ImageMapToRGBA->GetWindow() == window &&
      this->ImageMapToRGBA->GetLevel() == level)
    {
    return;
    }
  
  this->ImageMapToRGBA->SetWindow(window);
  this->ImageMapToRGBA->SetLevel(level);
  this->ImageMapToRGBA->Update();

  this->PerformInteractiveRender();

  vtkKW2DRenderWidget *rw2d = this->Get2DRenderWidget();
  if (rw2d)
    {
    rw2d->SetWindowLevel(window, level);
    }
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::Zoom()
{
  if (!this->Interactor)
    {
    return;
    }

  int y = this->Interactor->GetEventPosition()[1];
  int lastY = this->Interactor->GetLastEventPosition()[1];
  
  double zoomFactor = pow(1.02,(0.5*(lastY - y)));

  this->Zoom(zoomFactor);
  
  this->InCallback = 1;
  double args[2];
  args[0] = zoomFactor;
  args[1] = this->EventIdentifier;
  this->InvokeEvent(vtkKWEvent::ImageZoomFactorChangedEvent, args);
  this->InCallback = 0;
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::Zoom(double factor)
{
  if (this->InCallback)
    {
    return;
    }
  
  vtkKWRenderWidget *widget = this->GetRenderWidget();
  if (!widget)
    {
    return;
    }

  vtkRenderer *ren = widget->GetRenderer();
  if (!ren)
    {
    return;
    }

  vtkCamera *cam = ren->GetActiveCamera();
  if (!cam)
    {
    return;
    }

  if (cam->GetParallelProjection()) 
    {
    double parallelScale = cam->GetParallelScale()* factor;
    
    // Force a limit on the zoom - you must be able to see at least
    // 10 pixels at the minimum spacing (less for larger spacing values)
    // and not more than 4 times the maximum dimension.
    vtkKW2DRenderWidget *widget2d = this->Get2DRenderWidget();
    if (widget2d)
      {
      vtkImageData *input = widget2d->GetInput();
      double *spacing = input->GetSpacing();  
      double min = (spacing[0]<spacing[1])?(spacing[0]):(spacing[1]);
      min = (spacing[2] < spacing[1])?(spacing[2]):(spacing[1]);
      parallelScale = (parallelScale < 5*min)?(5*min):(parallelScale);
      
      int *dim = input->GetDimensions();
      double max = (dim[0]*spacing[0]);
      max = (dim[1]*spacing[1]>max)?(dim[1]*spacing[1]):(max);
      max = (dim[2]*spacing[2]>max)?(dim[2]*spacing[2]):(max);      
      parallelScale = (parallelScale > 2*max)?(2*max):(parallelScale);
      }
    cam->SetParallelScale(parallelScale);
    } 
  else 
    {
    cam->Dolly(factor);
    ren->ResetCameraClippingRange();
    }

  this->PerformInteractiveRender();
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::Pan()
{
  if (this->InCallback)
    {
    return;
    }
  
  vtkKWRenderWidget *widget = this->GetRenderWidget();
  if (!widget)
    {
    return;
    }

  vtkRenderer *ren = widget->GetRenderer();
  if (!ren)
    {
    return;
    }

  vtkCamera *cam = ren->GetActiveCamera();
  if (!cam)
    {
    return;
    }

  double FPoint[3];
  cam->GetFocalPoint(FPoint);

  double PPoint[3];
  cam->GetPosition(PPoint);

  ren->SetWorldPoint(FPoint[0], FPoint[1], FPoint[2], 1.0);
  ren->WorldToDisplay();

  double *DPoint = ren->GetDisplayPoint();
  double focalDepth = DPoint[2];

  int x = this->Interactor->GetEventPosition()[0];
  int y = this->Interactor->GetEventPosition()[1];
  
  int lastX = this->Interactor->GetLastEventPosition()[0];
  int lastY = this->Interactor->GetLastEventPosition()[1];
  
  double APoint0 = ren->GetCenter()[0] + (x - lastX);
  double APoint1 = ren->GetCenter()[1] - (lastY - y);

  ren->SetDisplayPoint(APoint0,APoint1,focalDepth);
  ren->DisplayToWorld();

  double *RPoint = ren->GetWorldPoint();
  if (RPoint[3] != 0.0)
    {
    RPoint[0] = RPoint[0] / RPoint[3];
    RPoint[1] = RPoint[1] / RPoint[3];
    RPoint[2] = RPoint[2] / RPoint[3];
    }

  double args[7];
  args[0] = (FPoint[0] - RPoint[0])/2.0 + FPoint[0];
  args[1] = (FPoint[1] - RPoint[1])/2.0 + FPoint[1];
  args[2] = (FPoint[2] - RPoint[2])/2.0 + FPoint[2];
  args[3] = (FPoint[0] - RPoint[0])/2.0 + PPoint[0];
  args[4] = (FPoint[1] - RPoint[1])/2.0 + PPoint[1];
  args[5] = (FPoint[2] - RPoint[2])/2.0 + PPoint[2];

  this->SetCameraFocalPointAndPosition(args);

  this->InCallback = 1;
  args[6] = this->EventIdentifier;
  this->InvokeEvent(
    vtkKWEvent::ImageCameraFocalPointAndPositionChangedEvent, args);
  this->InCallback = 0;
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::SetCameraFocalPointAndPosition(
  double fpx, double fpy, double fpz, double px, double py, double pz)
{
  if (this->InCallback)
    {
    return;
    }
  
  vtkKWRenderWidget *widget = this->GetRenderWidget();
  if (!widget)
    {
    return;
    }

  vtkRenderer *ren = widget->GetRenderer();
  if (!ren)
    {
    return;
    }

  vtkCamera *cam = ren->GetActiveCamera();
  if (!cam)
    {
    return;
    }

  cam->SetFocalPoint(fpx, fpy, fpz);
  cam->SetPosition(px, py, pz);
 
  this->PerformInteractiveRender();
}

//----------------------------------------------------------------------------
int vtkKWInteractorStyle2DView::StartAction(const char* action)
{
  int res = 0;
  if (!action)
    {
    return res;
    }

  if (this->Superclass::StartAction(action))
    {
    res = 1;
    }
  else
    {
    if (!strcmp(action, "WindowLevel"))
      {
      this->StartWindowLevel();
      res = 1;
      }
    else if (!strcmp(action, "Pan"))
      {
      this->StartPan();
      res = 1;
      }
    else if (!strcmp(action, "Zoom"))
      {
      this->StartZoom();
      res = 1;
      }
    }
  
  return res;
}

//----------------------------------------------------------------------------
int vtkKWInteractorStyle2DView::PerformAction(const char* action)
{
  if (!action)
    {
    return 0;
    }
  
  // FIXME: Superclass action is prefered rather current implementation
  if (this->Superclass::PerformAction(action))
    {
    return 1;
    }

  if (!strcmp(action, "WindowLevel"))
    {
    this->WindowLevel();
    return 1;
    }
  else if (!strcmp(action, "Pan"))
    {
    this->Pan();
    return 1;
    }
  else if (!strcmp(action, "Zoom"))
    {
    this->Zoom();
    return 1;
    }
  else if (!strcmp(action, "Reset"))
    {
    this->Reset();
    return 1;
    }
  else if (!strcmp(action, "DecrementSlice"))
    {
    this->DecrementSlice();
    return 1;
    }
  else if (!strcmp(action, "IncrementSlice"))
    {
    this->IncrementSlice();
    return 1;
    }
  else if (!strcmp(action, "DecrementPage"))
    {
    this->DecrementPage();
    return 1;
    }
  else if (!strcmp(action, "IncrementPage"))
    {
    this->IncrementPage();
    return 1;
    }
  else if (!strcmp(action, "GoToFirstSlice"))
    {
    this->GoToFirstSlice();
    return 1;
    }
  else if (!strcmp(action, "GoToLastSlice"))
    {
    this->GoToLastSlice();
    return 1;
    }

  return 0;
}

//----------------------------------------------------------------------------
int vtkKWInteractorStyle2DView::StopAction(const char* action)
{
  int res = 0;
  if (!action)
    {
    return res;
    }
  
  if (this->Superclass::StopAction(action))
    {
    res = 1;
    }
  else
    {
    if (!strcmp(action, "WindowLevel"))
      {
      this->StopWindowLevel();
      res = 1;
      }
    else if (!strcmp(action, "Pan"))
      {
      this->StopPan();
      res = 1;
      }
    else if (!strcmp(action, "Zoom"))
      {
      this->StopZoom();
      res = 1;
      }
    }

  return res;
}

//----------------------------------------------------------------------------
vtkKW2DRenderWidget* vtkKWInteractorStyle2DView::Get2DRenderWidget()
{
  return vtkKW2DRenderWidget::SafeDownCast(this->GetRenderWidget());
}

//----------------------------------------------------------------------------
int vtkKWInteractorStyle2DView::GetSliceOrientation()
{
  vtkKW2DRenderWidget *widget = this->Get2DRenderWidget();
  if (widget)
    {
    return widget->GetSliceOrientation();
    }
  return 0;
}

//----------------------------------------------------------------------------
char* vtkKWInteractorStyle2DView::GetDistanceUnits()
{
  vtkKW2DRenderWidget *widget = this->Get2DRenderWidget();
  if (widget)
    {
    return widget->GetDistanceUnits();
    }
  return 0;
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::InvokeSliceChangedEvent()
{
  vtkKW2DRenderWidget *widget = this->Get2DRenderWidget();
  if (widget)
    {
    int args[2];
    args[0] = widget->GetSlice();
    args[1] = this->GetEventIdentifier();
    this->InvokeEvent(vtkKWEvent::ImageSliceChangedEvent, args);
    }
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::DecrementSlice()
{
  vtkKW2DRenderWidget *widget = this->Get2DRenderWidget();
  if (widget)
    {
    widget->DecrementSlice();
    this->InvokeSliceChangedEvent();
    }
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::IncrementSlice()
{
  vtkKW2DRenderWidget *widget = this->Get2DRenderWidget();
  if (widget)
    {
    widget->IncrementSlice();
    this->InvokeSliceChangedEvent();
    }
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::DecrementPage()
{
  vtkKW2DRenderWidget *widget = this->Get2DRenderWidget();
  if (widget)
    {
    widget->DecrementPage();
    this->InvokeSliceChangedEvent();
    }
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::IncrementPage()
{
  vtkKW2DRenderWidget *widget = this->Get2DRenderWidget();
  if (widget)
    {
    widget->IncrementPage();
    this->InvokeSliceChangedEvent();
    }
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::GoToFirstSlice()
{
  vtkKW2DRenderWidget *widget = this->Get2DRenderWidget();
  if (widget)
    {
    widget->GoToFirstSlice();
    this->InvokeSliceChangedEvent();
    }
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::GoToLastSlice()
{
  vtkKW2DRenderWidget *widget = this->Get2DRenderWidget();
  if (widget)
    {
    widget->GoToLastSlice();
    this->InvokeSliceChangedEvent();
    }
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::OnMouseMove()
{
  this->Superclass::OnMouseMove();

  this->Probe();
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::Probe()
{
  vtkKW2DRenderWidget *widget = this->Get2DRenderWidget();
  if (!widget)
    {
    return;
    }

  char str[1024];

  vtkImageData *input = widget->GetInput();
  int *event_pos = this->Interactor->GetEventPosition();
  double RPoint[3];

  vtkKWWindowBase *win = vtkKWWindowBase::SafeDownCast(
    widget->GetParentTopLevel());
  
  if (!input || 
      !widget->ComputeWorldCoordinate(event_pos[0], event_pos[1], RPoint))
    {
    if (win)
      {
      win->SetStatusText(ks_("Probe|Location: off image"));
      }
    this->InvokeEvent(vtkKWEvent::ProbeInformationOffEvent, NULL);
    return;
    }
    
  double *origin = input->GetOrigin();
  double *spacing = input->GetSpacing();  

  int pos[3];
  pos[0] = (int)
    (floor(((double)RPoint[0]-(double)origin[0]) / (double)spacing[0] +0.5));
  pos[1] = (int)
    (floor(((double)RPoint[1]-(double)origin[1]) / (double)spacing[1] +0.5));
  pos[2] = (int)
    (floor(((double)RPoint[2]-(double)origin[2]) / (double)spacing[2] +0.5));
  
  input->SetUpdateExtent(pos[0], pos[0], pos[1], pos[1], pos[2], pos[2]);
  input->Update();

  int numComps = input->GetNumberOfScalarComponents();

  double *vals = new double[numComps];
  
  double args[9];
  args[0] = RPoint[0];
  args[1] = RPoint[1];
  args[2] = RPoint[2];
  args[3] = numComps;
  args[4] = input->GetScalarType();

  int idx;
  for (idx = 0; idx < numComps;  idx++) 
    {
    args[5 + idx] = vals[idx] =
      input->GetScalarComponentAsDouble(pos[0], pos[1], pos[2], idx);
    }
  
  this->FormatProbeDisplay(str, RPoint, vals);
  
  if (win)
    {
    win->SetStatusText(str);
    }

  this->InvokeEvent(vtkKWEvent::ProbeInformationChangedEvent, args);
  
  delete [] vals;
}

//---------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::FormatProbeDisplay(
  char *display, double location[3], double *values)
{
  vtkKW2DRenderWidget *widget = this->Get2DRenderWidget();
  if (!widget)
    {
    return;
    }

  vtkImageData *input = widget->GetInput();
  if (!input)
    {
    return;
    }
  
  int num_comps = input->GetNumberOfScalarComponents();
  int scalar_type = input->GetScalarType();
  int double_type = (scalar_type == VTK_FLOAT || scalar_type == VTK_DOUBLE);
  const char *distance_units = 
    widget->GetDistanceUnits() ? widget->GetDistanceUnits() : "unknown units";

  int i;
  const char *pattern;

  // Long format

  sprintf(display, 
          "Location: (%.5g, %.5g, %.5g) (%s), Value:",
          location[0], location[1], location[2], distance_units);
  pattern = (double_type ? "%s %.8g (%s)" : "%s %.0f (%s)");
  for (i = 0; i < num_comps; i++)
    {
    const char *scalar_units = 
      widget->GetScalarUnits(i) ? widget->GetScalarUnits(i) : "unknown units";
    sprintf(display, pattern, display, values[i], scalar_units);
    }

  // Shorter format (no units)

  if (strlen(display) > PROBE_DISPLAY_LENGTH)
    {
    sprintf(display, 
            "Location: (%.5g, %.5g, %.5g), Value: ",
            location[0], location[1], location[2]);
    pattern = (double_type ? "%s %.8g" : "%s %.0f");
    for (i = 0; i < num_comps; i++)
      {
      sprintf(display, pattern, display, values[i]);
      }
    }
  
  // Even shorter

  vtkKWWindowBase *win = vtkKWWindowBase::SafeDownCast(
    widget->GetParentTopLevel());
  if (win)
    {
    int width = atoi(
      widget->Script("winfo width %s",win->GetStatusFrame()->GetWidgetName()));
    if ((width - 330) / (double)strlen(display) <
        (570.0 / PROBE_DISPLAY_LENGTH)) // Do not Ask me about this one
      {
      sprintf(display, 
              "Location: (%.3g, %.3g, %.3g), Value: ",
              location[0], location[1], location[2]);
      pattern = (double_type ? "%s %.3g" : "%s %.0f");
      for (i = 0; i < num_comps; i++)
        {
        sprintf(display, pattern, display, values[i]);
        }
      }
    }
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::OnMouseWheelForward()
{
  this->DecrementSlice();
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyle2DView::OnMouseWheelBackward()
{
  this->IncrementSlice();
}

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