// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkMath.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPoints.h"
#include "vtkPolygon.h"
#include "vtkQuadraticPolygon.h"

#include <iostream>

class vtkQuadraticPolygonTest : public vtkQuadraticPolygon
{
public:
  static vtkQuadraticPolygonTest* New();
  vtkTypeMacro(vtkQuadraticPolygonTest, vtkQuadraticPolygon);

  bool IsClose(double v1, double v2);
  bool IsClose(double* point1, double* point2);

  void InitializeCircle();
  void InitializeSquare();
  void InitializeSquareWithQuadraticEdge();

  int TestGetSet();
  int TestGetPermutations();
  int TestInitializePolygon();
  int TestIntersectWithLine();
  int TestInterpolateFunctions();
  int TestInterpolateFunctionsUsingMVC();

  int TestAll();

protected:
  vtkQuadraticPolygonTest()
    : Tolerance(0.000001)
  {
  }

private:
  double Tolerance;
};

vtkStandardNewMacro(vtkQuadraticPolygonTest);

int TestQuadraticPolygon(int, char*[])
{
  vtkNew<vtkQuadraticPolygonTest> test;
  int result = test->TestAll();
  std::cout << ((result == EXIT_SUCCESS) ? "SUCCESS" : "FAILURE") << std::endl;
  return result;
}

int vtkQuadraticPolygonTest::TestAll()
{
  int result = EXIT_SUCCESS;

  this->InitializeSquareWithQuadraticEdge();
  result |= this->TestGetSet();
  result |= this->TestGetPermutations();
  result |= this->TestInitializePolygon();
  result |= this->TestIntersectWithLine();
  this->InitializeSquare();
  result |= this->TestInterpolateFunctions();
  result |= this->TestInterpolateFunctionsUsingMVC();

  return result;
}

bool vtkQuadraticPolygonTest::IsClose(double v1, double v2)
{
  return (v1 < v2 ? (v2 - v1 < this->Tolerance) : (v1 - v2 < this->Tolerance));
}

bool vtkQuadraticPolygonTest::IsClose(double* point1, double* point2)
{
  return (vtkMath::Distance2BetweenPoints(point1, point2) < this->Tolerance * this->Tolerance);
}

void vtkQuadraticPolygonTest::InitializeSquare()
{
  this->GetPointIds()->SetNumberOfIds(8);
  this->GetPointIds()->SetId(0, 0);
  this->GetPointIds()->SetId(1, 1);
  this->GetPointIds()->SetId(2, 2);
  this->GetPointIds()->SetId(3, 3);
  this->GetPointIds()->SetId(4, 4);
  this->GetPointIds()->SetId(5, 5);
  this->GetPointIds()->SetId(6, 6);
  this->GetPointIds()->SetId(7, 7);

  this->GetPoints()->SetNumberOfPoints(8);
  this->GetPoints()->SetPoint(0, 0.0, 0.0, 0.0);
  this->GetPoints()->SetPoint(1, 2.0, 0.0, 0.0);
  this->GetPoints()->SetPoint(2, 2.0, 2.0, 0.0);
  this->GetPoints()->SetPoint(3, 0.0, 2.0, 0.0);
  this->GetPoints()->SetPoint(4, 1.0, 0.0, 0.0);
  this->GetPoints()->SetPoint(5, 2.0, 1.0, 0.0);
  this->GetPoints()->SetPoint(6, 1.0, 2.0, 0.0);
  this->GetPoints()->SetPoint(7, 0.0, 1.0, 0.0);
}

void vtkQuadraticPolygonTest::InitializeSquareWithQuadraticEdge()
{
  this->InitializeSquare();
  this->GetPoints()->SetPoint(5, 3.0, 1.0, 0.0);
}

