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

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

#include "vtkCellData.h"
#include "vtkDistancePolyDataFilter.h"
#include "vtkDoubleArray.h"
#include "vtkFloatArray.h"
#include "vtkGenericCell.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkIntersectionPolyDataFilter.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkSmartPointer.h"

vtkStandardNewMacro(vtkBooleanOperationPolyDataFilter);

//-----------------------------------------------------------------------------
vtkBooleanOperationPolyDataFilter::vtkBooleanOperationPolyDataFilter()
  : vtkPolyDataAlgorithm()
{
  this->Tolerance = 1e-6;
  this->Operation = VTK_UNION;
  this->ReorientDifferenceCells = 1;

  this->SetNumberOfInputPorts(2);
  this->SetNumberOfOutputPorts(2);
}

//-----------------------------------------------------------------------------
vtkBooleanOperationPolyDataFilter::~vtkBooleanOperationPolyDataFilter() = default;

//-----------------------------------------------------------------------------
void vtkBooleanOperationPolyDataFilter::SortPolyData(
  vtkPolyData* input, vtkIdList* interList, vtkIdList* unionList)
{
  int numCells = input->GetNumberOfCells();

  vtkDoubleArray* distArray =
    vtkArrayDownCast<vtkDoubleArray>(input->GetCellData()->GetArray("Distance"));

  for (int cid = 0; cid < numCells; cid++)
  {

    if (distArray->GetValue(cid) > this->Tolerance)
    {
      unionList->InsertNextId(cid);
    }
    else
    {
      interList->InsertNextId(cid);
    }
  }
}

