//
// 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_SERIALIZER_HPP
#define BOOST_JSON_SERIALIZER_HPP

#include <boost/json/detail/config.hpp>
#include <boost/json/value.hpp>
#include <boost/json/detail/format.hpp>
#include <boost/json/detail/stack.hpp>
#include <boost/json/detail/stream.hpp>

namespace boost {
namespace json {

/** A serializer for JSON.

    This class traverses an instance of a library
    type and emits serialized JSON text by filling
    in one or more caller-provided buffers. To use,
    declare a variable and call @ref reset with
    a pointer to the variable you want to serialize.
    Then call @ref read over and over until
    @ref done returns `true`.

    @par Example

    This demonstrates how the serializer may
    be used to print a JSON value to an output
    stream.

    @code

    void print( std::ostream& os, value const& jv)
    {
        serializer sr;
        sr.reset( &jv );
        while( ! sr.done() )
        {
            char buf[ 4000 ];
            os << sr.read( buf );
        }
    }

    @endcode

    @par Thread Safety

    The same instance may not be accessed concurrently.
*/
class serializer
{
    enum class state : char;
    // VFALCO Too many streams
    using stream = detail::stream;
    using const_stream = detail::const_stream;
    using local_stream = detail::local_stream;
    using local_const_stream =
        detail::local_const_stream;

    using fn_t = bool (serializer::*)(stream&);

#ifndef BOOST_JSON_DOCS
    union
    {
        value const* pv_;
        array const* pa_;
        object const* po_;
    };
#endif
    fn_t fn0_ = &serializer::write_null<true>;
    fn_t fn1_ = &serializer::write_null<false>;
    value const* jv_ = nullptr;
    detail::stack st_;
    const_stream cs0_;
    char buf_[detail::max_number_chars + 1];
    bool done_ = false;

    inline bool suspend(state st);
    inline bool suspend(
        state st, array::const_iterator it, array const* pa);
    inline bool suspend(
        state st, object::const_iterator it, object const* po);
    template<bool StackEmpty> bool write_null   (stream& ss);
    template<bool StackEmpty> bool write_true   (stream& ss);
    template<bool StackEmpty> bool write_false  (stream& ss);
    template<bool StackEmpty> bool write_string (stream& ss);
    template<bool StackEmpty> bool write_number (stream& ss);
    template<bool StackEmpty> bool write_array  (stream& ss);
    template<bool StackEmpty> bool write_object (stream& ss);
    template<bool StackEmpty> bool write_value  (stream& ss);
    inline string_view read_some(char* dest, std::size_t size);

public:
    /// Move constructor (deleted)
    serializer(serializer&&) = delete;

    /** Destructor

        All temporary storage is deallocated.

        @par Complexity
        Constant

        @par Exception Safety
        No-throw guarantee.
    */
    BOOST_JSON_DECL
    ~serializer() noexcept;

    /** Constructor

        This constructs a serializer with no value.
        The value may be set later by calling @ref reset.
        If serialization is attempted with no value,
        the output is as if a null value is serialized.

        @par Complexity
        Constant.

        @par Exception Safety
        No-throw guarantee.
    */
    BOOST_JSON_DECL
    serializer() noexcept;

    /** Constructor

        This constructs a serializer with no value.
        The value may be set later by calling @ref reset.
        If serialization is attempted with no value,
        the output is as if a null value is serialized.

        @par Complexity
        Constant.

        @par Exception Safety
        No-throw guarantee.

        @param sp A pointer to the @ref memory_resource
        to use when producing partial output.
        Shared ownership of the memory resource
        is retained until the serializer is
        destroyed.

        @param buf An optional static buffer to
        use for temporary storage when producing
        partial output.

        @param buf_size The number of bytes of
        valid memory pointed to by `buf`.
    */
    BOOST_JSON_DECL
    serializer(
        storage_ptr sp,
        unsigned char* buf = nullptr,
        std::size_t buf_size = 0) noexcept;

    /** Returns `true` if the serialization is complete

        This function returns `true` when all of the
        characters in the serialized representation of
        the value have been read.

        @par Complexity
        Constant.

        @par Exception Safety
        No-throw guarantee.
    */
    bool
    done() const noexcept
    {
        return done_;
    }

    /** Reset the serializer for a new element

        This function prepares the serializer to emit
        a new serialized JSON representing `*p`.
        Any internally allocated memory is
        preserved and re-used for the new output.

        @param p A pointer to the element to serialize.
        Ownership is not transferred; The caller is
        responsible for ensuring that the lifetime of
        `*p` extends until it is no longer needed.
    */
    /** @{ */
    BOOST_JSON_DECL
    void
    reset(value const* p) noexcept;

    BOOST_JSON_DECL
    void
    reset(array const* p) noexcept;

    BOOST_JSON_DECL
    void
    reset(object const* p) noexcept;

    BOOST_JSON_DECL
    void
    reset(string const* p) noexcept;
    /** @} */

    /** Reset the serializer for a new string

        This function prepares the serializer to emit
        a new serialized JSON representing the string.
        Any internally allocated memory is
        preserved and re-used for the new output.

        @param sv The characters representing the string.
        Ownership is not transferred; The caller is
        responsible for ensuring that the lifetime of
        the characters reference by `sv` extends
        until it is no longer needed.
    */
    BOOST_JSON_DECL
    void
    reset(string_view sv) noexcept;

    /** Read the next buffer of serialized JSON

        This function attempts to fill the caller
        provided buffer starting at `dest` with
        up to `size` characters of the serialized
        JSON that represents the value. If the
        buffer is not large enough, multiple calls
        may be required.
\n
        If serialization completes during this call;
        that is, that all of the characters belonging
        to the serialized value have been written to
        caller-provided buffers, the function
        @ref done will return `true`.

        @par Preconditions
        @code
        this->done() == false
        @endcode

        @par Complexity
        Linear in `size`.

        @par Exception Safety
        Basic guarantee.
        Calls to `memory_resource::allocate` may throw.

        @return A @ref string_view containing the
        characters written, which may be less than
        `size`.

        @param dest A pointer to valid memory of at
        least `size` bytes.

        @param size The maximum number of characters
        to write to the memory pointed to by `dest`.
    */
    BOOST_JSON_DECL
    string_view
    read(char* dest, std::size_t size);

    /** Read the next buffer of serialized JSON

        This function allows reading into a
        character array, with a deduced maximum size.

        @par Preconditions
        @code
        this->done() == false
        @endcode

        @par Effects
        @code
        return this->read( dest, N );
        @endcode

        @par Complexity
        Linear in `N`.

        @par Exception Safety
        Basic guarantee.
        Calls to `memory_resource::allocate` may throw.

        @return A @ref string_view containing the
        characters written, which may be less than
        `size`.

        @param dest The character array to write to.
    */
    template<std::size_t N>
    string_view
    read(char(&dest)[N])
    {
        return read(dest, N);
    }

#ifndef BOOST_JSON_DOCS
    // Safety net for accidental buffer overflows
    template<std::size_t N>
    string_view
    read(char(&dest)[N], std::size_t n)
    {
        // If this goes off, check your parameters
        // closely, chances are you passed an array
        // thinking it was a pointer.
        BOOST_ASSERT(n <= N);
        return read(dest, n);
    }
#endif
};

} // namespace json
} // namespace boost

#endif
