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


namespace itk
{
template <typename TImage>
bool
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::InBounds() const
{
  if (m_IsInBoundsValid)
  {
    return m_IsInBounds;
  }

  bool ans = true;
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    if (m_Loop[i] < m_InnerBoundsLow[i] || m_Loop[i] >= m_InnerBoundsHigh[i])
    {
      m_InBounds[i] = ans = false;
    }
    else
    {
      m_InBounds[i] = true;
    }
  }
  m_IsInBounds = ans;
  m_IsInBoundsValid = true;
  return ans;
}

template <typename TImage>
bool
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::IndexInBounds(const NeighborIndexType n,
                                                              OffsetType &            internalIndex,
                                                              OffsetType &            offset) const
{
  if (!m_NeedToUseBoundaryCondition)
  {
    return true;
  }
  if (this->InBounds()) // Is this whole neighborhood in bounds?
  {
    return true;
  }
  else
  {
    bool flag = true;
    internalIndex = this->ComputeInternalIndex(n);

    // Is this pixel in bounds?
    for (DimensionValueType i = 0; i < Dimension; ++i)
    {
      if (m_InBounds[i])
      {
        offset[i] = 0; // this dimension in bounds
      }
      else // part of this dimension spills out of bounds
      {
        // Calculate overlap for this dimension
        const OffsetValueType OverlapLow = m_InnerBoundsLow[i] - m_Loop[i];
        if (internalIndex[i] < OverlapLow)
        {
          flag = false;
          offset[i] = OverlapLow - internalIndex[i];
        }
        else
        {
          const auto overlapHigh =
            static_cast<OffsetValueType>(this->GetSize(i) - ((m_Loop[i] + 2) - m_InnerBoundsHigh[i]));
          if (overlapHigh < internalIndex[i])
          {
            flag = false;
            offset[i] = overlapHigh - internalIndex[i];
          }
          else
          {
            offset[i] = 0;
          }
        }
      }
    }
    return flag;
  }
}

template <typename TImage>
auto
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::ComputeInternalIndex(NeighborIndexType n) const -> OffsetType
{
  OffsetType ans;
  auto       r = static_cast<unsigned long>(n);
  for (long i = long{ Dimension } - 1; i >= 0; --i)
  {
    ans[i] = static_cast<OffsetValueType>(r / this->GetStride(i));
    r = r % this->GetStride(i);
  }
  return ans;
}

template <typename TImage>
auto
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::GetBoundingBoxAsImageRegion() const -> RegionType
{
  constexpr IndexValueType zero{};
  const RegionType         ans(this->GetIndex(zero), this->GetSize());

  return ans;
}


template <typename TImage>
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::ConstNeighborhoodIteratorWithOnlyIndex(const SizeType &   radius,
                                                                                       const ImageType *  ptr,
                                                                                       const RegionType & region)
{
  this->Initialize(radius, ptr, region);
}

template <typename TImage>
void
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::SetEndIndex()
{
  if (m_Region.GetNumberOfPixels() > 0)
  {
    m_EndIndex = m_Region.GetIndex();
    m_EndIndex[Dimension - 1] =
      m_Region.GetIndex()[Dimension - 1] + static_cast<OffsetValueType>(m_Region.GetSize()[Dimension - 1]);
  }
  else
  {
    // Region has no pixels, so set the end index to be the begin index
    m_EndIndex = m_Region.GetIndex();
  }
}

template <typename TImage>
void
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::GoToBegin()
{
  this->SetLocation(m_BeginIndex);
}

template <typename TImage>
void
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::GoToEnd()
{
  this->SetLocation(m_EndIndex);
}

template <typename TImage>
void
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::Initialize(const SizeType &   radius,
                                                           const ImageType *  ptr,
                                                           const RegionType & region)
{
  m_ConstImage = ptr;
  m_Region = region;

  this->SetRadius(radius);
  this->SetBeginIndex(region.GetIndex());
  this->SetLocation(region.GetIndex());
  this->SetBound(region.GetSize());
  this->SetEndIndex();

  // now determine whether boundary conditions are going to be needed
  const IndexType bStart = ptr->GetBufferedRegion().GetIndex();
  const SizeType  bSize = ptr->GetBufferedRegion().GetSize();
  const IndexType rStart = region.GetIndex();
  const SizeType  rSize = region.GetSize();

  m_NeedToUseBoundaryCondition = false;
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    const auto overlapLow =
      static_cast<OffsetValueType>((rStart[i] - static_cast<OffsetValueType>(radius[i])) - bStart[i]);
    const auto overlapHigh = static_cast<OffsetValueType>(
      (bStart[i] + bSize[i]) - (rStart[i] + rSize[i] + static_cast<OffsetValueType>(radius[i])));

    if (overlapLow < 0) // out of bounds condition, define a region of
    {
      m_NeedToUseBoundaryCondition = true;
      break;
    }

    if (overlapHigh < 0)
    {
      m_NeedToUseBoundaryCondition = true;
      break;
    }
  }

  m_IsInBoundsValid = false;
  m_IsInBounds = false;
}

