/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#ifndef itkQuadEdgeMeshEulerOperatorsTestHelper_h
#define itkQuadEdgeMeshEulerOperatorsTestHelper_h

#include "itkQuadEdgeMeshTopologyChecker.h"
#include "itkQuadEdgeMeshPolygonCell.h"

using IdentifierType = unsigned long;

template <typename TMesh>
bool
AssertTopologicalInvariants(TMesh *        mesh,
                            IdentifierType NumVertices,
                            IdentifierType NumFaces,
                            IdentifierType NumEdges,
                            IdentifierType NumBorders,
                            IdentifierType Genus)
{
  using CheckerType = itk::QuadEdgeMeshTopologyChecker<TMesh>;
  auto check = CheckerType::New();
  check->SetMesh(mesh);
  check->SetExpectedNumberOfPoints(NumVertices);
  check->SetExpectedNumberOfEdges(NumFaces);
  check->SetExpectedNumberOfFaces(NumEdges);
  check->SetExpectedNumberOfBoundaries(NumBorders);
  check->SetExpectedGenus(Genus);
  return (check->ValidateEulerCharacteristic());
}

//----------------------------------------------------------------------------
template <typename TMesh>
std::vector<typename TMesh::PointType>
GeneratePointCoordinates(const unsigned int iN)
{
  using PointType = typename TMesh::PointType;
  using CoordRepType = typename PointType::CoordRepType;
  std::vector<PointType> oPt(iN * iN);

  for (unsigned int i = 0; i < iN; ++i)
  {
    for (unsigned int j = 0; j < iN; ++j)
    {
      oPt[i * iN + j][0] = static_cast<CoordRepType>(j);
      oPt[i * iN + j][1] = static_cast<CoordRepType>(i);
      oPt[i * iN + j][2] = static_cast<CoordRepType>(0.);
    }
  }

  return oPt;
}

//----------------------------------------------------------------------------
template <typename TMesh>
void
CreateSquareQuadMesh(typename TMesh::Pointer mesh)
{
  using MeshType = TMesh;
  using CellType = typename MeshType::CellType;

  using QEPolygonCellType = itk::QuadEdgeMeshPolygonCell<CellType>;

  if (mesh->GetNumberOfPoints())
  {
    mesh->Clear();
    mesh->ClearFreePointAndCellIndexesLists();
  }

  /////////////////////////////////////////////////////////////
  int expectedNumPts = 25;
  int expectedNumCells = 16;
  int simpleSquareCells[64] = { 0,  1,  6,  5,  1,  2,  7,  6,  2,  3,  8,  7,  3,  4,  9,  8,  5,  6,  11, 10, 6,  7,
                                12, 11, 7,  8,  13, 12, 8,  9,  14, 13, 10, 11, 16, 15, 11, 12, 17, 16, 12, 13, 18, 17,
                                13, 14, 19, 18, 15, 16, 21, 20, 16, 17, 22, 21, 17, 18, 23, 22, 18, 19, 24, 23 };

  using PointType = typename MeshType::PointType;

  std::vector<PointType> pts = GeneratePointCoordinates<MeshType>(5);

  for (int i = 0; i < expectedNumPts; ++i)
  {
    mesh->SetPoint(i, pts[i]);
  }

  typename CellType::CellAutoPointer cellpointer;
  QEPolygonCellType *                poly;

  for (int i = 0; i < expectedNumCells; ++i)
  {
    poly = new QEPolygonCellType(4);
    cellpointer.TakeOwnership(poly);
    cellpointer->SetPointId(0, simpleSquareCells[4 * i]);
    cellpointer->SetPointId(1, simpleSquareCells[4 * i + 1]);
    cellpointer->SetPointId(2, simpleSquareCells[4 * i + 2]);
    cellpointer->SetPointId(3, simpleSquareCells[4 * i + 3]);
    mesh->SetCell(i, cellpointer);
  }
}

