/*!
 * \file    operations.h
 * \brief   Integral constant operations and logical operations
 *
 * Copyright (C) 2018-2019 Christos Choutouridis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifndef __utl_meta_operations_h__
#define __utl_meta_operations_h__

#include <utl/core/impl.h>
#include <utl/meta/selection.h>


/*!
 * \ingroup meta
 * \defgroup logic
 * logic operators and type relations support
 */
//! @{
namespace utl {
namespace meta{

   /*!
    * Logical relation for types
    */
   //! @{

   //! Negate the *bool* constant parameter
   template <bool B>
   using not_c = bool_<!B>;

   //! not
   template<typename _Tp>
   using not_ = not_c<_Tp::type::value>;

   //! OR implementation
   //! @{
   namespace detail {
      template<typename...> struct _or_;

      template<>
      struct _or_<> : false_ { };

      template<typename _T1>
      struct _or_<_T1> : _T1 { };

      template<typename _T1, typename _T2>
      struct _or_ <_T1, _T2>
        : if_<_T1, _T1, _T2> { };

      template<typename _T1, typename _T2, typename _T3, typename... _Tn>
      struct _or_<_T1, _T2, _T3, _Tn...>
         : if_<_T1, _T1, _or_<_T2, _T3, _Tn...>> { };
   }

   template <typename... _Ts>
   using or_ = eval<detail::_or_<_Ts...>>;
   //! @}

   //! AND implementation
   //! @{
   namespace detail {
      template<typename...> struct _and_;

      template<>
      struct _and_<>
         : true_ { };

      template<typename _T1>
      struct _and_ <_T1>
         : _T1 { };

      template<typename _T1, typename _T2>
      struct _and_<_T1, _T2>
         : if_<_T1, _T2, _T1> { };

      template<typename _T1, typename _T2, typename _T3, typename... _Tn>
      struct _and_<_T1, _T2, _T3, _Tn...>
         : if_<_T1, _and_<_T2, _T3, _Tn...>, _T1> { };
   }

   template <typename... _Ts>
   using and_ = eval<detail::_and_<_Ts...>>;
   //! @}

   //! same
   //! @{
   template<typename _T1, typename _T2>
   struct same_ : false_ { };

   template<typename _Tp>
   struct same_ <_Tp, _Tp> : true_ { };
   //! @}

   //! not same
   //! @{
   template<typename _T1, typename _T2>
   using not_same_ = not_<eval<same_<_T1, _T2>>>;
   //! @}

   //! @}
}}

//! @}


/*!
 * \ingroup meta
 * \defgroup integral operators
 * Type arithmetic and operators
 */
//! @{

namespace utl {
namespace meta {

   /*!
    * Math operations
    * requires IntegralConstant(_Tp)
    */
   //! @{

   //! Negation
   template <typename _Tp>
   using negate = integral_<decltype(-_Tp()), -_Tp()>;
   //! Addition
   template <typename _Tp1, typename _Tp2>
   using add = integral_<
                  decltype(_Tp1() + _Tp2()),
                  _Tp1() + _Tp2()
               >;
   //! Multiplication
   template <typename _Tp1, typename _Tp2>
   using mult = integral_<
                   decltype(_Tp2() * _Tp2()),
                   _Tp1() * _Tp2()
                >;
   //! Division
   template <typename _Tp1, typename _Tp2>
   using divide = integral_<
                     decltype(_Tp2() / _Tp2()),
                     _Tp1() / _Tp2()
                  >;
    //! Modulo
    template <typename _Tp1, typename _Tp2>
    using modulo = integral_<
                      decltype(_Tp1() % _Tp2()),
                      _Tp1() % _Tp2()
                   >;
   //! Substruction
   template <typename _Tp1, typename _Tp2>
   using sub = add<_Tp1, negate<_Tp2>>;

   //! Increase
   template <typename _Tp>
   using inc = add<_Tp, int_<1>>;

   //! decrease
   template <typename _Tp>
   using dec = add<_Tp, int_<-1>>;

   //! @}

   /*!
    * Comparison operations
    * requires IntegralConstant(_Tp)
    */
   //! @{

   //! \return a true-valued Integral Constant if _Tp1 and _Tp2 are equal.
   template <typename _Tp1, typename _Tp2>   using comp_eq = bool_<_Tp1() == _Tp2()>;
   //! \return a true-valued Integral Constant if _Tp1 is less than _Tp2.
   template <typename _Tp1, typename _Tp2>   using comp_lt = bool_<(_Tp1() < _Tp2())>;

   //! Not equal
   template <typename _Tp1, typename _Tp2>   using comp_ne = not_<comp_eq<_Tp1, _Tp2>>;
   //! Greater than
   template <typename _Tp1, typename _Tp2>   using comp_gt = comp_lt <_Tp2, _Tp1>;
   //! Less or equal
   template <typename _Tp1, typename _Tp2>   using comp_le = not_<comp_lt<_Tp2, _Tp1>>;
   //! Greater or equal
   template <typename _Tp1, typename _Tp2>   using comp_ge = not_<comp_lt<_Tp1, _Tp2>>;
  //! @}

   /*!
    * Bitwise operations
    * requires IntegralConstant(_Tp)
    */
   //! @{

   //! \return bitwise not (~) operation of its argument.
   template <typename _T>  using bitnot_ = integral_<typename _T::value_type, (typename _T::value_type)(~_T())>;
   //! \return bitwise and (&) operation of its arguments
   template <typename _Tp1, typename _Tp2>
   using bitand_ = integral_<decltype(_Tp1() & _Tp2()), _Tp1() & _Tp2()>;
   //! \return bitwise or (|) operation of its arguments.
   template <typename _Tp1, typename _Tp2>
   using bitor_ = integral_<decltype(_Tp1() | _Tp2()), _Tp1() | _Tp2()>;

   //! \return bitwise xor (^) operation of its arguments.
   template <typename _Tp1, typename _Tp2>
   using bitxor_ = integral_<decltype(_Tp1() ^ _Tp2()), _Tp1() ^ _Tp2()>;
   //! \return the result of bitwise shift left (<<) operation on _Tp.
   template <typename _Tp, typename shift>
   using shift_left = integral_<typename _Tp::value_type, (typename _Tp::value_type)(_Tp() << shift())>;
   //! \return the result of bitwise shift right (>>) operation on _Tp.
   template <typename _Tp, typename shift>
   using shift_right = integral_<typename _Tp::value_type, (typename _Tp::value_type)(_Tp() >> shift())>;
   //! @}
}}
//!@}


#endif /* __utl_meta_operations_h__ */
