//
// 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>());
}

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

template<class T, typename std::enable_if<
    detail::value_constructible<T>::value>::type* = nullptr>
void
tag_invoke(
    value_from_tag,
    value& jv,
    T&& from)
{
    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
tag_invoke(
    value_from_tag,
    value& jv,
    T&&)
{
    // 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_generic(
    value& jv,
    T&& from,
    priority_tag<3>)
{
    jv.emplace_string().assign(
        from.data(), from.size());
}

// tuple-like types
template<class T, typename std::enable_if<
    (std::tuple_size<remove_cvref<T>>::value > 0)>::type* = nullptr>
void
value_from_generic(
    value& jv,
    T&& from,
    priority_tag<2>)
{
    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>());
}

// map-like types
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_generic(
    value& jv,
    T&& from,
    priority_tag<1>)
{
    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()));
}

// all other containers
template<class T, typename std::enable_if<
    has_value_from<typename container_traits<T>::
        value_type>::value>::type* = nullptr>
void
value_from_generic(
    value& jv,
    T&& from,
    priority_tag<0>)
{
    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()));
}

template<class T, void_t<typename std::enable_if<
    ! detail::value_constructible<T>::value && ! std::is_same<
        detail::remove_cvref<T>, std::nullptr_t>::value>::type,
    decltype(detail::value_from_generic(std::declval<value&>(), 
        std::declval<T&&>(), priority_tag<3>()))>* = nullptr>
void
tag_invoke(
    value_from_tag,
    value& jv,
    T&& from)
{
    detail::value_from_generic(jv,
        std::forward<T>(from), priority_tag<3>());
}

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

// 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, void_t<
    decltype(tag_invoke(std::declval<value_from_tag&>(),
        std::declval<value&>(), std::declval<T&&>()))>* = nullptr>
value
value_from_impl(
    T&& from,
    storage_ptr sp)
{
    value jv(std::move(sp));
    tag_invoke(value_from_tag(), jv, std::forward<T>(from));
    return jv;
}

} // detail
BOOST_JSON_NS_END

#endif
