/*=========================================================================
 *
 *  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 "itkConstantBoundaryCondition.h"
#include "itkPadImageFilter.h"

int
itkPadImageFilterTest(int, char *[])
{
  using ShortImage = itk::Image<short, 2>;
  using SizeType = ShortImage::SizeType;
  using SizeValueType = ShortImage::SizeValueType;

  using FilterType = itk::PadImageFilter<ShortImage, ShortImage>;
  auto padFilter = FilterType::New();

  SizeType           lowerBound = { { 1, 2 } };
  constexpr SizeType upperBound{ 3, 4 };
  SizeValueType      lowerBoundArray[2] = { 1, 2 };
  SizeValueType      upperBoundArray[2] = { 3, 4 };

  padFilter->SetPadLowerBound(lowerBound);
  if (padFilter->GetPadLowerBound() != lowerBound)
  {
    std::cerr << "Lower bound not recovered while testing SetLowerBound(SizeType). Got "
              << padFilter->GetPadLowerBound() << ", expected " << lowerBound << std::endl;
    return EXIT_FAILURE;
  }

  padFilter->SetPadLowerBound(lowerBoundArray);
  if (padFilter->GetPadLowerBound() != lowerBound)
  {
    std::cerr << "Lower bound not recovered while testing SetLowerBound(SizeValueType[]). Got "
              << padFilter->GetPadLowerBound() << ", expected " << lowerBound << std::endl;
    return EXIT_FAILURE;
  }

  padFilter->SetPadUpperBound(upperBound);
  if (padFilter->GetPadUpperBound() != upperBound)
  {
    std::cerr << "Upper bound not recovered while testing SetUpperBound(SizeType). Got "
              << padFilter->GetPadUpperBound() << ", expected " << upperBound << std::endl;
    return EXIT_FAILURE;
  }

  padFilter->SetPadUpperBound(upperBoundArray);
  if (padFilter->GetPadUpperBound() != upperBound)
  {
    std::cerr << "Upper bound not recovered while testing SetUpperBound(SizeValueType[]). Got "
              << padFilter->GetPadUpperBound() << ", expected " << upperBound << std::endl;
    return EXIT_FAILURE;
  }

  padFilter->SetPadBound(lowerBound);
  if (padFilter->GetPadLowerBound() != lowerBound)
  {
    std::cerr << "Lower bound not recovered while testing SetPadBound(). Got " << padFilter->GetPadLowerBound()
              << ", expected " << lowerBound << std::endl;
    return EXIT_FAILURE;
  }

  if (padFilter->GetPadUpperBound() != lowerBound)
  {
    std::cerr << "Upper bound not recovered while testing SetPadBound(). Got " << padFilter->GetPadUpperBound()
              << ", expected " << lowerBound << std::endl;
    return EXIT_FAILURE;
  }

  if (padFilter->GetBoundaryCondition() != nullptr)
  {
    std::cerr << "Default BoundaryCondition was not nullptr." << std::endl;
    return EXIT_FAILURE;
  }

  itk::ConstantBoundaryCondition<ShortImage> bc;
  bc.SetConstant(13);

  padFilter->SetBoundaryCondition(&bc);
  if (padFilter->GetBoundaryCondition() != &bc)
  {
    std::cerr << "Set/GetBoundaryCondition test failed." << std::endl;
    return EXIT_FAILURE;
  }

  // Fill in a test image
  auto                           inputImage = ShortImage::New();
  constexpr ShortImage::SizeType inputSize{ 1, 1 };
  const ShortImage::RegionType   inputRegion(inputSize);
  inputImage->SetRegions(inputRegion);
  inputImage->Allocate();
  inputImage->FillBuffer(1);

  lowerBound.Fill(2);
  padFilter->SetPadBound(lowerBound);

  padFilter->SetInput(inputImage);
  padFilter->UpdateLargestPossibleRegion();

  // Checkout output values
  const ShortImage::Pointer output = padFilter->GetOutput();
  for (int j = -2; j <= 2; ++j)
  {
    for (int i = -2; i <= 2; ++i)
    {
      ShortImage::IndexType index;
      index[0] = i;
      index[1] = j;

      const short pixel = output->GetPixel(index);
      std::cout << pixel << ' ';
      if (index[0] == 0 && index[1] == 0)
      {
        if (pixel != 1)
        {
          std::cerr << "Center pixel has unexpected value " << pixel << ", expected " << 1 << std::endl;
          return EXIT_FAILURE;
        }
      }
      else
      {
        if (pixel != 13)
        {
          std::cerr << "Padded pixel at index " << index << " has unexpected value " << pixel << ", expected " << 13
                    << std::endl;
          return EXIT_FAILURE;
        }
      }
    }
    std::cout << std::endl;
  }

  // Set the boundary condition back to nullptr and check that exception
  // is thrown.
  std::cout << "Testing that exception is thrown when boundary condition is nullptr." << std::endl;
  padFilter->SetBoundaryCondition(nullptr);
  try
  {
    padFilter->Update();
    std::cerr << "Failed to catch expected exception when boundary condition is nullptr." << std::endl;
    return EXIT_FAILURE;
  }
  catch (const itk::ExceptionObject & e)
  {
    std::cout << "Caught expected exception when boundary condition is nullptr." << std::endl;
    std::cout << e << std::endl;
  }
  catch (...)
  {
    std::cerr << "Exception caught, but not the expected kind." << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}
