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

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

#include "vtkObjectFactory.h"
#include "vtkCommand.h"
#include "vtkImageData.h"
#include "vtkContourFilter.h"
#include "vtkCutter.h"
#include "vtkPlane.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkActor.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkImageActor.h"
#include "vtkProperty.h"
#include "vtkMassProperties.h"
#include "vtkEvent.h"
#include "vtkSmartPointer.h"
#include "vtkTriangleFilter.h"
#include "vtkMassProperties.h"
#include "vtkFeatureEdges.h"
#include "vtkPolyDataConnectivityFilter.h"
#include "vtkRECISTCalculator.h"

#include "vtkKWProbeImageWidget.h"
#include "vtkKWImageWidget.h"
#include "vtkKWVolumeWidget.h"
#include "vtkKWEvent.h"

#include "vtkVVDataItemVolume.h"

//---------------------------------------------------------------------------
vtkCxxRevisionMacro(vtkVVDataItemVolumeContour, "$Revision: 1.18 $" );
vtkStandardNewMacro(vtkVVDataItemVolumeContour);

//---------------------------------------------------------------------------
vtkVVDataItemVolumeContour::vtkVVDataItemVolumeContour()
{
  this->ImageData      = NULL;
  this->PolyData       = NULL;
  this->DataItemVolume = NULL;
  this->ContourFilter = vtkContourFilter::New();
  this->ContourFilter->ComputeScalarsOff();
  this->Color[0] = this->Color[1] = this->Color[2] = 1.0;
  this->Opacity = 1.0;
  this->LineWidth = 2.0;
  this->IsoValue = 0.0;
  this->Visibility = 1;
  this->Internals = new vtkVVDataItemVolumeContourInternals;
  for (int i = 0; i < 3; i++)
    {
    this->Internals->SliceMapper[i]->SetInput( 
        this->Internals->SlicePlaneCutter[i]->GetOutput() );
    }

  // Filters to compute statitics on surfaces.
  this->Connectivity   = vtkPolyDataConnectivityFilter::New();
  this->FeatureEdges   = vtkFeatureEdges::New();
  this->MassProperties = vtkMassProperties::New();
  this->TriangleFilter = vtkTriangleFilter::New();
  this->Connectivity->SetInput( this->ContourFilter->GetOutput() );
  this->Connectivity->SetExtractionModeToAllRegions();
  this->FeatureEdges->SetInput(this->ContourFilter->GetOutput());
  this->FeatureEdges->NonManifoldEdgesOn();
  this->FeatureEdges->FeatureEdgesOff();
  this->FeatureEdges->ManifoldEdgesOff();
  this->FeatureEdges->BoundaryEdgesOn();
  this->TriangleFilter->SetInput(this->ContourFilter->GetOutput());
  this->MassProperties->SetInput(this->TriangleFilter->GetOutput());
  this->TriangleFilter->PassVertsOff();
  this->TriangleFilter->PassLinesOff();
  this->Description = NULL;
  this->RECISTCalculator = vtkRECISTCalculator::New();
  this->RECISTMeasure = 0;
  this->ComputeRECISTMeasure = 1;
}

//---------------------------------------------------------------------------
vtkVVDataItemVolumeContour::~vtkVVDataItemVolumeContour()
{
  this->Close();
  this->ContourFilter->Delete();
  this->Connectivity->Delete();
  this->FeatureEdges->Delete();
  this->MassProperties->Delete();
  this->TriangleFilter->Delete();
  delete this->Internals;
  this->SetDescription(NULL);
  this->RECISTCalculator->Delete();
}

//---------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::Close()
{
  // TODO Remove Actors
  this->SetDataItemVolume(NULL);
  this->SetImageData(NULL);
  this->SetPolyData(NULL);
}

//----------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::SetDataItemVolume(vtkVVDataItemVolume *arg)
{
  if (this->DataItemVolume == arg)
    {
    return;
    }

  if (this->DataItemVolume)
    {
    // remove actors from all the render widgets displaying this dataitem
    this->RemoveActors();

    // Remove added observers
    this->RemoveCallbackCommandObservers();
    }
    
  this->DataItemVolume = arg;

  if (this->DataItemVolume)
    {
    // Add actors to all the render widgets displaying this dataitem
    this->AddActors();

    // Observe the widgets for changes.
    this->AddCallbackCommandObservers();
    }

  this->Modified();
}

//----------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::SetImageData( vtkImageData *args )
{
  vtkSetObjectBodyMacro( ImageData, vtkImageData, args )
  this->ContourFilter->SetInput( this->ImageData );
  for (int i = 0; i < 3; i++)
    {
    this->Internals->SlicePlaneCutter[i]->SetInput( this->ContourFilter->GetOutput() );
    }
  this->Internals->VolumeMapper->SetInput( this->ContourFilter->GetOutput() );
}

