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

#include "itkMath.h"
#include "itkImageRegionIteratorWithIndex.h"
#include "itkNumericTraits.h"
#include "itkMath.h"


template <typename TPixelType>
class itkImageIteratorWithIndexTestIteratorTester
{

public:
  using PixelType = TPixelType;

  using ImageType = itk::Image<PixelType, 3>;

  using IteratorType = itk::ImageRegionIteratorWithIndex<ImageType>;

  using ConstIteratorType = itk::ImageRegionConstIteratorWithIndex<ImageType>;

  itkImageIteratorWithIndexTestIteratorTester(const PixelType & value)
  {
    m_Image = ImageType::New();

    typename ImageType::SizeType size;
    size.Fill(100);

    typename ImageType::IndexType start;
    start.Fill(0);

    typename ImageType::RegionType region;
    region.SetSize(size);
    region.SetIndex(start);

    m_Image->SetRegions(region);
    m_Image->Allocate();

    m_Image->FillBuffer(value);
  }

  bool
  TestIterator()
  {
    IteratorType       it(m_Image, m_Image->GetBufferedRegion());
    itk::SizeValueType i = 0;
    it.GoToBegin();
    while (!it.IsAtEnd())
    {
      PixelType value = it.Get();
      PixelType testValue = value * static_cast<typename itk::NumericTraits<PixelType>::ValueType>(2);
      it.Set(testValue);
      if (itk::Math::NotExactlyEquals(it.Get(), testValue))
      {
        std::cerr << "TestIterator failed!" << std::endl;
        return false;
      }
      ++it;
      i++;
    }
    if (i != m_Image->GetBufferedRegion().GetNumberOfPixels())
    {
      std::cerr << "TestConstIterator failed (NumberOfPixels)!" << std::endl;
      return false;
    }
    return true;
  }

  bool
  TestConstIterator()
  {
    ConstIteratorType  it(m_Image, m_Image->GetBufferedRegion());
    itk::SizeValueType i = 0;
    it.GoToBegin();
    while (!it.IsAtEnd())
    {
      PixelType value = it.Get();
      if (itk::Math::NotExactlyEquals(value, it.Get())) // check repeatibility
      {
        std::cerr << "TestConstIterator failed!" << std::endl;
        return false;
      }
      ++it;
      i++;
    }
    if (i != m_Image->GetBufferedRegion().GetNumberOfPixels())
    {
      std::cerr << "TestConstIterator failed (NumberOfPixels)!" << std::endl;
      return false;
    }
    return true;
  }

  bool
  TestReverseIteration()
  {
    ConstIteratorType  it(m_Image, m_Image->GetBufferedRegion());
    itk::SizeValueType i = 0;
    it.GoToReverseBegin();
    while (!it.IsAtReverseEnd())
    {
      PixelType value = it.Get();
      if (itk::Math::NotExactlyEquals(value, it.Get())) // check repeatibility
      {
        std::cerr << "TestReverseIteration failed!" << std::endl;
        return false;
      }
      --it;
      i++;
    }
    if (i != m_Image->GetBufferedRegion().GetNumberOfPixels())
    {
      std::cerr << "TestConstIterator failed (NumberOfPixels)!" << std::endl;
      return false;
    }
    return true;
  }

private:
  typename ImageType::Pointer m_Image;
};

