/*=========================================================================
 *
 *  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.
 *
 *=========================================================================*/
/*=========================================================================
 *
 *  Portions of this file are subject to the VTK Toolkit Version 3 copyright.
 *
 *  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
 *
 *  For complete copyright, license and disclaimer of warranty information
 *  please refer to the NOTICE file at the top of the ITK source tree.
 *
 *=========================================================================*/

#include <iostream>

#include "itkBSplineDownsampleImageFilter.h"
#include "itkBSplineUpsampleImageFilter.h"
#include "itkSimpleFilterWatcher.h"

using InputPixelType = double;
using IntInputPixelType = int;

// Set up for 2D Images
enum
{
  ImageDimension2D = 2
};

using ImageType2D = itk::Image<InputPixelType, ImageDimension2D>;
using ImageTypePtr2D = ImageType2D::Pointer;
using SizeType2D = ImageType2D::SizeType;
using InputIterator = itk::ImageRegionIterator<ImageType2D>;

using IntImageType2D = itk::Image<IntInputPixelType, ImageDimension2D>;
using IntImageTypePtr2D = IntImageType2D::Pointer;
using IntSizeType2D = IntImageType2D::SizeType;
using IntInputIterator = itk::ImageRegionIterator<IntImageType2D>;

void set2DData(ImageType2D::Pointer);

void
PrintImageData(ImageTypePtr2D imgPtr)
{
  using Iterator = itk::ImageLinearIteratorWithIndex<ImageType2D>;

  std::cout << "Size: " << imgPtr->GetLargestPossibleRegion().GetSize() << std::endl;
  int dim = ImageType2D::ImageDimension;

  std::cout << "Spacing: " << std::endl;
  for (int n = 0; n < dim; ++n)
  {
    std::cout << imgPtr->GetSpacing()[n] << ", ";
  }
  std::cout << std::endl;
  Iterator outIt(imgPtr, imgPtr->GetLargestPossibleRegion());
  outIt.SetDirection(0);

  SizeType2D size = imgPtr->GetLargestPossibleRegion().GetSize();

  std::cout << "Data: " << std::endl;
  for (int n = 0; n < dim - 1; ++n)
  {
    for (unsigned int jj = 0; jj < size[n + 1]; ++jj)
    {

      while (!outIt.IsAtEndOfLine())
      {
        std::cout << outIt.Get() << ", ";
        ++outIt;
      }
      outIt.NextLine();
      std::cout << std::endl;
    }
  }
}

void
set2DData(ImageType2D::Pointer imgPtr)
{
  SizeType2D size = { { 4, 4 } };
  double     mydata[49] = { 0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 3, 2 };

  ImageType2D::RegionType region;
  region.SetSize(size);

  imgPtr->SetLargestPossibleRegion(region);
  imgPtr->SetBufferedRegion(region);
  imgPtr->Allocate();

  // Set origin and spacing of physical coordinates
  double origin[] = { 0.5, 1.0 };
  double spacing[] = { 0.1, 0.5 };
  imgPtr->SetOrigin(origin);
  imgPtr->SetSpacing(spacing);

  InputIterator inIter(imgPtr, region);

  int j = 0;
  while (!inIter.IsAtEnd())
  {
    inIter.Set(mydata[j]);
    ++inIter;
    ++j;
  }
}

void
setInt2DData(IntImageType2D::Pointer imgPtr)
{
  IntSizeType2D size = { { 4, 4 } };
  int           mydata[49] = { 0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 3, 2 };

  IntImageType2D::RegionType region;
  region.SetSize(size);

  imgPtr->SetLargestPossibleRegion(region);
  imgPtr->SetBufferedRegion(region);
  imgPtr->Allocate();

  // Set origin and spacing of physical coordinates
  double origin[] = { 0.5, 1.0 };
  double spacing[] = { 0.1, 0.5 };
  imgPtr->SetOrigin(origin);
  imgPtr->SetSpacing(spacing);

  IntInputIterator inIter(imgPtr, region);

  int j = 0;
  while (!inIter.IsAtEnd())
  {
    inIter.Set(mydata[j]);
    ++inIter;
    ++j;
  }
}

