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

  Program:   Visualization Toolkit
  Module:    vtkSliceAlongPolyPlane.h

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

#include "vtkAppendArcLength.h"
#include "vtkCellArray.h"
#include "vtkCleanPolyData.h"
#include "vtkCompositeDataIterator.h"
#include "vtkCutter.h"
#include "vtkDataSetSurfaceFilter.h"
#include "vtkFloatArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkLine.h"
#include "vtkMPIMoveData.h"
#include "vtkMultiBlockDataSet.h"
#include "vtkMultiProcessController.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyLine.h"
#include "vtkPolyPlane.h"
#include "vtkProbeFilter.h"
#include "vtkSmartPointer.h"
#include "vtkThreshold.h"
#include "vtkTransform.h"
#include "vtkTransformPolyDataFilter.h"
#include "vtkType.h"

#include <list>
#include <algorithm>
#include <iostream>

vtkStandardNewMacro(vtkSliceAlongPolyPlane)

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

//----------------------------------------------------------------------------
vtkSliceAlongPolyPlane::vtkSliceAlongPolyPlane()
{
  this->SetNumberOfInputPorts(2);
  this->Tolerance = 10;
}

//----------------------------------------------------------------------------
vtkSliceAlongPolyPlane::~vtkSliceAlongPolyPlane()
{
}

//----------------------------------------------------------------------------
int vtkSliceAlongPolyPlane::RequestDataObject(
  vtkInformation*,
  vtkInformationVector** inputVector ,
  vtkInformationVector* outputVector)
{
  // If input is vtkCompositeDataSet, output will be a vtkMultiBlockDataSet
  // otherwise it will be a vtkPolyData.
  vtkDataObject* inputDO = vtkDataObject::GetData(inputVector[0], 0);
  if (vtkCompositeDataSet::SafeDownCast(inputDO))
    {
    if (vtkMultiBlockDataSet::GetData(outputVector, 0) == NULL)
      {
      vtkNew<vtkMultiBlockDataSet> output;
      outputVector->GetInformationObject(0)->Set(
        vtkDataObject::DATA_OBJECT(), output.GetPointer());
      }
    return 1;
    }
  else if (vtkDataSet::GetData(inputVector[0], 0))
    {
    if (vtkPolyData::GetData(outputVector, 0) == NULL)
      {
      vtkNew<vtkPolyData> output;
      outputVector->GetInformationObject(0)->Set(
        vtkDataObject::DATA_OBJECT(), output.GetPointer());
      }
    return 1;
    }
  return 0;
}

//----------------------------------------------------------------------------
int vtkSliceAlongPolyPlane::FillInputPortInformation(int port, vtkInformation *info)
{
  if (port == 0)
    {
    info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet");
    info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkCompositeDataSet");
    return 1;
    }
  else if (port == 1)
    {
    info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData");
    return 1;
    }
  return 0;
}

//----------------------------------------------------------------------------
// A struct that holds the id and direction of a polyline to be appended
typedef struct {
  vtkIdType id;
  bool forward;
} linePiece;

//----------------------------------------------------------------------------
static bool operator==(const linePiece &a, const linePiece &b)
{
  return a.id == b.id;
}

