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

#include "itkMath.h"

namespace itk
{
namespace Functor
{

/**
 * \class Add2
 * \brief
 * \ingroup ITKImageIntensity
 */
template< typename TInput1, typename TInput2 = TInput1, typename TOutput = TInput1 >
class ITK_TEMPLATE_EXPORT Add2
{
public:
  Add2() = default;
  ~Add2() = default;
  bool operator!=(const Add2 &) const
  {
    return false;
  }

  bool operator==(const Add2 & other) const
  {
    return !( *this != other );
  }

  inline TOutput operator()(const TInput1 & A, const TInput2 & B) const
  {
    return static_cast< TOutput >( A + B );
  }
};


/**
 * \class Add3
 * \brief
 * \ingroup ITKImageIntensity
 */
template< typename TInput1, typename TInput2, typename TInput3, typename TOutput >
class ITK_TEMPLATE_EXPORT Add3
{
public:
  Add3() = default;
  ~Add3() = default;
  bool operator!=(const Add3 &) const
  {
    return false;
  }

  bool operator==(const Add3 & other) const
  {
    return !( *this != other );
  }

  inline TOutput operator()(const TInput1 & A,
                            const TInput2 & B,
                            const TInput3 & C) const
  { return static_cast<TOutput>( A + B + C ); }
};


/**
 * \class Sub2
 * \brief
 * \ingroup ITKImageIntensity
 */
template< typename TInput1, typename TInput2 = TInput1, typename TOutput = TInput1 >
class ITK_TEMPLATE_EXPORT Sub2
{
public:
  Sub2() = default;
  ~Sub2() = default;
  bool operator!=(const Sub2 &) const
  {
    return false;
  }

  bool operator==(const Sub2 & other) const
  {
    return !( *this != other );
  }

  inline TOutput operator()(const TInput1 & A, const TInput2 & B) const
  { return static_cast<TOutput>( A - B ); }
};


/**
 * \class Mult
 * \brief
 * \ingroup ITKImageIntensity
 */
template< typename TInput1, typename TInput2 = TInput1, typename TOutput = TInput1 >
class ITK_TEMPLATE_EXPORT Mult
{
public:
  Mult() = default;
  ~Mult() = default;
  bool operator!=(const Mult &) const
  {
    return false;
  }

  bool operator==(const Mult & other) const
  {
    return !( *this != other );
  }

  inline TOutput operator()(const TInput1 & A, const TInput2 & B) const
  { return static_cast<TOutput>( A * B ); }
};


/**
 * \class Div
 * \brief
 * \ingroup ITKImageIntensity
 */
template< typename TInput1, typename TInput2, typename TOutput >
class ITK_TEMPLATE_EXPORT Div
{
public:
  Div() = default;
  ~Div() = default;
  bool operator!=(const Div &) const
  {
    return false;
  }

  bool operator==(const Div & other) const
  {
    return !( *this != other );
  }

  inline TOutput operator()(const TInput1 & A, const TInput2 & B) const
  {
    if ( itk::Math::NotAlmostEquals(B, NumericTraits<TInput2>::ZeroValue()) )
      {
      return (TOutput)( A / B );
      }
    else
      {
      return NumericTraits< TOutput >::max( static_cast<TOutput>(A) );
      }
  }
};


/**
 * \class DivideOrZeroOut
 * \brief
 * \ingroup ITKImageIntensity
 */
template< typename TNumerator, typename TDenominator=TNumerator, typename TOutput=TNumerator >
class ITK_TEMPLATE_EXPORT DivideOrZeroOut
{
public:
  DivideOrZeroOut()
  {
    m_Threshold = 1e-5 * NumericTraits< TDenominator >::OneValue();
    m_Constant = NumericTraits< TOutput >::ZeroValue();
  };

  ~DivideOrZeroOut() = default;

  bool operator!=( const DivideOrZeroOut & other ) const
  {
    return !(*this == other);
  }

  bool operator==( const DivideOrZeroOut & itkNotUsed(other) ) const
  {
    // Always return true for now.  Do a comparison to m_Threshold if it is
    // every made set-able.
    return true;
  }