bool
VerifyResultsHigherOrderSpline(ImageTypePtr2D ActualResults, double * ExpectedResults)
{
  double * ERptr;
  ERptr = ExpectedResults;

  InputIterator ActualResultsIter(ActualResults, ActualResults->GetLargestPossibleRegion());
  double        percentErr = 0;

  while (!ActualResultsIter.IsAtEnd())
  {
    double val1 = ActualResultsIter.Get();

    percentErr += itk::Math::abs((val1 - *ERptr) / val1);

    ++ActualResultsIter;
    ++ERptr;
  }

  // Mean error is determined over the number of pixels, which in the test case is 16
  if ((percentErr / 16) > 0.05)
  {
    // std::cout << "*** Error: total error is more than 10%: " << percentErr << std::endl;
    return false;
  }
  return true;
}

bool
VerifyResults3rdOrderSpline(ImageTypePtr2D ActualResults, double * ExpectedResults)
{
  double * ERptr;
  ERptr = ExpectedResults;

  InputIterator ActualResultsIter(ActualResults, ActualResults->GetLargestPossibleRegion());

  while (!ActualResultsIter.IsAtEnd())
  {
    double val1 = ActualResultsIter.Get();
    if (itk::Math::abs(val1 - *ERptr) > 1e-6)
    {
      // std::cout << "*** Error: value should be " << trueValue << std::endl;
      return false;
    }
    ++ActualResultsIter;
    ++ERptr;
  }
  return true;
}

bool
VerifyResults2ndOrderSpline(ImageTypePtr2D ActualResults, double * ExpectedResults)
{
  double * ERptr;
  ERptr = ExpectedResults;

  InputIterator ActualResultsIter(ActualResults, ActualResults->GetLargestPossibleRegion());
  double        percentErr = 0;

  while (!ActualResultsIter.IsAtEnd())
  {
    double val1 = ActualResultsIter.Get();

    percentErr += itk::Math::abs((val1 - *ERptr) / val1);

    ++ActualResultsIter;
    ++ERptr;
  }

  // Mean error is determined over the number of pixels, which in the test case is 16
  if ((percentErr / 16) > 0.1)
  {
    // std::cout << "*** Error: total error is more than 10%: " << percentErr << std::endl;
    return false;
  }
  return true;
}

// Spline order 1 or 0
bool
VerifyResultsLowerOrderSpline(ImageTypePtr2D ActualResults, double * ExpectedResults)
{
  double * ERptr;
  ERptr = ExpectedResults;

  InputIterator ActualResultsIter(ActualResults, ActualResults->GetLargestPossibleRegion());
  double        percentErr = 0;

  while (!ActualResultsIter.IsAtEnd())
  {
    double val1 = ActualResultsIter.Get();

    percentErr += itk::Math::abs((val1 - *ERptr) / val1);

    ++ActualResultsIter;
    ++ERptr;
  }

  // Mean error is determined over the number of pixels, which in the test case is 16
  // Threshold error is much higher since the order of the spline is lower.
  if ((percentErr) / 16 > 0.6)
  {
    return false;
  }
  return true;
}


