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

#include "itkGTest.h"
#include "itkPhysicalPointImageSource.h"
#include "itkGaussianImageSource.h"
#include "itkImageRegionConstIterator.h"

// This test verifies the principle that the SliceImageFilter should
// not change the physical location of the signal. This is done by
// constructing an image of points, each corresponding to the physical
// location of the center of the voxel, then verifying that the value
// still matches the physical location.

namespace
{

// This function checks that all values in an image are equivalent to
// the physical point of the image.
template <typename TImageType>
bool
CheckValueIsPhysicalPoint(const TImageType * img)
{

  using IteratorType = itk::ImageRegionConstIterator<TImageType>;
  IteratorType it(img, img->GetBufferedRegion());

  bool match = true;

  typename TImageType::PointType pt;
  img->TransformIndexToPhysicalPoint(it.GetIndex(), pt);
  while (!it.IsAtEnd())
  {
    img->TransformIndexToPhysicalPoint(it.GetIndex(), pt);
    for (unsigned int i = 0; i < TImageType::ImageDimension; ++i)
    {
      EXPECT_DOUBLE_EQ(pt[i], it.Get()[i])
        << "Index: " << it.GetIndex() << " Point: " << pt << " Value: " << it.Get() << std::endl,
        match = false;
    }

    ++it;
  }
  return match;
}

template <typename TImageType>
typename TImageType::Pointer
RunFilter(const TImageType *             img,
          typename TImageType::IndexType start,
          typename TImageType::IndexType stop,
          int                            step[TImageType::ImageDimension])
{
  using FilterType = itk::SliceImageFilter<TImageType, TImageType>;
  auto sliceFilter = FilterType::New();

  sliceFilter->SetInput(img);
  sliceFilter->SetStart(start);
  sliceFilter->SetStop(stop);
  sliceFilter->SetStep(step);
  sliceFilter->UpdateLargestPossibleRegion();

  return sliceFilter->GetOutput();
}


} // namespace

TEST(SliceImageFilterTests, PhysicalPoint1)
{
  constexpr unsigned int ImageDimension = 2;
  using PixelType = itk::Point<double, ImageDimension>;
  using ImageType = itk::Image<PixelType, ImageDimension>;

  using SourceType = itk::PhysicalPointImageSource<ImageType>;
  auto source = SourceType::New();


  // these size are chosen as a power of two and a prime number.
  SourceType::SizeValueType size[] = { 128, 127 };
  source->SetSize(size);

  float origin[] = { 1.1f, 2.22f };
  source->SetOrigin(origin);


  int                  step[ImageDimension];
  ImageType::IndexType start;
  ImageType::IndexType stop;
  stop[0] = size[0];
  stop[1] = size[1];

  for (start[0] = 0; start[0] < 10; ++start[0])
    for (start[1] = 0; start[1] < 10; ++start[1])
      for (step[0] = 1; step[0] < 10; ++step[0])
        for (step[1] = 1; step[1] < 10; ++step[1])
        {

          ImageType::Pointer img;

          img = RunFilter<ImageType>(source->GetOutput(), start, stop, step);

          EXPECT_TRUE(CheckValueIsPhysicalPoint(img.GetPointer()))
            << "== Failed - step:" << step[0] << ' ' << step[1] << " start: " << start[0] << ' ' << start[1]
            << std::endl;
        }
}

TEST(SliceImageFilterTests, PhysicalPoint2)
{
  constexpr unsigned int ImageDimension = 2;
  using PixelType = itk::Point<double, ImageDimension>;
  using ImageType = itk::Image<PixelType, ImageDimension>;

  using SourceType = itk::PhysicalPointImageSource<ImageType>;
  auto source = SourceType::New();


  // these size are chosen as a power of two and a prime number.
  SourceType::SizeValueType size[] = { 128, 127 };
  source->SetSize(size);

  float origin[] = { 3.33f, 4.4444f };
  source->SetOrigin(origin);


  int                  step[ImageDimension] = { 3, 4 };
  ImageType::IndexType start;
  ImageType::IndexType stop;

  ASSERT_TRUE(size[0] > 10);
  ASSERT_TRUE(size[1] > 10);

  for (start[0] = 0; start[0] < 10; ++start[0])
    for (start[1] = 0; start[1] < 10; ++start[1])
      for (stop[0] = size[0]; stop[0] > (ImageType::IndexValueType)(size[0] - 10); --stop[0])
        for (stop[1] = size[1]; stop[1] > (ImageType::IndexValueType)(size[1] - 10); --stop[1])
        {

          ImageType::Pointer img;

          img = RunFilter<ImageType>(source->GetOutput(), start, stop, step);

          EXPECT_TRUE(CheckValueIsPhysicalPoint(img.GetPointer()))
            << "== Failed - step:" << step[0] << ' ' << step[1] << " start: " << start[0] << ' ' << start[1]
            << " stop: " << stop[0] << ' ' << stop[1] << std::endl;
        }
}


