// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Copyright (C) Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// This file incorporates work covered by the following copyright and permission
// notice:
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
//
//===----------------------------------------------------------------------===//

#ifndef _ONEDPL_UTILS_H
#define _ONEDPL_UTILS_H

#include "onedpl_config.h"

#include <new>
#include <iterator>
#include <type_traits>

#if (_PSTL_CPP14_INTEGER_SEQUENCE_PRESENT || _ONEDPL_CPP14_INTEGER_SEQUENCE_PRESENT)
#    include <utility>
#endif

#if _ONEDPL_BACKEND_SYCL
#    include <CL/sycl.hpp>
#    include "hetero/dpcpp/sycl_iterator.h"
#endif

namespace oneapi
{
namespace dpl
{
namespace __internal
{

template <typename Iterator>
using is_const_iterator =
    typename ::std::is_const<typename ::std::remove_pointer<typename ::std::iterator_traits<Iterator>::pointer>::type>;

template <typename _Fp>
auto
__except_handler(_Fp __f) -> decltype(__f())
{
    try
    {
        return __f();
    }
    catch (const ::std::bad_alloc&)
    {
        throw; // re-throw bad_alloc according to the standard [algorithms.parallel.exceptions]
    }
    catch (...)
    {
        ::std::terminate(); // Good bye according to the standard [algorithms.parallel.exceptions]
    }
}

template <typename _Fp>
void
__invoke_if(::std::true_type, _Fp __f)
{
    __f();
}

template <typename _Fp>
void __invoke_if(::std::false_type, _Fp)
{
}

template <typename _Fp>
void
__invoke_if_not(::std::false_type, _Fp __f)
{
    __f();
}

template <typename _Fp>
void __invoke_if_not(::std::true_type, _Fp)
{
}

template <typename _F1, typename _F2>
auto
__invoke_if_else(::std::true_type, _F1 __f1, _F2) -> decltype(__f1())
{
    return __f1();
}

template <typename _F1, typename _F2>
auto
__invoke_if_else(::std::false_type, _F1, _F2 __f2) -> decltype(__f2())
{
    return __f2();
}

template <typename _Op>
struct __invoke_unary_op
{
    mutable _Op __op;

    template <typename _Input, typename _Output>
    void
    operator()(_Input&& __x, _Output&& __y) const
    {
        __y = __op(::std::forward<_Input>(__x));
    }
};

//! Unary operator that returns reference to its argument.
struct __no_op
{
    template <typename _Tp>
    _Tp&&
    operator()(_Tp&& __a) const
    {
        return ::std::forward<_Tp>(__a);
    }
};

//! Logical negation of a predicate
template <typename _Pred>
class __not_pred
{
    _Pred _M_pred;

  public:
    explicit __not_pred(_Pred __pred) : _M_pred(__pred) {}

    template <typename... _Args>
    bool
    operator()(_Args&&... __args) const
    {
        return !_M_pred(::std::forward<_Args>(__args)...);
    }
};

template <typename _Pred>
class __reorder_pred
{
    mutable _Pred _M_pred;

  public:
    explicit __reorder_pred(_Pred __pred) : _M_pred(__pred) {}

    template <typename _FTp, typename _STp>
    bool
    operator()(_FTp&& __a, _STp&& __b) const
    {
        return _M_pred(::std::forward<_STp>(__b), ::std::forward<_FTp>(__a));
    }
};

//! "==" comparison.
/** Not called "equal" to avoid (possibly unfounded) concerns about accidental invocation via
    argument-dependent name lookup by code expecting to find the usual ::std::equal. */
class __pstl_equal
{
  public:
    explicit __pstl_equal() {}

