/*=========================================================================
 *
 *  Copyright Insight Software Consortium
 *
 *  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
 *
 *         http://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

#include "itkConstNeighborhoodIteratorWithOnlyIndex.h"

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;
    }
  else 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 >
typename ConstNeighborhoodIteratorWithOnlyIndex< TImage >::OffsetType
ConstNeighborhoodIteratorWithOnlyIndex< TImage >
::ComputeInternalIndex(NeighborIndexType n) const
{
  OffsetType    ans;
  auto r = (unsigned long)n;
  for ( long i = static_cast<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 >
typename ConstNeighborhoodIteratorWithOnlyIndex< TImage >::RegionType
ConstNeighborhoodIteratorWithOnlyIndex< TImage >
::GetBoundingBoxAsImageRegion() const
{
  const IndexValueType zero = NumericTraits< IndexValueType >::ZeroValue();
  RegionType ans;
  ans.SetIndex( this->GetIndex(zero) );
  ans.SetSize( this->GetSize() );

  return ans;
}

template< typename TImage >
ConstNeighborhoodIteratorWithOnlyIndex< TImage >
::ConstNeighborhoodIteratorWithOnlyIndex()
{
  IndexType zeroIndex;
  zeroIndex.Fill(0);

  SizeType zeroSize;
  zeroSize.Fill(0);

  m_Bound.Fill(0);
  m_BeginIndex.Fill(0);
  m_EndIndex.Fill(0);
  m_Loop.Fill(0);
  m_Region.SetIndex(zeroIndex);
  m_Region.SetSize(zeroSize);

  for ( DimensionValueType i = 0; i < Dimension; i++ )
    {
    m_InBounds[i] = false;
    }

  m_IsInBounds = false;
  m_IsInBoundsValid = false;
  m_NeedToUseBoundaryCondition = false;
}

template< typename TImage >
ConstNeighborhoodIteratorWithOnlyIndex< TImage >
::ConstNeighborhoodIteratorWithOnlyIndex(const Self & orig):
  Neighborhood< DummyNeighborhoodPixelType, Dimension >(orig)
{
  m_Bound      = orig.m_Bound;
  m_BeginIndex = orig.m_BeginIndex;
  m_ConstImage = orig.m_ConstImage;
  m_EndIndex   = orig.m_EndIndex;
  m_Loop       = orig.m_Loop;
  m_Region     = orig.m_Region;

  m_NeedToUseBoundaryCondition = orig.m_NeedToUseBoundaryCondition;
  for ( DimensionValueType i = 0; i < Dimension; ++i )
    {
    m_InBounds[i] = orig.m_InBounds[i];
    }
  m_IsInBoundsValid = orig.m_IsInBoundsValid;
  m_IsInBounds = orig.m_IsInBounds;

  m_InnerBoundsLow  = orig.m_InnerBoundsLow;
  m_InnerBoundsHigh = orig.m_InnerBoundsHigh;
}

template< typename TImage >
ConstNeighborhoodIteratorWithOnlyIndex< TImage >
::ConstNeighborhoodIteratorWithOnlyIndex(const SizeType & radius, const ImageType *ptr, const RegionType & region)
{
  this->Initialize(radius, ptr, region);
  for ( unsigned int i = 0; i < Dimension; i++ )
    {
    m_InBounds[i] = false;
    }
}

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 >
ConstNeighborhoodIteratorWithOnlyIndex< TImage > &
ConstNeighborhoodIteratorWithOnlyIndex< TImage >
::operator=(const Self & orig)
{
  if(this != &orig)
    {
    Superclass::operator=(orig);

    m_Bound        = orig.m_Bound;
    m_ConstImage   = orig.m_ConstImage;
    m_EndIndex     = orig.m_EndIndex;
    m_Loop         = orig.m_Loop;
    m_Region       = orig.m_Region;
    m_BeginIndex = orig.m_BeginIndex;

    m_NeedToUseBoundaryCondition = orig.m_NeedToUseBoundaryCondition;

    m_InnerBoundsLow  = orig.m_InnerBoundsLow;
    m_InnerBoundsHigh = orig.m_InnerBoundsHigh;

    for ( DimensionValueType i = 0; i < Dimension; ++i )
      {
      m_InBounds[i] = orig.m_InBounds[i];
      }
    m_IsInBoundsValid = orig.m_IsInBoundsValid;
    m_IsInBounds = orig.m_IsInBounds;
    }
  return *this;
}

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
{
  DimensionValueType i;

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

  os << indent << ",  m_InnerBoundsLow = { ";
  for ( i = 0; i < Dimension; i++ )
    {
    os << m_InnerBoundsLow[i] << " ";
    }
  os << "}, m_InnerBoundsHigh = { ";
  for ( i = 0; i < Dimension; i++ )
    {
    os << m_InnerBoundsHigh[i] << " ";
    }
  os << "} }" << std::endl;
  Superclass::PrintSelf( os, indent.GetNextIndent() );
}

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
