/*=========================================================================
 *
 *  Copyright Insight Software Consortium
 *
 *  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
 *
 *         http://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 itkMeshFileTestHelper_h
#define itkMeshFileTestHelper_h

/*
 * This file is contains helper functions for the ITK MeshIO module testing.
 * It should not be considered as part of the toolkit API and it may change at
 * any time without notice. We mean it.
 */

#include "itkMeshFileReader.h"
#include "itkMeshFileWriter.h"
#include <itksys/SystemTools.hxx>

template< typename TMesh >
int
TestPointsContainer( typename TMesh::PointsContainerPointer points0,
                     typename TMesh::PointsContainerPointer points1 )
{
  using MeshType = TMesh;
  using PointsContainerConstIterator = typename MeshType::PointsContainerConstIterator;

  if ( points0.IsNotNull() && points1.IsNotNull() )
    {
    if( points0->Size() != points1->Size() )
      {
      std::cerr << "Input mesh and output mesh have different number of cells!" <<std::endl;
      return EXIT_FAILURE;
      }

    PointsContainerConstIterator pt0 = points0->Begin();
    PointsContainerConstIterator pt1 = points1->Begin();

    const double tol = 1e-6;
    while ( ( pt0 != points0->End() ) && ( pt1 != points1->End() ) )
      {
      if( pt0->Index() != pt1->Index() )
        {
        std::cerr << "Input mesh and output mesh are different in points!" << std::endl;
        std::cerr << "Input point ID  = "   << pt0.Index() << std::endl;
        std::cerr << "Output point ID = "   << pt1.Index() << std::endl;
        return EXIT_FAILURE;
        }
      if ( pt0.Value().SquaredEuclideanDistanceTo( pt1.Value() ) > tol )
        {
        std::cerr << "Input mesh and output mesh are different in points!" << std::endl;
        std::cerr << "Input point = "  << pt0.Value() << std::endl;
        std::cerr << "Output point = " << pt1.Value() << std::endl;
        return EXIT_FAILURE;
        }
       ++pt0;
       ++pt1;
      }
    }
  else
    {
    if ( points0 != points1.GetPointer() )
      {
      std::cerr << "Input mesh and output mesh are different in points!" << std::endl;
      std::cerr << "points0 = "  << points0.GetPointer() << std::endl;
      std::cerr << "points1 = " << points1.GetPointer() << std::endl;
      return EXIT_FAILURE;
      }
    }

  return EXIT_SUCCESS;
}

template< typename TMesh >
int
TestCellsContainer( typename TMesh::CellsContainerPointer cells0,
                    typename TMesh::CellsContainerPointer cells1 )
{
  using MeshType = TMesh;
  using CellsContainerConstIterator = typename MeshType::CellsContainerConstIterator;
  using CellPointIdIterator = typename MeshType::CellType::PointIdIterator;

  if ( cells0.IsNotNull() && cells1.IsNotNull() )
    {
    if( cells0->Size() != cells1->Size() )
      {
      std::cerr << "Input mesh and output mesh have different number of cells!" <<std::endl;
      return EXIT_FAILURE;
      }
    CellsContainerConstIterator ceIt0 = cells0->Begin();
    CellsContainerConstIterator ceIt1 = cells1->Begin();

    while ( ( ceIt0 != cells0->End() )  && ( ceIt1 != cells1->End() ) )
      {
      if ( ceIt0.Value()->GetType() != ceIt1.Value()->GetType() )
        {
        std::cerr << "Input mesh and output mesh are different in cell type!" << std::endl;
        return EXIT_FAILURE;
        }
      if ( ceIt0.Index() != ceIt1.Index() )
        {
        std::cerr << "Input mesh and output mesh have different cell IDs" << std::endl;
        std::cerr << "Input mesh cell ID: "   << ceIt0.Index() << std::endl;
        std::cerr << "Output mesh cell ID: "  << ceIt1.Index() << std::endl;
        return EXIT_FAILURE;
        }
      CellPointIdIterator pit0 = ceIt0.Value()->PointIdsBegin();
      CellPointIdIterator pit1 = ceIt1.Value()->PointIdsBegin();
      while ( pit0 != ceIt0.Value()->PointIdsEnd() )
        {
        if ( *pit0 != *pit1 )
          {
          std::cerr << "Input mesh and output mesh are different in cells!" << std::endl;
          return EXIT_FAILURE;
          }
        ++pit0;
        ++pit1;
        }
      ++ceIt0;
      ++ceIt1;
      }
    }
  else
    {
    if ( cells0 != cells1.GetPointer() )
      {
      std::cerr << "Input mesh and output mesh are different in cells!" << std::endl;
      std::cerr << "cells0 = "  << cells0.GetPointer() << std::endl;
      std::cerr << "cells1 = " << cells1.GetPointer() << std::endl;
      return EXIT_FAILURE;
      }
    }

  return EXIT_SUCCESS;
}

