//
// Copyright (c) 2020 Krystian Stasiowski (sdkrystian@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/json
//

#ifndef BOOST_JSON_DETAIL_VALUE_TRAITS_HPP
#define BOOST_JSON_DETAIL_VALUE_TRAITS_HPP

#include <boost/json/detail/config.hpp>
#include <type_traits>
#include <tuple>
#include <utility>

BOOST_JSON_NS_BEGIN
namespace detail {

template<std::size_t N>
struct priority_tag
    : priority_tag<N - 1> { };

template<>
struct priority_tag<0> { };

using std::begin;
using std::end;
#ifdef __cpp_lib_nonmember_container_access
using std::size;
#endif

template<typename T, typename = void>
struct container_traits
{
    static constexpr bool is_container = false;
};

template<typename T>
struct container_traits<T, typename std::enable_if<
    std::is_same<decltype(begin(std::declval<T&>())),
        decltype(end(std::declval<T&>()))>::value>::type>
{
private:
    template<typename U, typename std::enable_if<
        std::is_convertible<decltype(std::declval<U&>().size()),
            std::size_t>::value>::type* = nullptr>
    static
    std::size_t
    size_impl(
        U&& cont,
        priority_tag<2>)
    {
        return cont.size();
    }

    template<typename U, typename std::enable_if<
        std::is_convertible<decltype(size(std::declval<U&>())),
            std::size_t>::value>::type* = nullptr>
    static
    std::size_t
    size_impl(
        U& cont,
        priority_tag<1>)
    {
        return size(cont);
    }

    template<typename U, std::size_t N>
    static
    std::size_t
    size_impl(
        U(&)[N],
        priority_tag<1>)
    {
        return N;
    }

    template<typename U>
    static
    std::size_t
    size_impl(U&, priority_tag<0>)
    {
        return 0;
    }

    template<typename U>
    static
    auto
    reserve_impl(
        U& cont,
        std::size_t size,
        priority_tag<1>) ->
            void_t<decltype(
                std::declval<U&>().reserve(0))>
    {
        cont.reserve(size);
    }

    template<typename U>
    static
    void
    reserve_impl(
        U&,
        std::size_t,
        priority_tag<0>) { }
public:
    static constexpr bool is_container = true;
    using value_type = remove_cvref<
        decltype(*begin(std::declval<T&>()))>;

    template<typename U>
    static
    std::size_t
    try_size(U& cont)
    {
        return container_traits::size_impl(
            cont, priority_tag<2>());
    }

    template<typename U>
    static
    void
    try_reserve(
        U& cont,
        std::size_t size)
    {
        container_traits::reserve_impl(
            cont, size, priority_tag<1>());
    }
};

template<typename T, typename = void>
struct map_traits
{
    static constexpr bool is_map = false;
    static constexpr bool has_unique_keys = false;
};

template<typename T>
struct map_traits<T, void_t<typename remove_cvref<T>::key_type,
    typename std::enable_if<container_traits<T>::is_container &&
        std::tuple_size<typename remove_cvref<T>::
    value_type>::value == 2>::type>>
{
private:
    template<typename U, typename = void>
    struct unique_keys : std::false_type { };

    template<typename U>
    struct unique_keys<U, typename std::enable_if<
        (std::tuple_size<remove_cvref<decltype(std::declval<
            remove_cvref<U>&>().emplace(std::declval<typename 
        remove_cvref<U>::value_type>()))>>::value > 0)>::type>
            : std::true_type { };
public:
    static constexpr bool is_map = true;
    static constexpr bool has_unique_keys = unique_keys<T>::value;
    using pair_key_type = typename std::tuple_element<
        0, typename remove_cvref<T>::value_type>::type;
    using pair_value_type = typename std::tuple_element<
        1, typename remove_cvref<T>::value_type>::type;
    static constexpr bool key_converts_to_string =
        std::is_convertible<pair_key_type, string_view>::value;
};

// does not include std::nullptr_t
template<class T>
using value_constructible = std::integral_constant<bool,
    std::is_same<detail::remove_cvref<T>, value>::value || 
        std::is_same<detail::remove_cvref<T>, object>::value ||
    std::is_same<detail::remove_cvref<T>, array>::value ||
        std::is_same<detail::remove_cvref<T>, string>::value ||
    std::is_same<detail::remove_cvref<T>, string_view>::value ||
        std::is_arithmetic<detail::remove_cvref<T>>::value ||    
    std::is_same<detail::remove_cvref<T>, 
        std::initializer_list<value_ref>>::value ||
    std::is_same<detail::remove_cvref<T>, value_ref>::value>;

BOOST_STATIC_ASSERT(value_constructible<value>::value);

} // detail
BOOST_JSON_NS_END

#endif
