/*=========================================================================
 *
 *  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 itkConstShapedNeighborhoodIterator_hxx
#define itkConstShapedNeighborhoodIterator_hxx
namespace itk
{
template <typename TImage, typename TBoundaryCondition>
void
ConstShapedNeighborhoodIterator<TImage, TBoundaryCondition>::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);

  os << indent << "ActiveIndexList: [";
  for (auto it = m_ActiveIndexList.begin(); it != m_ActiveIndexList.end(); ++it)
  {
    os << indent.GetNextIndent() << *it << ' ';
  }
  os << "] ";

  itkPrintSelfBooleanMacro(CenterIsActive);
}

template <typename TImage, typename TBoundaryCondition>
void
ConstShapedNeighborhoodIterator<TImage, TBoundaryCondition>::ActivateIndex(NeighborIndexType n)
{
  const OffsetValueType * OffsetTable = this->m_ConstImage->GetOffsetTable();

  // Insert so that the list remains ordered.
  auto it = m_ActiveIndexList.begin();

  if (m_ActiveIndexList.empty())
  {
    m_ActiveIndexList.push_front(n);
  }
  else
  {
    while (n > *it)
    {
      ++it;
      if (it == m_ActiveIndexList.end())
      {
        break;
      }
    }
    if (it == m_ActiveIndexList.end())
    {
      m_ActiveIndexList.insert(it, n);
    }
    else if (n != *it)
    {
      m_ActiveIndexList.insert(it, n);
    }
  }

  // Did we just activate the index at the center of the neighborhood?
  if (n == this->GetCenterNeighborhoodIndex())
  {
    m_CenterIsActive = true;
  }

  // Set the pointer in the neighborhood location just activated.
  this->GetElement(n) = this->GetCenterPointer();
  for (unsigned int i = 0; i < Dimension; ++i)
  {
    this->GetElement(n) += OffsetTable[i] * this->GetOffset(n)[i];
  }
}

template <typename TImage, typename TBoundaryCondition>
void
ConstShapedNeighborhoodIterator<TImage, TBoundaryCondition>::DeactivateIndex(NeighborIndexType n)
{
  auto it = m_ActiveIndexList.begin();

  if (m_ActiveIndexList.empty())
  {
    return;
  }

  while (n != *it)
  {
    ++it;
    if (it == m_ActiveIndexList.end())
    {
      return;
    }
  }
  m_ActiveIndexList.erase(it);


  // Did we just deactivate the index at the center of the neighborhood?
  if (n == this->GetCenterNeighborhoodIndex())
  {
    m_CenterIsActive = false;
  }
}

template <typename TImage, typename TBoundaryCondition>
template <typename TNeighborPixel>
void
ConstShapedNeighborhoodIterator<TImage, TBoundaryCondition>::CreateActiveListFromNeighborhood(
  const Neighborhood<TNeighborPixel, Self::Dimension> & neighborhood)
{
  if (this->GetRadius() != neighborhood.GetRadius())
  {
    itkGenericExceptionMacro("Radius of shaped iterator(" << this->GetRadius()
                                                          << ") does not equal radius of neighborhood("
                                                          << neighborhood.GetRadius() << ')');
  }

  NeighborIndexType idx = 0;
  for (auto nit = neighborhood.Begin(); nit != neighborhood.End(); ++nit, ++idx)
  {
    if (*nit)
    {
      this->ActivateOffset(GetOffset(idx));
    }
    else
    {
      this->DeactivateOffset(GetOffset(idx));
    }
  }
}

template <typename TImage, typename TBoundaryCondition>
ConstShapedNeighborhoodIterator<TImage, TBoundaryCondition> &
ConstShapedNeighborhoodIterator<TImage, TBoundaryCondition>::operator++()
{
  // Repositioning neighborhood, previous bounds check on neighborhood
  // location is invalid.
  this->m_IsInBoundsValid = false;

  if (this->m_BoundaryCondition->RequiresCompleteNeighborhood())
  {
    // Some Boundary Conditions, such as ZeroFluxNeumann can return
    // values from anywhere in the neighborhood, thus we can not do
    // the shaped optimization.

    NeighborhoodIterator<TImage, TBoundaryCondition>::operator++();
  }
  else
  {
    // Center pointer must be updated whether or not it is active.
    if (!m_CenterIsActive)
    {
      this->GetElement(this->GetCenterNeighborhoodIndex())++;
    }

    // Increment pointers for only the active pixels.
    for (auto it = m_ActiveIndexList.begin(); it != m_ActiveIndexList.end(); ++it)
    {
      (this->GetElement(*it))++;
    }

    // Check loop bounds, wrap & add pointer offsets if needed.
    for (unsigned int ii = 0; ii < Dimension; ++ii)
    {
      this->m_Loop[ii]++;
      if (this->m_Loop[ii] == this->m_Bound[ii])
      {
        this->m_Loop[ii] = this->m_BeginIndex[ii];
        if (!m_CenterIsActive)
        {
          this->GetElement(this->GetCenterNeighborhoodIndex()) += this->m_WrapOffset[ii];
        }
        for (auto it = m_ActiveIndexList.begin(); it != m_ActiveIndexList.end(); ++it)
        {
          (this->GetElement(*it)) += this->m_WrapOffset[ii];
        }
      }
      else
      {
        break;
      }
    }
  }
  return *this;
}

template <typename TImage, typename TBoundaryCondition>
ConstShapedNeighborhoodIterator<TImage, TBoundaryCondition> &
ConstShapedNeighborhoodIterator<TImage, TBoundaryCondition>::operator--()
{
  // Repositioning neighborhood, previous bounds check on neighborhood
  // location is invalid.
  this->m_IsInBoundsValid = false;

  if (this->m_BoundaryCondition->RequiresCompleteNeighborhood())
  {
    // Some Boundary Conditions, such as ZeroFluxNeumann can return
    // values from anywhere in the neighborhood, thus we can not do
    // the shaped optimization.

    NeighborhoodIterator<TImage, TBoundaryCondition>::operator--();
  }
  else
  {
    // Center pointer must be updated whether or not it is active.
    if (!m_CenterIsActive)
    {
      this->GetElement(this->GetCenterNeighborhoodIndex())--;
    }

    // Decrement pointers for only the active pixels.
    for (auto it = m_ActiveIndexList.begin(); it != m_ActiveIndexList.end(); ++it)
    {
      (this->GetElement(*it))--;
    }

    // Check loop bounds, wrap & add pointer offsets if needed.
    for (unsigned int i = 0; i < Dimension; ++i)
    {
      if (this->m_Loop[i] == this->m_BeginIndex[i])
      {
        this->m_Loop[i] = this->m_Bound[i] - 1;
        if (!m_CenterIsActive)
        {
          this->GetElement(this->GetCenterNeighborhoodIndex()) -= this->m_WrapOffset[i];
        }
        for (auto it = m_ActiveIndexList.begin(); it != m_ActiveIndexList.end(); ++it)
        {
          (this->GetElement(*it)) -= this->m_WrapOffset[i];
        }
      }
      else
      {
        this->m_Loop[i]--;
        break;
      }
    }
  }
  return *this;
}

template <typename TImage, typename TBoundaryCondition>
ConstShapedNeighborhoodIterator<TImage, TBoundaryCondition> &
ConstShapedNeighborhoodIterator<TImage, TBoundaryCondition>::operator+=(const OffsetType & idx)
{
  OffsetValueType         accumulator = 0;
  const OffsetValueType * stride = this->GetImagePointer()->GetOffsetTable();

  // Repositioning neighborhood, previous bounds check on neighborhood
  // location is invalid.
  this->m_IsInBoundsValid = false;

  if (this->m_BoundaryCondition->RequiresCompleteNeighborhood())
  {
    // Some Boundary Conditions, such as ZeroFluxNeumann can return
    // values from anywhere in the neighborhood, thus we can not do
    // the shaped optimization.

    NeighborhoodIterator<TImage, TBoundaryCondition>::operator+=(idx);
  }
  else
  {
    // Offset from the increment in the lowest dimension
    accumulator += idx[0];

    // Offsets from the stride lengths in each dimension.
    //
    // Because the image offset table is based on its buffer size and
    // not its requested region size, we don't have to worry about
    // adding in the wrapping offsets.
    for (unsigned int i = 1; i < Dimension; ++i)
    {
      accumulator += idx[i] * stride[i];
    }

    // Center pointer is always updated even if not active.
    if (!m_CenterIsActive)
    {
      this->GetElement(this->GetCenterNeighborhoodIndex()) += accumulator;
    }

    // Increment pointers only for those active pixels
    for (auto it = m_ActiveIndexList.begin(); it != m_ActiveIndexList.end(); ++it)
    {
      (this->GetElement(*it)) += accumulator;
    }

    // Update loop counter values
    this->m_Loop += idx;
  }
  return *this;
}

template <typename TImage, typename TBoundaryCondition>
ConstShapedNeighborhoodIterator<TImage, TBoundaryCondition> &
ConstShapedNeighborhoodIterator<TImage, TBoundaryCondition>::operator-=(const OffsetType & idx)
{
  OffsetValueType         accumulator = 0;
  const OffsetValueType * stride = this->GetImagePointer()->GetOffsetTable();

  // Repositioning neighborhood, previous bounds check on neighborhood
  // location is invalid.
  this->m_IsInBoundsValid = false;

  if (this->m_BoundaryCondition->RequiresCompleteNeighborhood())
  {
    // Some Boundary Conditions, such as ZeroFluxNeumann can return
    // values from anywhere in the neighborhood, thus we can not do
    // the shaped optimization.

    NeighborhoodIterator<TImage, TBoundaryCondition>::operator-=(idx);
  }
  else
  {
    // Offset from the increment in the lowest dimension
    accumulator += idx[0];

    // Offsets from the stride lengths in each dimension.
    //
    // Because the image offset table is based on its buffer size and
    // not its requested region size, we don't have to worry about
    // adding in the wrapping offsets.
    for (unsigned int i = 1; i < Dimension; ++i)
    {
      accumulator += idx[i] * stride[i];
    }

    // Center pointer is always updated even if not active.
    if (!m_CenterIsActive)
    {
      this->GetElement(this->GetCenterNeighborhoodIndex()) -= accumulator;
    }

    // Increment pointers only for those active pixels
    for (auto it = m_ActiveIndexList.begin(); it != m_ActiveIndexList.end(); ++it)
    {
      (this->GetElement(*it)) -= accumulator;
    }

    // Update loop counter values
    this->m_Loop -= idx;
  }
  return *this;
}
} // namespace itk

#endif
