/*=========================================================================
 *
 *  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.
 *
 *=========================================================================*/

#include "itkTubeSpatialObject.h"
#include "itkLineSpatialObject.h"
#include "itkSpatialObjectToPointSetFilter.h"
#include "itkMath.h"
#include "itkTestingMacros.h"

int
itkSpatialObjectToPointSetFilterTest(int, char *[])
{

  // Typedefs
  using PixelType = float;
  using TubeType = itk::TubeSpatialObject<2>;
  using TubePointer = TubeType::Pointer;
  using PointSetType = itk::PointSet<PixelType, 2>;
  using TubePointListType = TubeType::TubePointListType;
  using TubePointType = TubeType::TubePointType;

  const TubePointer tube1 = TubeType::New();
  TubePointListType list;

  for (unsigned int i = 0; i < 10; ++i)
  {
    TubePointType p;
    p.SetPositionInObjectSpace(i, i);
    p.SetRadiusInObjectSpace(1);
    list.push_back(p);
  }

  tube1->SetPoints(list);
  tube1->Update();

  using SpatialObjectToPointSetFilterType = itk::SpatialObjectToPointSetFilter<TubeType, PointSetType>;
  auto pointSetFilter = SpatialObjectToPointSetFilterType::New();

  ITK_EXERCISE_BASIC_OBJECT_METHODS(pointSetFilter, SpatialObjectToPointSetFilter, MeshSource);


  unsigned int childrenDepth = 0;
  pointSetFilter->SetChildrenDepth(childrenDepth);
  ITK_TEST_SET_GET_VALUE(childrenDepth, pointSetFilter->GetChildrenDepth());

  constexpr unsigned int samplingFactor{ 1 };
  pointSetFilter->SetSamplingFactor(samplingFactor);
  ITK_TEST_SET_GET_VALUE(samplingFactor, pointSetFilter->GetSamplingFactor());

  std::cout << " tnop = " << tube1->GetNumberOfPoints() << std::endl;
  pointSetFilter->SetInput(tube1);

  pointSetFilter->Update();

  const PointSetType::Pointer pointSet = pointSetFilter->GetOutput();

  std::cout << "Testing pointSet exists : ";
  if (!pointSet.GetPointer())
  {
    std::cout << "[FAILURE]" << std::endl;
    return EXIT_FAILURE;
  }
  std::cout << "[PASSED]" << std::endl;

  std::cout << "Testing pointSet size : ";
  if (pointSet->GetPoints()->Size() != 10)
  {
    std::cout << "[FAILURE]" << std::endl;
    std::cout << "   " << pointSet->GetPoints()->Size() << " != 10 (ideal)" << std::endl;
    return EXIT_FAILURE;
  }
  std::cout << "[PASSED]" << std::endl;

  std::cout << "Testing pointSet validity : ";

  using PointIterator = PointSetType::PointsContainer::ConstIterator;
  PointIterator       pointItr = pointSet->GetPoints()->Begin();
  const PointIterator pointEnd = pointSet->GetPoints()->End();

  unsigned int val = 0;
  while (pointItr != pointEnd)
  {
    if ((itk::Math::NotExactlyEquals(pointItr.Value()[0], val)) ||
        (itk::Math::NotExactlyEquals(pointItr.Value()[1], val)))
    {
      std::cout << "[FAILURE]" << std::endl;
      return EXIT_FAILURE;
    }
    val++;
    pointItr++;
  }
  std::cout << "[PASSED]" << std::endl;

  // Create a group spatial object
  using Group3DType = itk::PointBasedSpatialObject<3>;
  using Tube3DType = itk::TubeSpatialObject<3>;
  using Tube3DPointListType = Tube3DType::TubePointListType;
  using Tube3DPointType = Tube3DType::TubePointType;

  auto group3D = Group3DType::New();


  auto                tube3D = Tube3DType::New();
  Tube3DPointListType tubePointList;
  for (unsigned int i = 0; i < 10; ++i)
  {
    Tube3DPointType p;
    p.SetPositionInObjectSpace(i, i + 1, i + 2);
    p.SetRadiusInObjectSpace(1);
    tubePointList.push_back(p);
  }
  tube3D->SetPoints(tubePointList);
  tube3D->Update();

  auto                tube3D2 = Tube3DType::New();
  Tube3DPointListType tubePointList2;
  for (unsigned int i = 10; i < 20; ++i)
  {
    Tube3DPointType p;
    p.SetPositionInObjectSpace(i, i + 1, i + 2);
    tubePointList2.push_back(p);
  }
  tube3D2->SetPoints(tubePointList2);
  tube3D2->Update();

  group3D->AddChild(tube3D);
  group3D->AddChild(tube3D2);


  // Create the 3D filter
  using PointSet3DType = itk::PointSet<PixelType, 3>;
  using SpatialObjectToPointSet3DFilterType = itk::SpatialObjectToPointSetFilter<Tube3DType, PointSet3DType>;
  auto pointSetFilter3D = SpatialObjectToPointSet3DFilterType::New();

  ITK_EXERCISE_BASIC_OBJECT_METHODS(pointSetFilter3D, SpatialObjectToPointSetFilter, MeshSource);

  childrenDepth = 999999;
  pointSetFilter3D->SetChildrenDepth(childrenDepth);
  ITK_TEST_SET_GET_VALUE(childrenDepth, pointSetFilter3D->GetChildrenDepth());

  pointSetFilter3D->SetInput(group3D);

  pointSetFilter3D->Update();

  const PointSet3DType::Pointer pointSet3D = pointSetFilter3D->GetOutput();

  std::cout << "Testing pointSet3D exists : ";
  if (!pointSet3D.GetPointer())
  {
    std::cout << "[FAILURE]" << std::endl;
    return EXIT_FAILURE;
  }
  std::cout << "[PASSED]" << std::endl;

  std::cout << "Testing pointSet3D size : ";
  if (pointSet3D->GetPoints()->Size() != 20)
  {
    std::cout << "[FAILURE]" << std::endl;
    return EXIT_FAILURE;
  }
  std::cout << "[PASSED]" << std::endl;

  std::cout << "Testing pointSet3D validity : ";

  using PointIterator3D = PointSet3DType::PointsContainer::ConstIterator;
  PointIterator3D       pointItr2 = pointSet3D->GetPoints()->Begin();
  const PointIterator3D pointEnd2 = pointSet3D->GetPoints()->End();

  val = 0;
  while (pointItr2 != pointEnd2)
  {
    if ((itk::Math::NotExactlyEquals(pointItr2.Value()[0], val)) ||
        (itk::Math::NotExactlyEquals(pointItr2.Value()[1], val + 1)) ||
        (itk::Math::NotExactlyEquals(pointItr2.Value()[2], val + 2)))
    {
      std::cout << pointItr2.Value()[0] << " :" << pointItr2.Value()[1] << " : " << pointItr2.Value()[2] << std::endl;
      std::cout << val << " :" << val + 1 << " : " << val + 2 << std::endl;
      std::cout << "[FAILURE]" << std::endl;
      return EXIT_FAILURE;
    }
    val++;
    pointItr2++;
  }
  std::cout << "[PASSED]" << std::endl;


  std::cout << "Test finished" << std::endl;
  return EXIT_SUCCESS;
}