//----------------------------------------------------------------------------
static void appendLinesIntoOnePolyLine(vtkPolyData* input, vtkPolyData *output)
{
  if (input->GetNumberOfPoints() == 0 || input->GetNumberOfCells() == 0)
    {
    output->ShallowCopy(input);
    return;
    }
  output->Initialize();
  output->Allocate();

  // copy points to the output
  output->GetPointData()->ShallowCopy(input->GetPointData());
  vtkSmartPointer< vtkPoints > points =
      vtkSmartPointer< vtkPoints >::New();
  points->ShallowCopy(input->GetPoints());
  output->SetPoints(points);
  vtkIdType numCells = input->GetNumberOfCells();
  // make sure the first cell is a line or polyline
  vtkCell* first = input->GetCell(0);
  vtkLine* firstLine = vtkLine::SafeDownCast(first);
  vtkPolyLine* firstPolyLine = vtkPolyLine::SafeDownCast(first);
  if (!firstLine && !firstPolyLine)
    {
    // if not it is an error
    return;
    }
  linePiece firstPiece = {0,true};
  vtkIdType totalPointsInLine = first->GetNumberOfPoints();

  std::list< linePiece > pieceList;
  pieceList.push_back(firstPiece);
  bool foundANewPiece = true;
  while (foundANewPiece)
    {
    foundANewPiece = false;
    // look for pieces that end at the start of the first segment
    {
    vtkCell* frontCell = input->GetCell(pieceList.front().id);
    vtkIdType frontPointNumberInCell =
        pieceList.front().forward ? 0 : frontCell->GetNumberOfPoints() - 1;
    vtkIdType firstPoint = frontCell->GetPointId(frontPointNumberInCell);
    for (int i = 0 ; i < numCells; ++i)
      {
      vtkCell* c = input->GetCell(i);
      if (!vtkPolyLine::SafeDownCast(c) && !vtkLine::SafeDownCast(c))
        {
        continue;
        }
      if (c->GetPointId(0) == firstPoint || c->GetPointId(c->GetNumberOfPoints() - 1) == firstPoint)
        {
        linePiece l = {i, c->GetPointId(0) != firstPoint};
        if (std::find(pieceList.begin(),pieceList.end(),l) == pieceList.end())
          {
          pieceList.push_front(l);
          foundANewPiece = true;
          totalPointsInLine += c->GetNumberOfPoints() - 1;
          break;
          }
        }
      }
    }
    // look for pieces that start at the end of the last segment
    {
    vtkCell* backCell = input->GetCell(pieceList.back().id);
    vtkIdType backPointNumberInCell =
        pieceList.back().forward ? backCell->GetNumberOfPoints() - 1 : 0;
    vtkIdType lastPoint = backCell->GetPointId(backPointNumberInCell);
    for (int i = 0; i < numCells; ++i)
      {
      vtkCell* c = input->GetCell(i);
      if (!vtkPolyLine::SafeDownCast(c) && !vtkLine::SafeDownCast(c))
        {
        continue;
        }
      if (c->GetPointId(0) == lastPoint || c->GetPointId(c->GetNumberOfPoints() - 1 == lastPoint))
        {
        linePiece l = {i, c->GetPointId(0) == lastPoint};
        if (std::find(pieceList.begin(),pieceList.end(),l) == pieceList.end())
          {
          pieceList.push_back(l);
          foundANewPiece = true;
          totalPointsInLine += c->GetNumberOfPoints() - 1;
          break;
          }
        }
      }
    }
    }
  vtkSmartPointer< vtkIdList > idList = vtkSmartPointer< vtkIdList >::New();
  idList->SetNumberOfIds(totalPointsInLine);
  vtkIdType current = 0;
  // Append the lines into a polyline
  for (std::list<linePiece>::iterator itr = pieceList.begin(); itr != pieceList.end();++itr)
    {
    vtkCell* cell = input->GetCell((*itr).id);
    vtkIdType start = (*itr).forward ? 0 : cell->GetNumberOfPoints() - 1;
    vtkIdType end = (*itr).forward ? cell->GetNumberOfPoints() : -1;
    vtkIdType increment = (*itr).forward ? 1 : -1;
    // except for the first time, we already have the first point,
    // it was the last point in the previous cell
    if (current != 0)
      {
      start += increment;
      }
    for (vtkIdType i = start; i != end; i += increment)
      {
      vtkIdType id = cell->GetPointId(i);
      assert(id < input->GetNumberOfPoints());
      idList->SetId(current,id);
      ++current;
      }
    }
  assert(current == totalPointsInLine);
  output->InsertNextCell(VTK_POLY_LINE,idList);
}

//----------------------------------------------------------------------------
void vtkSliceAlongPolyPlane::CleanPolyLine(
  vtkPolyData* input, vtkPolyData* output)
{
  output->ShallowCopy(input);

  // remove other cell types that we don't care about.
  output->SetVerts(NULL);
  output->SetPolys(NULL);
  output->SetVerts(NULL);

  vtkMultiProcessController* controller = vtkMultiProcessController::GetGlobalController();
  if (controller && controller->GetNumberOfProcesses() > 1)
    {
    // communicate this dataset among all processes. This will ensure that the
    // polyline is cloned and communicated to all ranks.
    vtkNew<vtkMPIMoveData> moveData;
    moveData->SetController(controller);
    moveData->SetServerToDataServer();
    moveData->SetMoveModeToClone();
    moveData->SetOutputDataType(VTK_POLY_DATA);
    moveData->SetInputData(output);
    moveData->Update();
    output->ShallowCopy(moveData->GetOutputDataObject(0));
    }

  // this method conditions the input polydata to extract a single polyline from
  // it. It also handles parallel use-cases.
  vtkNew< vtkCleanPolyData > cleanFilter;
  cleanFilter->SetInputData(output);
  cleanFilter->Update();

  appendLinesIntoOnePolyLine(cleanFilter->GetOutput(), output);
}