int
itkImageIteratorWithIndexTest(int, char *[])
{

  bool testPassed = true; // let's be optimistic

  // Instantiate image of various types and
  // test the iterators on them

  std::cout << "Testing with Image< char, 3 > " << std::endl;
  itkImageIteratorWithIndexTestIteratorTester<char> TesterC(10);
  if (TesterC.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterC.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterC.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< unsigned char, 3 > " << std::endl;
  itkImageIteratorWithIndexTestIteratorTester<unsigned char> TesterUC(10);
  if (TesterUC.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterUC.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterUC.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< short, 3 > " << std::endl;
  itkImageIteratorWithIndexTestIteratorTester<short> TesterS(10);
  if (TesterS.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterS.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterS.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< unsigned short, 3 > " << std::endl;
  itkImageIteratorWithIndexTestIteratorTester<unsigned short> TesterUS(10);
  if (TesterUS.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterUS.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterUS.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< int, 3 > " << std::endl;
  itkImageIteratorWithIndexTestIteratorTester<int> TesterI(10);
  if (TesterI.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterI.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterI.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< unsigned int, 3 > " << std::endl;
  itkImageIteratorWithIndexTestIteratorTester<unsigned int> TesterUI(10);
  if (TesterUI.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterUI.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterUI.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< float, 3 > " << std::endl;
  itkImageIteratorWithIndexTestIteratorTester<float> TesterF(10.0);
  if (TesterF.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterF.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterF.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< double, 3 > " << std::endl;
  itkImageIteratorWithIndexTestIteratorTester<double> TesterD(10.0);
  if (TesterD.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterD.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterD.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< itk::Vector<char,4>, 3 > " << std::endl;
  using VC = itk::Vector<char, 4>;
  VC vc;
  vc.Fill(127);
  itkImageIteratorWithIndexTestIteratorTester<VC> TesterVC(vc);
  if (TesterVC.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVC.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVC.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< itk::Vector<unsigned char,4>, 3 > " << std::endl;
  using VUC = itk::Vector<unsigned char, 4>;
  VUC vuc;
  vuc.Fill(10);
  itkImageIteratorWithIndexTestIteratorTester<VUC> TesterVUC(vuc);
  if (TesterVUC.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVUC.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVUC.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< itk::Vector<short,4>, 3 > " << std::endl;
  using VS = itk::Vector<short, 4>;
  VS vs;
  vs.Fill(10);
  itkImageIteratorWithIndexTestIteratorTester<VS> TesterVS(vs);
  if (TesterVS.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVS.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVS.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< itk::Vector<unsigned short,4>, 3 > " << std::endl;
  using VUS = itk::Vector<unsigned short, 4>;
  VUS vus;
  vus.Fill(10);
  itkImageIteratorWithIndexTestIteratorTester<VUS> TesterVUS(vus);
  if (TesterVUS.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVUS.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVUS.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< itk::Vector<int,4>, 3 > " << std::endl;
  using VI = itk::Vector<int, 4>;
  VI vi;
  vi.Fill(10);
  itkImageIteratorWithIndexTestIteratorTester<VI> TesterVI(vi);
  if (TesterVI.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVI.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVI.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< itk::Vector<unsigned int,4>, 3 > " << std::endl;
  using VUI = itk::Vector<unsigned int, 4>;
  VUI vui;
  vui.Fill(10);
  itkImageIteratorWithIndexTestIteratorTester<VUI> TesterVUI(vui);
  if (TesterVUI.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVUI.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVUI.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< itk::Vector<float,4>, 3 > " << std::endl;
  using VF = itk::Vector<float, 4>;
  VF vf;
  vf.Fill(10);
  itkImageIteratorWithIndexTestIteratorTester<VF> TesterVF(vf);
  if (TesterVF.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVF.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVF.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  std::cout << "Testing with Image< itk::Vector<double,4>, 3 > " << std::endl;
  using VD = itk::Vector<double, 4>;
  VD vd;
  vd.Fill(10);
  itkImageIteratorWithIndexTestIteratorTester<VD> TesterVD(vd);
  if (TesterVD.TestIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVD.TestConstIterator() == false)
  {
    testPassed = false;
  }
  if (TesterVD.TestReverseIteration() == false)
  {
    testPassed = false;
  }

  if (!testPassed)
  {
    std::cout << "Failed" << std::endl;
    return EXIT_FAILURE;
  }

  std::cout << "Success" << std::endl;
  return EXIT_SUCCESS;
}
