#include "vtkCGALAdvancingFront.h"

// VTK related includes
#include "vtkCellArrayIterator.h"
#include "vtkDataSet.h"
#include "vtkIdList.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"

// CGAL related includes
#include <CGAL/Advancing_front_surface_reconstruction.h>

vtkStandardNewMacro(vtkCGALAdvancingFront);

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

//------------------------------------------------------------------------------
int vtkCGALAdvancingFront::RequestData(
  vtkInformation*, vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{

  using Triangle = std::array<std::size_t, 3>;

  // Get the input and output data objects.
  vtkPolyData* input  = vtkPolyData::GetData(inputVector[0]);
  vtkPolyData* output = vtkPolyData::GetData(outputVector);

  // Create the point cloud for CGAL
  // -------------------------------

  vtkPoints*    vtkPts     = input->GetPoints();
  vtkIdType     nbPts      = input->GetNumberOfPoints();
  vtkDataArray* ptsArr     = vtkPts->GetData();
  const auto    pointRange = vtk::DataArrayTupleRange<3>(ptsArr);

  std::vector<CGAL_Kernel::Point_3> pts;
  pts.reserve(nbPts);
  for (const auto pt : pointRange)
  {
    pts.emplace_back(pt[0], pt[1], pt[2]);
  }

  std::vector<Triangle> triangles;

  // CGAL Processing
  // ---------------

  try
  {
    CGAL::advancing_front_surface_reconstruction(
      pts.begin(), pts.end(), std::back_inserter(triangles));
  }
  catch (std::exception& e)
  {
    vtkErrorMacro("CGAL Exception: " << e.what());
    return 0;
  }

  // VTK Output
  // ----------

  vtkNew<vtkPoints> outPts;
  const vtkIdType   outNPts = pts.size();
  outPts->Allocate(outNPts);
  for (auto vertex : pts)
  {
    double coords[3] = {
      vertex[0],
      vertex[1],
      vertex[2],
    };
    outPts->InsertNextPoint(coords);
  }
  outPts->Squeeze();

  // cells
  vtkNew<vtkCellArray> cells;
  cells->AllocateEstimate(triangles.size(), 3);

  for (auto face : triangles)
  {
    vtkNew<vtkIdList> ids;
    ids->InsertNextId(face[0]);
    ids->InsertNextId(face[1]);
    ids->InsertNextId(face[2]);

    cells->InsertNextCell(ids);
  }
  cells->Squeeze();

  // VTK dataset
  output->SetPoints(outPts);
  output->SetPolys(cells);

  return 1;
}