    template <typename _Xp, typename _Yp>
    bool
    operator()(_Xp&& __x, _Yp&& __y) const
    {
        return ::std::forward<_Xp>(__x) == ::std::forward<_Yp>(__y);
    }
};

//! "<" comparison.
class __pstl_less
{
  public:
    template <typename _Xp, typename _Yp>
    bool
    operator()(_Xp&& __x, _Yp&& __y) const
    {
        return ::std::forward<_Xp>(__x) < ::std::forward<_Yp>(__y);
    }
};

//! ">" comparison.
class __pstl_greater
{
  public:
    template <typename _Xp, typename _Yp>
    bool
    operator()(_Xp&& __x, _Yp&& __y) const
    {
        return ::std::forward<_Xp>(__x) > ::std::forward<_Yp>(__y);
    }
};

//! General "+" operation
class __pstl_plus
{
  public:
    template <typename _Xp, typename _Yp>
    auto
    operator()(_Xp&& __x, _Yp&& __y) const -> decltype(::std::forward<_Xp>(__x) + ::std::forward<_Yp>(__y))
    {
        return ::std::forward<_Xp>(__x) + ::std::forward<_Yp>(__y);
    }
};

//! min calculation.
class __pstl_min
{
  public:
    template <typename _Xp, typename _Yp>
    auto
    operator()(_Xp&& __x, _Yp&& __y) const
        -> decltype((::std::forward<_Xp>(__x) < ::std::forward<_Yp>(__y)) ? ::std::forward<_Xp>(__x)
                                                                          : ::std::forward<_Yp>(__y))
    {
        return (::std::forward<_Xp>(__x) < ::std::forward<_Yp>(__y)) ? ::std::forward<_Xp>(__x)
                                                                     : ::std::forward<_Yp>(__y);
    }
};

//! max calculation.
class __pstl_max
{
  public:
    template <typename _Xp, typename _Yp>
    auto
    operator()(_Xp&& __x, _Yp&& __y) const
        -> decltype((::std::forward<_Xp>(__x) > ::std::forward<_Yp>(__y)) ? ::std::forward<_Xp>(__x)
                                                                          : ::std::forward<_Yp>(__y))
    {
        return (::std::forward<_Xp>(__x) > ::std::forward<_Yp>(__y)) ? ::std::forward<_Xp>(__x)
                                                                     : ::std::forward<_Yp>(__y);
    }
};

//! Like a polymorphic lambda for pred(...,value)
template <typename _Tp, typename _Predicate>
class __equal_value_by_pred
{
    const _Tp _M_value;
    _Predicate _M_pred;

  public:
    __equal_value_by_pred(const _Tp& __value, _Predicate __pred) : _M_value(__value), _M_pred(__pred) {}

    template <typename _Arg>
    bool
    operator()(_Arg&& __arg) const
    {
        return _M_pred(::std::forward<_Arg>(__arg), _M_value);
    }
};

//! Like a polymorphic lambda for ==value
template <typename _Tp>
class __equal_value
{
    const _Tp _M_value;

  public:
    explicit __equal_value(const _Tp& __value) : _M_value(__value) {}

    template <typename _Arg>
    bool
    operator()(_Arg&& __arg) const
    {
        return ::std::forward<_Arg>(__arg) == _M_value;
    }
};

//! Logical negation of ==value
template <typename _Tp>
class __not_equal_value
{
    const _Tp _M_value;

  public:
    explicit __not_equal_value(const _Tp& __value) : _M_value(__value) {}

    template <typename _Arg>
    bool
    operator()(_Arg&& __arg) const
    {
        return !(::std::forward<_Arg>(__arg) == _M_value);
    }
};

template <typename _Pred>
class __transform_functor
{
    _Pred _M_pred;

  public:
    explicit __transform_functor(_Pred __pred) : _M_pred(__pred) {}

    template <typename _Input1Type, typename _Input2Type, typename _OutputType>
    void
    operator()(const _Input1Type& x, const _Input2Type& y, _OutputType& z) const
    {
        z = _M_pred(x, y);
    }
};

template <typename _Tp, typename _Pred>
class __replace_functor
{
    const _Tp _M_value;
    _Pred _M_pred;

  public:
    __replace_functor(const _Tp& __value, _Pred __pred) : _M_value(__value), _M_pred(__pred) {}

    template <typename _OutputType>
    void
    operator()(_OutputType& __elem) const
    {
        if (_M_pred(__elem))
            __elem = _M_value;
    }
};

template <typename _Tp, typename _Pred>
class __replace_copy_functor
{
    const _Tp _M_value;
    _Pred _M_pred;

  public:
    __replace_copy_functor(const _Tp& __value, _Pred __pred) : _M_value(__value), _M_pred(__pred) {}

