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

#include "itkFrustumSpatialFunction.h"

namespace itk
{
template< unsigned int VDimension, typename TInput >
FrustumSpatialFunction< VDimension, TInput >::FrustumSpatialFunction() :
  m_RotationPlane( RotateInXZPlane )
{
  m_Apex.Fill( 0.0f );
}

template< unsigned int VDimension, typename TInput >
typename FrustumSpatialFunction< VDimension, TInput >::OutputType
FrustumSpatialFunction< VDimension, TInput >
::Evaluate(const InputType & position) const
{
  using PointType = InputType;
  using VectorType = typename PointType::VectorType;

  VectorType   relativePosition = position - m_Apex;
  const double distanceToApex = relativePosition.GetNorm();

  // Check Top and Bottom planes. If the angle is negative, the top plane
  // value may be less than the bottom plane, which is still correct.
  if ( m_TopPlane <= m_BottomPlane )
    {
    if ( distanceToApex < m_TopPlane
         || distanceToApex > m_BottomPlane )
      {
      return 0;
      }
    }
  else
    {
    if ( distanceToApex > m_TopPlane
         || distanceToApex < m_BottomPlane )
      {
      return 0;
      }
    }

  if ( m_RotationPlane == RotateInXZPlane )
    {
    const double dx = relativePosition[0];
    const double dy = relativePosition[1];
    const double dz = relativePosition[2];

    const double distanceXZ = std::sqrt(dx * dx + dz * dz);

    const double deg2rad = std::atan(1.0) / 45.0;

    // Check planes along Y
    const double angleY = std::atan2(dy, distanceXZ);
    if ( std::fabs(angleY) > m_ApertureAngleY * deg2rad )
      {
      return 0;
      }

    // Check planes along X
    const double angleX = std::atan2(dx, dz);

    if ( std::cos(angleX  + ( 180.0 + m_AngleZ ) * deg2rad)  <
         std::cos(deg2rad * m_ApertureAngleX) )
      {
      return 0;
      }

    return 1;
    }
  else if ( m_RotationPlane == RotateInYZPlane )
    {
    const double dx = relativePosition[0];
    const double dy = relativePosition[1];
    const double dz = relativePosition[2];

    const double distanceYZ = std::sqrt(dy * dy + dz * dz);

    const double deg2rad = std::atan(1.0) / 45.0;

    // Check planes along X
    const double angleX = std::atan2(dx, distanceYZ);
    if ( std::fabs(angleX) > m_ApertureAngleX * deg2rad )
      {
      return 0;
      }

    // Check planes along Y
    const double angleY = std::atan2(dy, dz);

    if ( std::cos(angleY  + ( 180.0 + m_AngleZ ) * deg2rad)  <
         std::cos(deg2rad * m_ApertureAngleY) )
      {
      return 0;
      }

    return 1;
    }
  else
    {
    itkExceptionMacro(<< "Rotation plane not set or set to an unsupported value!");
    }
}

template< unsigned int VDimension, typename TInput >
void
FrustumSpatialFunction< VDimension, TInput >::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);

  os << indent << "Apex: " << m_Apex << std::endl;
  os << indent << "AngleZ: " << m_AngleZ << std::endl;
  os << indent << "ApertureAngleX: " << m_ApertureAngleX << std::endl;
  os << indent << "ApertureAngleY: " << m_ApertureAngleY << std::endl;
  os << indent << "TopPlane: " << m_TopPlane << std::endl;
  os << indent << "BottomPlane: " << m_BottomPlane << std::endl;
  os << indent << "RotationPlane: " << m_RotationPlane << std::endl;
}
} // end namespace itk

#endif
