// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
// Test that the proper amount of tuples exist in the
// point and cell data arrays after the vtkGeometryFilter
// is used.

#include <iostream>

#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkGeometryFilter.h"
#include "vtkIdTypeArray.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkSmartPointer.h"
#include "vtkStringArray.h"
#include "vtkUnstructuredGrid.h"

int TestGeometryFilter(vtkUnstructuredGrid* ug);
int CheckDataSet(vtkDataSet* d);
int CheckFieldData(vtkIdType numGridEntities, vtkFieldData* fd);

// Creates a vtkUnstructuredGrid
class GridFactory
{
public:
  GridFactory();

  void AddVertexCells();
  void AddLineCells();
  void AddTriangleCells();
  void AddTetraCells();
  vtkUnstructuredGrid* Get();

private:
  vtkSmartPointer<vtkUnstructuredGrid> Grid;
};

GridFactory::GridFactory()
  : Grid(vtkSmartPointer<vtkUnstructuredGrid>::New())
{
  // the points
  static float x[8][3] = { { 0, 0, 0 }, { 1, 0, 0 }, { 1, 1, 0 }, { 0, 1, 0 }, { 0, 0, 1 },
    { 1, 0, 1 }, { 1, 1, 1 }, { 0, 1, 1 } };

  std::cout << "Defining 8 points\n";
  // Create the points
  vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
  points->SetNumberOfPoints(8);
  for (int i = 0; i < 8; ++i)
  {
    points->SetPoint(i, x[i]);
  }
  this->Grid->SetPoints(points);
}

// Create 2 tetras
void GridFactory::AddTetraCells()
{
  if (!this->Grid)
    return;

  std::cout << "Adding 2 tetra cells\n";
  vtkIdType pts[4];
  pts[0] = 0;
  pts[1] = 1;
  pts[2] = 2;
  pts[3] = 3;
  this->Grid->InsertNextCell(VTK_TETRA, 4, pts);
  pts[0] = 2;
  pts[1] = 3;
  pts[2] = 4;
  pts[3] = 5;
  this->Grid->InsertNextCell(VTK_TETRA, 4, pts);
}

// Create 2 triangles
void GridFactory::AddTriangleCells()
{
  if (!this->Grid)
    return;

  std::cout << "Adding 2 triangle cells\n";

  vtkIdType pts[3];
  pts[0] = 1;
  pts[1] = 3;
  pts[2] = 5;
  this->Grid->InsertNextCell(VTK_TRIANGLE, 3, pts);
  pts[0] = 2;
  pts[1] = 4;
  pts[2] = 6;
  this->Grid->InsertNextCell(VTK_TRIANGLE, 3, pts);
}

// Create 2 lines
void GridFactory::AddLineCells()
{
  if (!this->Grid)
    return;

  std::cout << "Adding 2 line cells\n";

  vtkIdType pts[2];
  pts[0] = 3;
  pts[1] = 7;
  this->Grid->InsertNextCell(VTK_LINE, 2, pts);
  pts[0] = 0;
  pts[1] = 4;
  this->Grid->InsertNextCell(VTK_LINE, 2, pts);
}

// Create 2 points
void GridFactory::AddVertexCells()
{
  if (!this->Grid)
    return;

  std::cout << "Adding 2 vertex cells\n";

  vtkIdType pts[1];
  pts[0] = 7;
  this->Grid->InsertNextCell(VTK_VERTEX, 1, pts);
  pts[0] = 6;
  this->Grid->InsertNextCell(VTK_VERTEX, 1, pts);
}

