//
// Copyright 2013 Francisco Jerez
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//

#ifndef CLOVER_UTIL_FUNCTIONAL_HPP
#define CLOVER_UTIL_FUNCTIONAL_HPP

#include <type_traits>

namespace clover {
   struct identity {
      template<typename T>
      typename std::remove_reference<T>::type
      operator()(T &&x) const {
         return x;
      }
   };

   struct plus {
      template<typename T, typename S>
      typename std::common_type<T, S>::type
      operator()(T x, S y) const {
         return x + y;
      }
   };

   struct minus {
      template<typename T, typename S>
      typename std::common_type<T, S>::type
      operator()(T x, S y) const {
         return x - y;
      }
   };

   struct negate {
      template<typename T>
      T
      operator()(T x) const {
         return -x;
      }
   };

   struct multiplies {
      template<typename T, typename S>
      typename std::common_type<T, S>::type
      operator()(T x, S y) const {
         return x * y;
      }
   };

   struct divides {
      template<typename T, typename S>
      typename std::common_type<T, S>::type
      operator()(T x, S y) const {
         return x / y;
      }
   };

   struct modulus {
      template<typename T, typename S>
      typename std::common_type<T, S>::type
      operator()(T x, S y) const {
         return x % y;
      }
   };

   struct minimum {
      template<typename T>
      T
      operator()(T x) const {
         return x;
      }

      template<typename T, typename... Ts>
      T
      operator()(T x, Ts... xs) const {
         T y = minimum()(xs...);
         return x < y ? x : y;
      }
   };

   struct maximum {
      template<typename T>
      T
      operator()(T x) const {
         return x;
      }

      template<typename T, typename... Ts>
      T
      operator()(T x, Ts... xs) const {
         T y = maximum()(xs...);
         return x < y ? y : x;
      }
   };

   struct preincs {
      template<typename T>
      T &
      operator()(T &x) const {
         return ++x;
      }
   };

   struct predecs {
      template<typename T>
      T &
      operator()(T &x) const {
         return --x;
      }
   };

   template<typename T>
   class multiplies_by_t {
   public:
      multiplies_by_t(T x) : x(x) {
      }

      template<typename S>
      typename std::common_type<T, S>::type
      operator()(S y) const {
         return x * y;
      }

   private:
      T x;
   };

   template<typename T>
   multiplies_by_t<T>
   multiplies_by(T x) {
      return { x };
   }

   template<typename T>
   class preincs_by_t {
   public:
      preincs_by_t(T n) : n(n) {
      }

      template<typename S>
      S &
      operator()(S &x) const {
         return x += n;
      }

   private:
      T n;
   };

   template<typename T>
   preincs_by_t<T>
   preincs_by(T n) {
      return { n };
   }

   template<typename T>
   class predecs_by_t {
   public:
      predecs_by_t(T n) : n(n) {
      }

      template<typename S>
      S &
      operator()(S &x) const {
         return x -= n;
      }

   private:
      T n;
   };

   template<typename T>
   predecs_by_t<T>
   predecs_by(T n) {
      return { n };
   }

   struct greater {
      template<typename T, typename S>
      bool
      operator()(T x, S y) const {
         return x > y;
      }
   };

   struct evals {
      template<typename T>
      auto
      operator()(T &&x) const -> decltype(x()) {
         return x();
      }
   };

   struct derefs {
      template<typename T>
      auto
      operator()(T &&x) const -> decltype(*x) {
         return *x;
      }
   };

   struct addresses {
      template<typename T>
      T *
      operator()(T &x) const {
         return &x;
      }

      template<typename T>
      T *
      operator()(std::reference_wrapper<T> x) const {
         return &x.get();
      }
   };

   struct begins {
      template<typename T>
      auto
      operator()(T &x) const -> decltype(x.begin()) {
         return x.begin();
      }
   };

   struct ends {
      template<typename T>
      auto
      operator()(T &x) const -> decltype(x.end()) {
         return x.end();
      }
   };

   struct sizes {
      template<typename T>
      auto
      operator()(T &x) const -> decltype(x.size()) {
         return x.size();
      }
   };

   template<typename T>
   class advances_by_t {
   public:
      advances_by_t(T n) : n(n) {
      }

      template<typename S>
      S
      operator()(S &&it) const {
         std::advance(it, n);
         return it;
      }

   private:
      T n;
   };

   template<typename T>
   advances_by_t<T>
   advances_by(T n) {
      return { n };
   }

   struct zips {
      template<typename... Ts>
      std::tuple<Ts...>
      operator()(Ts &&... xs) const {
         return std::tuple<Ts...>(std::forward<Ts>(xs)...);
      }
   };

   struct is_zero {
      template<typename T>
      bool
      operator()(const T &x) const {
         return x == 0;
      }
   };

   struct keys {
      template<typename P>
      auto
      operator()(P &&p) const -> decltype(std::get<0>(std::forward<P>(p))) {
         return std::get<0>(std::forward<P>(p));
      }
   };

   struct values {
      template<typename P>
      auto
      operator()(P &&p) const -> decltype(std::get<1>(std::forward<P>(p))) {
         return std::get<1>(std::forward<P>(p));
      }
   };

   class name_equals {
   public:
      name_equals(const std::string &name) : name(name) {
      }

      template<typename T>
      bool
      operator()(const T &x) const {
         return std::string(x.name.begin(), x.name.end()) == name;
      }

   private:
      const std::string &name;
   };

   template<typename T>
   class key_equals_t {
   public:
      key_equals_t(T &&x) : x(x) {
      }

      template<typename S>
      bool
      operator()(const std::pair<T, S> &p) const {
         return p.first == x;
      }

   private:
      T x;
   };

   template<typename T>
   key_equals_t<T>
   key_equals(T &&x) {
      return { std::forward<T>(x) };
   }

   template<typename T>
   class type_equals_t {
   public:
      type_equals_t(T type) : type(type) {
      }

      template<typename S>
      bool
      operator()(const S &x) const {
         return x.type == type;
      }

   private:
      T type;
   };

   template<typename T>
   type_equals_t<T>
   type_equals(T x) {
      return { x };
   }

   struct interval_overlaps {
      template<typename T>
      bool
      operator()(T x0, T x1, T y0, T y1) {
         return ((x0 <= y0 && y0 < x1) ||
                 (y0 <= x0 && x0 < y1));
      }
   };
}

#endif