//-----------------------------------------------------------------------------
int vtkBooleanOperationPolyDataFilter::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  vtkInformation* inInfo0 = inputVector[0]->GetInformationObject(0);
  vtkInformation* inInfo1 = inputVector[1]->GetInformationObject(0);
  vtkInformation* outInfo0 = outputVector->GetInformationObject(0);
  vtkInformation* outInfo1 = outputVector->GetInformationObject(1);

  if (!inInfo0 || !inInfo1 || !outInfo0 || !outInfo1)
  {
    return 0;
  }

  vtkPolyData* input0 = vtkPolyData::SafeDownCast(inInfo0->Get(vtkDataObject::DATA_OBJECT()));
  vtkPolyData* input1 = vtkPolyData::SafeDownCast(inInfo1->Get(vtkDataObject::DATA_OBJECT()));

  vtkPolyData* outputSurface =
    vtkPolyData::SafeDownCast(outInfo0->Get(vtkDataObject::DATA_OBJECT()));
  vtkPolyData* outputIntersection =
    vtkPolyData::SafeDownCast(outInfo1->Get(vtkDataObject::DATA_OBJECT()));

  if (!input0 || !input1 || !outputSurface || !outputIntersection)
  {
    return 0;
  }

  // Get intersected versions
  vtkSmartPointer<vtkIntersectionPolyDataFilter> PolyDataIntersection =
    vtkSmartPointer<vtkIntersectionPolyDataFilter>::New();
  PolyDataIntersection->SetInputConnection(0, this->GetInputConnection(0, 0));
  PolyDataIntersection->SetInputConnection(1, this->GetInputConnection(1, 0));
  PolyDataIntersection->SplitFirstOutputOn();
  PolyDataIntersection->SplitSecondOutputOn();
  PolyDataIntersection->Update();

  if (PolyDataIntersection->GetStatus() != 1)
  {
    return 0;
  }

  outputIntersection->CopyStructure(PolyDataIntersection->GetOutput());
  outputIntersection->GetPointData()->PassData(PolyDataIntersection->GetOutput()->GetPointData());
  outputIntersection->GetCellData()->PassData(PolyDataIntersection->GetOutput()->GetCellData());

  // Compute distances
  vtkSmartPointer<vtkDistancePolyDataFilter> PolyDataDistance =
    vtkSmartPointer<vtkDistancePolyDataFilter>::New();

  PolyDataDistance->SetInputConnection(0, PolyDataIntersection->GetOutputPort(1));
  PolyDataDistance->SetInputConnection(1, PolyDataIntersection->GetOutputPort(2));
  PolyDataDistance->ComputeSecondDistanceOn();
  PolyDataDistance->Update();

  vtkPolyData* pd0 = PolyDataDistance->GetOutput();
  vtkPolyData* pd1 = PolyDataDistance->GetSecondDistanceOutput();

  pd0->BuildCells();
  pd0->BuildLinks();
  pd1->BuildCells();
  pd1->BuildLinks();

  // Set up field lists of both points and cells that are shared by
  // the input data sets.
  vtkDataSetAttributes::FieldList pointFields(2);
  pointFields.InitializeFieldList(pd0->GetPointData());
  pointFields.IntersectFieldList(pd1->GetPointData());

  vtkDataSetAttributes::FieldList cellFields(2);
  cellFields.InitializeFieldList(pd0->GetCellData());
  cellFields.IntersectFieldList(pd1->GetCellData());

  // Sort union/intersection.
  vtkSmartPointer<vtkIdList> interList = vtkSmartPointer<vtkIdList>::New();
  vtkSmartPointer<vtkIdList> unionList = vtkSmartPointer<vtkIdList>::New();

  this->SortPolyData(pd0, interList, unionList);

  outputSurface->AllocateCopy(pd0);
  outputSurface->GetPointData()->CopyAllocate(pointFields);
  outputSurface->GetCellData()->CopyAllocate(cellFields);

  if (this->Operation == VTK_UNION || this->Operation == VTK_DIFFERENCE)
  {
    this->CopyCells(pd0, outputSurface, 0, pointFields, cellFields, unionList, false);
  }
  else if (this->Operation == VTK_INTERSECTION)
  {
    this->CopyCells(pd0, outputSurface, 0, pointFields, cellFields, interList, false);
  }

  // Label sources for each point and cell.
  vtkSmartPointer<vtkIntArray> pointSourceLabel = vtkSmartPointer<vtkIntArray>::New();
  pointSourceLabel->SetNumberOfComponents(1);
  pointSourceLabel->SetName("PointSource");
  pointSourceLabel->SetNumberOfTuples(outputSurface->GetNumberOfPoints());
  for (vtkIdType ii = 0; ii < outputSurface->GetNumberOfPoints(); ii++)
  {
    pointSourceLabel->InsertValue(ii, 0);
  }

  vtkSmartPointer<vtkIntArray> cellSourceLabel = vtkSmartPointer<vtkIntArray>::New();
  cellSourceLabel->SetNumberOfComponents(1);
  cellSourceLabel->SetName("CellSource");
  cellSourceLabel->SetNumberOfValues(outputSurface->GetNumberOfCells());
  for (vtkIdType ii = 0; ii < outputSurface->GetNumberOfCells(); ii++)
  {
    cellSourceLabel->InsertValue(ii, 0);
  }

  interList->Reset();
  unionList->Reset();

  this->SortPolyData(pd1, interList, unionList);

  if (this->Operation == VTK_UNION)
  {
    this->CopyCells(pd1, outputSurface, 1, pointFields, cellFields, unionList, false);
  }
  else if (this->Operation == VTK_INTERSECTION || this->Operation == VTK_DIFFERENCE)
  {
    this->CopyCells(pd1, outputSurface, 1, pointFields, cellFields, interList,
      (this->ReorientDifferenceCells == 1 && this->Operation == VTK_DIFFERENCE));
  }

  vtkIdType i;
  i = pointSourceLabel->GetNumberOfTuples();
  pointSourceLabel->Resize(outputSurface->GetNumberOfPoints());
  for (; i < outputSurface->GetNumberOfPoints(); i++)
  {
    pointSourceLabel->InsertValue(i, 1);
  }

  i = cellSourceLabel->GetNumberOfTuples();
  cellSourceLabel->Resize(outputSurface->GetNumberOfCells());
  for (; i < outputSurface->GetNumberOfCells(); i++)
  {
    cellSourceLabel->InsertValue(i, 1);
  }

  outputSurface->GetPointData()->AddArray(pointSourceLabel);
  outputSurface->GetCellData()->AddArray(cellSourceLabel);

  outputSurface->Squeeze();
  outputSurface->GetPointData()->Squeeze();
  outputSurface->GetCellData()->Squeeze();

  return 1;
}

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

  os << indent << "Tolerance: " << this->Tolerance << "\n";
  os << indent << "Operation: ";
  switch (this->Operation)
  {
    case VTK_UNION:
      os << "UNION";
      break;

    case VTK_INTERSECTION:
      os << "INTERSECTION";
      break;

    case VTK_DIFFERENCE:
      os << "DIFFERENCE";
      break;
  }
  os << "\n";
  os << indent << "ReorientDifferenceCells: " << this->ReorientDifferenceCells << "\n";
}