//----------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::SetPolyData( vtkPolyData *args )
{
  vtkSetObjectBodyMacro( PolyData, vtkPolyData, args )
  this->TriangleFilter->SetInput(this->PolyData);
  this->MassProperties->SetInput(this->PolyData);
  this->Internals->VolumeMapper->SetInput( this->PolyData );
  this->Internals->ObliquePlaneCutter->SetInput( this->PolyData );
  this->Connectivity->SetInput( this->PolyData );
  this->FeatureEdges->SetInput(this->PolyData);
  for (int i = 0; i < 3; i++)
    {
    this->Internals->SlicePlaneCutter[i]->SetInput( this->PolyData );
    }
}

//----------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::Update()
{
  if (this->GetMTime() > this->BuildTime )
    {
    if (this->DataItemVolume)
      {
      this->ContourFilter->SetValue(0, this->IsoValue);
      if (this->ImageData)
        {
        this->ContourFilter->Update();
        }

      const int nRenderWidgets = this->DataItemVolume->GetNumberOfRenderWidgets();

      // Traverse through all the render widgets that display this data item.
      for (int i = 0; i < nRenderWidgets; i++)
        {
        vtkKWRenderWidget *rw = this->DataItemVolume->GetNthRenderWidget(i);
        vtkKWVolumeWidget *vw = vtkKWVolumeWidget::SafeDownCast(rw);
        vtkKWImageWidget *iw = vtkKWImageWidget::SafeDownCast(rw);
        vtkKWProbeImageWidget *pw = vtkKWProbeImageWidget::SafeDownCast(rw);

        // On the volume widget, show the surface.
        if (vw)
          {
          this->Internals->VolumeActor->GetProperty()->SetColor(this->Color);
          this->Internals->VolumeActor->GetProperty()->SetEdgeColor(this->Color);
          this->Internals->VolumeActor->GetProperty()->SetOpacity(this->Opacity);
          this->Internals->VolumeActor->SetVisibility(this->Visibility);
          }
        else if (pw)
          {
          /*
          // Copy the parameters of the oblique plane cut function onto the 
          // oblique contour cutter.
          vtkCutter *cutter = vtkCutter::SafeDownCast(pw->GetProbeInputAlgorithm());
          vtkPlane *plane = vtkPlane::SafeDownCast(cutter->GetCutFunction());
          this->Internals->ObliquePlane->SetNormal(plane->GetNormal());
          this->Internals->ObliquePlane->SetOrigin(plane->GetOrigin());
          //this->Internals->ObliquePlaneCutter->Update(); // FIXME

          this->Internals->ObliqueActor->GetProperty()->SetColor(this->Color);
          this->Internals->ObliqueActor->GetProperty()->SetOpacity(this->Opacity);
          this->Internals->ObliqueActor->GetProperty()->SetLineWidth(this->LineWidth);
          this->Internals->ObliqueActor->SetVisibility(this->Visibility);
          */
          }
        else if (iw)
          {
          const int orientation = iw->GetSliceOrientation();
          double normal[3] = {0, 0, 0}, bounds[6];
          normal[orientation] = 1.0;
          iw->GetImage()->GetBounds(bounds);
          this->Internals->SlicePlane[orientation]->SetOrigin(bounds[0], bounds[2], bounds[4]);
          this->Internals->SlicePlane[orientation]->SetNormal(normal);
          this->Internals->SlicePlaneCutter[orientation]->Update();

          this->Internals->SliceActor[orientation]->GetProperty()->SetColor(this->Color);
          this->Internals->SliceActor[orientation]->GetProperty()->SetEdgeColor(this->Color);
          this->Internals->SliceActor[orientation]->GetProperty()->SetLineWidth(this->LineWidth);
          this->Internals->SliceActor[orientation]->SetVisibility(this->Visibility);
          }
        else
          {
          continue;
          }
        }
      }
    }

  this->BuildTime.Modified();
}

//---------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::Render()
{
  this->Update();

  // Traverse through all the render widgets that display this data item and
  // invoke render on them
  if (this->DataItemVolume)
    {
    const int nRenderWidgets = this->DataItemVolume->GetNumberOfRenderWidgets();
    for (int i = 0; i < nRenderWidgets; i++)
      {
      vtkKWRenderWidget *rw = this->DataItemVolume->GetNthRenderWidget(i);
      rw->Render();
      }
    }
}

