/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef ECXX_CTL_UTILITY_H
#define ECXX_CTL_UTILITY_H


#include <eCxx/CTL/Foundation.h>


namespace eCxx {


//
// ignoreUnused<>()
//
template <typename... Types> inline void ignoreUnused(const Types&...) noexcept {}


//
// move<>()
//
template <typename ValueT>
constexpr typename eCxx::removeReference<ValueT>::Type&& move(ValueT&& arg) noexcept
{ return static_cast<typename eCxx::removeReference<ValueT>::Type&&>(arg); }


//
// forward<>()
//
template <typename ValueT>
constexpr ValueT&& forward(typename eCxx::removeReference<ValueT>::Type& arg) noexcept
{ return static_cast<ValueT&&>(arg); }

template <typename ValueT>
constexpr ValueT&& forward(typename eCxx::removeReference<ValueT>::Type&& arg) noexcept
{
    static_assert(!isLValueReference<ValueT>::Value, "template argument substituting ValueT is an lvalue reference type");

    return static_cast<ValueT&&>(arg);
}


//
// invokeResult<>
//
template <typename ValueT>
class referenceWrapper;

namespace internal {

    template <typename ValueT> struct IsReferenceWrapper                                   : eCxx::falseType {};
    template <typename ValueT> struct IsReferenceWrapper< eCxx::referenceWrapper<ValueT> > : eCxx::trueType  {};

    template <typename ValueT>
    struct InvokeStruct {
        template <typename ValueF, typename... ArgsT>
        static auto call(ValueF&& func, ArgsT&&... args)
            -> decltype( eCxx::forward<ValueF>(func)( eCxx::forward<ArgsT>(args)... ) );
    };

    template <typename ValueB, typename ValueR>
    struct InvokeStruct<ValueR ValueB::*> {
        template <
            typename ValueT,
            typename ValueD = typename eCxx::decay<ValueT>::Type,
            typename        = typename eCxx::enableIf< eCxx::isBaseOf<ValueB, ValueD>::Value >::Type
        >
        static auto get(ValueT&&) -> ValueT&&;

        template <
            typename ValueT,
            typename ValueD = typename eCxx::decay<ValueT>::Type,
            typename        = typename eCxx::enableIf< IsReferenceWrapper<ValueD>::Value >::Type
        >
        static auto get(ValueT&& value) -> decltype( value.get() );

        template <
            typename ValueT,
            typename ValueD = typename eCxx::decay<ValueT>::Type,
            typename        = typename eCxx::enableIf< !eCxx::isBaseOf<ValueB, ValueD>::Value >::Type,
            typename        = typename eCxx::enableIf< !IsReferenceWrapper<ValueD>    ::Value >::Type
        >
        static auto get(ValueT&& value) -> decltype( *eCxx::forward<ValueT>(value) );

        template <typename ValueT>
        static auto call(ValueR ValueB::*func, ValueT&& value)
            -> decltype( InvokeStruct::get( eCxx::forward<ValueT>(value) ).*func );

        template <
            typename ValueT,
            typename... ArgsT,
            typename ValueU,
            typename = typename eCxx::enableIf< eCxx::isFunction<ValueU>::Value >::Type
        >
        static auto call(ValueU ValueB::*func, ValueT&& value, ArgsT&&... args)
            -> decltype( ( InvokeStruct::get( eCxx::forward<ValueT>(value) ).*func )( eCxx::forward<ArgsT>(args)... ) );
    };

    template <typename ValueF, typename... ArgsT, typename ValueD = typename eCxx::decay<ValueF>::Type>
    auto invoke_helper(ValueF&& func, ArgsT&&... args)
        -> decltype( InvokeStruct<ValueD>::call( eCxx::forward<ValueF>(func), eCxx::forward<ArgsT>(args)... ) );

    template <typename Void, typename, typename...>
    struct InvokeResult {};

    template <typename ValueF, typename...ArgsT>
    struct InvokeResult<
        decltype( void( invoke_helper( eCxx::declval<ValueF>(), eCxx::declval<ArgsT>()... ) ) ),
        ValueF,
        ArgsT...
    > {
        using Type = decltype( invoke_helper( eCxx::declval<ValueF>(), eCxx::declval<ArgsT>()... ) );
    };

} // namespace internal

template <typename ValueF, typename... ArgsT>
struct invokeResult : internal::InvokeResult<void, ValueF, ArgsT...> {};


//
// resultOf<>
//
template <typename> struct resultOf;

template <typename ValueF, typename... ArgsT>
struct resultOf<ValueF(ArgsT...)> : internal::InvokeResult<void, ValueF, ArgsT...> {};


//
// invoke<>()
//
template <typename ValueF, class... ArgsT>
constexpr eCxx::invokeResult<ValueF, ArgsT...> invoke(ValueF&& func, ArgsT&&... args)
{ return internal::invoke_helper( eCxx::forward<ValueF>(func), eCxx::forward<ArgsT>(args)... ); }


//
// referenceWrapper<>
//
namespace internal {