//----------------------------------------------------------------------------
template <typename TMesh>
void
CreateSquareTriangularMesh(typename TMesh::Pointer mesh)
{
  using MeshType = TMesh;
  using CellType = typename MeshType::CellType;

  using QEPolygonCellType = itk::QuadEdgeMeshPolygonCell<CellType>;

  if (mesh->GetNumberOfPoints())
  {
    mesh->Clear();
    mesh->ClearFreePointAndCellIndexesLists();
  }

  /////////////////////////////////////////////////////////////
  int expectedNumPts = 25;
  int expectedNumCells = 32;
  int simpleSquareCells[96] = { 0,  1,  6,  0,  6,  5,  1,  2,  7,  1,  7,  6,  2,  3,  8,  2,  8,  7,  3,  4,
                                9,  3,  9,  8,  5,  6,  11, 5,  11, 10, 6,  7,  12, 6,  12, 11, 7,  8,  13, 7,
                                13, 12, 8,  9,  14, 8,  14, 13, 10, 11, 16, 10, 16, 15, 11, 12, 17, 11, 17, 16,
                                12, 13, 18, 12, 18, 17, 13, 14, 19, 13, 19, 18, 15, 16, 21, 15, 21, 20, 16, 17,
                                22, 16, 22, 21, 17, 18, 23, 17, 23, 22, 18, 19, 24, 18, 24, 23 };

  using PointType = typename TMesh::PointType;
  std::vector<PointType> pts = GeneratePointCoordinates<TMesh>(5);

  for (int i = 0; i < expectedNumPts; ++i)
  {
    mesh->SetPoint(i, pts[i]);
  }

  typename CellType::CellAutoPointer cellpointer;
  QEPolygonCellType *                poly;

  for (int i = 0; i < expectedNumCells; ++i)
  {
    poly = new QEPolygonCellType(3);
    cellpointer.TakeOwnership(poly);
    cellpointer->SetPointId(0, simpleSquareCells[3 * i]);
    cellpointer->SetPointId(1, simpleSquareCells[3 * i + 1]);
    cellpointer->SetPointId(2, simpleSquareCells[3 * i + 2]);
    mesh->SetCell(i, cellpointer);
  }
}

//----------------------------------------------------------------------------
template <typename TMesh>
void
CreateTetraedronMesh(typename TMesh::Pointer mesh)
{
  using MeshType = TMesh;
  using CellType = typename MeshType::CellType;

  using QEPolygonCellType = itk::QuadEdgeMeshPolygonCell<CellType>;

  if (mesh->GetNumberOfPoints())
  {
    mesh->Clear();
    mesh->ClearFreePointAndCellIndexesLists();
  }

  /////////////////////////////////////////////////////////////
  int expectedNumPts = 4;
  int expectedNumCells = 4;
  int simpleSquareCells[12] = { 0, 1, 2, 1, 0, 3, 1, 3, 2, 2, 3, 0 };

  using PointType = typename TMesh::PointType;
  std::vector<PointType> pts(4);
  int                    i(0);
  pts[i][0] = 0.;
  pts[i][1] = 1.;
  pts[i++][2] = 0.;
  pts[i][0] = 0.;
  pts[i][1] = -1.;
  pts[i++][2] = 0.;
  pts[i][0] = -1.;
  pts[i][1] = 0.;
  pts[i++][2] = 0.;
  pts[i][0] = 0.;
  pts[i][1] = 0.;
  pts[i++][2] = 1.;

  for (i = 0; i < expectedNumPts; ++i)
  {
    mesh->SetPoint(i, pts[i]);
  }

  typename CellType::CellAutoPointer cellpointer;
  QEPolygonCellType *                poly;

  for (i = 0; i < expectedNumCells; ++i)
  {
    poly = new QEPolygonCellType(3);
    cellpointer.TakeOwnership(poly);
    cellpointer->SetPointId(0, simpleSquareCells[3 * i]);
    cellpointer->SetPointId(1, simpleSquareCells[3 * i + 1]);
    cellpointer->SetPointId(2, simpleSquareCells[3 * i + 2]);
    mesh->SetCell(i, cellpointer);
  }
}


//----------------------------------------------------------------------------
template <typename TMesh>
void
CreateSamosa(typename TMesh::Pointer mesh)
{
  using MeshType = TMesh;
  using CellType = typename MeshType::CellType;

  using QEPolygonCellType = itk::QuadEdgeMeshPolygonCell<CellType>;

  if (mesh->GetNumberOfPoints())
  {
    mesh->Clear();
    mesh->ClearFreePointAndCellIndexesLists();
  }

  /////////////////////////////////////////////////////////////
  int expectedNumPts = 3;
  int expectedNumCells = 2;
  int simpleSquareCells[6] = { 0, 1, 2, 1, 0, 2 };

  using PointType = typename TMesh::PointType;
  std::vector<PointType> pts(3);
  int                    i(0);
  pts[i][0] = 0.;
  pts[i][1] = 1.;
  pts[i++][2] = 0.;
  pts[i][0] = 0.;
  pts[i][1] = -1.;
  pts[i++][2] = 0.;
  pts[i][0] = -1.;
  pts[i][1] = 0.;
  pts[i++][2] = 0.;

  for (i = 0; i < expectedNumPts; ++i)
  {
    mesh->SetPoint(i, pts[i]);
  }

  typename CellType::CellAutoPointer cellpointer;
  QEPolygonCellType *                poly;

  for (i = 0; i < expectedNumCells; ++i)
  {
    poly = new QEPolygonCellType(3);
    cellpointer.TakeOwnership(poly);
    cellpointer->SetPointId(0, simpleSquareCells[3 * i]);
    cellpointer->SetPointId(1, simpleSquareCells[3 * i + 1]);
    cellpointer->SetPointId(2, simpleSquareCells[3 * i + 2]);
    mesh->SetCell(i, cellpointer);
  }
}


#endif