int
test2D_Standard_l2_NthOrderSpline_filter(unsigned int splineOrder)
{
  int flag = 0;

  /* Allocate a simple test image */
  ImageTypePtr2D image = ImageType2D::New();

  set2DData(image);
  double ExpectedResults[] = { 0.517188, 1.575548, 2.634784, 2.847124, 1.547224, 2.323852, 3.101771, 3.257328,
                               2.578121, 3.073447, 3.570484, 3.669343, 2.784775, 3.223312, 3.663641, 3.751054 };

  // l2 norm resampler.
  using DownsamplerType2D = itk::BSplineDownsampleImageFilter<ImageType2D, ImageType2D>;
  using UpsamplerType2D = itk::BSplineUpsampleImageFilter<ImageType2D, ImageType2D>;

  auto                     downSampler = DownsamplerType2D::New();
  itk::SimpleFilterWatcher downWatcher(downSampler, "test2D_Standard_l2_filter");

  auto                     upSampler = UpsamplerType2D::New();
  itk::SimpleFilterWatcher upWatcher(upSampler, "test2D_Standard_l2_filter");

  downSampler->SetSplineOrder(splineOrder);
  upSampler->SetSplineOrder(splineOrder);

  downSampler->SetInput(image);
  downSampler->Update();
  ImageTypePtr2D outImage1 = downSampler->GetOutput();
  PrintImageData(outImage1);
  upSampler->SetInput(outImage1);
  upSampler->Update();
  ImageTypePtr2D outImage2 = upSampler->GetOutput();
  PrintImageData(outImage2);

  bool sameResults = false;
  if (splineOrder == 3)
  {
    sameResults = VerifyResults3rdOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 2)
  {
    sameResults = VerifyResultsLowerOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 1)
  {
    sameResults = VerifyResultsLowerOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 0)
  {
    sameResults = VerifyResultsLowerOrderSpline(outImage2, ExpectedResults);
  }

  if (!sameResults)
  {
    flag = 1;
    std::cout << "*** Error: unexpected value in Standard l2 - resampler with order " << splineOrder << "  spline."
              << std::endl;
    std::cout << "" << std::endl;
  }
  else
  {
    std::cout << "Tests for Standard l2 - resampler with order " << splineOrder << "  spline PASSED " << std::endl;
    std::cout << "" << std::endl;
  }

  return flag;
}


int
test2D_Standard_L2_NthOrderSpline_filter(unsigned int splineOrder)
{
  int flag = 0;

  // Allocate a simple test image
  ImageTypePtr2D image = ImageType2D::New();

  set2DData(image);
  double ExpectedResults[] = { 0.527796, 1.584850, 2.642784, 2.854860, 1.554069, 2.328161, 3.103548, 3.258594,
                               2.581206, 3.072767, 3.566038, 3.664140, 2.787102, 3.221627, 3.657944, 3.744552 };

  // L2 norm resampler.
  using ResamplerType = itk::BSplineL2ResampleImageFilterBase<ImageType2D, ImageType2D>;
  using DownsamplerType2D = itk::BSplineDownsampleImageFilter<ImageType2D, ImageType2D, ResamplerType>;
  using UpsamplerType2D = itk::BSplineUpsampleImageFilter<ImageType2D, ImageType2D, ResamplerType>;

  auto                     downSampler = DownsamplerType2D::New();
  itk::SimpleFilterWatcher downWatcher(downSampler, "test2D_Standard_L2_filter");

  auto                     upSampler = UpsamplerType2D::New();
  itk::SimpleFilterWatcher upWatcher(upSampler, "test2D_Standard_L2_filter");

  downSampler->SetSplineOrder(splineOrder);
  upSampler->SetSplineOrder(splineOrder);

  downSampler->SetInput(image);
  downSampler->Update();
  ImageTypePtr2D outImage1 = downSampler->GetOutput();
  PrintImageData(outImage1);
  upSampler->SetInput(outImage1);
  upSampler->Update();
  ImageTypePtr2D outImage2 = upSampler->GetOutput();
  PrintImageData(outImage2);

  bool sameResults = false;
  if (splineOrder == 5)
  {
    sameResults = VerifyResultsHigherOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 3)
  {
    sameResults = VerifyResults3rdOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 1)
  {
    sameResults = VerifyResultsLowerOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 0)
  {
    sameResults = VerifyResultsLowerOrderSpline(outImage2, ExpectedResults);
  }

  if (!sameResults)
  {
    flag = 1;
    std::cout << "*** Error: unexpected value in Standard L2 - resampler with order " << splineOrder << "  spline."
              << std::endl;
    std::cout << "" << std::endl;
  }
  else
  {
    std::cout << "Tests for Standard L2 - resampler with order " << splineOrder << "  spline PASSED " << std::endl;
    std::cout << "" << std::endl;
  }

  return flag;
}