    template <typename _InputType, typename _OutputType>
    void
    operator()(const _InputType& __x, _OutputType& __y) const
    {
        __y = _M_pred(__x) ? _M_value : __x;
    }
};

//! Like ::std::next, but with specialization for dpcpp case
template <typename _Iter>
_Iter
__pstl_next(_Iter __iter, typename ::std::iterator_traits<_Iter>::difference_type __n = 1)
{
    return ::std::next(__iter, __n);
}

#if _ONEDPL_BACKEND_SYCL
template <sycl::access::mode _Mode, typename... _Params>
oneapi::dpl::__internal::sycl_iterator<_Mode, _Params...>
__pstl_next(
    oneapi::dpl::__internal::sycl_iterator<_Mode, _Params...> __iter,
    typename ::std::iterator_traits<oneapi::dpl::__internal::sycl_iterator<_Mode, _Params...>>::difference_type __n = 1)
{
    return __iter + __n;
}
#endif

template <typename _ForwardIterator, typename _Compare, typename _CompareIt>
_ForwardIterator
__cmp_iterators_by_values(_ForwardIterator __a, _ForwardIterator __b, _Compare __comp, _CompareIt __comp_it)
{
    if (__comp_it(__a, __b))
    { // we should return closer iterator
        return __comp(*__b, *__a) ? __b : __a;
    }
    else
    {
        return __comp(*__a, *__b) ? __a : __b;
    }
}

template <typename _Acc, typename _Size1, typename _Value, typename _Compare>
_Size1
__pstl_lower_bound(_Acc __acc, _Size1 __first, _Size1 __last, const _Value& __value, _Compare __comp)
{
    auto __n = __last - __first;
    auto __cur = __n;
    _Size1 __it;
    while (__n > 0)
    {
        __it = __first;
        __cur = __n / 2;
        __it += __cur;
        if (__comp(__acc[__it], __value))
        {
            __n -= __cur + 1, __first = ++__it;
        }
        else
            __n = __cur;
    }
    return __first;
}

template <typename _Acc, typename _Size1, typename _Value, typename _Compare>
_Size1
__pstl_upper_bound(_Acc __acc, _Size1 __first, _Size1 __last, const _Value& __value, _Compare __comp)
{
    return __pstl_lower_bound(__acc, __first, __last, __value,
                              oneapi::dpl::__internal::__not_pred<oneapi::dpl::__internal::__reorder_pred<_Compare>>{
                                  oneapi::dpl::__internal::__reorder_pred<_Compare>{__comp}});
}

// Searching for the first element strongly greater than a passed value - right bound
template <typename _Buffer, typename _Index, typename _Value, typename _Compare>
_Index
__pstl_right_bound(_Buffer& __a, _Index __first, _Index __last, const _Value& __val, _Compare __comp)
{
    return __pstl_upper_bound(__a, __first, __last, __val, __comp);
}

template <typename _IntType, typename _Acc>
struct _ReverseCounter
{
    typedef typename ::std::make_signed<_IntType>::type difference_type;

    _IntType __my_cn;

    _ReverseCounter&
    operator++()
    {
        --__my_cn;
        return *this;
    }

    template <typename _DiffType>
    _ReverseCounter&
    operator+=(_DiffType __val)
    {
        __my_cn -= __val;
        return *this;
    }

    difference_type
    operator-(const _ReverseCounter& __a)
    {
        return __a.__my_cn - __my_cn;
    }

    operator _IntType() { return __my_cn; }

// TODO: Temporary hotfix. Investigate the necessity of _ReverseCounter
// Investigate potential user types convertible to integral
// This is the compile-time trick where we define the conversion operator to sycl::id
// conditionally. If we can call accessor::operator[] with the type that converts to the
// same integral type as _ReverseCounter (it means that we can call accessor::operator[]
// with the _ReverseCounter itself) then we don't need conversion operator to sycl::id.
// Otherwise, we define conversion operator to sycl::id.
#if _ONEDPL_BACKEND_SYCL
    struct __integral
    {
        operator _IntType();
    };

    template <typename _Tp>
    static auto
    __check_braces(int) -> decltype(::std::declval<_Tp>()[::std::declval<__integral>()], ::std::false_type{});

    template <typename _Tp>
    static auto
    __check_braces(...) -> ::std::true_type;

    class __private_class;