template< typename TMesh >
int
TestPointDataContainer( typename TMesh::PointDataContainerPointer pointData0,
                       typename TMesh::PointDataContainerPointer pointData1 )
{
  using MeshType = TMesh;
  using PointDataContainerIterator = typename MeshType::PointDataContainerIterator;

  if ( pointData0.IsNotNull() && pointData1.IsNotNull() )
    {
    if( pointData0->Size() != pointData1->Size() )
      {
      std::cerr << "Input mesh and output mesh have different number of cells!" <<std::endl;
      return EXIT_FAILURE;
      }
    PointDataContainerIterator pdIt0 = pointData0->Begin();
    PointDataContainerIterator pdIt1 = pointData1->Begin();

    while ( ( pdIt0 != pointData0->End() ) && ( pdIt1 != pointData1->End() ) )
      {
      if( pdIt0->Index() != pdIt1->Index() )
        {
        std::cerr << "Input mesh and output mesh are different in point data!" << std::endl;
        std::cerr << "Input point ID  = "   << pdIt0.Index() << std::endl;
        std::cerr << "Output point ID = "   << pdIt1.Index() << std::endl;
        return EXIT_FAILURE;
        }
      if ( itk::Math::NotExactlyEquals(pdIt0.Value(), pdIt1.Value()) )
        {
        std::cerr << "Input mesh and output mesh are different in point data!" << std::endl;
        std::cerr << "Input = "  << pdIt0.Value() << std::endl;
        std::cerr << "Output = " << pdIt1.Value() << std::endl;
        return EXIT_FAILURE;
        }
      ++pdIt0;
      ++pdIt1;
      }
    }
  else
    {
    if ( pointData0 != pointData1.GetPointer() )
      {
      std::cerr << "Input mesh and output mesh are different in point data!" << std::endl;
      std::cerr << "pointData0 = "  << pointData0.GetPointer() << std::endl;
      std::cerr << "pointData1 = " << pointData1.GetPointer() << std::endl;
      return EXIT_FAILURE;
      }
    }
  return EXIT_SUCCESS;
}

template< typename TMesh >
int
TestCellDataContainer( typename TMesh::CellDataContainerPointer cellData0,
                      typename TMesh::CellDataContainerPointer cellData1 )
{
  using MeshType = TMesh;
  using CellDataContainerIterator = typename MeshType::CellDataContainerIterator;

  if ( cellData0.IsNotNull() && cellData1.IsNotNull() )
    {
    if( cellData0->Size() != cellData1->Size() )
      {
      std::cerr << "Input mesh and output mesh have different number of cells!" <<std::endl;
      return EXIT_FAILURE;
      }

    CellDataContainerIterator cdIt0 = cellData0->Begin();
    CellDataContainerIterator cdIt1 = cellData1->Begin();
    while ( cdIt0 != cellData0->End() )
      {
      if( cdIt0->Index() != cdIt1->Index() )
        {
        std::cerr << "Input mesh and output mesh are different in cell data!" << std::endl;
        std::cerr << "Input cell ID  = "   << cdIt0.Index() << std::endl;
        std::cerr << "Output cell ID = "   << cdIt1.Index() << std::endl;
        return EXIT_FAILURE;
        }
      if ( itk::Math::NotExactlyEquals(cdIt0.Value(), cdIt1.Value()) )
        {
        std::cerr << "Input mesh and output mesh are different in cell data!" << std::endl;
        std::cerr << "Input = "  << cdIt0.Value() << std::endl;
        std::cerr << "Output = " << cdIt1.Value() << std::endl;
        return EXIT_FAILURE;
        }
      ++cdIt0;
      ++cdIt1;
      }
    }
  else
    {
    if ( cellData0 != cellData1.GetPointer() )
      {
      std::cerr << "Input mesh and output mesh are different in cell data!" << std::endl;
      std::cerr << "pointData0 = "  << cellData0.GetPointer() << std::endl;
      std::cerr << "pointData1 = " << cellData1.GetPointer() << std::endl;
      return EXIT_FAILURE;
      }
    }
  return EXIT_SUCCESS;
}