//-----------------------------------------------------------------------------
int vtkBooleanOperationPolyDataFilter::FillInputPortInformation(int port, vtkInformation* info)
{
  if (!this->Superclass::FillInputPortInformation(port, info))
  {
    return 0;
  }
  if (port == 0)
  {
    info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData");
  }
  else if (port == 1)
  {
    info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData");
    info->Set(vtkAlgorithm::INPUT_IS_OPTIONAL(), 0);
  }
  return 1;
}

//-----------------------------------------------------------------------------
void vtkBooleanOperationPolyDataFilter ::CopyCells(vtkPolyData* in, vtkPolyData* out, int idx,
  vtkDataSetAttributes::FieldList& pointFieldList, vtkDataSetAttributes::FieldList& cellFieldList,
  vtkIdList* cellIds, bool reverseCells)
{
  // Largely copied from vtkPolyData::CopyCells, but modified to
  // use the special form of CopyData that uses a field list to
  // determine which data values to copy over.

  vtkPointData* outPD = out->GetPointData();
  vtkCellData* outCD = out->GetCellData();

  vtkFloatArray* outNormals = nullptr;
  if (reverseCells)
  {
    outNormals = vtkArrayDownCast<vtkFloatArray>(outPD->GetArray("Normals"));
  }

  vtkIdType numPts = in->GetNumberOfPoints();

  if (out->GetPoints() == nullptr)
  {
    vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
    out->SetPoints(points);
  }

  vtkPoints* newPoints = out->GetPoints();

  vtkSmartPointer<vtkIdList> pointMap = vtkSmartPointer<vtkIdList>::New();
  pointMap->SetNumberOfIds(numPts);
  for (vtkIdType i = 0; i < numPts; i++)
  {
    pointMap->SetId(i, -1);
  }

  // Filter the cells
  vtkSmartPointer<vtkGenericCell> cell = vtkSmartPointer<vtkGenericCell>::New();
  vtkSmartPointer<vtkIdList> newCellPts = vtkSmartPointer<vtkIdList>::New();
  for (vtkIdType cellId = 0; cellId < cellIds->GetNumberOfIds(); cellId++)
  {
    in->GetCell(cellIds->GetId(cellId), cell);
    vtkIdList* cellPts = cell->GetPointIds();
    vtkIdType numCellPts = cell->GetNumberOfPoints();

    for (vtkIdType i = 0; i < numCellPts; i++)
    {
      vtkIdType ptId = cellPts->GetId(i);
      vtkIdType newId = pointMap->GetId(ptId);
      if (newId < 0)
      {
        double x[3];
        in->GetPoint(ptId, x);
        newId = newPoints->InsertNextPoint(x);
        pointMap->SetId(ptId, newId);
        outPD->CopyData(pointFieldList, in->GetPointData(), idx, ptId, newId);

        if (reverseCells && outNormals)
        {
          float normal[3];
          outNormals->GetTypedTuple(newId, normal);
          normal[0] *= -1.0;
          normal[1] *= -1.0;
          normal[2] *= -1.0;
          outNormals->SetTypedTuple(newId, normal);
        }
      }
      newCellPts->InsertId(i, newId);
    }
    if (reverseCells)
    {
      for (vtkIdType i = 0; i < newCellPts->GetNumberOfIds() / 2; i++)
      {
        vtkIdType i1 = i;
        vtkIdType i2 = newCellPts->GetNumberOfIds() - i - 1;

        vtkIdType id = newCellPts->GetId(i1);
        newCellPts->SetId(i1, newCellPts->GetId(i2));
        newCellPts->SetId(i2, id);
      }
    }

    vtkIdType newCellId = out->InsertNextCell(cell->GetCellType(), newCellPts);
    outCD->CopyData(cellFieldList, in->GetCellData(), idx, cellIds->GetId(cellId), newCellId);

    newCellPts->Reset();
  } // for all cells
}