TEST(SliceImageFilterTests, PhysicalPoint3)
{
  constexpr unsigned int ImageDimension = 2;
  using PixelType = itk::Point<double, ImageDimension>;
  using ImageType = itk::Image<PixelType, ImageDimension>;

  using SourceType = itk::PhysicalPointImageSource<ImageType>;
  auto source = SourceType::New();


  // these size are chosen as a power of two and a prime number.
  SourceType::SizeValueType size[] = { 16, 17 };
  source->SetSize(size);

  float origin[] = { 3.33f, 4.4444f };
  source->SetOrigin(origin);

  int                  step[ImageDimension] = { -2, -2 };
  ImageType::IndexType start;
  ImageType::IndexType stop;

  for (start[0] = 10; start[0] < 20; ++start[0])
    for (start[1] = 10; start[1] < 20; ++start[1])
      for (stop[0] = -5; stop[0] > 12; --stop[0])
        for (stop[1] = -5; stop[1] > 12; --stop[1])
        {

          ImageType::Pointer img;

          img = RunFilter<ImageType>(source->GetOutput(), start, stop, step);

          EXPECT_TRUE(CheckValueIsPhysicalPoint(img.GetPointer()))
            << "== Failed - step:" << step[0] << ' ' << step[1] << " start: " << start[0] << ' ' << start[1]
            << " stop: " << stop[0] << ' ' << stop[1] << std::endl;
        }
}


TEST(SliceImageFilterTests, Empty)
{
  constexpr unsigned int ImageDimension = 2;
  using PixelType = itk::Point<double, ImageDimension>;
  using ImageType = itk::Image<PixelType, ImageDimension>;

  using SourceType = itk::PhysicalPointImageSource<ImageType>;
  auto source = SourceType::New();


  // these size are chosen as a power of two and a prime number.
  SourceType::SizeValueType size[] = { 32, 32 };
  source->SetSize(size);

  int                  step[ImageDimension] = { 1, 1 };
  ImageType::IndexType start;
  start.Fill(10);
  ImageType::IndexType stop;
  stop.Fill(10);


  ImageType::Pointer img;

  img = RunFilter<ImageType>(source->GetOutput(), start, stop, step);
  std::cout << img;

  for (unsigned int i = 0; i < ImageDimension; ++i)
  {
    EXPECT_EQ(0u, img->GetLargestPossibleRegion().GetSize()[i]);
  }

  start[0] = 2;
  start[1] = 2;
  stop[0] = 10;
  stop[1] = 2;

  img = RunFilter<ImageType>(source->GetOutput(), start, stop, step);

  EXPECT_EQ(8u, img->GetLargestPossibleRegion().GetSize()[0]);
  EXPECT_EQ(0u, img->GetLargestPossibleRegion().GetSize()[1]);
}

TEST(SliceImageFilterTests, Coverage)
{
  constexpr unsigned int ImageDimension = 3;
  using ImageType = itk::Image<float, ImageDimension>;

  using FilterType = itk::SliceImageFilter<ImageType, ImageType>;

  auto filter = FilterType::New();
  std::cout << filter;

  FilterType::IndexType idx;
  idx.Fill(10);

  filter->SetStart(idx);
  EXPECT_EQ(idx, filter->GetStart());

  idx.Fill(11);
  filter->SetStart(11);
  EXPECT_EQ(idx, filter->GetStart());

  idx.Fill(12);
  filter->SetStop(idx);
  EXPECT_EQ(idx, filter->GetStop());

  idx.Fill(13);
  filter->SetStop(13);
  EXPECT_EQ(idx, filter->GetStop());

  FilterType::ArrayType a;
  a.Fill(14);
  filter->SetStep(a);
  EXPECT_EQ(a, filter->GetStep());

  a.Fill(15);
  filter->SetStep(15);
  EXPECT_EQ(a, filter->GetStep());
}

