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

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

#include "vtkAppendPolyData.h"
#include "vtkCellData.h"
#include "vtkCompositeDataIterator.h"
#include "vtkCompositeDataSet.h"
#include "vtkContourValues.h"
#include "vtkCutter.h"
#include "vtkDataObject.h"
#include "vtkDataSet.h"
#include "vtkFloatArray.h"
#include "vtkIdTypeArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPProbeFilter.h"
#include "vtkPlane.h"
#include "vtkPointData.h"
#include "vtkPointSource.h"
#include "vtkUnsignedIntArray.h"

#include <math.h>

vtkStandardNewMacro(vtkThreeSliceFilter);

//----------------------------------------------------------------------------
vtkThreeSliceFilter::vtkThreeSliceFilter()
{
  // Setup output ports [ Merge, SliceX, SliceY, SliceZ]
  this->SetNumberOfOutputPorts(4);

  // Setup Merge filter
  this->CombinedFilteredInput = vtkAppendPolyData::New();

  for(int i = 0; i < 3; ++i)
    {
    // Allocate internal vars
    this->Slices[i] = vtkCutter::New();
    this->Planes[i] = vtkPlane::New();
    this->Slices[i]->SetCutFunction(this->Planes[i]);

    // Bind pipeline
    this->CombinedFilteredInput->AddInputConnection(this->Slices[i]->GetOutputPort());
    }
  this->Probe = NULL;
  this->PointToProbe = vtkPointSource::New();
  this->PointToProbe->SetNumberOfPoints(1);
  this->PointToProbe->SetRadius(0);
  this->SetToDefaultSettings();
}

//----------------------------------------------------------------------------
vtkThreeSliceFilter::~vtkThreeSliceFilter()
{
  for(int i=0; i < 3; ++i)
    {
    this->Slices[i]->Delete();
    this->Slices[i] = NULL;
    this->Planes[i]->Delete();
    this->Planes[i] = NULL;
    }

  this->CombinedFilteredInput->Delete();
  this->CombinedFilteredInput = NULL;

  if(this->PointToProbe)
    {
    this->PointToProbe->Delete();
    this->PointToProbe = NULL;
    }
  if(this->Probe)
    {
    this->Probe->ReleaseDataFlagOn();
    this->Probe->Delete();
    this->Probe = NULL;
    }
}

//----------------------------------------------------------------------------
void vtkThreeSliceFilter::SetToDefaultSettings()
{
  double origin[3] = {0,0,0};
  for(int i = 0; i < 3; ++i)
    {
    // Setup normal
    double normal[3] = {0,0,0};
    normal[i] = 1.0;

    // Reset planes origin/normal
    this->Planes[i]->SetOrigin(origin);
    this->Planes[i]->SetNormal(normal);

    // Reset number of slice
    this->Slices[i]->SetNumberOfContours(0);
    }
  this->Modified();
}

//----------------------------------------------------------------------------
unsigned long vtkThreeSliceFilter::GetMTime()
{
  unsigned long mTime=this->Superclass::GetMTime();
  unsigned long internalMTime = 0;

  // Test Append filter
  internalMTime = this->CombinedFilteredInput->GetMTime();
  mTime = ( internalMTime > mTime ? internalMTime : mTime );

  // Test slices
  for(int i = 0; i < 3; ++i)
    {
    internalMTime = this->Slices[i]->GetMTime();
    mTime = ( internalMTime > mTime ? internalMTime : mTime );
    }

  return mTime;
}
//----------------------------------------------------------------------------
void vtkThreeSliceFilter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  for(int i=0; i < 3; ++i)
    {
    os << indent << " - Plane[" << i << "]: normal("
       << this->Planes[i]->GetNormal()[0] << ", "
       << this->Planes[i]->GetNormal()[1] << ", "
       << this->Planes[i]->GetNormal()[2] << ") - origin("
       << this->Planes[i]->GetOrigin()[0] << ", "
       << this->Planes[i]->GetOrigin()[1] << ", "
       << this->Planes[i]->GetOrigin()[2] << ")\n";
    }
}

//----------------------------------------------------------------------------
void vtkThreeSliceFilter::SetCutNormal(int cutIndex, double normal[3])
{
  this->Planes[cutIndex]->SetNormal(normal);
}

//----------------------------------------------------------------------------
void vtkThreeSliceFilter::SetCutOrigin(int cutIndex, double origin[3])
{
  this->Planes[cutIndex]->SetOrigin(origin);
}

//----------------------------------------------------------------------------
void vtkThreeSliceFilter::SetCutOrigins(double origin[3])
{
  this->Planes[0]->SetOrigin(origin);
  this->Planes[1]->SetOrigin(origin);
  this->Planes[2]->SetOrigin(origin);
  this->PointToProbe->SetCenter(origin);
}