//----------------------------------------------------------------------------
int vtkSliceAlongPolyPlane::RequestData(vtkInformation* vtkNotUsed(request),
                               vtkInformationVector** inputVector,
                               vtkInformationVector* outputVector)
{

  vtkNew<vtkPolyData> polyLinePD;
  this->CleanPolyLine(vtkPolyData::GetData(inputVector[1], 0), polyLinePD.GetPointer());

  if (vtkPolyLine::SafeDownCast(polyLinePD->GetCell(0)) == NULL)
    {
    vtkErrorMacro(<< " First cell in input polydata is not a vtkPolyLine.");
    return 0;
    }

  vtkDataObject* inputDO = vtkDataObject::GetData(inputVector[0], 0);
  if (vtkCompositeDataSet* cd = vtkCompositeDataSet::SafeDownCast(inputDO))
    {
    vtkCompositeDataSet* output = vtkCompositeDataSet::GetData(outputVector, 0);
    assert(output);
    output->CopyStructure(cd);

    vtkSmartPointer<vtkCompositeDataIterator> iter;
    iter.TakeReference(cd->NewIterator());
    for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
      {
      if (vtkDataSet* ds = vtkDataSet::SafeDownCast(iter->GetCurrentDataObject()))
        {
        vtkNew<vtkPolyData> pd;
        if (!this->Execute(ds, polyLinePD.GetPointer(), pd.GetPointer()))
          {
          return 0;
          }
        output->SetDataSet(iter, pd.GetPointer());
        }
      }
    return 1;
    }
  else if (vtkDataSet* ds = vtkDataSet::SafeDownCast(inputDO))
    {
    vtkPolyData* output = vtkPolyData::GetData(outputVector, 0);
    assert(output);
    return this->Execute(ds, polyLinePD.GetPointer(), output)? 1 : 0;
    }

  return 0;
}

//----------------------------------------------------------------------------
bool vtkSliceAlongPolyPlane::Execute(
  vtkDataSet* inputDataset, vtkPolyData* lineDataSet, vtkPolyData* output)
{
  assert(inputDataset && lineDataSet && output);

  vtkPolyLine* line = vtkPolyLine::SafeDownCast(lineDataSet->GetCell(0));
  assert(line);

  vtkNew< vtkPolyPlane > planes;
  planes->SetPolyLine(line);

  vtkNew< vtkCutter > cutter;
  cutter->SetCutFunction(planes.GetPointer());
  cutter->SetInputData(inputDataset);
  cutter->Update();

  vtkDataSet* cutterOutput = vtkDataSet::SafeDownCast(cutter->GetOutputDataObject(0));
  if (cutterOutput == NULL || cutterOutput->GetNumberOfPoints() == 0)
    {
    // shortcut if the cut produces an empty dataset.
    return 1;
    }

  vtkNew< vtkTransformPolyDataFilter > transformOutputFilter;
  vtkSmartPointer< vtkTransform > transform = vtkSmartPointer< vtkTransform >::New();
  transform->Identity();
  transform->Scale(1,1,0);
  transformOutputFilter->SetTransform(transform);
  transformOutputFilter->SetInputData(cutterOutput);
  transformOutputFilter->Update();

  vtkNew< vtkAppendArcLength > arcLengthFilter;
  arcLengthFilter->SetInputData(lineDataSet);
  arcLengthFilter->Update();

  vtkNew< vtkTransformPolyDataFilter > transformLineFilter;
  transformLineFilter->SetTransform(transform);
  transformLineFilter->SetInputConnection(arcLengthFilter->GetOutputPort());
  transformLineFilter->Update();

  vtkNew< vtkProbeFilter > probe;
  probe->SetInputConnection(transformOutputFilter->GetOutputPort());
  probe->SetSourceConnection(transformLineFilter->GetOutputPort());
  probe->SetPassPointArrays(1);
  probe->SetPassCellArrays(1);
  probe->SetValidPointMaskArrayName("vtkValidPointMaskArrayName");
  probe->ComputeToleranceOff();
  probe->SetTolerance(this->Tolerance);
  probe->Update();

  vtkNew< vtkPolyData > combinationPolyData;
  combinationPolyData->ShallowCopy(cutter->GetOutput());
  combinationPolyData->GetPointData()->ShallowCopy(
        probe->GetOutput()->GetPointData());

  vtkNew< vtkThreshold > threshold;
  threshold->SetInputData(combinationPolyData.GetPointer());
  threshold->SetInputArrayToProcess(
        0,0,0,vtkDataObject::FIELD_ASSOCIATION_POINTS,"vtkValidPointMaskArrayName");
  threshold->ThresholdBetween(0.5,1.5);
  threshold->Update();

  vtkNew< vtkDataSetSurfaceFilter > toPolyData;
  toPolyData->SetInputConnection(threshold->GetOutputPort());
  toPolyData->Update();

  output->ShallowCopy(toPolyData->GetOutput());
  output->GetPointData()->RemoveArray("functionValues");
  output->GetPointData()->RemoveArray("vtkValidPointMaskArrayName");

  return true;
}