int
test2D_Centered_l2_NthOrderSpline_filter(unsigned int splineOrder)
{
  int flag = 0;

  // Allocate a simple test image
  ImageTypePtr2D image = ImageType2D::New();

  set2DData(image);
  double ExpectedResults[] = { 0.124139, 0.606412, 1.322693, 1.803619, 0.580514, 1.719005, 2.834988, 3.584282,
                               1.244998, 2.731395, 3.214231, 3.538417, 1.691146, 3.411135, 3.468862, 3.507621 };

  // L2 norm resampler.
  using ResamplerType = itk::BSplineCenteredResampleImageFilterBase<ImageType2D, ImageType2D>;
  using DownsamplerType2D = itk::BSplineDownsampleImageFilter<ImageType2D, ImageType2D, ResamplerType>;
  using UpsamplerType2D = itk::BSplineUpsampleImageFilter<ImageType2D, ImageType2D, ResamplerType>;

  auto                     downSampler = DownsamplerType2D::New();
  itk::SimpleFilterWatcher downWatcher(downSampler, "test2D_Centered_l2_filter");
  auto                     upSampler = UpsamplerType2D::New();
  itk::SimpleFilterWatcher upWatcher(upSampler, "test2D_Centered_l2_filter");
  downSampler->SetSplineOrder(splineOrder);
  upSampler->SetSplineOrder(splineOrder);

  downSampler->SetInput(image);
  downSampler->Update();
  ImageTypePtr2D outImage1 = downSampler->GetOutput();
  PrintImageData(outImage1);
  upSampler->SetInput(outImage1);
  upSampler->Update();
  ImageTypePtr2D outImage2 = upSampler->GetOutput();
  PrintImageData(outImage2);
  bool sameResults = false;
  if (splineOrder == 4)
  {
    sameResults = VerifyResultsHigherOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 3)
  {
    sameResults = VerifyResults3rdOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 2)
  {
    sameResults = VerifyResults2ndOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 1)
  {
    sameResults = VerifyResultsLowerOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 0)
  {
    sameResults = VerifyResultsLowerOrderSpline(outImage2, ExpectedResults);
  }
  if (!sameResults)
  {
    flag = 1;
    std::cout << "*** Error: unexpected value in Centered l2 - resampler with order " << splineOrder << "  spline."
              << std::endl;
    std::cout << "" << std::endl;
  }
  else
  {
    std::cout << "Tests for Centered l2 - resampler with order " << splineOrder << "  spline PASSED " << std::endl;
    std::cout << "" << std::endl;
  }


  return flag;
}

int
testIntInputDoubleOutput()
{
  int flag = 0;

  // Note this only tests the downsampling using Int input and double output.
  // TODO:  Modify to test upsampling also.
  // Allocate a simple test image
  IntImageTypePtr2D image = IntImageType2D::New();

  setInt2DData(image);
  double ExpectedResults[] = { 0.124139, 0.606412, 1.322693, 1.803619, 0.580514, 1.719005, 2.834988, 3.584282,
                               1.244998, 2.731395, 3.214231, 3.538417, 1.691146, 3.411135, 3.468862, 3.507621 };

  // L2 norm resampler.
  using ResamplerType = itk::BSplineCenteredResampleImageFilterBase<IntImageType2D, ImageType2D>;
  using DownsamplerType2D = itk::BSplineDownsampleImageFilter<IntImageType2D, ImageType2D, ResamplerType>;
  using ResamplerType2 = itk::BSplineCenteredResampleImageFilterBase<ImageType2D, ImageType2D>;

  using UpsamplerType2D = itk::BSplineUpsampleImageFilter<ImageType2D, ImageType2D, ResamplerType2>;

  auto downSampler = DownsamplerType2D::New();
  auto upSampler = UpsamplerType2D::New();
  int  splineOrder = 3;
  downSampler->SetSplineOrder(splineOrder);
  upSampler->SetSplineOrder(splineOrder);

  downSampler->SetInput(image);
  downSampler->Update();
  ImageTypePtr2D outImage1 = downSampler->GetOutput();
  PrintImageData(outImage1);
  //  interp->Print( std::cout );
  //  PrintImageData(image);
  //  upSampler->SetInput( downSampler->GetOutput() );
  upSampler->SetInput(outImage1);
  upSampler->Update();
  ImageTypePtr2D outImage2 = upSampler->GetOutput();
  PrintImageData(outImage2);
  bool sameResults = VerifyResults3rdOrderSpline(outImage2, ExpectedResults);
  if (!sameResults)
  {
    flag = 1;
    std::cout << "*** Error: unexpected value in Centered l2 - resampler (integer input, double output)" << std::endl;
  }
  else
  {
    std::cout << "Tests for Centered l2 - resampler (integer input, double output) PASSED" << std::endl;
  }

  return flag;
}