//----------------------------------------------------------------------------
void vtkThreeSliceFilter::SetCutValue(int cutIndex, int index, double value)
{
  vtkCutter* slice = this->Slices[cutIndex];
  if(slice->GetValue(index) != value)
    {
    slice->SetValue(index, value);
    this->Modified();
    }
}

//----------------------------------------------------------------------------
void vtkThreeSliceFilter::SetNumberOfSlice(int cutIndex, int size)
{
  vtkCutter* slice = this->Slices[cutIndex];
  if(size != slice->GetNumberOfContours())
    {
    slice->SetNumberOfContours(size);
    this->Modified();
    }
}
//-----------------------------------------------------------------------
int vtkThreeSliceFilter::FillInputPortInformation(int, vtkInformation *info)
{
  info->Set( vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet" );
  return 1;
}
//-----------------------------------------------------------------------
int vtkThreeSliceFilter::RequestData( vtkInformation *,
                                           vtkInformationVector **inputVector,
                                           vtkInformationVector *outputVector)
{
  // get input
  vtkDataObject *input =
      inputVector[0]->GetInformationObject(0)->Get(
        vtkDataObject::DATA_OBJECT());
  vtkCompositeDataSet* inputComposite = vtkCompositeDataSet::SafeDownCast(input);
  vtkDataSet* inputDataset = vtkDataSet::SafeDownCast(input);

  // get outputs
  vtkPolyData *outputs[4];
  for(int i=0; i < 4; ++i)
    {
    outputs[i] =
        vtkPolyData::SafeDownCast(
          outputVector->GetInformationObject(i)->Get(
            vtkDataObject::DATA_OBJECT()));
    }

  if(inputComposite)
    {
    //FIXME:UDA
    abort();
    // Composite DS
    vtkCompositeDataSet* innerOutputs[4];
    for(int i=0;i<4;++i)
      {
      innerOutputs[i] = inputComposite->NewInstance();
      innerOutputs[i]->CopyStructure(inputComposite);
      }
    this->Process(inputComposite, innerOutputs);

    // Append polydata
    this->Merge(innerOutputs, outputs);

    // Free internal structure
    for(int i=0;i<4;++i)
      {
      innerOutputs[i]->Delete();
      }
    }
  else if(inputDataset)
    {
    this->Process(inputDataset, outputs, VTK_UNSIGNED_INT_MAX);
    }


  // Probe input if needed
  if(this->Probe)
    {
    this->PointToProbe->Update();
    this->Probe->SetInputData(1, input);
    this->Probe->SetInputData(0, this->PointToProbe->GetOutput());
    this->Probe->Update();
    }

  return 1;
}
//----------------------------------------------------------------------------
void vtkThreeSliceFilter::Process(vtkCompositeDataSet* input, vtkCompositeDataSet* outputs[4])
{
  // Init outputs
  for(int i=0;i<4;++i)
    {
    outputs[i]->CopyStructure(input);
    }

  // Manage the composite dataset
  vtkCompositeDataIterator* iter = input->NewIterator();
  iter->SetSkipEmptyNodes(1);
  int idx = 0;
  for ( iter->InitTraversal(); ! iter->IsDoneWithTraversal(); iter->GoToNextItem(), ++idx )
    {
    vtkDataObject* obj = input->GetDataSet( iter );
    vtkCompositeDataSet* inputComposite = vtkCompositeDataSet::SafeDownCast(obj);
    vtkDataSet* inputDataset = vtkDataSet::SafeDownCast(obj);

    if(inputComposite)
      {
      vtkCompositeDataSet* innerOutputs[4];
      for(int i=0;i<4;++i)
        {
        innerOutputs[i] = inputComposite->NewInstance();
        outputs[i]->SetDataSet(iter, innerOutputs[i]);
        innerOutputs[i]->FastDelete();
        }
      this->Process(inputComposite, innerOutputs);
      }
    else if(inputDataset)
      {
      vtkPolyData* innerOutputs[4];
      for(int i=0;i<4;++i)
        {
        vtkNew<vtkPolyData> ds;
        outputs[i]->SetDataSet(iter, ds.GetPointer());
        innerOutputs[i] = ds.GetPointer();
        }
      this->Process(inputDataset, innerOutputs, static_cast<int>(iter->GetCurrentFlatIndex()));
      }
    }
  iter->Delete();
}
//----------------------------------------------------------------------------
void vtkThreeSliceFilter::Process(vtkDataSet* input, vtkPolyData* outputs[4], unsigned int compositeIndex)
{
  // Process dataset
  // Add CellIds to allow cell selection to work
  vtkIdType nbCells = input->GetNumberOfCells();
  vtkNew<vtkIdTypeArray> originalCellIds;
  originalCellIds->SetName("vtkSliceOriginalCellIds");
  originalCellIds->SetNumberOfComponents(1);
  originalCellIds->SetNumberOfTuples(nbCells);
  input->GetCellData()->AddArray(originalCellIds.GetPointer());

  // Fill the array with proper id values
  for(vtkIdType id = 0; id < nbCells; ++id)
    {
    originalCellIds->SetValue(id, id);
    }

  // Add composite index information if we have any
  if(compositeIndex != VTK_UNSIGNED_INT_MAX)
    {
    vtkNew<vtkUnsignedIntArray> compositeIndexArray;
    compositeIndexArray->SetName("vtkSliceCompositeIndex");
    compositeIndexArray->SetNumberOfComponents(1);
    compositeIndexArray->SetNumberOfTuples(nbCells);
    compositeIndexArray->FillComponent(0, compositeIndex);
    input->GetCellData()->AddArray(compositeIndexArray.GetPointer());
    }

  // Setup internal pipeline
  this->Slices[0]->SetInputData(input);
  this->Slices[1]->SetInputData(input);
  this->Slices[2]->SetInputData(input);

  // Update the internal pipeline
  this->CombinedFilteredInput->Update();

  // Copy generated output to filter output
  outputs[0]->ShallowCopy(this->CombinedFilteredInput->GetOutput());
  outputs[1]->ShallowCopy(this->Slices[0]->GetOutput());
  outputs[2]->ShallowCopy(this->Slices[1]->GetOutput());
  outputs[3]->ShallowCopy(this->Slices[2]->GetOutput());

  // Add meta-data for sliced data
  vtkNew<vtkFloatArray> sliceAtOrigin;
  sliceAtOrigin->SetName("SliceAt");
  sliceAtOrigin->SetNumberOfComponents(3);
  sliceAtOrigin->SetNumberOfTuples(1);
  sliceAtOrigin->SetTuple(0, this->PointToProbe->GetCenter());
  for(int i=0; i < 3; ++i)
    {
    vtkNew<vtkIdTypeArray> sliceIndex;
    sliceIndex->SetName("SliceAlongAxis");
    sliceIndex->SetNumberOfComponents(1);
    sliceIndex->SetNumberOfTuples(1);
    sliceIndex->SetTuple1(0, i);
    outputs[i+1]->GetFieldData()->AddArray(sliceAtOrigin.GetPointer());
    outputs[i+1]->GetFieldData()->AddArray(sliceIndex.GetPointer());
    }
}
//----------------------------------------------------------------------------
void vtkThreeSliceFilter::Merge(vtkCompositeDataSet* compositeOutput[4],
                                vtkPolyData* polydataOutput[4])
{
  for(int i=0; i < 4; ++i)
    {
    vtkNew<vtkAppendPolyData> appender;
    this->Append(compositeOutput[i], appender.GetPointer());
    appender->Update();
    polydataOutput[i]->ShallowCopy(appender->GetOutput());
    }
}

//----------------------------------------------------------------------------
void vtkThreeSliceFilter::Append(vtkCompositeDataSet* composite,
                                 vtkAppendPolyData* appender)
{
  vtkCompositeDataIterator* iter = composite->NewIterator();
  iter->SetSkipEmptyNodes(1);
  for ( iter->InitTraversal(); ! iter->IsDoneWithTraversal(); iter->GoToNextItem() )
    {
    vtkDataObject* obj = composite->GetDataSet( iter );
    vtkCompositeDataSet* compositeDS = vtkCompositeDataSet::SafeDownCast(obj);
    vtkPolyData* polyDS = vtkPolyData::SafeDownCast(obj);
    if(compositeDS)
      {
      this->Append(compositeDS, appender);
      }
    else if(polyDS)
      {
      this->Append(polyDS, appender);
      }
    }
  iter->Delete();
}
//----------------------------------------------------------------------------
void vtkThreeSliceFilter::Append(vtkPolyData* polydata,
                                vtkAppendPolyData* appender)
{
  appender->AddInputData(polydata);
}
//----------------------------------------------------------------------------
void vtkThreeSliceFilter::EnableProbe(int enable)
{
  if(enable != 0 && this->Probe == NULL)
    {
    this->Probe = vtkPProbeFilter::New();
    }
  else if(this->Probe != NULL)
    {
    this->Probe->Delete();
    this->Probe = NULL;
    }
}
//----------------------------------------------------------------------------
bool vtkThreeSliceFilter::GetProbedPointData(const char* arrayName, double &value)
{
  if(this->Probe && this->Probe->GetOutput() && this->Probe->GetOutput()->GetPointData())
    {
    vtkDataArray* array = this->Probe->GetOutput()->GetPointData()->GetArray(arrayName);
    vtkDataArray* mask = this->Probe->GetOutput()->GetPointData()->GetArray("vtkValidPointMask");
    if(array && array->GetNumberOfComponents() == 1 && array->GetNumberOfTuples() > 0)
      {
      bool valid = false;
      value = array->GetVariantValue(0).ToDouble(&valid);
      if(valid && mask)
        {
        return (array->GetVariantValue(0).ToUnsignedChar() != 0);
        }
      return valid;
      }
    }
  return false;
}
