/*=========================================================================
 *
 *  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 "itkNeighborhoodIteratorTestCommon.hxx"
#include "itkConstShapedNeighborhoodIterator.h"
#include <array>

void
PrintShapedNeighborhood(const itk::ConstShapedNeighborhoodIterator<TestImageType> & n)
{
  std::cout << n.GetIndex() << "->[";
  for (itk::ConstShapedNeighborhoodIterator<TestImageType>::ConstIterator it = n.Begin(); !it.IsAtEnd(); ++it)
  {
    std::cout << it.Get();
  }
  std::cout << ']' << std::endl;
}

int
itkConstShapedNeighborhoodIteratorTest(int, char *[])
{
  const TestImageType::Pointer                                   img = GetTestImage(10, 10, 5, 3);
  itk::ConstShapedNeighborhoodIterator<TestImageType>::IndexType loc;
  loc[0] = 4;
  loc[1] = 4;
  loc[2] = 2;
  loc[3] = 1;

  // radius of the iterator
  itk::ConstShapedNeighborhoodIterator<TestImageType>::RadiusType radius{ 1, 1, 1, 1 };
  // region over which the iterator is defined

  constexpr itk::ConstShapedNeighborhoodIterator<TestImageType>::SizeType  sz{ 10, 10, 5, 1 };
  constexpr itk::ConstShapedNeighborhoodIterator<TestImageType>::IndexType idx{ 0, 0, 0, 1 };
  itk::ConstShapedNeighborhoodIterator<TestImageType>::RegionType          reg = { idx, sz };

  // initialize an iterator
  println("Creating ConstShapedNeighborhoodIterator");
  itk::ConstShapedNeighborhoodIterator<TestImageType> it(radius, img, reg);
  it.Print(std::cout);

  println("Moving iterator using SetLocation()");
  it.SetLocation(loc);
  it.Print(std::cout);

  println("Testing GoToBegin()");
  it.GoToBegin();
  it.Print(std::cout);

  println("Testing IsAtBegin()");
  std::cout << it.IsAtBegin() << std::endl;

  println("Testing GoToEnd()");
  it.GoToEnd();
  it.Print(std::cout);

  println("Testing IsAtEnd()");
  std::cout << it.IsAtEnd() << std::endl;

  println("Testing forward iteration");
  it.GoToBegin();
  itk::ConstShapedNeighborhoodIterator<TestImageType>::OffsetType off;
  off[0] = 0;
  off[1] = 0;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);
  while (!it.IsAtEnd())
  {
    PrintShapedNeighborhood(it);
    ++it;
  }

  println("Testing reverse iteration");
  it.GoToEnd();
  while (!it.IsAtBegin())
  {
    PrintShapedNeighborhood(it);
    --it;
  }

  println("Moving iterator: it.GoToBegin(); it += (1, 1, 1, 1)");
  it.GoToBegin();
  off[0] = 1;
  off[1] = 1;
  off[2] = 1;
  off[3] = 1;
  it += off;
  PrintShapedNeighborhood(it);

  println("Moving iterator: it -= (1, 1, 1, 1)");
  it -= off;
  PrintShapedNeighborhood(it);

  println("Moving iterator using SetLocation()");
  it.SetLocation(loc);
  it.Print(std::cout);

  println("Initializing ConstShapedNeighborhoodIterator");
  println("...turn on [0,0,0,0], the center pixel");
  off[0] = 0;
  off[1] = 0;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);
  it.Print(std::cout);

  for (unsigned int r = 0; r < 1; ++r)
  {
    println("...turn on [1,0,0,0]");
    off[0] = 1;
    off[1] = 0;
    off[2] = 0;
    off[3] = 0;
    it.ActivateOffset(off);
    it.Print(std::cout);

    println("...turn on [1,0,0,0] again");
    off[0] = 1;
    off[1] = 0;
    off[2] = 0;
    off[3] = 0;
    it.ActivateOffset(off);
    it.Print(std::cout);

    println("...turn on [-1,0,0,0]");
    off[0] = -1;
    off[1] = 0;
    off[2] = 0;
    off[3] = 0;
    it.ActivateOffset(off);
    it.Print(std::cout);

    println("...turn on [0,-1,0,0]");
    off[0] = 0;
    off[1] = -1;
    off[2] = 0;
    off[3] = 0;
    it.ActivateOffset(off);
    it.Print(std::cout);

    println("...turn on [0,1,0,0]");
    off[0] = 0;
    off[1] = 1;
    off[2] = 0;
    off[3] = 0;
    it.ActivateOffset(off);
    it.Print(std::cout);

    println("...turn off [-1,0,0,0]");
    off[0] = -1;
    off[1] = 0;
    off[2] = 0;
    off[3] = 0;
    it.DeactivateOffset(off);
    it.Print(std::cout);

    println("...turn off [1,0,0,0]");
    off[0] = 1;
    off[1] = 0;
    off[2] = 0;
    off[3] = 0;
    it.DeactivateOffset(off);
    it.Print(std::cout);

    println("...turn off [0,1,0,0]");
    off[0] = 0;
    off[1] = 1;
    off[2] = 0;
    off[3] = 0;
    it.DeactivateOffset(off);
    it.Print(std::cout);

    println("...turn off [0,-1,0,0]");
    off[0] = 0;
    off[1] = -1;
    off[2] = 0;
    off[3] = 0;
    it.DeactivateOffset(off);
    it.Print(std::cout);

    println("...turn off [0,-1,0,0] again");
    off[0] = 0;
    off[1] = -1;
    off[2] = 0;
    off[3] = 0;
    it.DeactivateOffset(off);
    it.Print(std::cout);

    println("...turn off [0,0 ,0,0]");
    off[0] = 0;
    off[1] = 0;
    off[2] = 0;
    off[3] = 0;
    it.DeactivateOffset(off);
    it.Print(std::cout);

    println("...turn on [1,0,0,0]");
    off[0] = 1;
    off[1] = 0;
    off[2] = 0;
    off[3] = 0;
    it.ActivateOffset(off);
    it.Print(std::cout);

    println("...turn on [1,0,0,0] again");
    off[0] = 1;
    off[1] = 0;
    off[2] = 0;
    off[3] = 0;
    it.ActivateOffset(off);
    it.Print(std::cout);

    println("...turn on [-1,0,0,0]");
    off[0] = -1;
    off[1] = 0;
    off[2] = 0;
    off[3] = 0;
    it.ActivateOffset(off);
    it.Print(std::cout);

    println("...turn on [0,-1,0,0]");
    off[0] = 0;
    off[1] = -1;
    off[2] = 0;
    off[3] = 0;
    it.ActivateOffset(off);
    it.Print(std::cout);

    println(" Testing it.ClearActiveList() ");
    it.ClearActiveList();
    it.Print(std::cout);

    println(" NOW REPEAT ");
  }

  println("...turn on [1,0,0,0]");
  off[0] = 1;
  off[1] = 0;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);
  it.Print(std::cout);

  println("...turn on [1,0,0,0] again");
  off[0] = 1;
  off[1] = 0;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);
  it.Print(std::cout);

  println("...turn on [-1,0,0,0]");
  off[0] = -1;
  off[1] = 0;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);
  it.Print(std::cout);

  println("...turn on [0,-1,0,0]");
  off[0] = 0;
  off[1] = -1;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);
  it.Print(std::cout);

  println("...turn on [0,1,0,0]");
  off[0] = 0;
  off[1] = 1;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);
  it.Print(std::cout);

  std::cout << "it.GetActiveIndexListSize()=" << it.GetActiveIndexListSize();

  println("Testing GetActiveIndexList()");
  itk::ConstShapedNeighborhoodIterator<TestImageType>::IndexListType l = it.GetActiveIndexList();
  auto                                                               ali = l.begin();
  while (ali != l.end())
  {
    std::cout << *ali << ' ';
    ++ali;
  }
  std::cout << std::endl;

  println("Testing const iteration through the neighborhood.");
  {
    println("Testing using IsAtEnd()");
    itk::ConstShapedNeighborhoodIterator<TestImageType>::ConstIterator ci = it.Begin();
    while (!ci.IsAtEnd())
    {
      std::cout << ci.GetNeighborhoodIndex() << " -> " << ci.GetNeighborhoodOffset() << " = " << ci.Get() << std::endl;
      ++ci;
    }
  }

  println("Testing using != it.End()");
  for (auto ci = it.Begin(); ci != it.End(); ++ci)
  {
    std::cout << ci.GetNeighborhoodIndex() << " -> " << ci.GetNeighborhoodOffset() << " = " << ci.Get() << std::endl;
  }

  println("Testing reverse iteration using != it.Begin()");
  auto ci = it.End();
  --ci;
  while (ci != it.Begin())
  {
    std::cout << ci.GetNeighborhoodIndex() << " -> " << ci.GetNeighborhoodOffset() << " = " << ci.Get() << std::endl;
    --ci;
  }
  std::cout << ci.GetNeighborhoodIndex() << " -> " << ci.GetNeighborhoodOffset() << " = " << ci.Get() << std::endl;

  std::cout << std::endl;
  std::cout << "------------------------------" << std::endl;
  std::cout << std::endl;
  println("Testing activating and deactivating pixels on-the-fly");
  println("it.GoToBegin(); it.ClearActiveList();  Activate 1 0 0 0 and -1 0 0 0 and 0 0 0 0");
  it.GoToBegin();
  it.ClearActiveList();
  off[0] = -1;
  off[1] = 0;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);

  off[0] = 1;
  off[1] = 0;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);

  off[0] = 0;
  off[1] = 0;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);

  PrintShapedNeighborhood(it);

  println("Move the neighborhood two pixels using operator ++");
  ++it;
  ++it;
  PrintShapedNeighborhood(it);

  println("Clear the active list");
  it.ClearActiveList();
  PrintShapedNeighborhood(it);

  println("Move the neighborhood one pixel using operator ++");
  ++it;

  println("Reactivate the same indices");
  off[0] = -1;
  off[1] = 0;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);
  off[0] = 1;
  off[1] = 0;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);
  off[0] = 0;
  off[1] = 0;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);

  PrintShapedNeighborhood(it);

  println("Activate 0 1 0 0");
  off[0] = 0;
  off[1] = 1;
  off[2] = 0;
  off[3] = 0;
  it.ActivateOffset(off);
  PrintShapedNeighborhood(it);

  println("Testing operator=");
  itk::ConstShapedNeighborhoodIterator<TestImageType> oeIt;
  oeIt = it;
  PrintShapedNeighborhood(it);
  PrintShapedNeighborhood(oeIt);

  it.Print(std::cout);
  oeIt.Print(std::cout);

  int result = EXIT_SUCCESS;

  // Iterate over a region, then change the region and iterate over the new region
  {
    // Create an image
    using ChangeRegionTestImageType = itk::Image<int, 2>;
    auto                                        imageSize = ChangeRegionTestImageType::SizeType::Filled(4);
    const ChangeRegionTestImageType::RegionType imageRegion{ imageSize };

    auto image = ChangeRegionTestImageType::New();
    image->SetRegions(imageRegion);
    image->Allocate();

    itk::ImageRegionIterator<ChangeRegionTestImageType> createImageIterator(image, imageRegion);

    // Set all pixels with first index == 0 to 0, and set the rest of the image to 255
    while (!createImageIterator.IsAtEnd())
    {
      if (createImageIterator.ComputeIndex()[0] == 0)
      {
        createImageIterator.Set(0);
      }
      else
      {
        createImageIterator.Set(255);
      }

      ++createImageIterator;
    }

    // Setup and iterate over the first region
    auto region1Start = ChangeRegionTestImageType::IndexType::Filled(1);

    auto regionSize = ChangeRegionTestImageType::SizeType::Filled(1);

    const ChangeRegionTestImageType::RegionType region1(region1Start, regionSize);

    // Create the radius (a 3x3 region)
    auto neighborhoodRadius = ChangeRegionTestImageType::SizeType::Filled(1);

    // Use the first two offsets
    std::vector<itk::Offset<2>>           offsets;
    ChangeRegionTestImageType::OffsetType offset = { { -1, -1 } };
    offsets.push_back(offset);
    offset[0] = 0;
    offset[1] = 0;
    offsets.push_back(offset);

    using ShapedNeighborhoodIteratorType = itk::ConstShapedNeighborhoodIterator<ChangeRegionTestImageType>;
    ShapedNeighborhoodIteratorType shapedNeighborhoodIterator(neighborhoodRadius, image, region1);

    // Activate all of the offsets
    for (const auto & offset_item : offsets)
    {
      shapedNeighborhoodIterator.ActivateOffset(offset_item);
    }

    static constexpr std::array<int, 2> expectedValuesRegion1{ 0, 255 };

    unsigned int counter = 0;
    // while(!shapedNeighborhoodIterator.IsAtEnd()) // no need for this loop as we are only iterating over a 1x1 region
    //{
    ShapedNeighborhoodIteratorType::ConstIterator pixelIterator = shapedNeighborhoodIterator.Begin();

    while (!pixelIterator.IsAtEnd())
    {
      if (pixelIterator.Get() != expectedValuesRegion1[counter])
      {
        result = EXIT_FAILURE;
      }
      counter++;
      ++pixelIterator;
    }

    //++imageIterator;
    //}

    // Change iteration region
    auto region2start = ChangeRegionTestImageType::IndexType::Filled(2);

    const ChangeRegionTestImageType::RegionType region2(region2start, regionSize);

    shapedNeighborhoodIterator.SetRegion(region2);
    shapedNeighborhoodIterator.GoToBegin();

    static constexpr std::array<int, 2> expectedValuesRegion2{ 255, 255 };

    counter = 0;
    // while(!shapedNeighborhoodIterator.IsAtEnd()) // no need for this loop as we are only iterating over a 1x1 region
    //{
    pixelIterator = shapedNeighborhoodIterator.Begin();
    while (!pixelIterator.IsAtEnd())
    {
      if (pixelIterator.Get() != expectedValuesRegion2[counter])
      {
        result = EXIT_FAILURE;
      }
      counter++;
      ++pixelIterator;
    }
    //++imageIterator;
    //}

  } // end "Change Region" test

  return result;
}

//
// this is kind of a duff test, in that it doesn't fail w/the old code
// at runtime, it won't compile at all.  But it does at least do
// coverage of the newly exposed methods.
template <typename ImageType>
class MyDerivedCSNI : public itk::ConstShapedNeighborhoodIterator<ImageType>
{
public:
  using Superclass = itk::ConstShapedNeighborhoodIterator<ImageType>;
  using typename Superclass::SizeType;
  using typename Superclass::IndexType;
  using typename Superclass::RadiusType;
  using typename Superclass::RegionType;

  void
  TestNewExposedProtectedMembers();
  MyDerivedCSNI(const SizeType & radius, const ImageType * ptr, const RegionType & region)
    : Superclass(radius, const_cast<ImageType *>(ptr), region)
  {}
};

template <typename ImageType>
void
MyDerivedCSNI<ImageType>::TestNewExposedProtectedMembers()
{
  bool needToUseBoundaryCondition(this->GetNeedToUseBoundaryCondition());
  this->NeedToUseBoundaryConditionOn();
  this->NeedToUseBoundaryConditionOff();
  this->SetNeedToUseBoundaryCondition(needToUseBoundaryCondition);
}