// Test for Centered_L2 filter with Nth order spline
int
test2D_Centered_L2_NthOrderSpline_filter(unsigned int splineOrder)
{
  int flag = 0;

  // Allocate a simple test image
  ImageTypePtr2D image = ImageType2D::New();

  set2DData(image);
  double ExpectedResults[] = { 0.119494, 0.600647, 1.323863, 1.802788, 0.574571, 1.712082, 2.837723, 3.583139,
                               1.245641, 2.733425, 3.217399, 3.537894, 1.690034, 3.409774, 3.468826, 3.507932 };

  // L2 norm resampler.
  using ResamplerType = itk::BSplineCenteredL2ResampleImageFilterBase<ImageType2D, ImageType2D>;
  using DownsamplerType2D = itk::BSplineDownsampleImageFilter<ImageType2D, ImageType2D, ResamplerType>;
  using UpsamplerType2D = itk::BSplineUpsampleImageFilter<ImageType2D, ImageType2D, ResamplerType>;

  auto                     downSampler = DownsamplerType2D::New();
  itk::SimpleFilterWatcher downWatcher(downSampler, "test2D_Centered_L2_filter");
  auto                     upSampler = UpsamplerType2D::New();
  itk::SimpleFilterWatcher upWatcher(upSampler, "test2D_Centered_L2_filter");
  // int splineOrder = 2;
  downSampler->SetSplineOrder(splineOrder);
  upSampler->SetSplineOrder(splineOrder);

  downSampler->SetInput(image);
  downSampler->Update();
  ImageTypePtr2D outImage1 = downSampler->GetOutput();
  PrintImageData(outImage1);
  //  interp->Print( std::cout );
  //  PrintImageData(image);
  //  upSampler->SetInput( downSampler->GetOutput() );
  upSampler->SetInput(outImage1);
  upSampler->Update();
  ImageTypePtr2D outImage2 = upSampler->GetOutput();
  PrintImageData(outImage2);

  bool sameResults = false;
  if (splineOrder == 4)
  {
    sameResults = VerifyResultsHigherOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 3)
  {
    sameResults = VerifyResults3rdOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 2)
  {
    sameResults = VerifyResults2ndOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 1)
  {
    sameResults = VerifyResultsLowerOrderSpline(outImage2, ExpectedResults);
  }
  else if (splineOrder == 0)
  {
    sameResults = VerifyResultsLowerOrderSpline(outImage2, ExpectedResults);
  }
  if (!sameResults)
  {
    flag = 1;
    std::cout << "*** Error: unexpected value in Centered L2 - resampler with order " << splineOrder << "  spline."
              << std::endl;
    std::cout << "" << std::endl;
  }
  else
  {
    std::cout << "Tests for Centered L2 - resampler with order " << splineOrder << "  spline PASSED " << std::endl;
    std::cout << "" << std::endl;
  }

  return flag;
}

