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

#include "itkAnnulusOperator.h"
#include "itkMath.h"
#include "itkSphereSpatialFunction.h"

namespace itk
{
/** Create the operator */
template< typename TPixel, unsigned int TDimension, typename TAllocator >
void
AnnulusOperator< TPixel, TDimension, TAllocator >
::CreateOperator()
{
  CoefficientVector coefficients;

  coefficients = this->GenerateCoefficients();

  this->Fill(coefficients);
}

/** This function fills the coefficients into the corresponding
 *  neighborhood. */
template< typename TPixel, unsigned int TDimension, typename TAllocator >
void
AnnulusOperator< TPixel, TDimension, TAllocator >
::Fill(const CoefficientVector & coeff)
{
  auto * temp_slice = new std::slice(0, coeff.size(), 1);

  typename Self::SliceIteratorType data(this, *temp_slice);
  delete temp_slice;

  auto it = coeff.begin();

  // Copy the coefficients into the neighborhood
  for ( data = data.Begin(); data < data.End(); ++data, ++it )
    {
    *data = *it;
    }
}

template< typename TPixel, unsigned int TDimension, typename TAllocator >
typename AnnulusOperator< TPixel, TDimension, TAllocator >
::CoefficientVector
AnnulusOperator< TPixel, TDimension, TAllocator >
::GenerateCoefficients()
{
  // Determine the initial kernel values...
  double interiorV, annulusV, exteriorV;

  if ( m_Normalize )
    {
    double bright = ( m_BrightCenter ? 1.0 : -1.0 );

    // Initial values for a normalized kernel
    interiorV = bright;
    annulusV = -1.0 * bright;
    exteriorV = 0.0;
    }
  else
    {
    // values for a specified kernel
    interiorV = m_InteriorValue;
    annulusV = m_AnnulusValue;
    exteriorV = m_ExteriorValue;
    }

  // Compute the size of the kernel in pixels
  SizeType     r;
  unsigned int i, j;
  double       outerRadius = m_InnerRadius + m_Thickness;
  for ( i = 0; i < TDimension; ++i )
    {
    r[i] = Math::Ceil< SizeValueType >(outerRadius / m_Spacing[i]);
    }
  this->SetRadius(r);

  // Use a couple of sphere spatial functions...
  using SphereType = SphereSpatialFunction< TDimension >;
  typename SphereType::Pointer innerS = SphereType::New();
  typename SphereType::Pointer outerS = SphereType::New();

  innerS->SetRadius(m_InnerRadius);
  outerS->SetRadius(m_InnerRadius + m_Thickness);

  // Walk the neighborhood (this) and evaluate the sphere spatial
  // functions
  double       sumNotExterior = 0.0;
  double       sumNotExteriorSq = 0.0;
  unsigned int countNotExterior = 0;

  const typename SizeType::SizeValueType w = this->Size();

  std::vector< bool > outside(w);
  CoefficientVector   coeffP(w);
  OffsetType          offset;
  typename SphereType::InputType point;

  for ( i = 0; i < w; ++i )
    {
    // get the offset from the center pixel
    offset = this->GetOffset(i);

    // convert to a position
    for ( j = 0; j < TDimension; ++j )
      {
      point[j] = m_Spacing[j] * offset[j];
      }

    // evaluate the spheres
    const bool inInner = innerS->Evaluate(point);
    const bool inOuter = outerS->Evaluate(point);

    // set the coefficients
    if ( !inOuter )
      {
      // outside annulus
      coeffP[i] = exteriorV;
      outside[i] = true;
      }
    else if ( !inInner )
      {
      // inside the outer circle but outside the inner circle
      coeffP[i] = annulusV;
      sumNotExterior += annulusV;
      sumNotExteriorSq += ( annulusV * annulusV );
      countNotExterior++;
      outside[i] = false;
      }
    else
      {
      // inside inner circle
      coeffP[i] = interiorV;
      sumNotExterior += interiorV;
      sumNotExteriorSq += ( interiorV * interiorV );
      countNotExterior++;
      outside[i] = false;
      }
    }

  // Normalize the kernel if necessary
  if ( m_Normalize )
    {
    // Calculate the mean and standard deviation of kernel values NOT
    // the exterior
    auto num = static_cast< double >( countNotExterior );
    double mean = sumNotExterior / num;
    double var = ( sumNotExteriorSq - ( sumNotExterior * sumNotExterior / num ) )
      / ( num - 1.0 );
    double std = std::sqrt(var);

    // convert std to a scaling factor k such that
    //
    //        || (coeffP - mean) / k || = 1.0
    //
    double k = std * std::sqrt(num - 1.0);

    // Run through the kernel again, shifting and normalizing the
    // elements that are not exterior to the annulus.  This forces the
    // kernel to have mean zero and norm 1 AND forces the region
    // outside the annulus to have no influence.
    for ( i = 0; i < w; ++i )
      {
      // normalize the coefficient if it is inside the outer circle
      // (exterior to outer circle is already zero)
      if ( !outside[i] )
        {
        coeffP[i] = ( coeffP[i] - mean ) / k;
        }
      }
    }

  return coeffP;
}

} // namespace itk

#endif