//---------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::AddActors()
{
  const int nRenderWidgets = this->DataItemVolume->GetNumberOfRenderWidgets();

  // Traverse through all the render widgets that display this data item.
  for (int i = 0; i < nRenderWidgets; i++)
    {
    vtkKWRenderWidget *rw = this->DataItemVolume->GetNthRenderWidget(i);
    vtkKWVolumeWidget *vw = vtkKWVolumeWidget::SafeDownCast(rw);
    vtkKWImageWidget *iw = vtkKWImageWidget::SafeDownCast(rw);
    vtkKWProbeImageWidget *pw = vtkKWProbeImageWidget::SafeDownCast(rw);
    vtkRenderer *ren = rw->GetRenderer();

    if (vw)
      {
      ren->AddViewProp(this->Internals->VolumeActor);
      }
    else if (pw)
      {
      // FIXME Put the ObliqueActor once it gets done.
      //ren->AddViewProp(this->Internals->ObliqueActor);
      }
    else if (iw)
      {
      ren->AddViewProp(this->Internals->SliceActor[iw->GetSliceOrientation()]);
      }    
    }
}

//---------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::RemoveActors()
{
  const int nRenderWidgets = this->DataItemVolume->GetNumberOfRenderWidgets();

  // Traverse through all the render widgets that display this data item.
  for (int i = 0; i < nRenderWidgets; i++)
    {
    vtkKWRenderWidget *rw = this->DataItemVolume->GetNthRenderWidget(i);
    vtkKWVolumeWidget *vw = vtkKWVolumeWidget::SafeDownCast(rw);
    vtkKWImageWidget *iw = vtkKWImageWidget::SafeDownCast(rw);
    vtkKWProbeImageWidget *pw = vtkKWProbeImageWidget::SafeDownCast(rw);
    vtkRenderer *ren = rw->GetRenderer();

    if (vw)
      {
      ren->RemoveViewProp(this->Internals->VolumeActor);
      }
    else if (pw)
      {
      ren->RemoveViewProp(this->Internals->ObliqueActor);
      }
    else if (iw)
      {
      ren->RemoveViewProp(this->Internals->SliceActor[iw->GetSliceOrientation()]);
      }    
    }
}

//---------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::AddCallbackCommandObservers()
{
  // Add command observers. When these events occur, we'll need to recontour
  // the lesion on the new view.
  const int nb_render_widgets = this->DataItemVolume->GetNumberOfRenderWidgets();
  for (int i = 0; i < nb_render_widgets; i++)
    {
    vtkKWRenderWidget *renderWidget = this->DataItemVolume->GetNthRenderWidget(i);
    vtkKWVolumeWidget *vw = vtkKWVolumeWidget::SafeDownCast(renderWidget);
    vtkKWImageWidget *iw = vtkKWImageWidget::SafeDownCast(renderWidget);
    vtkKWProbeImageWidget *pw = vtkKWProbeImageWidget::SafeDownCast(renderWidget);
    if (vw)
      {
      }
    else if (pw)
      {
      this->AddCallbackCommandObserver(pw, vtkKWEvent::ProbeImageTranslatePlaneEvent);
      this->AddCallbackCommandObserver(pw, vtkKWEvent::ProbeImageRollPlaneEvent);
      this->AddCallbackCommandObserver(pw, vtkKWEvent::ProbeImageTiltPlaneEvent);
      }
    else if (iw)
      {
      this->AddCallbackCommandObserver(iw, vtkKW2DRenderWidget::UpdateDisplayExtentEvent);
      }
    }
}

//---------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::RemoveCallbackCommandObservers()
{
  // Remove command observers. When these events occur, we'll need to recontour
  // the lesion on the new view.
  const int nb_render_widgets = this->DataItemVolume->GetNumberOfRenderWidgets();
  for (int i = 0; i < nb_render_widgets; i++)
    {
    vtkKWRenderWidget *renderWidget = this->DataItemVolume->GetNthRenderWidget(i);
    vtkKWVolumeWidget *vw = vtkKWVolumeWidget::SafeDownCast(renderWidget);
    vtkKWImageWidget *iw = vtkKWImageWidget::SafeDownCast(renderWidget);
    vtkKWProbeImageWidget *pw = vtkKWProbeImageWidget::SafeDownCast(renderWidget);
    if (vw)
      {
      }
    else if (pw)
      {
      this->RemoveCallbackCommandObserver(pw, vtkKWEvent::ProbeImageTranslatePlaneEvent);
      this->RemoveCallbackCommandObserver(pw, vtkKWEvent::ProbeImageRollPlaneEvent);
      this->RemoveCallbackCommandObserver(pw, vtkKWEvent::ProbeImageTiltPlaneEvent);
      }
    else if (iw)
      {
      this->RemoveCallbackCommandObserver(iw, vtkKW2DRenderWidget::UpdateDisplayExtentEvent);
      }
    }
}

