//
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@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_VALUE_REF_HPP
#define BOOST_JSON_VALUE_REF_HPP

#include <boost/json/detail/config.hpp>
#include <boost/json/storage_ptr.hpp>
#include <boost/json/string.hpp>
#include <initializer_list>
#include <type_traits>
#include <utility>

namespace boost {
namespace json {

#ifndef BOOST_JSON_DOCS
class value;
class object;
class array;
class string;
#endif

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

/** The type used in initializer lists.

    This type is used in initializer lists for
    lazy construction of and assignment to the
    container types @ref value, @ref array,
    and @ref object. The two types of initializer
    lists used are:

    @li `std::initializer_list< value_ref >` for
    constructing or assigning a @ref value or
    @ref array, and

    @li `std::initializer_list< std::pair< string_view, value_ref > >`
    for constructing or assigning an @ref object.

    A `value_ref` uses reference semantics. Creation of the actual container
    from the initializer list is lazily deferred until the list is used. This
    means that the `boost::container::pmr::memory_resource` used to construct a
    container can be specified after the point where the initializer list is
    specified.

    @par Example

    This example demonstrates how a user-defined type
    containing a JSON value can be constructed from
    an initializer list:

    @code

    class my_type
    {
        value jv_;

    public:
        my_type( std::initializer_list< value_ref > init )
            : jv_(init)
        {
        }
    };

    @endcode

    @note Never declare a variable of type
    `std::initializer_list` except in function
    parameter lists, otherwise the behavior may
    be undefined.

    @see
        @ref value,
        @ref array,
        @ref object
*/
class value_ref
{
    friend class value;
    friend class object;
    friend class array;

    friend class value_ref_test;

    enum class what
    {
        str,
        ini,
        func,
        cfunc,
        strfunc,
    };

    using init_list =
        std::initializer_list<value_ref>;

    struct func_type
    {
        value(*f)(void*, storage_ptr);
        void* p;
    };

    struct cfunc_type
    {
        value(*f)(void const*, storage_ptr);
        void const* p;
    };

    union arg_type
    {
        string_view         str_;
        init_list           init_list_;

        signed char         schar_;
        short               short_;
        int                 int_;
        long                long_;
        long long           long_long_;
        unsigned char       uchar_;
        unsigned short      ushort_;
        unsigned int        uint_;
        unsigned long       ulong_;
        unsigned long long  ulong_long_;
        float               float_;
        double              double_;
        bool                bool_;
        std::nullptr_t      nullptr_;

        arg_type() {}
        explicit arg_type(string_view t) noexcept : str_(t) {}
        explicit arg_type(init_list t) noexcept : init_list_(t) {}
        explicit arg_type(signed char t) noexcept : schar_(t) {}
        explicit arg_type(short t) noexcept : short_(t) {}
        explicit arg_type(int t) noexcept : int_(t) {}
        explicit arg_type(long t) noexcept : long_(t) {}
        explicit arg_type(long long t) noexcept : long_long_(t) {}
        explicit arg_type(unsigned char t) noexcept : uchar_(t) {}
        explicit arg_type(unsigned short t) noexcept : ushort_(t) {}
        explicit arg_type(unsigned int t) noexcept : uint_(t) {}
        explicit arg_type(unsigned long t) noexcept : ulong_(t) {}
        explicit arg_type(unsigned long long t) noexcept : ulong_long_(t) {}
        explicit arg_type(float t) noexcept : float_(t) {}
        explicit arg_type(double t) noexcept : double_(t) {}
        explicit arg_type(bool t) noexcept : bool_(t) {}
        explicit arg_type(std::nullptr_t) noexcept : nullptr_() {}
    };

    arg_type arg_;
#ifndef BOOST_JSON_DOCS
    // VFALCO doc toolchain erroneously
    // displays private, anonymous unions as public
    union
    {
        func_type f_;
        cfunc_type cf_;
    };
#endif
    what what_;

public:
    /// Constructor
    value_ref(
        value_ref const&) = default;

    /// Constructor
    value_ref(
        value_ref&&) = default;

    /// Constructor
#ifdef BOOST_JSON_DOCS
    value_ref(string_view s) noexcept;
#else
    template<
        class T
        ,class = typename
            std::enable_if<
                std::is_constructible<
                    string_view, T>::value>::type
    >
    value_ref(
        T const& t) noexcept
        : arg_(string_view(t))
        , what_(what::str)
    {

    }
#endif