// Add cell data and point data for all cells/points, and
// return the unstructured grid.
vtkUnstructuredGrid* GridFactory::Get()
{
  if (!this->Grid)
    return nullptr;

  // Create a point data array
  const char* name = "foo";
  int num = this->Grid->GetNumberOfPoints();
  std::cout << "Adding point data array '" << name << "' with data for " << num << " points\n";
  vtkSmartPointer<vtkIdTypeArray> pointDataArray = vtkSmartPointer<vtkIdTypeArray>::New();
  pointDataArray->SetName(name);
  // Creating data for 8 points
  for (int i = 0; i < num; ++i)
  {
    vtkIdType value = i + 100;
    pointDataArray->InsertNextTypedTuple(&value);
  }
  this->Grid->GetPointData()->AddArray(pointDataArray);

  // Create the cell data array
  name = "bar";
  num = this->Grid->GetNumberOfCells();
  std::cout << "Adding cell data array '" << name << "' with data for " << num << " cells\n";
  vtkSmartPointer<vtkIdTypeArray> cellDataArray = vtkSmartPointer<vtkIdTypeArray>::New();
  cellDataArray->SetName(name);
  cellDataArray->SetNumberOfComponents(1);
  for (int i = 0; i < num; ++i)
  {
    vtkIdType value = i + 200;
    cellDataArray->InsertNextTypedTuple(&value);
  }
  this->Grid->GetCellData()->AddArray(cellDataArray);

  // Create a new string cell data array
  name = "foobar";
  vtkSmartPointer<vtkStringArray> sArray = vtkSmartPointer<vtkStringArray>::New();
  sArray->SetName(name);
  sArray->SetNumberOfComponents(1);
  std::string v("Value");
  for (int i = 0; i < num; ++i)
  {
    sArray->InsertNextValue(v);
  }
  this->Grid->GetCellData()->AddArray(sArray);

  return this->Grid;
}

int TestGeometryFilterCellData(int, char*[])
{
  vtkSmartPointer<vtkUnstructuredGrid> ug;

  // Build the unstructured grid
  GridFactory g;
  g.AddTetraCells();
  g.AddTriangleCells();
  g.AddLineCells();
  g.AddVertexCells();
  ug = g.Get();

  // Run it through vtkGeometryFilter
  int retVal = TestGeometryFilter(ug);

  return retVal;
}

// Runs the unstructured grid through the vtkGeometryFilter and prints the
// output
int TestGeometryFilter(vtkUnstructuredGrid* ug)
{
  // Print the input unstructured grid dataset
  std::cout << "\nvtkGeometryFilter input:\n";
  int retVal = CheckDataSet(ug);

  // Do the filtering
  vtkSmartPointer<vtkGeometryFilter> gf = vtkSmartPointer<vtkGeometryFilter>::New();
  gf->SetInputData(ug);
  gf->Update();

  // Print the output poly data
  std::cout << "\nvtkGeometryFilter output:\n";
  vtkPolyData* poly = vtkPolyData::SafeDownCast(gf->GetOutput());
  retVal += CheckDataSet(poly);
  return retVal;
}

int CheckDataSet(vtkDataSet* d)
{
  if (!d)
  {
    std::cout << "No dataset\n";
    return 1;
  }

  const char* name;
  if (vtkUnstructuredGrid::SafeDownCast(d))
    name = "vtkUnstructuredGrid";
  else if (vtkPolyData::SafeDownCast(d))
    name = "vtkPolyData";
  else
    name = "vtkDataSet";

  std::cout << name << " dimensions: #cells=" << d->GetNumberOfCells()
            << " #points=" << d->GetNumberOfPoints() << "\n";
  int retVal = CheckFieldData(d->GetNumberOfPoints(), d->GetPointData());
  retVal += CheckFieldData(d->GetNumberOfCells(), d->GetCellData());
  return retVal;
}

int CheckFieldData(vtkIdType numGridEntities, vtkFieldData* fd)
{
  int retVal = 0;
  if (!fd)
  {
    std::cout << "No field data\n";
    return 1;
  }

  const char* name;
  if (vtkCellData::SafeDownCast(fd))
  {
    name = "cell data";
  }
  else if (vtkPointData::SafeDownCast(fd))
  {
    name = "point data";
  }
  else
  {
    name = "field data";
  }

  for (int i = 0; i < fd->GetNumberOfArrays(); ++i)
  {
    vtkAbstractArray* a = fd->GetAbstractArray(i);
    std::cout << "\t" << name << " array '" << a->GetName()
              << "' has #tuples=" << a->GetNumberOfTuples() << std::endl;
    if (a->GetNumberOfTuples() != numGridEntities)
    {
      vtkGenericWarningMacro(<< name << " array '" << a->GetName() << "' has #tuples="
                             << a->GetNumberOfTuples() << " but should have " << numGridEntities);
      retVal = 1;
    }
  }
  return retVal;
}