int vtkQuadraticPolygonTest::TestGetSet()
{
  if (this->GetCellType() != VTK_QUADRATIC_POLYGON)
  {
    std::cerr << "ERROR:  quadratic polygon type is " << this->GetCellType() << ", should be "
              << VTK_QUADRATIC_POLYGON << std::endl;
    return EXIT_FAILURE;
  }

  if (this->GetCellDimension() != 2)
  {
    std::cerr << "ERROR:  quadratic polygon dim is " << this->GetCellDimension() << ", should be 2"
              << std::endl;
    return EXIT_FAILURE;
  }

  if (this->GetNumberOfEdges() != 4)
  {
    std::cerr << "ERROR:  quadratic polygon edges number is " << this->GetNumberOfEdges()
              << ", should be 4" << std::endl;
    return EXIT_FAILURE;
  }

  if (this->GetNumberOfFaces() != 0)
  {
    std::cerr << "ERROR:  quadratic polygon faces number is " << this->GetNumberOfFaces()
              << ", should be 0" << std::endl;
    return EXIT_FAILURE;
  }

  if (this->GetFace(0) != nullptr)
  {
    std::cerr << "ERROR:  quadratic polygon face is " << this->GetFace(0) << ", should be 0"
              << std::endl;
    return EXIT_FAILURE;
  }

  if (this->GetEdge(0)->PointIds->GetId(0) != 0)
  {
    std::cerr << "ERROR:  quadratic polygon edge[0] point[0] id is "
              << this->GetEdge(0)->PointIds->GetId(0) << ", should be 0" << std::endl;
    return EXIT_FAILURE;
  }
  if (this->GetEdge(0)->PointIds->GetId(1) != 1)
  {
    std::cerr << "ERROR:  quadratic polygon edge[0] point[1] id is "
              << this->GetEdge(0)->PointIds->GetId(1) << ", should be 1" << std::endl;
    return EXIT_FAILURE;
  }
  if (this->GetEdge(0)->PointIds->GetId(2) != 4)
  {
    std::cerr << "ERROR:  quadratic polygon edge[0] point[2] id is "
              << this->GetEdge(0)->PointIds->GetId(2) << ", should be 4" << std::endl;
    return EXIT_FAILURE;
  }

  if (this->IsPrimaryCell() != 0)
  {
    std::cerr << "ERROR:  quadratic polygon primary boolean is " << this->IsPrimaryCell()
              << ", should be 0" << std::endl;
    return EXIT_FAILURE;
  }

  if (!this->GetUseMVCInterpolation())
  {
    std::cerr << "ERROR:  quadratic polygon MVC boolean is " << this->GetUseMVCInterpolation()
              << ", should be 1" << std::endl;
    return EXIT_FAILURE;
  }

  this->SetUseMVCInterpolation(false);
  if (this->GetUseMVCInterpolation())
  {
    std::cerr << "ERROR:  quadratic polygon MVC boolean is " << this->GetUseMVCInterpolation()
              << ", should be 0" << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}

int vtkQuadraticPolygonTest::TestGetPermutations()
{
  // reference permutation
  vtkIdType temp[] = { 0, 2, 4, 6, 1, 3, 5, 7 };
  vtkNew<vtkIdList> permutationToPolygonRef;
  permutationToPolygonRef->SetNumberOfIds(8);
  for (vtkIdType i = 0; i < 8; i++)
  {
    permutationToPolygonRef->SetId(i, temp[i]);
  }

  // computed permutation
  vtkNew<vtkIdList> permutationToPolygon;
  vtkQuadraticPolygon::GetPermutationToPolygon(8, permutationToPolygon);

  // reference permutation
  vtkIdType temp2[] = { 0, 4, 1, 5, 2, 6, 3, 7 };
  vtkNew<vtkIdList> permutationFromPolygonRef;
  permutationFromPolygonRef->SetNumberOfIds(8);
  for (vtkIdType i = 0; i < 8; i++)
  {
    permutationFromPolygonRef->SetId(i, temp2[i]);
  }

  // computed permutation
  vtkNew<vtkIdList> permutationFromPolygon;
  vtkQuadraticPolygon::GetPermutationFromPolygon(8, permutationFromPolygon);

  for (vtkIdType i = 0; i < 8; i++)
  {
    if (permutationToPolygonRef->GetId(i) != permutationToPolygon->GetId(i))
    {
      std::cerr << "ERROR:  permutation to polygon is wrong" << std::endl;
      return EXIT_FAILURE;
    }
    if (permutationFromPolygonRef->GetId(i) != permutationFromPolygon->GetId(i))
    {
      std::cerr << "ERROR:  permutation from polygon is wrong" << std::endl;
      return EXIT_FAILURE;
    }
  }

  return EXIT_SUCCESS;
}

int vtkQuadraticPolygonTest::TestInitializePolygon()
{
  // reference permutation
  vtkIdType temp[] = { 0, 2, 4, 6, 1, 3, 5, 7 };
  vtkNew<vtkIdList> permutationToPolygonRef;
  permutationToPolygonRef->SetNumberOfIds(8);
  for (vtkIdType i = 0; i < 8; i++)
  {
    permutationToPolygonRef->SetId(i, temp[i]);
  }

  this->InitializePolygon();
  vtkPolygon* polygon = this->Polygon;

  for (vtkIdType i = 0; i < 8; i++)
  {
    if (this->GetPointIds()->GetId(i) !=
      polygon->GetPointIds()->GetId(permutationToPolygonRef->GetId(i)))
    {
      std::cerr << "ERROR:  quadratic polygon point id at index " << i << " is "
                << this->GetPointIds()->GetId(i) << ", should be "
                << polygon->GetPointIds()->GetId(permutationToPolygonRef->GetId(i)) << std::endl;
      return EXIT_FAILURE;
    }
    for (int j = 0; j < 3; j++)
    {
      if (!this->IsClose(this->GetPoints()->GetPoint(i)[j],
            polygon->GetPoints()->GetPoint(permutationToPolygonRef->GetId(i))[j]))
      {
        std::cerr << "ERROR:  quadratic polygon point at index " << i << " (coord " << j << ") is "
                  << this->GetPoints()->GetPoint(i)[j] << ", should be "
                  << polygon->GetPoints()->GetPoint(permutationToPolygonRef->GetId(i))[j]
                  << std::endl;
        return EXIT_FAILURE;
      }
    }
  }

  return EXIT_SUCCESS;
}

int vtkQuadraticPolygonTest::TestIntersectWithLine()
{
  double t, x[3], pcoords[3];
  int subId;

  double p1[3] = { 2.5, 1.0, -1.0 };
  double p2[3] = { 2.5, 1.0, 1.0 };
  int intersect = this->IntersectWithLine(p1, p2, 0.0, t, x, pcoords, subId);

  if (x[0] != 2.5 || x[1] != 1.0 || x[2] != 0.0)
  {
    std::cerr << "ERROR:  vtkQuadraticPolygon::IntersectWithLine returns point (" << x[0] << ","
              << x[1] << "," << x[2] << ")"
              << ", should return point (2.5,1.0,0.0)" << std::endl;
    return EXIT_FAILURE;
  }

  if (!intersect)
  {
    std::cerr << "ERROR:  vtkQuadraticPolygon::IntersectWithLine returns " << intersect
              << ", should return 1" << std::endl;
    return EXIT_FAILURE;
  }

  p1[0] = 3.5;
  p2[0] = 3.5;
  intersect = this->IntersectWithLine(p1, p2, 0.0, t, x, pcoords, subId);

  if (intersect)
  {
    std::cerr << "ERROR:  vtkQuadraticPolygon::IntersectWithLine returns " << intersect
              << ", should return 0" << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}

int vtkQuadraticPolygonTest::TestInterpolateFunctions()
{
  int nbPoints = this->GetNumberOfPoints();

  double x[3] = { 1.0, 1.0, 0.0 };
  double* weights = new double[nbPoints];
  double w1 = 1. / 12.;
  double w2 = 1. / 6.;
  this->SetUseMVCInterpolation(false);
  this->InterpolateFunctions(x, weights);

  int i;
  for (i = 0; i < nbPoints / 2; i++)
  {
    if (!this->IsClose(weights[i], w1))
    {
      std::cerr << "ERROR:  quadratic polygon weights is " << weights[i] << ", should be " << w1
                << std::endl;
      delete[] weights;
      return EXIT_FAILURE;
    }
  }
  for (; i < nbPoints; i++)
  {
    if (!this->IsClose(weights[i], w2))
    {
      std::cerr << "ERROR:  quadratic polygon weights is " << weights[i] << ", should be " << w2
                << std::endl;
      delete[] weights;
      return EXIT_FAILURE;
    }
  }

  delete[] weights;
  return EXIT_SUCCESS;
}

int vtkQuadraticPolygonTest::TestInterpolateFunctionsUsingMVC()
{
  int nbPoints = this->GetNumberOfPoints();

  double x[3] = { 1.0, 1.0, 0.0 };
  double* weights = new double[nbPoints];
  double w1 = (sqrt(2.) - 1.) / 4.;
  double w2 = (sqrt(2.) - 1.) / (2. * sqrt(2.));
  this->SetUseMVCInterpolation(true);
  this->InterpolateFunctions(x, weights);

  int i;
  for (i = 0; i < nbPoints / 2; i++)
  {
    if (!this->IsClose(weights[i], w1))
    {
      std::cerr << "ERROR:  quadratic polygon weights is " << weights[i] << ", should be " << w1
                << std::endl;
      delete[] weights;
      return EXIT_FAILURE;
    }
  }
  for (; i < nbPoints; i++)
  {
    if (!this->IsClose(weights[i], w2))
    {
      std::cerr << "ERROR:  quadratic polygon weights is " << weights[i] << ", should be " << w2
                << std::endl;
      delete[] weights;
      return EXIT_FAILURE;
    }
  }

  delete[] weights;
  return EXIT_SUCCESS;
}