    template <typename ValueT> constexpr ValueT& rwHelper(ValueT& value) noexcept { return value; }
    template <typename ValueT> constexpr void rwHelper(ValueT&&) = delete;

} // namespace internal

template <typename ValueT>
class referenceWrapper {
    private:
        ValueT* _ptr;

    public:
        typedef ValueT Type;

    public:
        template <
            typename ValueU,
            typename = decltype(
                internal::rwHelper<ValueT>( eCxx::declval<ValueU>() ),
                eCxx::enableIf< !eCxx::isSame< referenceWrapper, eCxx::removeCVRef<ValueU> >::Value >::Type()
            )
        >
        constexpr referenceWrapper(ValueU&& value) noexcept(
            noexcept( internal::rwHelper<ValueT>( eCxx::forward<ValueU>(value) ) )
        )
        : _ptr( eCxx::addressOf( internal::rwHelper<ValueT>( eCxx::forward<ValueU>(value) ) ) )
        {}

        constexpr referenceWrapper(const referenceWrapper&) noexcept = default;

        constexpr referenceWrapper& operator=(const referenceWrapper&) noexcept = default;

        constexpr operator ValueT& () const noexcept
        { return *_ptr; }

        constexpr ValueT& get() const noexcept
        { return *_ptr; }

        template <typename... ArgsT >
        constexpr eCxx::invokeResult<ValueT&, ArgsT...> operator()(ArgsT&&... args) const
        { return eCxx::invoke( get(), eCxx::forward<ArgsT>(args)... ); }
};


//
// bitSize<>()
//
template <typename ValueT>
constexpr size_t bitSize() noexcept { return sizeof(ValueT) * CHAR_BIT; }


//
// arraySize<>()
//
template <typename ValueT, size_t N>
constexpr size_t arraySize(ValueT (&)[N]) noexcept { return N; }

template <typename ValueT, size_t N>
constexpr size_t arraySize(ValueT (&&)[N]) noexcept { return N; }


//
// makeArray<>()
//
namespace internal {

    template <typename ValueT, ValueT... Args>
    struct makeArray {
        using Type = const ValueT(&)[sizeof...(Args)];
        static constexpr ValueT Value[sizeof...(Args)] = { Args... };
    };

    template <typename ValueT, ValueT... Args>
    constexpr ValueT makeArray<ValueT, Args...>::Value[sizeof...(Args)];

} // namespace internal

template <typename ValueT, ValueT... Args>
constexpr typename internal::makeArray<ValueT, Args...>::Type makeArray() noexcept
{ return internal::makeArray<ValueT, Args...>::Value; }


//
// copyArray<>()
//
namespace internal {

    template <size_t N_MinusOne, typename ValueT>
    struct copyArray {
        static inline __ECXX_ATTR_ALWAYS_INLINE void _(ValueT* const dst, const ValueT* const src)
        {
            internal::copyArray<N_MinusOne - 1, ValueT>::_(dst, src);
            dst[N_MinusOne] = src[N_MinusOne];
        }
    };

    template <typename ValueT>
    struct copyArray<0, ValueT> {
        static inline __ECXX_ATTR_ALWAYS_INLINE void _(ValueT* const dst, const ValueT* const src)
        { dst[0] = src[0]; }
    };

} // namespace internal

template <size_t N, typename ValueT>
static inline __ECXX_ATTR_ALWAYS_INLINE void copyArray(ValueT* const dst, const ValueT* const src)
{ internal::copyArray<N - 1, ValueT>::_(dst, src); }


//
// processArray<>()
//
namespace internal {

    template <size_t N_MinusOne, typename ValueT, typename FunctorT>
    struct processArray {
        static inline __ECXX_ATTR_ALWAYS_INLINE void _(ValueT* const ptr, FunctorT f)
        {
            internal::processArray<N_MinusOne - 1, ValueT, FunctorT>::_(ptr, f);
            f( ptr[N_MinusOne] );
        }
    };

