// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkGenericCell.h"
#include "vtkPoints.h"

#include <iostream>

static constexpr unsigned int depth = 5;
static unsigned char HigherOrderCell[][depth] = {
  { VTK_LINE, VTK_QUADRATIC_EDGE, VTK_NUMBER_OF_CELL_TYPES, VTK_NUMBER_OF_CELL_TYPES,
    VTK_NUMBER_OF_CELL_TYPES },
  { VTK_TRIANGLE, VTK_QUADRATIC_TRIANGLE, VTK_BIQUADRATIC_TRIANGLE, VTK_NUMBER_OF_CELL_TYPES,
    VTK_NUMBER_OF_CELL_TYPES },
  { VTK_QUAD, VTK_QUADRATIC_QUAD, VTK_QUADRATIC_LINEAR_QUAD, VTK_BIQUADRATIC_QUAD,
    VTK_NUMBER_OF_CELL_TYPES },
  { VTK_TETRA, VTK_QUADRATIC_TETRA, VTK_NUMBER_OF_CELL_TYPES, VTK_NUMBER_OF_CELL_TYPES,
    VTK_NUMBER_OF_CELL_TYPES },
  { VTK_HEXAHEDRON, VTK_QUADRATIC_HEXAHEDRON, VTK_BIQUADRATIC_QUADRATIC_HEXAHEDRON,
    VTK_TRIQUADRATIC_HEXAHEDRON, VTK_NUMBER_OF_CELL_TYPES },
  { VTK_WEDGE, VTK_QUADRATIC_WEDGE, VTK_QUADRATIC_LINEAR_WEDGE, VTK_BIQUADRATIC_QUADRATIC_WEDGE,
    VTK_NUMBER_OF_CELL_TYPES },
  { VTK_PYRAMID, VTK_QUADRATIC_PYRAMID, VTK_TRIQUADRATIC_PYRAMID, VTK_NUMBER_OF_CELL_TYPES,
    VTK_NUMBER_OF_CELL_TYPES }
};

//------------------------------------------------------------------------------
// Simply set the points to the pcoords coordinate
// and the point id to the natural order
void InitializeACell(vtkCell* cell)
{
  if (cell)
  {
    double* pcoords = cell->GetParametricCoords();
    int numPts = cell->GetNumberOfPoints();
    for (int i = 0; i < numPts; ++i)
    {
      double* point = pcoords + 3 * i;
      cell->GetPointIds()->SetId(i, i);
      // std::cerr << point[0] << "," << point[1] << "," << point[2] << std::endl;
      cell->GetPoints()->SetPoint(i, point);
    }
  }
}

//------------------------------------------------------------------------------
// c1 is the reference cell. In the test this is the linear cell
// and thus c2 is the higher order one. We need to check that result on c1
// are consistent with result on c2 (but we cannot say anything after that)
int CompareHigherOrderCell(vtkCell* c1, vtkCell* c2)
{
  int rval = 0;
  // c1->Print( std::cout );
  // c2->Print( std::cout );
  int c1numPts = c1->GetNumberOfPoints();
  int c2numPts = c2->GetNumberOfPoints();
  int numPts = c1numPts < c2numPts ? c1numPts : c2numPts;
  for (int p = 0; p < numPts; ++p)
  {
    vtkIdType pid1 = c1->GetPointId(p);
    vtkIdType pid2 = c2->GetPointId(p);
    if (pid1 != pid2)
    {
      std::cerr << "Problem with pid:" << pid1 << " != " << pid2 << " in cell #"
                << c1->GetCellType() << " and #" << c2->GetCellType() << std::endl;
      ++rval;
    }
    double* pt1 = c1->Points->GetPoint(p);
    double* pt2 = c2->Points->GetPoint(p);
    if (pt1[0] != pt2[0] || pt1[1] != pt2[1] || pt1[2] != pt2[2])
    {
      std::cerr << "Problem with points coord:" << pt1[0] << "," << pt1[1] << "," << pt1[2]
                << " != " << pt2[0] << "," << pt2[1] << "," << pt2[2] << " in cell #"
                << c1->GetCellType() << " and #" << c2->GetCellType() << std::endl;
      ++rval;
    }
  }
  return rval;
}