template <typename TImage>
bool
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::operator<(const Self & it) const
{
  for (DimensionValueType i = 1; i <= Dimension; ++i)
  {
    if (this->GetIndex()[Dimension - i] < it.GetIndex()[Dimension - i])
    {
      return true;
    }
    if (this->GetIndex()[Dimension - i] > it.GetIndex()[Dimension - i])
    {
      return false;
    }
  }
  return false;
}

template <typename TImage>
bool
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::operator<=(const Self & it) const
{
  if (this->operator==(it))
  {
    return true;
  }
  return this->operator<(it);
}

template <typename TImage>
bool
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::operator>(const Self & it) const
{
  for (DimensionValueType i = 1; i <= Dimension; ++i)
  {
    if (this->GetIndex()[Dimension - i] > it.GetIndex()[Dimension - i])
    {
      return true;
    }
    if (this->GetIndex()[Dimension - i] < it.GetIndex()[Dimension - i])
    {
      return false;
    }
  }
  return false;
}

template <typename TImage>
bool
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::operator>=(const Self & it) const
{
  if (this->operator==(it))
  {
    return true;
  }
  return this->operator>(it);
}

template <typename TImage>
bool
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::IsAtEnd() const
{
  // m_EndIndex is at end of iteration when its greatest dimension
  // is at the bound for that dimension.
  if (this->GetIndex()[Dimension - 1] > this->m_EndIndex[Dimension - 1])
  {
    ExceptionObject    e(__FILE__, __LINE__);
    std::ostringstream msg;
    msg << "In method IsAtEnd, GetIndex()[Dimension - 1] = " << GetIndex()[Dimension - 1]
        << " is greater than m_EndIndex[Dimension - 1] = " << this->m_EndIndex[Dimension - 1] << std::endl
        << "  " << *this;
    e.SetDescription(msg.str().c_str());
    throw e;
  }
  return this->GetIndex()[Dimension - 1] == this->m_EndIndex[Dimension - 1];
}

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

  // Check loop bounds, wrap.
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    m_Loop[i]++;
    if (m_Loop[i] == m_Bound[i])
    {
      // Let the last dimension stay at m_Bound[i],
      // signifying we are at the end.
      if (i < (Dimension - 1))
      {
        m_Loop[i] = m_BeginIndex[i];
      }
    }
    else
    {
      break;
    }
  }
  return *this;
}

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

  // Check loop bounds, wrap.
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    if (m_Loop[i] == m_BeginIndex[i])
    {
      m_Loop[i] = m_Bound[i] - 1;
    }
    else
    {
      m_Loop[i]--;
      break;
    }
  }
  return *this;
}

template <typename TImage>
void
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);

  os << indent;
  os << "ConstNeighborhoodIteratorWithOnlyIndex {this= " << this;
  os << ", m_Region = { Start = {";
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    os << m_Region.GetIndex()[i] << ' ';
  }
  os << "}, Size = { ";
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    os << m_Region.GetSize()[i] << ' ';
  }
  os << "} }";
  os << ", m_BeginIndex = { ";
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    os << m_BeginIndex[i] << ' ';
  }
  os << "} , m_EndIndex = { ";
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    os << m_EndIndex[i] << ' ';
  }
  os << "} , m_Loop = { ";
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    os << m_Loop[i] << ' ';
  }
  os << "}, m_Bound = { ";
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    os << m_Bound[i] << ' ';
  }
  os << "}, m_IsInBounds = {" << m_IsInBounds;
  os << "}, m_IsInBoundsValid = {" << m_IsInBoundsValid;

  os << indent << ",  m_InnerBoundsLow = { ";
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    os << m_InnerBoundsLow[i] << ' ';
  }
  os << "}, m_InnerBoundsHigh = { ";
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    os << m_InnerBoundsHigh[i] << ' ';
  }
  os << "} }" << std::endl;
}

template <typename TImage>
void
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::SetBound(const SizeType & size)
{
  SizeType        radius = this->GetRadius();
  const IndexType imageBRStart = m_ConstImage->GetBufferedRegion().GetIndex();
  SizeType        imageBRSize = m_ConstImage->GetBufferedRegion().GetSize();

  // Set the bounds and the wrapping offsets. Inner bounds are the loop
  // indices where the iterator will begin to overlap the edge of the image
  // buffered region.
  for (DimensionValueType i = 0; i < Dimension; ++i)
  {
    m_Bound[i] = m_BeginIndex[i] + static_cast<OffsetValueType>(size[i]);
    m_InnerBoundsHigh[i] = static_cast<IndexValueType>(imageBRStart[i] + static_cast<OffsetValueType>(imageBRSize[i]) -
                                                       static_cast<OffsetValueType>(radius[i]));
    m_InnerBoundsLow[i] = static_cast<IndexValueType>(imageBRStart[i] + static_cast<OffsetValueType>(radius[i]));
  }
}

template <typename TImage>
ConstNeighborhoodIteratorWithOnlyIndex<TImage> &
ConstNeighborhoodIteratorWithOnlyIndex<TImage>::operator+=(const OffsetType & idx)
{
  // Repositioning neighborhood, previous bounds check on neighborhood
  // location is invalid.
  m_IsInBoundsValid = false;

  // Update loop counter values
  m_Loop += idx;

  return *this;
}

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

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

#endif