//----------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::ProcessCallbackCommandEvents(
              vtkObject *caller, unsigned long e, void *calldata)
{
  if (vtkKWRenderWidget *renderWidget = vtkKWRenderWidget::SafeDownCast(caller))
    {
    
    vtkKWVolumeWidget *vw = vtkKWVolumeWidget::SafeDownCast(caller);
    vtkKWImageWidget *iw = vtkKWImageWidget::SafeDownCast(caller);
    vtkKWProbeImageWidget *pw = vtkKWProbeImageWidget::SafeDownCast(caller);

    // Get the selection frame corresponding to the render widget.
    if (this->DataItemVolume)
      {
      
      // For the image widget, we will have to update the contours when 
      // the slice changes
      if (iw && e == vtkKW2DRenderWidget::UpdateDisplayExtentEvent)
        {
        const int sliceOrientation = iw->GetSliceOrientation();
        double bounds[6];
        iw->GetImage()->GetBounds(bounds);
        this->Internals->SlicePlane[sliceOrientation]
            ->SetOrigin(bounds[0], bounds[2], bounds[4]);
        // iw->Render();
        }

      // For the oblique widget, we will have to update the contours when 
      // the plane changes
      if (pw && (e == vtkKWEvent::ProbeImageTranslatePlaneEvent ||
                 e == vtkKWEvent::ProbeImageTiltPlaneEvent ||
                 e == vtkKWEvent::ProbeImageRollPlaneEvent))
        {
        vtkCutter *cutter = vtkCutter::SafeDownCast(pw->GetProbeInputAlgorithm());
        vtkPlane *plane = vtkPlane::SafeDownCast(cutter->GetCutFunction());
        this->Internals->ObliquePlane->SetNormal(plane->GetNormal());
        this->Internals->ObliquePlane->SetOrigin(plane->GetOrigin());
        // pw->Render();
        }
      }
    }

  this->Superclass::ProcessCallbackCommandEvents( caller, e, calldata );
}

//----------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::ComputeStatistics()
{
  this->Connectivity->Update();
  this->FeatureEdges->Update();

  int nb_of_lines = this->FeatureEdges->GetOutput()->GetNumberOfLines();
  this->SimpleSurface = nb_of_lines > 0 ? 0 : 1;
  
  this->NumberOfSurfaces = this->Connectivity->GetNumberOfExtractedRegions();

  // Compute the volume etc..
  if (this->SimpleSurface)
    {
    this->MassProperties->Update();
    this->Volume = this->MassProperties->GetVolume();
    this->SurfaceArea = this->MassProperties->GetSurfaceArea();

    if (this->ComputeRECISTMeasure)
      {
      this->RECISTCalculator->SetInput( this->MassProperties->GetInput() );
      this->RECISTCalculator->SetImageData( 
          this->DataItemVolume->GetImageData() );
      std::cout << "Computing the RECIST measure.." << std::endl;
      this->RECISTMeasure = this->RECISTCalculator->GetRECISTMeasure();
      std::cout << "RECIST measure = " << this->RECISTMeasure 
                << " mm" << std::endl;
      }
    }
  else
    {
    this->Volume = this->SurfaceArea = this->RECISTMeasure = 0;
    }
}

//----------------------------------------------------------------------------
int vtkVVDataItemVolumeContour::IsSimpleSurface()
{
  this->ComputeStatistics();
  return this->SimpleSurface;
}

//----------------------------------------------------------------------------
double vtkVVDataItemVolumeContour::GetVolume()
{
  this->ComputeStatistics();
  return this->Volume;
}

//----------------------------------------------------------------------------
int vtkVVDataItemVolumeContour::GetNumberOfSurfaces()
{
  this->ComputeStatistics();
  return this->NumberOfSurfaces;
}

//----------------------------------------------------------------------------
double vtkVVDataItemVolumeContour::GetSurfaceArea()
{
  this->ComputeStatistics();
  return this->SurfaceArea;
}

//----------------------------------------------------------------------------
vtkPolyData *vtkVVDataItemVolumeContour::GetPolyData()
{
  if (this->PolyData)
    {
    return this->PolyData;
    }
  else if (this->ImageData)
    {
    this->ContourFilter->Update();
    return this->ContourFilter->GetOutput();
    }
  return NULL;
}

//----------------------------------------------------------------------------
void vtkVVDataItemVolumeContour::GetContourBounds( double bounds[6] )
{
  if (vtkPolyData *pd = this->GetPolyData())
    {
    pd->GetBounds(bounds);
    }
  else
    {
    bounds[0] = bounds[1] = bounds[2] =
    bounds[3] = bounds[4] = bounds[5] = 0;
    }
}

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

