//===----------------------------------------------------------------------===//
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

/**
 * @file algorithm
 * Parallel algorithms
 *
 * A parallel algorithm is a function template described by n4507
 * Specification declared in namespace std::experimental::parallel::v1 with
 * a formal template parameter named ExecutionPolicy.
 *
 * Each corresponding algorithm has an additional template type parameter
 * named ExecutionPolicy as the first function parameter of type
 * ExecutionPolicy&&.
 */

#pragma once
#include "../hc.hpp"

#include "execution_policy"

// Implementation of SPMD algorithms (details:: namespace)
#include "impl/algorithm_impl.inl"

namespace std {
namespace experimental {
namespace parallel {
inline namespace v1 {

/**
 * Parallel version of std::transform (unary version) in <algorithm>
 */
template <class ExecutionPolicy,
          class InputIterator, class OutputIterator,
          class UnaryOperation,
          utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
          utils::EnableIf<utils::isInputIt<InputIterator>> = nullptr>
OutputIterator
transform(ExecutionPolicy&& exec,
          InputIterator first, InputIterator last,
          OutputIterator d_first,
          UnaryOperation unary_op) {
  if (utils::isParallel(exec)) {
    return details::transform_impl(first, last, d_first, unary_op,
             typename std::iterator_traits<InputIterator>::iterator_category());
  } else {
    return details::transform_impl(first, last, d_first, unary_op,
             std::input_iterator_tag{});
  }
}


/**
 * Parallel version of std::transform (binary version) in <algorithm>
 */
template<class ExecutionPolicy,
         class InputIterator, class OutputIterator,
         class BinaryOperation,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIterator>> = nullptr>
OutputIterator
transform(ExecutionPolicy&& exec,
          InputIterator first1, InputIterator last1,
          InputIterator first2, OutputIterator d_first,
          BinaryOperation binary_op) {
  if (utils::isParallel(exec)) {
    return details::transform_impl(first1, last1, first2, d_first, binary_op,
             typename std::iterator_traits<InputIterator>::iterator_category());
  } else {
    return details::transform_impl(first1, last1, first2, d_first, binary_op,
             std::input_iterator_tag{});
  }
}


/**
 * Parallel version of std::generate in <algorithm>
 */
template<typename ExecutionPolicy,
         typename ForwardIterator, typename Generator,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isForwardIt<ForwardIterator>> = nullptr>
void
generate(ExecutionPolicy&& exec,
         ForwardIterator first, ForwardIterator last,
         Generator g) {
  if (utils::isParallel(exec)) {
    details::generate_impl(first, last, g,
      typename std::iterator_traits<ForwardIterator>::iterator_category());
  } else {
    details::generate_impl(first, last, g,
      std::input_iterator_tag{});
  }
}


/**
 * Parallel version of std::generate_n in <algorithm>
 */
template<typename ExecutionPolicy,
         typename OutputIterator, typename Size, typename Generator,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr>
OutputIterator
generate_n(ExecutionPolicy&& exec,
           OutputIterator first, Size count,
           Generator g) {
  if (count >= Size()) {
    if (utils::isParallel(exec)) {
      details::generate_impl(first, first + count, g,
        typename std::iterator_traits<OutputIterator>::iterator_category());
    } else {
      details::generate_impl(first, first + count, g,
        std::input_iterator_tag{});
    }
  }
  return (count < Size()) ? first : first + count;
}


/**
 * Parallel version of std::for_each in <algorithm>
 *
 *
 * Effects: Applies f to the result of dereferencing every iterator in the
 * range [first,last).
 *
 * Complexity: Applies f exactly last - first times.
 *
 * Remarks: If f returns a result, the result is ignored.
 *
 * Notes: Unlike its sequential form, the parallel overload of for_each does
 * not return a copy of its Function parameter, since parallelization may
 * not permit efficient state accumulation.
 *
 * Requires: Unlike its sequential form, the parallel overload of for_each
 * requires Function to meet the requirements of CopyConstructible
 *
 * @param first The beginning of the range to apply the function to
 * @param last The end of the range to apply the function to
 * @param f function object to be applied to the range [first, last)
 *
 * Return: None
 *
 */
template<typename ExecutionPolicy,
         typename InputIterator, typename Function,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIterator>> = nullptr>
void
for_each(ExecutionPolicy&& exec,
         InputIterator first, InputIterator last,
         Function f) {
  if (utils::isParallel(exec)) {
    details::for_each_impl(first, last, f,
      typename std::iterator_traits<InputIterator>::iterator_category());
  } else {
    details::for_each_impl(first, last, f,
      std::input_iterator_tag{});
  }
}


/**
 *  Requires: Function shall meet the requirements of MoveConstructible
 *
 *  Effects: Applies f to the result of dereferencing every iterator in the
 *  range [first,first + n), *  starting from first and proceeding to
 *  first + n - 1.
 *
 *  Remarks: If f returns a result, the result is ignored.
 *
 *  Return: first + n for non-negative values of n and first for negative
 *  values.
 */
template<typename InputIterator, typename Size, typename Function,
         utils::EnableIf<utils::isInputIt<InputIterator>> = nullptr>
InputIterator
for_each_n(InputIterator first, Size n,
           Function f) {
  if (n >= Size()) {
    details::for_each_impl(first, first + n, f,
      typename std::iterator_traits<InputIterator>::iterator_category());
  }
  return (n < Size()) ? first : first + n;
}


/**
 * Effects: Applies f to the result of dereferencing every iterator in the
 * range [first,first + n), starting from first and proceeding to first + n - 1.
 *
 * Remarks: If f returns a result, the result is ignored.
 *
 * Notes: Unlike its sequential form, the parallel overload of for_each_n
 * requires Function to meet the requirements of CopyConstructible.
 *
 * Return: first + n for non-negative values of n and first for negative values.
 */
template<typename ExecutionPolicy,
         typename InputIterator, typename Size, typename Function,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIterator>> = nullptr>
InputIterator
for_each_n(ExecutionPolicy&& exec,
           InputIterator first, Size n,
           Function f) {
  if (n >= Size()) {
    if (utils::isParallel(exec)) {
      for_each_n(first, n, f);
    } else {
      details::for_each_impl(first, first + n, f,
        std::input_iterator_tag{});
    }
  }
  return (n < Size()) ? first : first + n;
}


/**
 * Parallel version of std::replace_if in <algorithm>
 */
template<typename ExecutionPolicy,
         typename ForwardIterator, typename Function, typename T,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isForwardIt<ForwardIterator>> = nullptr>
void
replace_if(ExecutionPolicy&& exec,
           ForwardIterator first, ForwardIterator last,
           Function f, const T& new_value) {
  if (utils::isParallel(exec)) {
    details::replace_if_impl(first, last, f, new_value,
      typename std::iterator_traits<ForwardIterator>::iterator_category());
  } else {
    details::replace_if_impl(first, last, f, new_value,
      std::input_iterator_tag{});
  }
}


/**
 * Parallel version of std::replace_copy_if in <algorithm>
 */
template<typename ExecutionPolicy,
         typename InputIterator, typename OutputIterator,
         typename Function, typename T,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIterator>> = nullptr>
OutputIterator
replace_copy_if(ExecutionPolicy&& exec,
                InputIterator first, InputIterator last,
                OutputIterator d_first,
                Function f, const T& new_value) {
  if (utils::isParallel(exec)) {
    return details::replace_copy_if_impl(first, last, d_first, f, new_value,
             typename std::iterator_traits<InputIterator>::iterator_category());
  } else {
    return details::replace_copy_if_impl(first, last, d_first, f, new_value,
             std::input_iterator_tag{});
  }
}


/**
 * Parallel version of std::adjacent_difference (with predicate version) in <algorithm>
 */
template<typename ExecutionPolicy,
         typename InputIterator, typename OutputIterator, typename Function,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIterator>> = nullptr>
OutputIterator
adjacent_difference(ExecutionPolicy&& exec,
                    InputIterator first, InputIterator last,
                    OutputIterator d_first,
                    Function f) {
  if (utils::isParallel(exec)) {
    return details::adjacent_difference_impl(first, last, d_first, f,
             typename std::iterator_traits<InputIterator>::iterator_category());
  } else {
    return details::adjacent_difference_impl(first, last, d_first, f,
             std::input_iterator_tag{});
  }
}


/**
 * Parallel version of std::swap_ranges in <algorithm>
 */
template<typename ExecutionPolicy,
         typename InputIterator, typename OutputIterator,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIterator>> = nullptr>
OutputIterator
swap_ranges(ExecutionPolicy&& exec,
            InputIterator first, InputIterator last,
            OutputIterator d_first) {
  if (utils::isParallel(exec)) {
    return details::swap_ranges_impl(first, last, d_first,
             typename std::iterator_traits<InputIterator>::iterator_category());
  } else {
    return details::swap_ranges_impl(first, last, d_first,
             std::input_iterator_tag{});
  }
}


/**
 * Parallel version of std::fill in <algorithm>
 */
template<typename ExecutionPolicy,
         typename ForwardIterator, typename T,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isForwardIt<ForwardIterator>> = nullptr>
void
fill(ExecutionPolicy&& exec,
     ForwardIterator first, ForwardIterator last,
     const T& value) {
  // fill kernel
  auto k = [=](typename std::iterator_traits<ForwardIterator>::value_type& v) [[hc,cpu]] {
    v = value;
  };

  // launch kernel
  for_each(exec, first, last, k);
}


/**
 * Parallel version of std::fill_n in <algorithm>
 */
template<typename ExecutionPolicy,
         typename OutputIterator, typename Size, typename T,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr>
OutputIterator
fill_n(ExecutionPolicy&& exec,
       OutputIterator first, Size count,
       const T& value) {
  if (count >= Size()) {
    // fill_n kernel
    auto k = [=](typename std::iterator_traits<OutputIterator>::value_type& v) [[hc,cpu]] {
      v = value;
    };

    // launch kernel
    for_each_n(exec, first, count, k);
  }
  return (count < Size()) ? first : first + count;
}


/**
 * Parallel version of std::copy in <algorithm>
 */
template<typename ExecutionPolicy,
         typename InputIterator, typename OutputIterator,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIterator>> = nullptr>
OutputIterator
copy(ExecutionPolicy&& exec,
     InputIterator first, InputIterator last,
     OutputIterator d_first) {
  // copy kernel
  auto k = [](typename std::iterator_traits<InputIterator>::value_type& v) [[hc,cpu]] {
    return v;
  };

  // launch kernel
  return transform(exec, first, last, d_first, k);
}


/**
 * Parallel version of std::copy_n in <algorithm>
 */
template<typename ExecutionPolicy,
         typename InputIterator, typename Size, typename OutputIterator,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIterator>> = nullptr>
OutputIterator
copy_n(ExecutionPolicy&& exec,
       InputIterator first, Size count,
       OutputIterator result) {
  if (count >= Size()) {
    // use copy to implement copy_n
    auto last = std::next(first, count);
    return copy(exec, first, last, result);
  }
  // do no copy in case count is negative
  return result;
}


/**
 * Parallel version of std::replace in <algorithm>
 */
template<typename ExecutionPolicy,
         typename ForwardIterator, typename T,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isForwardIt<ForwardIterator>> = nullptr>
void
replace(ExecutionPolicy&& exec,
        ForwardIterator first, ForwardIterator last,
        const T& old_value, const T& new_value) {
  // replace kernel
  auto k = [=](typename std::iterator_traits<ForwardIterator>::value_type& v) [[hc,cpu]] {
    if (v == old_value)
      v = new_value;
  };

  // launch kernel
  for_each(exec, first, last, k);
}


/**
 * Parallel version of std::replace_copy in <algorithm>
 */
template<typename ExecutionPolicy,
         typename InputIterator, typename OutputIterator, typename T,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIterator>> = nullptr>
OutputIterator
replace_copy(ExecutionPolicy&& exec,
             InputIterator first, InputIterator last,
             OutputIterator d_first,
             const T& old_value, const T& new_value) {
  // replace_copy kernel
  auto k = [=](typename std::iterator_traits<InputIterator>::value_type& v) [[hc,cpu]] {
    return (v == old_value) ? new_value : v;
  };

  // launch kernel
  return transform(exec, first, last, d_first, k);
}


/**
 * Parallel version of std::adjacent_difference in <algorithm>
 */
template<typename ExecutionPolicy,
         typename InputIterator, typename OutputIterator,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIterator>> = nullptr>
OutputIterator
adjacent_difference(ExecutionPolicy&& exec,
                    InputIterator first, InputIterator last,
                    OutputIterator d_first) {
  typedef typename std::iterator_traits<InputIterator>::value_type Type;
  // adjacent_difference kernel
  auto k = [](const Type& a, const Type& b) [[hc,cpu]] {
    return a - b;
  };

  return adjacent_difference(exec, first, last, d_first, k);
}


/**
 * Parallel version of std::lexicographical_compare in <algorithm>
 * @{
 */
template<typename ExecutionPolicy, typename InputIt1, typename InputIt2,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt1>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt2>> = nullptr>
bool
lexicographical_compare(ExecutionPolicy&& exec,
                        InputIt1 first1, InputIt1 last1,
                        InputIt2 first2, InputIt2 last2) {
  return lexicographical_compare(exec, first1, last1, first2, last2,
           std::less<typename std::iterator_traits<InputIt1>::value_type>());

}


template<typename ExecutionPolicy, typename InputIt1, typename InputIt2,
         typename Compare,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt1>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt2>> = nullptr>
bool
lexicographical_compare(ExecutionPolicy&& exec,
                        InputIt1 first1, InputIt1 last1,
                        InputIt2 first2, InputIt2 last2,
                        Compare comp) {
  if (utils::isParallel(exec)) {
    return details::lexicographical_compare_impl(first1, last1, first2, last2,
             comp,
             typename std::iterator_traits<InputIt1>::iterator_category());
  } else {
    return details::lexicographical_compare_impl(first1, last1, first2, last2,
             comp,
             std::input_iterator_tag{});
  }
}
/**@}*/

/**
 * Parallel version of std::sort in <algorithm>
 * @{
 */
template<typename ExecutionPolicy, typename InputIt,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt>> = nullptr>
void sort(ExecutionPolicy&& exec, InputIt first, InputIt last) {
    sort(exec, first, last,
         std::less<typename std::iterator_traits<InputIt>::value_type>());
}


template<typename ExecutionPolicy, typename InputIt, typename Compare,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt>> = nullptr>
void sort(ExecutionPolicy&& exec, InputIt first, InputIt last, Compare comp) {
  if (utils::isParallel(exec)) {
      details::sort_impl(first, last, comp,
                         typename std::iterator_traits<InputIt>::iterator_category());
  } else {
      details::sort_impl(first, last, comp, std::input_iterator_tag{});
  }
}
/**@}*/


/**
 * Parallel version of std::stable_sort in <algorithm>
 * @{
 */
template<typename ExecutionPolicy, typename InputIt,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt>> = nullptr>
void stable_sort(ExecutionPolicy&& exec, InputIt first, InputIt last) {
    stable_sort(exec, first, last,
         std::less<typename std::iterator_traits<InputIt>::value_type>());
}


template<typename ExecutionPolicy, typename InputIt, typename Compare,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt>> = nullptr>
void stable_sort(ExecutionPolicy&& exec, InputIt first, InputIt last, Compare comp) {
  if (utils::isParallel(exec)) {
      details::stablesort_impl(first, last, comp,
                         typename std::iterator_traits<InputIt>::iterator_category());
  } else {
      details::stablesort_impl(first, last, comp, std::input_iterator_tag{});
  }
}
/**@}*/

/**
 * Parallel version of std::equal in <algorithm>
 * @{
 */
template<typename ExecutionPolicy,
         typename InputIt1, typename InputIt2,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt1>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt2>> = nullptr>
bool
equal(ExecutionPolicy&& exec,
      InputIt1 first1, InputIt1 last1,
      InputIt2 first2) {
  return equal(exec, first1, last1, first2,
           std::equal_to<typename std::iterator_traits<InputIt1>::value_type>());
}

template<typename ExecutionPolicy,
         typename InputIt1, typename InputIt2,
         typename BinaryPredicate,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt1>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt2>> = nullptr>
bool
equal(ExecutionPolicy&& exec,
      InputIt1 first1, InputIt1 last1,
      InputIt2 first2,
      BinaryPredicate p) {
  if (utils::isParallel(exec)) {
    return details::equal_impl(first1, last1, first2, p,
             typename std::iterator_traits<InputIt1>::iterator_category());
  } else {
    return details::equal_impl(first1, last1, first2, p,
             std::input_iterator_tag{});
  }
}
/**@}*/


/**
 * Parallel version of std::count_if in <algorithm>
 */
template<typename ExecutionPolicy,
         typename InputIt,
         typename UnaryPredicate,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt>> = nullptr>
typename std::iterator_traits<InputIt>::difference_type
count_if(ExecutionPolicy&& exec,
         InputIt first, InputIt last,
         UnaryPredicate p) {
  if (utils::isParallel(exec)) {
    typedef typename std::iterator_traits<InputIt>::value_type T;
    typedef typename std::iterator_traits<InputIt>::difference_type DT;

    const size_t N = static_cast<size_t>(std::distance(first, last));
    if (N <= details::PARALLELIZE_THRESHOLD) {
      return std::count_if(first, last, p);
    }

    return transform_reduce(first, last,
                            [p](const T &v) -> DT { return DT(p(v)); },
                            DT{},
                            std::plus<DT>());
  } else {
    return std::count_if(first, last, p);
  }
}


/**
 * Parallel version of std::count in <algorithm>
 */
template<typename ExecutionPolicy,
         typename InputIt,
         typename T,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt>> = nullptr>
typename std::iterator_traits<InputIt>::difference_type
count(ExecutionPolicy&& exec,
      InputIt first, InputIt last,
      const T& value) {
  return count_if(exec, first, last,
                  [=](const T &v) -> bool { return v == value; });
}


/**
 * Parallel version of std::max_element in <algorithm>
 * @{
 */
template<typename ExecutionPolicy,
         typename ForwardIt,
         typename Compare,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isForwardIt<ForwardIt>> = nullptr>
ForwardIt
max_element(ExecutionPolicy&& exec,
            ForwardIt first, ForwardIt last,
            Compare cmp) {
  return std::max_element(first, last, cmp);
}


template<typename ExecutionPolicy,
         typename ForwardIt,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isForwardIt<ForwardIt>> = nullptr>
ForwardIt
max_element(ExecutionPolicy&& exec,
            ForwardIt first, ForwardIt last) {
  typedef typename std::iterator_traits<ForwardIt>::value_type T;
  return max_element(exec, first, last, std::less<T>());
}
/**@}*/


/**
 * Parallel version of std::min_element in <algorithm>
 * @{
 */
template<typename ExecutionPolicy,
         typename ForwardIt,
         typename Compare,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isForwardIt<ForwardIt>> = nullptr>
ForwardIt
min_element(ExecutionPolicy&& exec,
            ForwardIt first, ForwardIt last,
            Compare cmp) {
  return std::min_element(first, last, cmp);
}


template<typename ExecutionPolicy,
         typename ForwardIt,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isForwardIt<ForwardIt>> = nullptr>
ForwardIt
min_element(ExecutionPolicy&& exec,
            ForwardIt first, ForwardIt last) {
  typedef typename std::iterator_traits<ForwardIt>::value_type T;
  return min_element(exec, first, last, std::less<T>());
}
/**@}*/


/**
 * Parallel version of std::minmax_element in <algorithm>
 * @{
 */
template<typename ExecutionPolicy,
         typename ForwardIt,
         typename Compare,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isForwardIt<ForwardIt>> = nullptr>
std::pair<ForwardIt, ForwardIt>
minmax_element(ExecutionPolicy&& exec,
               ForwardIt first, ForwardIt last,
               Compare cmp) {
  if (utils::isParallel(exec)) {
    return {
      min_element(first, last, cmp),
      max_element(first, last, cmp)
    };
  } else {
    return std::minmax_element(first, last, cmp);
  }
}


template<typename ExecutionPolicy,
         typename ForwardIt,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isForwardIt<ForwardIt>> = nullptr>
std::pair<ForwardIt, ForwardIt>
minmax_element(ExecutionPolicy&& exec,
               ForwardIt first, ForwardIt last) {
  typedef typename std::iterator_traits<ForwardIt>::value_type T;
  return minmax_element(exec, first, last, std::less<T>());
}
/**@}*/


/**
 * Parallel version of std::all_of in <algorithm>
 */
template<typename ExecutionPolicy,
         typename InputIt,
         typename UnaryPredicate,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt>> = nullptr>
bool
all_of(ExecutionPolicy&& exec,
       InputIt first, InputIt last,
       UnaryPredicate p) {
  if (utils::isParallel(exec)) {
    return transform_reduce(exec, first, last, p, true,
                            std::logical_and<bool>());
  } else {
    return std::all_of(first, last, p);
  }
}


/**
 * Parallel version of std::any_of in <algorithm>
 */
template<typename ExecutionPolicy,
         typename InputIt, typename UnaryPredicate,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt>> = nullptr>
bool
any_of(ExecutionPolicy&& exec,
       InputIt first, InputIt last,
       UnaryPredicate p) {
  if (utils::isParallel(exec)) {
    return transform_reduce(first, last, p, false,
                            std::logical_or<bool>());
  } else {
    return std::any_of(first, last, p);
  }
}


/**
 * Parallel version of std::none_of in <algorithm>
 */
template<typename ExecutionPolicy,
         typename InputIt,
         typename UnaryPredicate,
         utils::EnableIf<utils::isExecutionPolicy<ExecutionPolicy>> = nullptr,
         utils::EnableIf<utils::isInputIt<InputIt>> = nullptr>
bool
none_of(ExecutionPolicy&& exec,
        InputIt first, InputIt last,
        UnaryPredicate p ) {
  if (utils::isParallel(exec)) {
    return any_of(exec, first, last, p) == false;
  } else {
    return std::none_of(first, last, p);
  }
}


} // inline namespace v1
} // namespace parallel
} // namespace experimental
} // namespace std

// sequential versions of algorithms are implemented inside this  file
// FIXME: gradually move them to SPMD-version of algorithms
#include "impl/algorithm_impl_seq.inl"

