/*=========================================================================
 *
 *  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.
 *
 *=========================================================================*/
#ifndef itkBinaryErodeImageFilter_hxx
#define itkBinaryErodeImageFilter_hxx

#include "itkImageRegionIteratorWithIndex.h"
#include "itkNeighborhoodInnerProduct.h"
#include "itkImageRegionConstIterator.h"
#include "itkNeighborhoodAlgorithm.h"
#include "itkConstantBoundaryCondition.h"
#include "itkOffset.h"
#include "itkProgressReporter.h"
#include "itkMath.h"

namespace itk
{
template <typename TInputImage, typename TOutputImage, typename TKernel>
BinaryErodeImageFilter<TInputImage, TOutputImage, TKernel>::BinaryErodeImageFilter()
{
  this->m_BoundaryToForeground = true;
}

template <typename TInputImage, typename TOutputImage, typename TKernel>
void
BinaryErodeImageFilter<TInputImage, TOutputImage, TKernel>::GenerateData()
{
  this->AllocateOutputs();

  // Retrieve input and output pointers
  const typename OutputImageType::Pointer     output = this->GetOutput();
  const typename InputImageType::ConstPointer input = this->GetInput();

  // Get values from superclass
  const InputPixelType                    foregroundValue = this->GetForegroundValue();
  const InputPixelType                    backgroundValue = this->GetBackgroundValue();
  const KernelType                        kernel = this->GetKernel();
  auto                                    radius = InputSizeType::Filled(1);
  const typename TOutputImage::RegionType outputRegion = output->GetBufferedRegion();

  // compute the size of the temp image. It is needed to create the progress
  // reporter.
  // The tmp image needs to be large enough to support:
  //   1. The size of the structuring element
  //   2. The size of the connectivity element (typically one)
  typename TInputImage::RegionType tmpRequestedRegion = outputRegion;
  typename TInputImage::RegionType paddedInputRegion = input->GetBufferedRegion();
  paddedInputRegion.PadByRadius(radius); // to support boundary values
  InputSizeType padBy = radius;
  for (unsigned int i = 0; i < KernelDimension; ++i)
  {
    padBy[i] = (padBy[i] > kernel.GetRadius(i) ? padBy[i] : kernel.GetRadius(i));
  }
  tmpRequestedRegion.PadByRadius(padBy);
  tmpRequestedRegion.Crop(paddedInputRegion);

  typename TInputImage::RegionType requiredInputRegion = input->GetBufferedRegion();
  requiredInputRegion.Crop(tmpRequestedRegion);

  // Support progress methods/callbacks
  // Setup a progress reporter.  We have 4 stages to the algorithm so
  // pretend we have 4 times the number of pixels
  ProgressReporter progress(this,
                            0,
                            outputRegion.GetNumberOfPixels() * 3 + tmpRequestedRegion.GetNumberOfPixels() +
                              requiredInputRegion.GetNumberOfPixels());

  // Allocate and reset output. We copy the input to the output,
  // except for pixels with DilateValue.  These pixels are initially
  // replaced with BackgroundValue and potentially replaced later with
  // DilateValue as the Minkowski sums are performed.
  ImageRegionIterator<OutputImageType> outIt(output, outputRegion);
  // ImageRegionConstIterator<InputImageType> inIt( input, outputRegion );

  for (outIt.GoToBegin(); !outIt.IsAtEnd(); ++outIt)
  {
    outIt.Set(foregroundValue);
    progress.CompletedPixel();
  }

  // Create the temp image for surface encoding
  // The temp image size is equal to the output requested region for thread
  // padded by max( connectivity neighborhood radius, SE kernel radius ).
  using TempImageType = itk::Image<unsigned char, TInputImage::ImageDimension>;
  auto tmpImage = TempImageType::New();

  // Define regions of temp image
  tmpImage->SetRegions(tmpRequestedRegion);

  // Allocation.
  // Pay attention to the fact that here, the output is still not
  // allocated (so no extra memory needed for tmp image, if you
  // consider that you reserve some memory space for output)
  tmpImage->Allocate();

  // First Stage
  // Copy the input image to the tmp image.
  // Tag the tmp Image.
  //     zero means background
  //     one means pixel on but not treated
  //     two means border pixel
  //     three means inner pixel
  constexpr unsigned char backgroundTag{ 0 };
  constexpr unsigned char onTag{ 1 };
  constexpr unsigned char borderTag{ 2 };
  constexpr unsigned char innerTag{ 3 };

  if (!this->m_BoundaryToForeground)
  {
    tmpImage->FillBuffer(onTag);
  }
  else
  {
    tmpImage->FillBuffer(backgroundTag);
  }

  // Iterators on input and tmp image
  // iterator on input
  ImageRegionConstIterator<TInputImage> iRegIt(input, requiredInputRegion);
  // iterator on tmp image
  ImageRegionIterator<TempImageType> tmpRegIt(tmpImage, requiredInputRegion);

  for (iRegIt.GoToBegin(), tmpRegIt.GoToBegin(); !tmpRegIt.IsAtEnd(); ++iRegIt, ++tmpRegIt)
  {
    const OutputPixelType pxl = iRegIt.Get();
    if (Math::NotExactlyEquals(pxl, foregroundValue))
    {
      tmpRegIt.Set(onTag);
    }
    else
    {
      // by default if it is not foreground, consider
      // it as background
      tmpRegIt.Set(backgroundTag);
    }
    progress.CompletedPixel();
  }

  // Second stage
  // Border tracking and encoding

  // Need to record index, use an iterator with index
  // Define iterators that will traverse the OUTPUT requested region
  // for thread and not the padded one. The tmp image has been padded
  // because in that way we will take care carefully at boundary
  // pixels of output requested region.  Take care means that we will
  // check if a boundary pixel is or not a border pixel.
  ImageRegionIteratorWithIndex<TempImageType> tmpRegIndexIt(tmpImage, tmpRequestedRegion);

  ConstNeighborhoodIterator<TempImageType> oNeighbIt(radius, tmpImage, tmpRequestedRegion);

  // Define boundaries conditions
  ConstantBoundaryCondition<TempImageType> cbc;
  cbc.SetConstant(backgroundTag);
  oNeighbIt.OverrideBoundaryCondition(&cbc);

  const unsigned int neighborhoodSize = oNeighbIt.Size();
  const unsigned int centerPixelCode = neighborhoodSize / 2;

  std::queue<IndexType> propagQueue;

  // Neighborhood iterators used to track the surface.
  //
  // Note the region specified for the first neighborhood iterator is
  // the requested region for the tmp image not the output image. This
  // is necessary because the NeighborhoodIterator relies on the
  // specified region to determine if you will ever query a boundary
  // condition pixel.  Since we call SetLocation on the neighbor of a
  // specified pixel, we have to set the region for the iterator to
  // include any pixel we may set our location to.
  NeighborhoodIterator<TempImageType> nit(radius, tmpImage, tmpRequestedRegion);
  nit.OverrideBoundaryCondition(&cbc);
  nit.GoToBegin();

  ConstNeighborhoodIterator<TempImageType> nnit(radius, tmpImage, tmpRequestedRegion);
  nnit.OverrideBoundaryCondition(&cbc);
  nnit.GoToBegin();

  for (tmpRegIndexIt.GoToBegin(), oNeighbIt.GoToBegin(); !tmpRegIndexIt.IsAtEnd(); ++tmpRegIndexIt, ++oNeighbIt)
  {
    const unsigned char tmpValue = tmpRegIndexIt.Get();

    // Test current pixel: it is active ( on ) or not?
    if (tmpValue == onTag)
    {
      // The current pixel has not been treated previously.  That
      // means that we do not know that it is an inner pixel of a
      // border pixel.

      // Test current pixel: it is a border pixel or an inner pixel?
      bool bIsOnContour = false;

      for (unsigned int i = 0; i < neighborhoodSize; ++i)
      {
        // If at least one neighbour pixel is off the center pixel
        // belongs to contour
        if (oNeighbIt.GetPixel(i) == backgroundTag)
        {
          bIsOnContour = true;
          break;
        }
      }

      if (bIsOnContour)
      {
        // center pixel is a border pixel and due to the parsing, it is also
        // a pixel which belongs to a new border connected component
        // Now we will parse this border thanks to a burn procedure

        // mark pixel value as a border pixel
        tmpRegIndexIt.Set(borderTag);

        // add it to border container.
        // its code is center pixel code because it is the first pixel
        // of the connected component border

        // paint the structuring element
        typename NeighborIndexContainer::const_iterator itIdx;
        NeighborIndexContainer &                        idxDifferenceSet = this->GetDifferenceSet(centerPixelCode);
        for (itIdx = idxDifferenceSet.begin(); itIdx != idxDifferenceSet.end(); ++itIdx)
        {
          const IndexType idx = tmpRegIndexIt.GetIndex() + *itIdx;
          if (outputRegion.IsInside(idx))
          {
            output->SetPixel(idx, backgroundValue);
          }
        }

        // add it to queue
        propagQueue.push(tmpRegIndexIt.GetIndex());

        // now find all the border pixels
        while (!propagQueue.empty())
        {
          // Extract pixel index from queue
          const IndexType currentIndex = propagQueue.front();
          propagQueue.pop();

          nit += currentIndex - nit.GetIndex();

          for (unsigned int i = 0; i < neighborhoodSize; ++i)
          {
            // If pixel has not been already treated and it is a pixel
            // on, test if it is an inner pixel or a border pixel

            // Remark: all the pixels outside the image are set to
            // backgroundTag thanks to boundary conditions. That means that if
            // we enter in the next if-statement we are sure that the
            // current neighbour pixel is in the image
            if (nit.GetPixel(i) == onTag)
            {
              // Check if it is an inner or border neighbour pixel
              // Get index of current neighbour pixel
              const IndexType neighbIndex = nit.GetIndex(i);

              // Force location of neighbour iterator
              nnit += neighbIndex - nnit.GetIndex();

              bool bIsOnBorder = false;

              for (unsigned int j = 0; j < neighborhoodSize; ++j)
              {
                // If at least one neighbour pixel is off the center
                // pixel belongs to border
                if (nnit.GetPixel(j) == backgroundTag)
                {
                  bIsOnBorder = true;
                  break;
                }
              }

              if (bIsOnBorder)
              {
                // neighbour pixel is a border pixel
                // mark it
                bool status = false;
                nit.SetPixel(i, borderTag, status);

                // check whether we could set the pixel.  can only set
                // the pixel if it is within the tmpimage
                if (status)
                {
                  // add it to queue
                  propagQueue.push(neighbIndex);

                  // paint the structuring element
                  NeighborIndexContainer & indexDifferenceSet = this->GetDifferenceSet(i);
                  const auto               staticEndIndex = indexDifferenceSet.end();
                  for (auto itIndex = indexDifferenceSet.begin(); itIndex != staticEndIndex; ++itIndex)
                  {
                    const IndexType idx(neighbIndex + *itIndex);
                    if (outputRegion.IsInside(idx))
                    {
                      output->SetPixel(idx, backgroundValue);
                    }
                  }
                }
              }
              else
              {
                // neighbour pixel is an inner pixel
                bool status = false;
                nit.SetPixel(i, innerTag, status);
              }

              progress.CompletedPixel();
            } // if( nit.GetPixel( i ) == onTag )
          } // for (i = 0; i < neighborhoodSize; ++i)
        } // while ( !propagQueue.empty() )
      } // if( bIsOnContour )
      else
      {
        tmpRegIndexIt.Set(innerTag);
      }

      progress.CompletedPixel();
    } // if( tmpRegIndexIt.Get() == onTag )
    else if (tmpValue == backgroundTag)
    {
      progress.CompletedPixel();
    }
    // Here, the pixel is a background pixel ( value at 0 ) or an
    // already treated pixel:
    //     2 for border pixel, 3 for inner pixel
  }

  // Deallocate tmpImage
  tmpImage->Initialize();

  // Third Stage
  // traverse structure of border and SE CCs, and paint output image

  // Let's consider the set of the ON elements of the input image as X.
  //
  // Let's consider the structuring element as B = {B0, B1, ..., Bn},
  // where Bi denotes a connected component of B.
  //
  // Let's consider bi, i in [0,n], an arbitrary point of Bi.
  //

  // We use hence the next property in order to compute minkoswki
  // addition ( which will be written (+) ):
  //
  // X (+) B = ( Xb0 UNION Xb1 UNION ... Xbn ) UNION ( BORDER(X) (+) B ),
  //
  // where Xbi is the set X translated with respect to vector bi :
  //
  // Xbi = { x + bi, x belongs to X } where BORDER(X) is the extracted
  // border of X ( 8 connectivity in 2D, 26 in 3D )

  // Define boundaries conditions
  ConstantBoundaryCondition<TOutputImage> obc;
  obc.SetConstant(backgroundValue);

  NeighborhoodIterator<OutputImageType> onit(kernel.GetRadius(), output, outputRegion);
  onit.OverrideBoundaryCondition(&obc);
  onit.GoToBegin();

  // Paint input image translated with respect to the SE CCs vectors
  // --> "( Xb0 UNION Xb1 UNION ... Xbn )"
  typename Superclass::ComponentVectorConstIterator vecIt;
  auto                                              vecBeginIt = this->KernelCCVectorBegin();
  auto                                              vecEndIt = this->KernelCCVectorEnd();

  // iterator on output image
  ImageRegionIteratorWithIndex<OutputImageType> ouRegIndexIt(output, outputRegion);

  // InputRegionForThread is the output region for thread padded by
  // kernel radius We must traverse this padded region because some
  // border pixel in the added band ( the padded band is the region
  // added after padding ) may be responsible to the painting of some
  // pixel in the non padded region.  This happens typically when a
  // non centered SE is used, a kind of shift is done on the "on"
  // pixels of image. Consequently some pixels in the added band can
  // appear in the current region for thread due to shift effect.
  typename InputImageType::RegionType inputRegionForThread = outputRegion;

  // Pad the input region by the kernel
  inputRegionForThread.PadByRadius(kernel.GetRadius());
  inputRegionForThread.Crop(input->GetBufferedRegion());

  if (!this->m_BoundaryToForeground)
  {
    while (!ouRegIndexIt.IsAtEnd())
    {
      // Retrieve index of current output pixel
      const IndexType currentIndex = ouRegIndexIt.GetIndex();
      for (vecIt = vecBeginIt; vecIt != vecEndIt; ++vecIt)
      {
        // Translate
        const IndexType translatedIndex = currentIndex - *vecIt;

        // translated index now is an index in input image in the
        // output requested region padded. Theoretically, this translated
        // index must be inside the padded region.
        // If the pixel in the input image at the translated index
        // has a value equal to the dilate one, this means
        // that the output pixel at currentIndex will be on in the output.
        if (!inputRegionForThread.IsInside(translatedIndex) ||
            Math::NotExactlyEquals(input->GetPixel(translatedIndex), foregroundValue))
        {
          ouRegIndexIt.Set(backgroundValue);
          break; // Do not need to examine other offsets because at least one
          // input pixel has been translated on current output pixel.
        }
      }

      ++ouRegIndexIt;
      progress.CompletedPixel();
    }
  }
  else
  {
    while (!ouRegIndexIt.IsAtEnd())
    {
      const IndexType currentIndex = ouRegIndexIt.GetIndex();
      for (vecIt = vecBeginIt; vecIt != vecEndIt; ++vecIt)
      {
        const IndexType translatedIndex = currentIndex - *vecIt;

        if (inputRegionForThread.IsInside(translatedIndex) &&
            Math::NotExactlyEquals(input->GetPixel(translatedIndex), foregroundValue))
        {
          ouRegIndexIt.Set(backgroundValue);
          break;
        }
      }

      ++ouRegIndexIt;
      progress.CompletedPixel();
    }
  }

  // now, we must to restore the background values
  ImageRegionConstIterator<InputImageType> inIt(input, outputRegion);

  for (inIt.GoToBegin(), outIt.GoToBegin(); !outIt.IsAtEnd(); ++outIt, ++inIt)
  {
    const InputPixelType  inValue = inIt.Get();
    const OutputPixelType outValue = outIt.Get();
    if (Math::ExactlyEquals(outValue, backgroundValue) && Math::NotExactlyEquals(inValue, foregroundValue))
    {
      outIt.Set(static_cast<OutputPixelType>(inValue));
    }
    progress.CompletedPixel();
  }
}

/**
 * Standard "PrintSelf" method
 */
template <typename TInputImage, typename TOutput, typename TKernel>
void
BinaryErodeImageFilter<TInputImage, TOutput, TKernel>::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);
  os << indent
     << "Dilate Value: " << static_cast<typename NumericTraits<InputPixelType>::PrintType>(this->GetForegroundValue())
     << std::endl;
}
} // end namespace itk

#endif
