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

#include "itkTestingMacros.h"
#include "itkImageFileWriter.h"

//
// This is a simple test of the AntiAliasBinaryImageFilter class.  It creates a
// binary image of a sphere, then applies the filter.  If you want to view the
// output, uncomment the lines which write the binary file.  Output will be the
// floating point distance transform from the surface, which can be directly
// rendered using Marching Cubes or some other suitable algorithm.  See
// documentation for AntiAliasBinaryImageFilter and
// SparseFieldLevelSetImageFilter for more information.
//
namespace AntiAliasBinaryImageFilterTestNamespace
{

constexpr int V_WIDTH = 64;
constexpr int V_HEIGHT = 64;
constexpr int V_DEPTH = 64;

float
sphere(float x, float y, float z)
{
  float dis;
  dis = (x - static_cast<float>(V_WIDTH) / 2.0) * (x - static_cast<float>(V_WIDTH) / 2.0) /
          ((0.2f * V_WIDTH) * (0.2f * V_WIDTH)) +
        (y - static_cast<float>(V_HEIGHT) / 2.0) * (y - static_cast<float>(V_HEIGHT) / 2.0) /
          ((0.2f * V_HEIGHT) * (0.2f * V_HEIGHT)) +
        (z - static_cast<float>(V_DEPTH) / 2.0) * (z - static_cast<float>(V_DEPTH) / 2.0) /
          ((0.2f * V_DEPTH) * (0.2f * V_DEPTH));
  return (1.0f - dis);
}

void
evaluate_function(itk::Image<char, 3> * im, float (*f)(float, float, float))
{
  itk::Image<char, 3>::IndexType idx;

  for (int z = 0; z < V_DEPTH; ++z)
  {
    idx[2] = z;
    for (int y = 0; y < V_HEIGHT; ++y)
    {
      idx[1] = y;
      for (int x = 0; x < V_WIDTH; ++x)
      {
        idx[0] = x;
        if (f(static_cast<float>(x), static_cast<float>(y), static_cast<float>(z)) >= 0.0)
        {
          im->SetPixel(idx, 1);
        }
        else
        {
          im->SetPixel(idx, 0);
        }
      }
    }
  }
}

} // namespace AntiAliasBinaryImageFilterTestNamespace

int
itkAntiAliasBinaryImageFilterTest(int argc, char * argv[])
{
  if (argc < 2)
  {
    std::cerr << "Missing arguments." << std::endl;
    std::cerr << "Usage: " << std::endl;
    std::cerr << itkNameOfTestExecutableMacro(argv) << " outputImage " << std::endl;
    return EXIT_FAILURE;
  }
  const char * outputImage = argv[1];

  using InputDataType = char;
  using BinaryImageType = itk::Image<InputDataType, 3>;
  using RealImageType = itk::Image<float, 3>;

  itk::AntiAliasBinaryImageFilter<BinaryImageType, RealImageType>::Pointer antialiaser =
    itk::AntiAliasBinaryImageFilter<BinaryImageType, RealImageType>::New();

  // Create a binary image of a sphere.
  auto                                   image = BinaryImageType::New();
  BinaryImageType::RegionType            region;
  BinaryImageType::RegionType::SizeType  sz;
  BinaryImageType::RegionType::IndexType idx;
  for (unsigned int k = 0; k < 3; ++k)
  {
    sz[k] = 64;
    idx[k] = 0;
  }
  region.SetSize(sz);
  region.SetIndex(idx);
  image->SetRegions(region);
  image->Allocate();
  AntiAliasBinaryImageFilterTestNamespace::evaluate_function(image, AntiAliasBinaryImageFilterTestNamespace::sphere);

  // Set up and apply the filter.
  antialiaser->SetInput(image);
  antialiaser->SetMaximumRMSError(0.02);

  // For increased code coverage.  Does nothing.
  antialiaser->GetMaximumRMSError();


  // Generally a good idea to set this value as a safeguard against infinite
  // loops if the MaximumRMSError has been set too low.
  antialiaser->SetNumberOfIterations(100);

  ITK_TRY_EXPECT_NO_EXCEPTION(antialiaser->Update());

  std::cout << "Maximum RMS change value threshold was: 0.02 " << std::endl;
  std::cout << "Last RMS change value was: " << antialiaser->GetRMSChange() << std::endl;
  if (antialiaser->GetRMSChange() > 0.02)
  {
    std::cerr << "Unexpected RMSChange." << std::endl;
    return EXIT_FAILURE;
  }

  if (antialiaser->GetElapsedIterations() >= 100)
  {
    std::cout << "ERROR: Filter did not converge.";
    return EXIT_FAILURE;
  }
  else
  {
    using WriterType = itk::ImageFileWriter<RealImageType>;
    auto writer = WriterType::New();
    writer->SetInput(antialiaser->GetOutput());
    writer->SetFileName(outputImage);
    ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update());

    // Repeat just to make sure we reinitialize properly.
    antialiaser->SetNumberOfIterations(200);
    antialiaser->Update();

    antialiaser->GetLowerBinaryValue();
    antialiaser->GetUpperBinaryValue();

    std::cout << "Maximum RMS change value threshold was: 0.02 " << std::endl;
    std::cout << "Last RMS change value was: " << antialiaser->GetRMSChange() << std::endl;

    if (antialiaser->GetRMSChange() > 0.02)
    {
      std::cerr << "Unexpected RMSChange." << std::endl;
      return EXIT_FAILURE;
    }
  }

  return EXIT_SUCCESS;
}