    template <typename ValueT, typename FunctorT>
    struct processArray<0, ValueT, FunctorT> {
        static inline __ECXX_ATTR_ALWAYS_INLINE void _(ValueT* const ptr, FunctorT f)
        { f( ptr[0] ); }
    };

} // namespace internal

template <size_t N, typename ValueT, typename FunctorT>
static inline __ECXX_ATTR_ALWAYS_INLINE void processArray(ValueT* const ptr, FunctorT f)
{ internal::processArray<N - 1, ValueT, FunctorT>::_(ptr, f); }


//
// min<>() and max<>()
//
template <typename ValueT>
constexpr const ValueT& min(const ValueT& a, const ValueT& b) noexcept
{ return (b < a) ? b : a; }

template <typename ValueT, typename CompareT>
constexpr const ValueT& min(const ValueT& a, const ValueT& b, CompareT compare) noexcept
{ return compare(b, a) ? b : a; }

template <typename ValueT>
constexpr const ValueT& max(const ValueT& a, const ValueT& b) noexcept
{ return (a < b) ? b : a; }

template <typename ValueT, typename CompareT>
constexpr const ValueT& max(const ValueT& a, const ValueT& b, CompareT compare) noexcept
{ return compare(a, b) ? b : a; }


//
// swap<>()
//
template <typename ValueT>
inline void swap(ValueT& a, ValueT& b)
{
    ValueT t = eCxx::move(a);
    a = eCxx::move(b);
    b = eCxx::move(t);
}


//
// exchange<>()
//
template <typename ValueT, typename ValueU = ValueT>
constexpr ValueT exchange(ValueT& a, ValueU&& b)
{
    ValueT o = eCxx::move(a);
    a = eCxx::forward<ValueU>(b);
    return o;
}


//
// integerSequence<>, indexSequence<>, makeIntegerSequence<>, makeIndexSequence<>, indexSequenceFor<>, and get<>()
//
template<typename ValueT, ValueT... Ints>
struct integerSequence {
    static_assert(eCxx::isIntegral<ValueT>::Value, "template argument substituting ValueT is not an integral");

    typedef ValueT ValueType;

    static constexpr size_t size() noexcept
    { return sizeof...(Ints); }
};

template<size_t... Ints>
using indexSequence = integerSequence<size_t, Ints...>;

template<typename ValueT, size_t N, ValueT... Ints>
struct makeIntegerSequence : makeIntegerSequence<ValueT, N - 1, N - 1, Ints...> {};

template<typename ValueT, ValueT... Ints>
struct makeIntegerSequence<ValueT, 0, Ints...> : integerSequence<ValueT, Ints...> {};

template<size_t N>
using makeIndexSequence = makeIntegerSequence<size_t, N>;

template<typename... ValueT>
using indexSequenceFor = makeIndexSequence< sizeof...(ValueT) >;

template<size_t IndexN, typename ValueT, ValueT... Ints>
constexpr ValueT get(eCxx::integerSequence<ValueT, Ints...>) noexcept
{ return eCxx::makeArray<ValueT, Ints...>()[IndexN]; }


//
// select<> and scase<>
// Based on https://stackoverflow.com/questions/32785105/implementing-a-switch-type-trait-with-stdconditional-t-chain-calls
//
template <bool Condition, typename ValueT>
struct scase {
    using Type = ValueT;
    static constexpr bool Value = Condition;
};

template <typename ValueH, typename... ValueTs>
struct select : eCxx::conditional< ValueH::Value, ValueH, select<ValueTs...> >::Type
{};

template <typename ValueT>
struct select<ValueT> {
    using Type = ValueT;
};

template <bool Condition, typename ValueT>
struct select< scase<Condition, ValueT> > {
    static_assert( !!Condition, "the last template argument substituting Condition does not evaluate to true" );
    using Type = ValueT;
};


//
// SafeOperatorBool<>
// Based on https://www.artima.com/cppsource/safebool.html
//
template <typename ImplementationT>
class SafeOperatorBool {
    private:
        using BoolType = void (SafeOperatorBool::*)() const;
        constexpr void OperatorBoolHelper() const {}

    public:
        inline SafeOperatorBool() = default;
        inline SafeOperatorBool(const SafeOperatorBool&) = default;
        inline SafeOperatorBool(SafeOperatorBool&&) = default;
        inline ~SafeOperatorBool() = default;

        inline SafeOperatorBool& operator=(const SafeOperatorBool&) = default;
        inline SafeOperatorBool& operator=(SafeOperatorBool&&) = default;

    public:
        inline operator BoolType() const
        {
            return static_cast<const ImplementationT*>(this)->operatorBool_impl()
                   ? &SafeOperatorBool::OperatorBoolHelper
                   : nullptr;
        }
};


} // namespace eCxx


#endif
