/*=========================================================================
 *
 *  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 itkConstShapedNeighborhoodIterator_hxx
#define itkConstShapedNeighborhoodIterator_hxx
#include "itkConstShapedNeighborhoodIterator.h"
namespace itk
{
template< typename TImage, typename TBoundaryCondition >
void
ConstShapedNeighborhoodIterator< TImage, TBoundaryCondition >
::PrintSelf(std::ostream & os, Indent indent) const
{
  os << indent <<  "ConstShapedNeighborhoodIterator {this = " << this;
  os << " m_ActiveIndexList = [";
  for ( auto it = m_ActiveIndexList.begin();
        it != m_ActiveIndexList.end();
        ++it )
    {
    os << *it << " ";
    }
  os << "] ";
  os << " m_CenterIsActive = " << m_CenterIsActive;
  os << "}" << std::endl;
  Superclass::PrintSelf( os, indent.GetNextIndent() );
}

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 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;
    }
  else
    {
    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 >
void
ConstShapedNeighborhoodIterator< TImage, TBoundaryCondition >
::CreateActiveListFromNeighborhood(const NeighborhoodType &neighborhood)
{
  if (this->GetRadius() != neighborhood.GetRadius())
    {
    itkGenericExceptionMacro(<< "Radius of shaped iterator("
                             << this->GetRadius()
                             << ") does not equal radius of neighborhood("
                             << neighborhood.GetRadius() << ")");
    }
  typename NeighborhoodType::ConstIterator nit;
  NeighborIndexType idx = 0;
  for (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
    {
    IndexListConstIterator it;

    // 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 ( 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 ( 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--()
{
  unsigned int                  i;
  IndexListConstIterator        it;

  // 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 ( it = m_ActiveIndexList.begin();
          it != m_ActiveIndexList.end();
          it++ )
      {
      ( this->GetElement(*it) )--;
      }

    // Check loop bounds, wrap & add pointer offsets if needed.
    for ( 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 ( 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)
{
  unsigned int                  i;
  IndexListConstIterator        it;
  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 ( 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 ( 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)
{
  unsigned int                  i;
  IndexListConstIterator        it;
  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 ( 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 ( 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
