//
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
// 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_FROM_HPP
#define BOOST_JSON_DETAIL_VALUE_FROM_HPP

#include <boost/json/storage_ptr.hpp>
#include <boost/json/value.hpp>
#include <boost/json/detail/value_traits.hpp>

BOOST_JSON_NS_BEGIN

struct value_from_tag { };

template<class T, class = void>
struct has_value_from;

namespace detail {

// The integral_constant parameter here is an
// rvalue reference to make the standard conversion
// sequence to that parameter better, see
// http://eel.is/c++draft/over.ics.rank#3.2.6
template<std::size_t N, class T>
void
tuple_to_array(
    T&&,
    array&,
    std::integral_constant<std::size_t, N>&&)
{
}

template<std::size_t N, std::size_t I, class T>
void
tuple_to_array(
    T&& t,
    array& arr,
    const std::integral_constant<std::size_t, I>&)
{
    using std::get;
    arr.emplace_back(value_from(
        get<I>(std::forward<T>(t)), arr.storage()));
    return detail::tuple_to_array<N>(std::forward<T>(t),
        arr, std::integral_constant<std::size_t, I + 1>());
}

//----------------------------------------------------------
// User-provided conversion

template<class T, void_t<decltype(tag_invoke(value_from_tag(),
    std::declval<value&>(), std::declval<T&&>()))>* = nullptr>
void
value_from_helper(
    value& jv,
    T&& from,
    priority_tag<5>)
{
    tag_invoke(value_from_tag(), jv, std::forward<T>(from));
}


//----------------------------------------------------------
// Native conversion

template<class T, typename std::enable_if<
    detail::value_constructible<T>::value>::type* = nullptr>
void
value_from_helper(
    value& jv,
    T&& from,
    priority_tag<4>)
{
    jv = std::forward<T>(from);
}

template<class T, typename std::enable_if<
    std::is_same<detail::remove_cvref<T>,
        std::nullptr_t>::value>::type* = nullptr>
void
value_from_helper(
    value& jv,
    T&&,
    priority_tag<4>)
{
    // do nothing
    BOOST_ASSERT(jv.is_null());
    (void)jv;
}

//----------------------------------------------------------
// Generic conversions

// string-like types
// NOTE: original check for size used is_convertible but
// MSVC-140 selects wrong specialisation if used
template<class T, typename std::enable_if<
    std::is_constructible<remove_cvref<T>, const char*, std::size_t>::value &&
    std::is_convertible<decltype(std::declval<T&>().data()), const char*>::value &&
    std::is_integral<decltype(std::declval<T&>().size())>::value
>::type* = nullptr>
void
value_from_helper(
    value& jv,
    T&& from,
    priority_tag<3>)
{
    jv.emplace_string().assign(
        from.data(), from.size());
}

// map-like types; should go before ranges, so that we can differentiate
// map-like and other ranges
template<class T, typename std::enable_if<
    map_traits<T>::has_unique_keys &&
        has_value_from<typename map_traits<T>::pair_value_type>::value &&
    std::is_convertible<typename map_traits<T>::pair_key_type,
        string_view>::value>::type* = nullptr>
void
value_from_helper(
    value& jv,
    T&& from,
    priority_tag<2>)
{
    using std::get;
    object& obj = jv.emplace_object();
    obj.reserve(container_traits<T>::try_size(from));
    for (auto&& elem : from)
        obj.emplace(get<0>(elem), value_from(
            get<1>(elem), obj.storage()));
}

// ranges; should go before tuple-like in order for std::array being handled
// by this overload
template<class T, typename std::enable_if<
    has_value_from<typename container_traits<T>::
        value_type>::value>::type* = nullptr>
void
value_from_helper(
    value& jv,
    T&& from,
    priority_tag<1>)
{
    array& result = jv.emplace_array();
    result.reserve(container_traits<T>::try_size(from));
    for (auto&& elem : from)
        result.emplace_back(
            value_from(elem, result.storage()));
}

// tuple-like types
template<class T, typename std::enable_if<
    (std::tuple_size<remove_cvref<T>>::value > 0)>::type* = nullptr>
void
value_from_helper(
    value& jv,
    T&& from,
    priority_tag<0>)
{
    constexpr std::size_t n =
        std::tuple_size<remove_cvref<T>>::value;
    array& arr = jv.emplace_array();
    arr.reserve(n);
    detail::tuple_to_array<n>(std::forward<T>(from),
        arr, std::integral_constant<std::size_t, 0>());
}

//----------------------------------------------------------

// Calls to value_from are forwarded to this function
// so we can use ADL and hide the built-in tag_invoke
// overloads in the detail namespace
template<class T, class = void_t<
    decltype(detail::value_from_helper(std::declval<value&>(),
        std::declval<T&&>(), priority_tag<5>()))>>
value
value_from_impl(
    T&& from,
    storage_ptr sp)
{
    value jv(std::move(sp));
    detail::value_from_helper(jv, std::forward<T>(from), priority_tag<5>());
    return jv;
}

} // detail
BOOST_JSON_NS_END

#endif