    operator typename ::std::conditional<decltype(__check_braces<_Acc>(0))::value, sycl::id<1>, __private_class>::type()
    {
        return sycl::id<1>(__my_cn);
    }
#endif
};

// Reverse searching for the first element strongly less than a passed value - left bound
template <typename _Buffer, typename _Index, typename _Value, typename _Compare>
_Index
__pstl_left_bound(_Buffer& __a, _Index __first, _Index __last, const _Value& __val, _Compare __comp)
{
    auto __beg = _ReverseCounter<_Index, _Buffer>{__last - 1};
    auto __end = _ReverseCounter<_Index, _Buffer>{__first - 1};

    return __pstl_upper_bound(__a, __beg, __end, __val, __reorder_pred<_Compare>{__comp});
}

#if (_PSTL_CPP14_INTEGER_SEQUENCE_PRESENT || _ONEDPL_CPP14_INTEGER_SEQUENCE_PRESENT)

template <::std::size_t... _Sp>
using __index_sequence = ::std::index_sequence<_Sp...>;
template <::std::size_t _Np>
using __make_index_sequence = ::std::make_index_sequence<_Np>;

#else

template <::std::size_t... _Sp>
class __index_sequence
{
};

template <::std::size_t _Np, ::std::size_t... _Sp>
struct __make_index_sequence_impl : __make_index_sequence_impl<_Np - 1, _Np - 1, _Sp...>
{
};

template <::std::size_t... _Sp>
struct __make_index_sequence_impl<0, _Sp...>
{
    using type = __index_sequence<_Sp...>;
};

template <::std::size_t _Np>
using __make_index_sequence = typename oneapi::dpl::__internal::__make_index_sequence_impl<_Np>::type;

#endif /* _PSTL_CPP14_INTEGER_SEQUENCE_PRESENT || _ONEDPL_CPP14_INTEGER_SEQUENCE_PRESENT */

// Required to support GNU libstdc++ below 5.x
template <typename _Tp>
using __has_trivial_copy_assignemnt =
#if _ONEDPL_CPP11_IS_TRIVIALLY_COPY_ASSIGNABLE_PRESENT
    ::std::is_trivially_copy_assignable<
#else
    ::std::has_trivial_copy_assign<
#endif /* _ONEDPL_CPP11_IS_TRIVIALLY_COPY_ASSIGNABLE_PRESENT */
        _Tp>;

// Aliases for adjacent_find compile-time dispatching
using __or_semantic = ::std::true_type;
using __first_semantic = ::std::false_type;

// Define __void_type via this structure to handle redefinition issue.
// See CWG 1558 for information about it.
template <typename... _Ts>
struct __make_void_type
{
    using __type = void;
};

template <typename... _Ts>
using __void_type = typename __make_void_type<_Ts...>::__type;

// is_callable_object
template <typename _Tp, typename = void>
struct __is_callable_object : ::std::false_type
{
};

template <typename _Tp>
struct __is_callable_object<_Tp, __void_type<decltype(&_Tp::operator())>> : ::std::true_type
{
};

// is_pointer_to_const_member
template <typename _Tp>
struct __is_pointer_to_const_member_impl : ::std::false_type
{
};

template <typename _R, typename _U, typename... _Args>
struct __is_pointer_to_const_member_impl<_R (_U::*)(_Args...) const> : ::std::true_type
{
};

#if __cplusplus >= 201703L
template <typename _R, typename _U, typename... _Args>
struct __is_pointer_to_const_member_impl<_R (_U::*)(_Args...) const noexcept> : ::std::true_type
{
};
#endif

template <typename _Tp, bool = __is_callable_object<_Tp>::value>
struct __is_pointer_to_const_member : ::std::false_type
{
};

template <typename _Tp>
struct __is_pointer_to_const_member<_Tp, true> : __is_pointer_to_const_member_impl<decltype(&_Tp::operator())>
{
};

// is_const_callable_object to check whether we call const or non-const object
template <typename _Tp>
using __is_const_callable_object =
    ::std::integral_constant<bool, __is_callable_object<_Tp>::value && __is_pointer_to_const_member<_Tp>::value>;

struct __next_to_last
{
    template <typename _Iterator>
    typename ::std::enable_if<::std::is_same<typename ::std::iterator_traits<_Iterator>::iterator_category,
                                             ::std::random_access_iterator_tag>::value,
                              _Iterator>::type
    operator()(_Iterator __it, _Iterator __last, typename ::std::iterator_traits<_Iterator>::difference_type __n)
    {
        return __n > __last - __it ? __last : __it + __n;
    }

    template <typename _Iterator>
    typename ::std::enable_if<!::std::is_same<typename ::std::iterator_traits<_Iterator>::iterator_category,
                                              ::std::random_access_iterator_tag>::value,
                              _Iterator>::type
    operator()(_Iterator __it, _Iterator __last, typename ::std::iterator_traits<_Iterator>::difference_type __n)
    {
        for (; --__n >= 0 && __it != __last; ++__it)
            ;
        return __it;
    }
};

template <typename _T, class _Enable = void>
class __future;

template <typename... _Bs>
struct __conjunction : ::std::true_type
{
};

template <typename _B1>
struct __conjunction<_B1> : _B1
{
};

template <typename _B1, typename... _Bs>
struct __conjunction<_B1, _Bs...> : ::std::conditional<!bool(_B1::value), _B1, __conjunction<_Bs...>>::type
{
};

} // namespace __internal
} // namespace dpl
} // namespace oneapi

#endif /* _ONEDPL_UTILS_H */