    /// Constructor
    template<class T>
    value_ref(
        T const& t
#ifndef BOOST_JSON_DOCS
        ,typename std::enable_if<
            ! std::is_constructible<
                string_view, T>::value &&
            ! std::is_same<bool, T>::value
                >::type* = 0
#endif
        ) noexcept
        : cf_{&from_const<T>, &t}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    template<class T>
    value_ref(
        T&& t
#ifndef BOOST_JSON_DOCS
        ,typename std::enable_if<
            (! std::is_constructible<
                string_view, T>::value ||
            std::is_same<string, T>::value) &&
            ! std::is_same<bool,
                detail::remove_cvref<T>>::value &&
            std::is_same<T, detail::remove_cvref<T>>
                ::value>::type* = 0
#endif
        ) noexcept
        : f_{&from_rvalue<
            detail::remove_cvref<T>>, &t}
        , what_(std::is_same<string, T>::value ?
                what::strfunc : what::func)
    {
    }

    /// Constructor
#ifdef BOOST_JSON_DOCS
    value_ref(bool b) noexcept;
#else
    template<
        class T
        ,class = typename std::enable_if<
            std::is_same<T, bool>::value>::type
    >
    value_ref(
        T b) noexcept
        : arg_(b)
        , cf_{&from_builtin<bool>, &arg_.bool_}
        , what_(what::cfunc)
    {
    }
#endif

    /// Constructor
    value_ref(
        std::initializer_list<
            value_ref> t) noexcept
        : arg_(t)
        , what_(what::ini)
    {
    }

    /// Constructor
    value_ref(signed char t) noexcept
        : arg_(t)
        , cf_{&from_builtin<signed char>, &arg_.schar_}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    value_ref(short t) noexcept
        : arg_(t)
        , cf_{&from_builtin<short>, &arg_.short_}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    value_ref(int t) noexcept
        : arg_(t)
        , cf_{&from_builtin<int>, &arg_.int_}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    value_ref(long t) noexcept
        : arg_(t)
        , cf_{&from_builtin<
            long>, &arg_.long_}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    value_ref(long long t) noexcept
        : arg_(t)
        , cf_{&from_builtin<
            long long>, &arg_.long_long_}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    value_ref(unsigned char t) noexcept
        : arg_(t)
        , cf_{&from_builtin<
            unsigned char>, &arg_.uchar_}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    value_ref(unsigned short t) noexcept
        : arg_(t)
        , cf_{&from_builtin<
            unsigned short>, &arg_.ushort_}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    value_ref(unsigned int t) noexcept
        : arg_(t)
        , cf_{&from_builtin<
            unsigned int>, &arg_.uint_}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    value_ref(unsigned long t) noexcept
        : arg_(t)
        , cf_{&from_builtin<
            unsigned long>, &arg_.ulong_}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    value_ref(unsigned long long t) noexcept
        : arg_(t)
        , cf_{&from_builtin<
            unsigned long long>, &arg_.ulong_long_}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    value_ref(float t) noexcept
        : arg_(t)
        , cf_{&from_builtin<
            float>, &arg_.float_}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    value_ref(double t) noexcept
        : arg_(t)
        , cf_{&from_builtin<
            double>, &arg_.double_}
        , what_(what::cfunc)
    {
    }

    /// Constructor
    value_ref(std::nullptr_t) noexcept
        : arg_(nullptr)
        , cf_{&from_builtin<
            std::nullptr_t>, &arg_.nullptr_}
        , what_(what::cfunc)
    {
    }

#ifndef BOOST_JSON_DOCS
// Not public
//private:
    // VFALCO Why is this needed?
    /** Operator conversion to @ref value

        This allows creation of a @ref value from
        an initializer list element.
    */
    BOOST_JSON_DECL
    operator value() const;
#endif

private:
    template<class T>
    static
    value
    from_builtin(
        void const* p,
        storage_ptr sp) noexcept;

    template<class T>
    static
    value
    from_const(
        void const* p,
        storage_ptr sp);

    template<class T>
    static
    value
    from_rvalue(
        void* p,
        storage_ptr sp);

    static
    BOOST_JSON_DECL
    value
    from_init_list(
        void const* p,
        storage_ptr sp);

    inline
    bool
    is_key_value_pair() const noexcept;

    static
    inline
    bool
    maybe_object(
        std::initializer_list<
            value_ref> init) noexcept;

    inline
    string_view
    get_string() const noexcept;

    BOOST_JSON_DECL
    value
    make_value(
        storage_ptr sp) const;

    BOOST_JSON_DECL
    static
    value
    make_value(
        std::initializer_list<
            value_ref> init,
        storage_ptr sp);

    BOOST_JSON_DECL
    static
    object
    make_object(
        std::initializer_list<value_ref> init,
        storage_ptr sp);

    BOOST_JSON_DECL
    static
    array
    make_array(
        std::initializer_list<
            value_ref> init,
        storage_ptr sp);

    BOOST_JSON_DECL
    static
    void
    write_array(
        value* dest,
        std::initializer_list<
            value_ref> init,
        storage_ptr const& sp);
};

} // namespace json
} // namespace boost

// Must be included here for this file to stand alone
#include <boost/json/value.hpp>

// includes are at the bottom of <boost/json/value.hpp>
//#include <boost/json/impl/value.hpp>
//#include <boost/json/impl/value.ipp>

#endif