//------------------------------------------------------------------------------
int TestHigherOrderCell(int, char*[])
{
  int rval = 0;
  if (sizeof(HigherOrderCell[0]) != depth)
  {
    std::cerr << sizeof(HigherOrderCell[0]) << std::endl;
    std::cerr << "Problem in the test" << std::endl;
    return 1;
  }

  auto isQuadraticWedge = [](int cellType) -> bool
  {
    return (cellType == VTK_QUADRATIC_LINEAR_WEDGE) ||
      (cellType == VTK_BIQUADRATIC_QUADRATIC_WEDGE) || (cellType == VTK_QUADRATIC_WEDGE);
  };

  const unsigned char* orderCell;
  constexpr unsigned int nCells = sizeof(HigherOrderCell) / depth;
  vtkCell* cellArray[depth];
  for (unsigned int i = 0; i < nCells; ++i)
  {
    orderCell = HigherOrderCell[i];
    // std::cerr << "Higher : " << (int)orderCell[0] << "," << (int)orderCell[1]
    // << "," << (int)orderCell[2] << "," << (int)orderCell[3] << ","
    // << (int)orderCell[4] << std::endl;
    for (unsigned int c = 0; c < depth; ++c)
    {
      const int cellType = orderCell[c];
      cellArray[c] = vtkGenericCell::InstantiateCell(cellType);
      InitializeACell(cellArray[c]);
    }
    vtkCell* linCell = cellArray[0];  // this is the reference linear cell
    vtkCell* quadCell = cellArray[1]; // this is the reference quadratic cell (serendipity)
    // const int numPts   = linCell->GetNumberOfPoints();
    const int numEdges = linCell->GetNumberOfEdges();
    const int numFaces = linCell->GetNumberOfFaces();
    const int dim = linCell->GetCellDimension();
    // First check consistency across cell of higher dimension:
    // Technically doing the loop from 1 to depth will be redundant when doing the
    // CompareHigherOrderCell on the quadratic cell since we will compare the exactly
    // same cell...
    for (unsigned int c = 1; c < depth; ++c)
    {
      vtkCell* cell = cellArray[c];
      if (cell)
      {
        if (cell->GetCellType() != (int)orderCell[c])
        {
          std::cerr << "Problem in the test" << std::endl;
          ++rval;
        }
        if (cell->GetCellDimension() != dim)
        {
          std::cerr << "Wrong dim for cellId #" << cell->GetCellType() << std::endl;
          ++rval;
        }
        if (cell->GetNumberOfEdges() != numEdges)
        {
          std::cerr << "Wrong numEdges for cellId #" << cell->GetCellType() << std::endl;
          ++rval;
        }
        if (cell->GetNumberOfFaces() != numFaces)
        {
          std::cerr << "Wrong numFace for cellId #" << cell->GetCellType() << std::endl;
          ++rval;
        }
        // Make sure that edge across all different cell are identical
        for (int e = 0; e < numEdges; ++e)
        {
          vtkCell* c1 = linCell->GetEdge(e);
          vtkCell* c2 = cell->GetEdge(e);
          std::cerr << "Doing Edge: #" << e << " comp:" << linCell->GetCellType() << " vs "
                    << cell->GetCellType() << std::endl;
          if (cell->GetCellType() != VTK_TRIQUADRATIC_PYRAMID)
          {
            rval += CompareHigherOrderCell(c1, c2);
          }
          vtkCell* qc1 = quadCell->GetEdge(e);
          std::cerr << "Doing Edge: #" << e << " comp:" << quadCell->GetCellType() << " vs "
                    << cell->GetCellType() << std::endl;
          if (cell->GetCellType() != VTK_QUADRATIC_LINEAR_QUAD &&
            cell->GetCellType() != VTK_QUADRATIC_LINEAR_WEDGE &&
            cell->GetCellType() != VTK_TRIQUADRATIC_PYRAMID)
          {
            rval += CompareHigherOrderCell(qc1, c2);
          }
        }
        // Make sure that face across all different cell are identical
        for (int f = 0; f < numFaces; ++f)
        {
          vtkCell* f1 = linCell->GetFace(f);
          vtkCell* f2 = cell->GetFace(f);
          std::cerr << "Doing Face: #" << f << " comp:" << linCell->GetCellType() << " vs "
                    << cell->GetCellType() << std::endl;
          if ((!isQuadraticWedge(cell->GetCellType())) &&
            cell->GetCellType() != VTK_TRIQUADRATIC_PYRAMID)
          {
            rval += CompareHigherOrderCell(f1, f2);
          }
          vtkCell* qf1 = quadCell->GetFace(f);
          std::cerr << "Doing Face: #" << f << " comp:" << quadCell->GetCellType() << " vs "
                    << cell->GetCellType() << std::endl;
          if (cell->GetCellType() != VTK_QUADRATIC_LINEAR_QUAD &&
            (!isQuadraticWedge(cell->GetCellType())) &&
            cell->GetCellType() != VTK_TRIQUADRATIC_PYRAMID)
          {
            rval += CompareHigherOrderCell(qf1, f2);
          }
        }
      }
    }
    // Cleanup
    for (unsigned int c = 0; c < depth; ++c)
    {
      vtkCell* cell = cellArray[c];
      if (cell)
      {
        cell->Delete();
      }
    }
  }

  return rval;
}
