/*=========================================================================
 *
 *  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 itkPolylineMask2DImageFilter_hxx
#define itkPolylineMask2DImageFilter_hxx

#include "itkLineIterator.h"
#include "itkImageLinearIteratorWithIndex.h"
#include "itkPathIterator.h"
#include "itkImageRegionIterator.h"
#include "itkProgressReporter.h"
#include "itkVectorContainer.h"

namespace itk
{

template <typename TInputImage, typename TPolyline, typename TOutputImage>
PolylineMask2DImageFilter<TInputImage, TPolyline, TOutputImage>::PolylineMask2DImageFilter()
{
  this->SetNumberOfRequiredInputs(2);
}

template <typename TInputImage, typename TPolyline, typename TOutputImage>
void
PolylineMask2DImageFilter<TInputImage, TPolyline, TOutputImage>::SetInput1(const TInputImage * input)

{
  // Process object is not const-correct so the const_cast is required here
  this->ProcessObject::SetNthInput(0, const_cast<TInputImage *>(input));
}

template <typename TInputImage, typename TPolyline, typename TOutputImage>
void
PolylineMask2DImageFilter<TInputImage, TPolyline, TOutputImage>::SetInput2(const TPolyline * input)
{
  // Process object is not const-correct so the const_cast is required here
  this->ProcessObject::SetNthInput(1, const_cast<TPolyline *>(input));
}

template <typename TInputImage, typename TPolyline, typename TOutputImage>
void
PolylineMask2DImageFilter<TInputImage, TPolyline, TOutputImage>::GenerateData()
{
  using LineIteratorType = LineIterator<TOutputImage>;
  using ImageLineIteratorType = ImageLinearIteratorWithIndex<TOutputImage>;

  using InputImageConstIteratorType = ImageRegionConstIterator<TInputImage>;

  using ImageIndexType = typename TOutputImage::IndexType;
  using PixelType = typename TOutputImage::PixelType;
  using OutputImageIteratorType = ImageRegionIterator<TOutputImage>;

  using VertexType = typename TPolyline::VertexType;
  using VertexListType = typename TPolyline::VertexListType;

  typename TInputImage::ConstPointer inputImagePtr(dynamic_cast<const TInputImage *>(this->ProcessObject::GetInput(0)));
  typename TPolyline::ConstPointer   polylinePtr(dynamic_cast<const TPolyline *>(this->ProcessObject::GetInput(1)));
  typename TOutputImage::Pointer     outputImagePtr(dynamic_cast<TOutputImage *>(this->ProcessObject::GetOutput(0)));

  outputImagePtr->SetOrigin(inputImagePtr->GetOrigin());
  outputImagePtr->SetSpacing(inputImagePtr->GetSpacing());
  outputImagePtr->SetDirection(inputImagePtr->GetDirection());
  outputImagePtr->SetRequestedRegion(inputImagePtr->GetRequestedRegion());
  outputImagePtr->SetBufferedRegion(inputImagePtr->GetBufferedRegion());
  outputImagePtr->SetLargestPossibleRegion(inputImagePtr->GetLargestPossibleRegion());
  outputImagePtr->Allocate();


  const VertexListType * container = polylinePtr->GetVertexList();

  typename VertexListType::ConstIterator piter = container->Begin();

  /* Rasterize each polyline segment using bresenham line iterator  */

  VertexType startVertex;
  VertexType endVertex;
  VertexType pstartVertex;
  VertexType tmpVertex;

  /* Check if the polyline coordinates are within the input image */
  while (piter != container->End())
  {
    tmpVertex = piter.Value();
    const auto tmpIndex = outputImagePtr->TransformPhysicalPointToIndex(tmpVertex);
    if (!outputImagePtr->GetBufferedRegion().IsInside(tmpIndex))
    {
      itkExceptionMacro("Polyline vertex is out of bounds (Vertex,Index): " << tmpVertex << ", " << tmpIndex);
    }
    ++piter;
  }

  // reset piter
  piter = container->Begin();

  /* define flag to indicate the line segment slope */
  bool pflag;

  /* define background, foreground pixel values and unlabeled pixel value */
  PixelType zero_val{};
  auto      u_val = static_cast<PixelType>(0);
  auto      b_val = static_cast<PixelType>(2);
  auto      f_val = static_cast<PixelType>(255);
  outputImagePtr->FillBuffer(u_val);

  pstartVertex = piter.Value();

  tmpVertex = pstartVertex;
  ++piter;

  ImageIndexType tmpImageIndex;
  tmpImageIndex.Fill(0);

  ImageLineIteratorType imit(outputImagePtr, outputImagePtr->GetLargestPossibleRegion());
  imit.SetDirection(0);

  itkDebugMacro("Generating the mask defined by the polyline.....");

  while (piter != container->End())
  {
    pflag = false;
    startVertex = tmpVertex;
    endVertex = piter.Value();

    const auto startImageIndex = outputImagePtr->TransformPhysicalPointToIndex(startVertex);
    const auto endImageIndex = outputImagePtr->TransformPhysicalPointToIndex(endVertex);

    // itkDebugMacro(<<"Projection image (index,physical
    // coordinate):"<<startImageIndex<<","<<startVertex<<std::endl);

    if (endImageIndex[1] > startImageIndex[1])
    {
      pflag = true;
    }

    LineIteratorType it(outputImagePtr, startImageIndex, endImageIndex);
    it.GoToBegin();

    while (!it.IsAtEnd())
    {
      tmpImageIndex[0] = it.GetIndex()[0];
      tmpImageIndex[1] = it.GetIndex()[1];

      // initialize imit using it
      imit.SetIndex(tmpImageIndex);
      while (!imit.IsAtEndOfLine())
      {
        if (pflag)
        {
          if (imit.Get() == u_val)
          {
            imit.Set(f_val);
          }
        }
        else
        {
          imit.Set(b_val);
        }
        ++imit;
      }
      ++it;
    }
    tmpVertex = endVertex;
    ++piter;
  }

  /* Close the polygon */
  pflag = false;
  startVertex = tmpVertex;
  endVertex = pstartVertex;

  const auto startImageIndex = outputImagePtr->TransformPhysicalPointToIndex(startVertex);
  const auto endImageIndex = outputImagePtr->TransformPhysicalPointToIndex(endVertex);

  if (endImageIndex[1] > startImageIndex[1])
  {
    pflag = true;
  }

  LineIteratorType it(outputImagePtr, startImageIndex, endImageIndex);

  while (!it.IsAtEnd())
  {
    tmpImageIndex[0] = it.GetIndex()[0];
    tmpImageIndex[1] = it.GetIndex()[1];

    // initialize imit using it
    imit.SetIndex(tmpImageIndex);
    while (!imit.IsAtEndOfLine())
    {
      if (pflag)
      {
        if (imit.Get() == u_val)
        {
          imit.Set(f_val);
        }
      }
      else
      {
        imit.Set(b_val);
      }
      ++imit;
    }
    ++it;
  }

  /* Mask the input image with the mask generated */
  InputImageConstIteratorType inputI(inputImagePtr, inputImagePtr->GetLargestPossibleRegion());
  OutputImageIteratorType     outputI(outputImagePtr, outputImagePtr->GetLargestPossibleRegion());
  while (!outputI.IsAtEnd())
  {
    if (outputI.Get() == f_val)
    {
      outputI.Set(inputI.Get());
    }
    else
    {
      outputI.Set(zero_val);
    }
    ++inputI;
    ++outputI;
  }
}
} // end namespace itk
#endif
