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

#include "itkZeroFluxNeumannBoundaryCondition.h"

namespace itk
{
template< typename TInputImage, typename TOutputImage >
typename ZeroFluxNeumannBoundaryCondition< TInputImage, TOutputImage >::OutputPixelType
ZeroFluxNeumannBoundaryCondition< TInputImage, TOutputImage >
::operator()(const OffsetType & point_index, const OffsetType & boundary_offset,
             const NeighborhoodType *data) const
{
  int linear_index = 0;

  // Return the value of the pixel at the closest boundary point.
  for ( unsigned int i = 0; i < ImageDimension; ++i )
    {
    linear_index += ( point_index[i] + boundary_offset[i] ) * data->GetStride(i);
    }

  // The reinterpret_cast is necessary, cause we will have a warning if we
  // do not do this. (In fact this function exists for legacy
  // reasons. The overloaded function below should be (and is) used instead).
  // See any of the neighborhood iterators.
  //
  // (data->operator[](linear_index)) is guaranteed to be a pointer to
  // TImage::PixelType except for VectorImage, in which case, it will be a
  // pointer to TImage::InternalPixelType.
  //
  // A typical neighborhood iterator working on an image will use the boundary
  // condition in the following manner:
  //
  // \code
  // // Initialize the functor typically in the constructor.
  // m_NeighborhoodAccessorFunctor = image->GetNeighborhoodAccessor();
  // m_NeighborhoodAccessorFunctor->SetBegin( image->GetBufferPointer() );
  //
  // m_NeighborhoodAccessorFunctor.BoundaryCondition(
  //    point_index, boundary_offset, data, m_ChosenBoundaryCondition );
  // \endcode
  //
  return static_cast< OutputPixelType >
    (*( reinterpret_cast< PixelType * >( ( data->operator[](linear_index) ) ) ) );
}

template< typename TInputImage, typename TOutputImage >
typename ZeroFluxNeumannBoundaryCondition< TInputImage, TOutputImage >::OutputPixelType
ZeroFluxNeumannBoundaryCondition< TInputImage, TOutputImage >
::operator()(const OffsetType & point_index, const OffsetType & boundary_offset,
             const NeighborhoodType *data,
             const NeighborhoodAccessorFunctorType & neighborhoodAccessorFunctor) const
{
  int linear_index = 0;

  // Return the value of the pixel at the closest boundary point.
  for ( unsigned int i = 0; i < ImageDimension; ++i )
    {
    linear_index += ( point_index[i] + boundary_offset[i] ) * data->GetStride(i);
    }

  return static_cast< OutputPixelType >
    ( neighborhoodAccessorFunctor.Get( data->operator[](linear_index) ) );
}


template< typename TInputImage, typename TOutputImage >
typename ZeroFluxNeumannBoundaryCondition< TInputImage, TOutputImage >::RegionType
ZeroFluxNeumannBoundaryCondition< TInputImage, TOutputImage >
::GetInputRequestedRegion( const RegionType & inputLargestPossibleRegion,
                           const RegionType & outputRequestedRegion ) const
{
  IndexType inputIndex = inputLargestPossibleRegion.GetIndex();
  SizeType  inputSize  = inputLargestPossibleRegion.GetSize();

  IndexType outputIndex = outputRequestedRegion.GetIndex();
  SizeType  outputSize  = outputRequestedRegion.GetSize();

  IndexType  requestIndex;
  SizeType   requestSize;
  RegionType requestRegion;

  for ( unsigned int i = 0; i < ImageDimension; i++)
    {
    // Check if the output region is entirely below the low index of
    // the image region.
    if ( outputIndex[i] + static_cast< OffsetValueType >( outputSize[i] ) <= inputIndex[i] )
      {
      // Include an image layer one pixel thick closest to the outputRequestedRegion
      requestIndex[i] = inputIndex[i];
      requestSize[i]  = 1;
      }
    // Check if the output is entirely above the high index of the
    // image region.
    else if (  outputIndex[i] >= inputIndex[i] + static_cast< OffsetValueType >( inputSize[i] ) )
      {
      // Include an image layer one pixel thick closest to the outputRequestedRegion
      requestIndex[i] = inputIndex[i] + static_cast< OffsetValueType >( inputSize[i] ) - 1;
      requestSize[i]  = 1;
      }
    // The output region intersects the image region.
    else
      {
      requestIndex[i] = inputIndex[i];
      requestSize[i]  = inputSize[i];

      // First check the start index
      if ( requestIndex[i] < outputIndex[i] )
        {
        // How much do we need to adjust
        OffsetValueType crop = outputIndex[i] - requestIndex[i];

        // Adjust the start index and the size of the current region
        requestIndex[i] += crop;
        requestSize[i] -= static_cast< SizeValueType >( crop );
        }
      // Now check the final size
      if ( requestIndex[i] + static_cast< OffsetValueType >( requestSize[i] ) >
           outputIndex[i] + static_cast< OffsetValueType >( outputSize[i] ) )
        {
        // How much do we need to adjust
        OffsetValueType crop = requestIndex[i] + static_cast< OffsetValueType >( requestSize[i] )
          - outputIndex[i] - static_cast< OffsetValueType >( outputSize[i] );

        // Adjust the size
        requestSize[i] -= static_cast< SizeValueType >( crop );
        }
      }
    }

  RegionType inputRequestedRegion( requestIndex, requestSize );

  return inputRequestedRegion;
}


template< typename TInputImage, typename TOutputImage >
typename ZeroFluxNeumannBoundaryCondition< TInputImage, TOutputImage >::OutputPixelType
ZeroFluxNeumannBoundaryCondition< TInputImage, TOutputImage >
::GetPixel( const IndexType & index, const TInputImage * image ) const
{
  RegionType imageRegion = image->GetLargestPossibleRegion();
  IndexType  imageIndex  = imageRegion.GetIndex();
  SizeType   imageSize   = imageRegion.GetSize();

  IndexType lookupIndex;

  for ( unsigned int i = 0; i < ImageDimension; ++i )
    {
    IndexValueType lowerIndex = imageIndex[i];
    IndexValueType upperIndex = imageIndex[i] + static_cast< IndexValueType >( imageSize[i] ) - 1;
    if ( index[i] < lowerIndex )
      {
      lookupIndex[i] = lowerIndex;
      }
    else if ( index[i] > upperIndex )
      {
      lookupIndex[i] = upperIndex;
      }
    else // in bounds
      {
      lookupIndex[i] = index[i];
      }
    }

  return static_cast< OutputPixelType >( image->GetPixel( lookupIndex ) );
}

} // end namespace itk

#endif