int
itkBSplineResampleImageFilterTest(int itkNotUsed(argc), char * itkNotUsed(argv)[])
{
  int flag = 0;
  int dummyflag = 0;

  std::cout << "Testing B Spline up and down sampling methods: \n";

  flag += testIntInputDoubleOutput();

  // Test for Standard l2 BSplines for different orders (3,2,1,0)
  flag += test2D_Standard_l2_NthOrderSpline_filter(3);
  flag += test2D_Standard_l2_NthOrderSpline_filter(2);
  flag += test2D_Standard_l2_NthOrderSpline_filter(0);
  // The error for spline order 1 is much higher than allowable threshold
  // Hence, a different reference set is needed for comparison. Therefore, a
  // dummy flag is used to test that the code compiles. The accuracy of this
  // filter remains to be tested
  dummyflag += test2D_Standard_l2_NthOrderSpline_filter(1);

  // Test for Centered l2 BSplines for different orders (4-1)
  flag += test2D_Centered_l2_NthOrderSpline_filter(4);
  flag += test2D_Centered_l2_NthOrderSpline_filter(3);
  flag += test2D_Centered_l2_NthOrderSpline_filter(2);
  flag += test2D_Centered_l2_NthOrderSpline_filter(1);

  // Test for Standard L2 BSplines for different orders (5,3,1,0)
  flag += test2D_Standard_L2_NthOrderSpline_filter(5);
  flag += test2D_Standard_L2_NthOrderSpline_filter(3);
  flag += test2D_Standard_L2_NthOrderSpline_filter(1);
  flag += test2D_Standard_L2_NthOrderSpline_filter(0);

  // Test for Centered L2 BSplines for different orders (4-1)
  flag += test2D_Centered_L2_NthOrderSpline_filter(4);
  flag += test2D_Centered_L2_NthOrderSpline_filter(3);
  flag += test2D_Centered_L2_NthOrderSpline_filter(2);
  flag += test2D_Centered_L2_NthOrderSpline_filter(1);

  // Test for the exceptions for unsupported spline orders
  bool passed = false;
  try
  {
    std::cout << "Test when Standard_l2 spline order is unsupported" << std::endl;
    dummyflag += test2D_Standard_l2_NthOrderSpline_filter(6);
  }
  catch (const itk::ExceptionObject & err)
  {
    std::cout << "Caught expected error." << std::endl;
    std::cout << err << std::endl;
    passed = true;
  }
  if (!passed)
  {
    std::cout << "*** " << flag << " expected exception was not caught." << std::endl;
  }
  passed = false;

  try
  {
    std::cout << "Test when Centered_l2 spline order is unsupported" << std::endl;
    dummyflag += test2D_Centered_l2_NthOrderSpline_filter(6);
  }
  catch (const itk::ExceptionObject & err)
  {
    std::cout << "Caught expected error." << std::endl;
    std::cout << err << std::endl;
    passed = true;
  }
  if (!passed)
  {
    std::cout << "*** " << flag << " expected exception was not caught." << std::endl;
  }
  passed = false;

  try
  {
    std::cout << "Test when Standard_L2 spline order is unsupported" << std::endl;
    dummyflag += test2D_Standard_L2_NthOrderSpline_filter(6);
  }
  catch (const itk::ExceptionObject & err)
  {
    std::cout << "Caught expected error." << std::endl;
    std::cout << err << std::endl;
    passed = true;
  }
  if (!passed)
  {
    std::cout << "*** " << flag << " expected exception was not caught." << std::endl;
  }
  passed = false;

  try
  {
    std::cout << "Test when Centered_L2 spline order is unsupported" << std::endl;
    dummyflag += test2D_Centered_L2_NthOrderSpline_filter(6);
  }
  catch (const itk::ExceptionObject & err)
  {
    std::cout << "Caught expected error." << std::endl;
    std::cout << err << std::endl;
    passed = true;
  }
  if (!passed)
  {
    std::cout << "*** " << flag << " expected exception was not caught." << std::endl;
  }

  std::cout << "dummyflag: " << dummyflag << std::endl;
  // Return results of test
  if (flag != 0)
  {
    std::cout << "*** " << flag << " tests failed" << std::endl;

    return EXIT_FAILURE;
  }
  else
  {
    std::cout << "All tests successfully passed" << std::endl;
    return EXIT_SUCCESS;
  }
}