TEST(SliceImageFilterTests, Sizes)
{
  constexpr unsigned int ImageDimension = 3;
  using ImageType = itk::Image<float, ImageDimension>;

  using SourceType = itk::GaussianImageSource<ImageType>;
  auto source = SourceType::New();

  SourceType::SizeType size = { { 64, 64, 64 } };
  source->SetSize(size);
  source->ReleaseDataFlagOn();

  using FilterType = itk::SliceImageFilter<ImageType, ImageType>;

  auto filter = FilterType::New();
  filter->SetInput(source->GetOutput());
  // check with default start, stop, step
  EXPECT_NO_THROW(filter->Update());
  EXPECT_EQ(filter->GetOutput()->GetLargestPossibleRegion().GetSize(), size) << "Check full size for defaults";


  filter = FilterType::New();
  filter->SetInput(source->GetOutput());
  filter->SetStart(64);
  filter->SetStop(-1);
  filter->SetStep(-1);
  EXPECT_NO_THROW(filter->Update());
  EXPECT_EQ(filter->GetOutput()->GetLargestPossibleRegion().GetSize(), size) << "Check full size for negative step";

  using namespace itk::GTest::TypedefsAndConstructors::Dimension3;

  source->SetSize(itk::MakeSize(19, 17, 11));

  filter = FilterType::New();
  filter->SetInput(source->GetOutput());
  filter->SetStep(2);
  EXPECT_NO_THROW(filter->Update()) << "Check same start and stop";
  EXPECT_EQ(10, filter->GetOutput()->GetLargestPossibleRegion().GetSize(0));
  EXPECT_EQ(9, filter->GetOutput()->GetLargestPossibleRegion().GetSize(1));
  EXPECT_EQ(6, filter->GetOutput()->GetLargestPossibleRegion().GetSize(2));

  source->SetSize(itk::MakeSize(5, 2, 3));

  std::cout << "CHECK" << std::endl;

  filter = FilterType::New();
  filter->SetInput(source->GetOutput());
  filter->SetStep(2);
  EXPECT_NO_THROW(filter->Update()) << "Check same start and stop";
  EXPECT_EQ(3, filter->GetOutput()->GetLargestPossibleRegion().GetSize(0));
  EXPECT_EQ(1, filter->GetOutput()->GetLargestPossibleRegion().GetSize(1));
  EXPECT_EQ(2, filter->GetOutput()->GetLargestPossibleRegion().GetSize(2));
}

TEST(SliceImageFilterTests, ExceptionalCases)
{
  constexpr unsigned int ImageDimension = 3;
  using ImageType = itk::Image<float, ImageDimension>;

  using SourceType = itk::GaussianImageSource<ImageType>;
  auto source = SourceType::New();


  using FilterType = itk::SliceImageFilter<ImageType, ImageType>;

  auto filter = FilterType::New();
  filter->SetInput(source->GetOutput());

  filter->SetStep(0);
  EXPECT_ANY_THROW(filter->Update()) << "Check with 0 step";

  // for some reason after the above exception, the pipeline is not
  // clean, and Verify input information will not get executed again,
  // so just create a new filter...
  filter = FilterType::New();
  filter->SetInput(source->GetOutput());
  filter->SetStart(10000);
  filter->SetStop(10001);
  EXPECT_NO_THROW(filter->Update()) << "Check with over-sized start stop are clamped to zero";
  EXPECT_EQ(0u, filter->GetOutput()->GetLargestPossibleRegion().GetNumberOfPixels());


  filter = FilterType::New();
  filter->SetInput(source->GetOutput());
  filter->SetStart(12);
  filter->SetStop(10);
  EXPECT_NO_THROW(filter->Update()) << "Check stop is clamped";
  EXPECT_EQ(0u, filter->GetOutput()->GetLargestPossibleRegion().GetNumberOfPixels());


  filter = FilterType::New();
  filter->SetInput(source->GetOutput());
  filter->SetStart(-12);
  filter->SetStop(-10);
  EXPECT_NO_THROW(filter->Update()) << "Check undersized start and stop";
  EXPECT_EQ(0u, filter->GetOutput()->GetLargestPossibleRegion().GetNumberOfPixels());

  filter = FilterType::New();
  filter->SetInput(source->GetOutput());
  filter->SetStart(1);
  filter->SetStop(1);
  EXPECT_NO_THROW(filter->Update()) << "Check same start and stop";
  EXPECT_EQ(0u, filter->GetOutput()->GetLargestPossibleRegion().GetNumberOfPixels());

  filter = FilterType::New();
  filter->SetInput(source->GetOutput());
  filter->SetStart(1);
  filter->SetStop(1);
  filter->SetStep(2);
  EXPECT_NO_THROW(filter->Update()) << "Check same start and stop";
  EXPECT_EQ(0u, filter->GetOutput()->GetLargestPossibleRegion().GetNumberOfPixels());

  filter = FilterType::New();
  filter->SetInput(source->GetOutput());
  filter->SetStart(-12);
  filter->SetStop(-10);
  filter->SetStep(-1);
  EXPECT_NO_THROW(filter->Update()) << "Check undersized start and stop";
  EXPECT_EQ(0u, filter->GetOutput()->GetLargestPossibleRegion().GetNumberOfPixels());

  filter = FilterType::New();
  filter->SetInput(source->GetOutput());
  filter->SetStart(4);
  filter->SetStop(4);
  filter->SetStep(-1);
  EXPECT_NO_THROW(filter->Update()) << "Check same start and stop with negative step";
  EXPECT_EQ(0u, filter->GetOutput()->GetLargestPossibleRegion().GetNumberOfPixels());

  filter = FilterType::New();
  filter->SetInput(source->GetOutput());
  filter->SetStart(4);
  filter->SetStop(4);
  filter->SetStep(-3);
  EXPECT_NO_THROW(filter->Update()) << "Check same start and stop with negative step";
  EXPECT_EQ(0u, filter->GetOutput()->GetLargestPossibleRegion().GetNumberOfPixels());
}
