//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================

#include "smtk/extension/vtk/io/mesh/ExportVTKData.h"
#include "smtk/extension/vtk/io/mesh/ImportVTKData.h"
#include "smtk/mesh/core/Resource.h"
#include "smtk/mesh/testing/cxx/helpers.h"

#include "smtk/mesh/moab/Interface.h"

#include "vtkAppendFilter.h"
#include "vtkCellIterator.h"
#include "vtkNew.h"
#include "vtkParametricBoy.h"
#include "vtkParametricFunctionSource.h"
#include "vtkPoints.h"
#include "vtkSmartPointer.h"
#include "vtkTetra.h"
#include "vtkUnstructuredGrid.h"
#include "vtkWedge.h"

namespace
{

vtkSmartPointer<vtkPolyData> make_EmptyPolyData()
{
  return vtkSmartPointer<vtkPolyData>::New();
}

vtkSmartPointer<vtkPolyData> make_TrianglePolyData()
{
  //use a vtk parametric source to construct polydata on the fly
  vtkNew<vtkParametricBoy> PB;
  vtkNew<vtkParametricFunctionSource> PFS;
  PFS->SetParametricFunction(PB.Get());

  PFS->SetUResolution(400);
  PFS->SetVResolution(500);
  PFS->Update();

  vtkSmartPointer<vtkPolyData> result = vtkSmartPointer<vtkPolyData>::New();

  result->ShallowCopy(PFS->GetOutput());
  return result;
}

vtkSmartPointer<vtkUnstructuredGrid> make_TriangleUGrid()
{
  //use a vtk parametric source to construct polydata on the fly
  vtkNew<vtkParametricBoy> PB;
  vtkNew<vtkParametricFunctionSource> PFS;
  PFS->SetParametricFunction(PB.Get());

  PFS->SetUResolution(400);
  PFS->SetVResolution(500);
  PFS->Update();

  vtkNew<vtkAppendFilter> appendFilter;
  appendFilter->AddInputData(PFS->GetOutput());
  appendFilter->Update();

  vtkSmartPointer<vtkUnstructuredGrid> result = vtkSmartPointer<vtkUnstructuredGrid>::New();
  result->ShallowCopy(appendFilter->GetOutput());
  return result;
}

vtkSmartPointer<vtkUnstructuredGrid> make_MixedVolUGrid()
{
  //manually create a mixed wedge and tet volume
  vtkNew<vtkPoints> points;
  // points->SetNumberOfPoints(8);
  points->SetNumberOfPoints(7);
  points->InsertPoint(0, 0, 1, 0);
  points->InsertPoint(1, 0, 0, 0);
  points->InsertPoint(2, 0, .5, .5);
  points->InsertPoint(3, 1, 1, 0);
  points->InsertPoint(4, 1, 0, 0);
  points->InsertPoint(5, 1, .5, .5);
  points->InsertPoint(6, .5, 1, 0);
  // points->InsertPoint(7, .5, .5, 1);

  vtkNew<vtkWedge> aWedge;
  aWedge->GetPointIds()->SetId(0, 0);
  aWedge->GetPointIds()->SetId(1, 1);
  aWedge->GetPointIds()->SetId(2, 2);
  aWedge->GetPointIds()->SetId(3, 3);
  aWedge->GetPointIds()->SetId(4, 4);
  aWedge->GetPointIds()->SetId(5, 5);

  vtkNew<vtkTetra> aTetra;
  aTetra->GetPointIds()->SetId(0, 0);
  aTetra->GetPointIds()->SetId(1, 4);
  aTetra->GetPointIds()->SetId(2, 6);
  aTetra->GetPointIds()->SetId(3, 3);

  vtkSmartPointer<vtkUnstructuredGrid> result = vtkSmartPointer<vtkUnstructuredGrid>::New();
  result->SetPoints(points.GetPointer());
  result->Allocate(2);
  result->InsertNextCell(aTetra->GetCellType(), aTetra->GetPointIds());
  result->InsertNextCell(aWedge->GetCellType(), aWedge->GetPointIds());

  return result;
}

double EPSILON = 1.e-6;

void test_same_datasets(vtkDataSet* ds, vtkDataSet* ds2)
{
  test(ds->GetNumberOfPoints() == ds2->GetNumberOfPoints());
  test(ds->GetNumberOfCells() == ds2->GetNumberOfCells());

  vtkCellIterator* it = ds->NewCellIterator();
  vtkCellIterator* it2 = ds2->NewCellIterator();
  it->InitTraversal();
  it2->InitTraversal();
  for (; !it->IsDoneWithTraversal() && !it2->IsDoneWithTraversal();
       it->GoToNextCell(), it2->GoToNextCell())
  {
    test(it->GetCellType() == it2->GetCellType());
    test(it->GetNumberOfPoints() == it2->GetNumberOfPoints());
    vtkPoints* points = it->GetPoints();
    vtkPoints* points2 = it2->GetPoints();
    double xyz[3], xyz2[3];
    for (vtkIdType i = 0; i < points->GetNumberOfPoints(); i++)
    {
      points->GetPoint(i, xyz);
      points2->GetPoint(i, xyz2);
      for (vtkIdType j = 0; j < 3; j++)
      {
        test(fabs(xyz[j] - xyz2[j]) < EPSILON);
      }
    }
  }
  it->Delete();
  it2->Delete();
}

void verify_null_polydata()
{
  smtk::mesh::InterfacePtr interface = smtk::mesh::moab::make_interface();
  smtk::extension::vtk::io::mesh::ImportVTKData imprt;

  vtkPolyData* pd = nullptr;
  smtk::mesh::ResourcePtr meshResource = imprt(pd, interface);
  test(!meshResource, "resource should be invalid for a NULL poly data");
}

void verify_empty_polydata()
{
  smtk::mesh::InterfacePtr interface = smtk::mesh::moab::make_interface();
  smtk::extension::vtk::io::mesh::ImportVTKData imprt;

  smtk::mesh::ResourcePtr meshResource = imprt(make_EmptyPolyData(), interface);
  test(!meshResource, "resource should invalid for empty poly data");
}

void verify_tri_polydata()
{
  smtk::mesh::InterfacePtr interface = smtk::mesh::moab::make_interface();
  smtk::extension::vtk::io::mesh::ImportVTKData imprt;

  vtkSmartPointer<vtkPolyData> pd = make_TrianglePolyData();
  smtk::mesh::ResourcePtr meshResource = imprt(pd, interface);
  test(meshResource && meshResource->isValid(), "resource should be valid");
  test(meshResource->numberOfMeshes() == 1, "resource should only have a single mesh");
  test(meshResource->cells().size() == static_cast<std::size_t>(pd->GetNumberOfCells()));

  //this is a triangle pd so it is 2d only
  smtk::mesh::MeshSet meshes = meshResource->meshes(smtk::mesh::Dims2);
  test(meshes.size() == 1);
  test(meshes.cells() == meshResource->cells());

  smtk::mesh::MeshSet meshes1d = meshResource->meshes(smtk::mesh::Dims1);
  test(meshes1d.size() == 0);

  smtk::extension::vtk::io::mesh::ExportVTKData exprt;
  vtkSmartPointer<vtkPolyData> pd2 = vtkSmartPointer<vtkPolyData>::New();
  exprt(meshes, pd2);
  test_same_datasets(pd, pd2);
}

void verify_tri_ugrid()
{
  smtk::mesh::InterfacePtr interface = smtk::mesh::moab::make_interface();
  smtk::extension::vtk::io::mesh::ImportVTKData imprt;

  vtkSmartPointer<vtkUnstructuredGrid> ug = make_TriangleUGrid();
  smtk::mesh::ResourcePtr meshResource = imprt(ug, interface);
  test(meshResource && meshResource->isValid(), "resource should be valid");
  test(meshResource->numberOfMeshes() == 1, "resource should only have a single mesh");
  test(meshResource->cells().size() == static_cast<std::size_t>(ug->GetNumberOfCells()));

  //this is a triangle grid so it is 2d only
  smtk::mesh::MeshSet meshes = meshResource->meshes(smtk::mesh::Dims2);
  test(meshes.size() == 1);
  test(meshes.cells() == meshResource->cells());

  smtk::mesh::MeshSet meshes1d = meshResource->meshes(smtk::mesh::Dims1);
  test(meshes1d.size() == 0);

  smtk::extension::vtk::io::mesh::ExportVTKData exprt;
  vtkSmartPointer<vtkUnstructuredGrid> ug2 = vtkSmartPointer<vtkUnstructuredGrid>::New();
  exprt(meshes, ug2);
  test_same_datasets(ug, ug2);
}

void verify_mixed_cell_ugrid()
{
  smtk::mesh::InterfacePtr interface = smtk::mesh::moab::make_interface();
  smtk::extension::vtk::io::mesh::ImportVTKData imprt;

  vtkSmartPointer<vtkUnstructuredGrid> ug = make_MixedVolUGrid();
  smtk::mesh::ResourcePtr meshResource = imprt(ug, interface);

  std::cout << "number of cells: " << meshResource->cells().size() << std::endl;
  std::cout << "number of cells ug: " << ug->GetNumberOfCells() << std::endl;

  test(meshResource && meshResource->isValid(), "resource should be valid");
  test(meshResource->numberOfMeshes() == 1, "resource should only have a single mesh");
  test(
    meshResource->cells().size() == static_cast<std::size_t>(ug->GetNumberOfCells()),
    "number of cells in mesh don't match");

  //this is a volume only grid
  smtk::mesh::MeshSet meshes = meshResource->meshes(smtk::mesh::Dims3);
  test(meshes.size() == 1);
  test(meshes.cells() == meshResource->cells());

  smtk::extension::vtk::io::mesh::ExportVTKData exprt;
  vtkSmartPointer<vtkUnstructuredGrid> ug2 = vtkSmartPointer<vtkUnstructuredGrid>::New();
  exprt(meshResource->meshes(), ug2);
  test_same_datasets(ug, ug2);
}
} // namespace

int UnitTestImportExportVTKData(int argc, char* argv[])
{
  (void)argc;
  (void)argv;

  verify_null_polydata();

  verify_empty_polydata();

  verify_tri_polydata();

  verify_tri_ugrid();

  verify_mixed_cell_ugrid();

  return 0;
}
