#include <LegacyMeshGenerator.h>

// vtk
#include <vtkContourFilter.h>
#include <vtkPointData.h>
#include <vtkPolyDataNormals.h>
#include <vtkReverseSense.h>
#include <vtkSmoothPolyDataFilter.h>
#include <vtkUnsignedLongArray.h>
#include <vtkTriangleFilter.h>
#include <vtkCleanPolyData.h>
#include <vtkWindowedSincPolyDataFilter.h>
#include <vtkDecimatePro.h>
#include <vtkButterflySubdivisionFilter.h>
#include <vtkPolyDataToImageData.h>

#include <vtkMetaImageWriter.h>

#include "CustomSurfaceReconstructionFilter.h"

// local files
#ifdef SW_USE_POWERCRUST
//#include "vtkPowerCrustSurfaceReconstruction.h"
#endif

//---------------------------------------------------------------------------
LegacyMeshGenerator::LegacyMeshGenerator()
{
  this->usePowerCrust = false;
  this->smoothingEnabled = false;

  this->points = vtkSmartPointer<vtkPoints>::New();
  this->points->SetDataTypeToDouble();
  this->pointSet = vtkSmartPointer<vtkPolyData>::New();
  this->pointSet->SetPoints(this->points);

  this->surfaceReconstruction = vtkSmartPointer<CustomSurfaceReconstructionFilter>::New();
  this->surfaceReconstruction->SetInputData(this->pointSet);

#ifdef SW_USE_POWERCRUST
  this->polydataToImageData = vtkSmartPointer<vtkPolyDataToImageData>::New();
  this->triangleFilter = vtkSmartPointer<vtkTriangleFilter>::New();
  this->powercrust = vtkSmartPointer<vtkPowerCrustSurfaceReconstruction>::New();
  this->powercrust->SetInput( this->pointSet );
  this->triangleFilter->SetInputConnection( this->powercrust->GetOutputPort() );
  this->polydataToImageData->SetInputConnection( this->triangleFilter->GetOutputPort() );
#endif // ifdef SW_USE_POWERCRUST

  this->contourFilter = vtkSmartPointer<vtkContourFilter>::New();
  this->contourFilter->SetInputConnection(this->surfaceReconstruction->GetOutputPort());
  this->contourFilter->SetValue(0, 0.0);
  this->contourFilter->ComputeNormalsOn();

  this->windowSincFilter = vtkSmartPointer<vtkWindowedSincPolyDataFilter>::New();
  this->windowSincFilter->SetInputConnection(this->contourFilter->GetOutputPort());

  this->polydataNormals = vtkSmartPointer<vtkPolyDataNormals>::New();
  this->polydataNormals->SplittingOff();

  this->updatePipeline();
}

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

//---------------------------------------------------------------------------
void LegacyMeshGenerator::setNeighborhoodSize(int size)
{
  this->surfaceReconstruction->SetNeighborhoodSize(size);
}

//---------------------------------------------------------------------------
void LegacyMeshGenerator::setSampleSpacing(double spacing)
{
  this->surfaceReconstruction->SetSampleSpacing(spacing);
}

//---------------------------------------------------------------------------
void LegacyMeshGenerator::setUsePowerCrust(bool enabled)
{
  this->usePowerCrust = enabled;
  this->updatePipeline();
}

//---------------------------------------------------------------------------
void LegacyMeshGenerator::setSmoothingAmount(float amount)
{
  if (amount <= 0) {
    this->smoothingEnabled = false;
  }
  else {
    this->smoothingEnabled = true;
    this->windowSincFilter->SetNumberOfIterations(amount);
    this->windowSincFilter->SetPassBand(0.05);
  }

  this->updatePipeline();
}

//---------------------------------------------------------------------------
vtkSmartPointer<vtkPolyData> LegacyMeshGenerator::buildMesh(const Eigen::VectorXd& shape)
{
  // copy shape points into point set
  int numPoints = shape.size() / 3;
  this->points->SetNumberOfPoints(numPoints);
  unsigned int k = 0;
  for (unsigned int i = 0; i < numPoints; i++) {
    if (std::isnan(shape[k])) { // reconstruction will crash
      return vtkSmartPointer<vtkPolyData>(nullptr);
    }
    double x = shape[k++];
    double y = shape[k++];
    double z = shape[k++];
    this->points->SetPoint(i, x, y, z);
  }
  this->points->Modified();

  if (this->usePowerCrust) {
    if (this->smoothingEnabled) {
      this->polydataToImageData->Update();
      this->contourFilter->Update();
    }
  }
  else {
    this->surfaceReconstruction->Modified();
    this->surfaceReconstruction->Update();
    this->contourFilter->Update();
  }

  this->polydataNormals->Update();

  // make a copy of the vtkPolyData output to return
  vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New();
  polyData->DeepCopy(this->polydataNormals->GetOutput());

  return polyData;
}

//---------------------------------------------------------------------------
void LegacyMeshGenerator::updatePipeline()
{
  if (this->usePowerCrust && this->smoothingEnabled) {
    this->contourFilter->SetInputConnection(this->polydataToImageData->GetOutputPort());
    this->contourFilter->SetValue(0, 0.5);
    this->polydataNormals->SetInputConnection(this->windowSincFilter->GetOutputPort());
  }
  else if (this->usePowerCrust && !this->smoothingEnabled) {
#ifdef SW_USE_POWERCRUST
    this->polydataNormals->SetInputConnection( this->powercrust->GetOutputPort() );
#endif
  }
  else if (!this->usePowerCrust && this->smoothingEnabled) {
    this->contourFilter->SetInputConnection(this->surfaceReconstruction->GetOutputPort());
    this->contourFilter->SetValue(0, 0.0);
    this->polydataNormals->SetInputConnection(this->windowSincFilter->GetOutputPort());
  }
  else if (!this->usePowerCrust && !this->smoothingEnabled) {
    this->polydataNormals->SetInputConnection(this->contourFilter->GetOutputPort());
  }
}