  inline TOutput operator()( const TNumerator & n, const TDenominator & d ) const
  {
    if ( d < m_Threshold )
      {
      return m_Constant;
      }
    return static_cast< TOutput >( n ) / static_cast< TOutput >( d );
  }
  TDenominator m_Threshold;
  TOutput      m_Constant;
};


/** \class Modulus
 *
 * \ingroup ITKImageIntensity
 */
template< typename TInput1, typename TInput2, typename TOutput >
class ITK_TEMPLATE_EXPORT Modulus
{
public:
  Modulus() = default;
  ~Modulus() = default;

  bool operator!=(const Modulus &) const
  {
    return false;
  }

  bool operator==(const Modulus & other) const
  {
    return !( *this != other );
  }

 inline TOutput operator()(const TInput1 & A, const TInput2 & B) const
 {
   if ( B != NumericTraits<TInput2>::ZeroValue() )
     {
     return static_cast< TOutput >( A % B );
     }
   else
     {
     return NumericTraits< TOutput >::max( static_cast<TOutput>(A) );
     }
  }

};

#if ! defined ( ITK_FUTURE_LEGACY_REMOVE )

/** \class ModulusTransform
 *
 * \deprecated The two template parametered ModulusTransform functor
 * is depricated. Please use the version with 3 template parameters.
 *
 * \ingroup ITKImageIntensity
 */
template< typename TInput, typename  TOutput >
class ITK_TEMPLATE_EXPORT ModulusTransform
{
public:
  ModulusTransform() { m_Dividend = 5; }
  ~ModulusTransform() = default;
  void SetDividend(TOutput dividend) { m_Dividend = dividend; }

  bool operator!=(const ModulusTransform & other) const
    {
      if ( m_Dividend != other.m_Dividend )
        {
        return true;
        }
      return false;
    }

  bool operator==(const ModulusTransform & other) const
    {
      return !( *this != other );
    }

  inline TOutput operator()(const TInput & x) const
    {
      auto result = static_cast< TOutput >( x % m_Dividend );

      return result;
    }

private:
  TInput m_Dividend;
};

#endif

/**
 * \class DivFloor
 * \brief Cast arguments to double, performs division then takes the floor.
 *
 * This division operator is intended to implement Pythons PEP 238
 * "Floor Division"-style operation.
 *
 * \ingroup ITKImageIntensity
 */
template< class TInput1, class TInput2, class TOutput >
class DivFloor
{
public:

  bool operator!=(const DivFloor &) const
    {
      return false;
    }

  bool operator==(const DivFloor & other) const
    {
      return !( *this != other );
    }

  inline TOutput operator()(const TInput1 & A, const TInput2 & B) const
    {
      const double temp = std::floor( double(A) / double(B) );
      if(NumericTraits< TOutput >::IsInteger && Math::isinf(temp))
        {
        if ( temp > 0 )
          {
          return NumericTraits< TOutput >::max( A );
          }
        else
          {
          return NumericTraits< TOutput >::NonpositiveMin( A );
          }
        }
      return static_cast< TOutput >(temp);
    }
};

/**
 * \class DivReal
 * \brief Promotes arguments to real type and performs division.
 *
 * This division operator is intended to implement Pythons PEP 238
 * "True Division". The output type is intended to be a real
 * type. Division by zero is expected to produce an "inf" floating
 * point value.
 *
 * \ingroup ITKImageIntensity
 */
template< class TInput1, class TInput2, class TOutput >
class DivReal
{
public:
  // Use default copy, assigned and destructor
  bool operator!=(const DivReal &) const
  {
    return false;
  }

  bool operator==(const DivReal & other) const
  {
    return !( *this != other );
  }

  inline TOutput operator()(const TInput1 & A, const TInput2 & B) const
  {
    return static_cast<TOutput>( static_cast<typename NumericTraits<TInput1>::RealType>(A)
                                 /
                                 static_cast<typename NumericTraits<TInput2>::RealType >(B) );
  }
};
/**
 * \class UnaryMinus
 * \brief Apply the unary minus operator
 *
 * Assumed that the output type is signed.
 * \ingroup ITKImageIntensity
 */
template< class TInput1, class TOutput = TInput1 >
class UnaryMinus
{
public:
  UnaryMinus() = default;
  ~UnaryMinus() = default;
  bool operator!=(const UnaryMinus &) const
  {
    return false;
  }

  bool operator==(const UnaryMinus & other) const
  {
    return !( *this != other );
  }

  inline TOutput operator()(const TInput1 & A ) const
  { return (TOutput)( -A ); }
};
}
}

#endif