template< typename TMesh >
int
test(char *INfilename, char *OUTfilename, bool IsBinary)
{
  using MeshType = TMesh;

  using MeshFileReaderType = itk::MeshFileReader< MeshType >;
  using MeshFileReaderPointer = typename MeshFileReaderType::Pointer;

  using MeshFileWriterType = itk::MeshFileWriter< MeshType >;
  using MeshFileWriterPointer = typename MeshFileWriterType::Pointer;

  MeshFileReaderPointer reader = MeshFileReaderType::New();
  reader->SetFileName(INfilename);
  try
    {
    reader->Update();
    }
  catch ( itk::ExceptionObject & err )
    {
    std::cerr << "Read file " << INfilename << " failed " << std::endl;
    std::cerr << err << std::endl;
    return EXIT_FAILURE;
    }
  reader->GetMeshIO()->Print( std::cout );

  if( TMesh::PointDimension != reader->GetMeshIO()->GetPointDimension() )
    {
    std::cerr << "Unexpected PointDimension" << std::endl;
    return EXIT_FAILURE;
    }

  MeshFileWriterPointer writer = MeshFileWriterType::New();
  if( itksys::SystemTools::GetFilenameLastExtension(INfilename) ==
      itksys::SystemTools::GetFilenameLastExtension(OUTfilename) )
    {
    writer->SetMeshIO(reader->GetModifiableMeshIO());
    }
  writer->SetFileName(OUTfilename);
  writer->SetInput( reader->GetOutput() );

  // NOTE ALEX: we should document the usage
  if ( IsBinary )
    {
    writer->SetFileTypeAsBINARY();
    }

  try
    {
    writer->Update();
    }
  catch ( itk::ExceptionObject & err )
    {
    std::cerr << "Write file " << OUTfilename << " failed " << std::endl;
    std::cerr << err << std::endl;
    return EXIT_FAILURE;
    }

  if ( !itksys::SystemTools::FilesDiffer(INfilename, OUTfilename) )
    {
    return EXIT_SUCCESS;
    }

  typename MeshFileReaderType::Pointer reader1 = MeshFileReaderType::New();
  reader1->SetFileName(OUTfilename);
  try
    {
    reader1->Update();
    }
  catch ( itk::ExceptionObject & err )
    {
    std::cerr << "Read file " << OUTfilename << " failed " << std::endl;
    std::cerr << err << std::endl;
    return EXIT_FAILURE;
    }

  // Test points
  if( TestPointsContainer< MeshType >(  reader->GetOutput()->GetPoints(),
                                        reader1->GetOutput()->GetPoints() ) == EXIT_FAILURE )
    {
    return EXIT_FAILURE;
    }


  // Test cells
  if( TestCellsContainer< MeshType >( reader->GetOutput()->GetCells(),
                                      reader1->GetOutput()->GetCells() ) == EXIT_FAILURE )
    {
    return EXIT_FAILURE;
    }


  // Test point data
  if( TestPointDataContainer< MeshType >( reader->GetOutput()->GetPointData(),
                                          reader1->GetOutput()->GetPointData() ) == EXIT_FAILURE )
    {
    return EXIT_FAILURE;
    }


  // Test cell data
  if( TestCellDataContainer< MeshType >( reader->GetOutput()->GetCellData(),
                                         reader1->GetOutput()->GetCellData() ) == EXIT_FAILURE )
    {
    return EXIT_FAILURE;
    }

  return EXIT_SUCCESS;
}
#endif
