//
// 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
//

// Test that header file is self-contained.
#include <boost/json/stream_parser.hpp>

#include <boost/json/monotonic_resource.hpp>
#include <boost/json/parse.hpp>
#include <boost/json/serialize.hpp>

#include <sstream>
#include <iostream>
#include <cstring>

#include "parse-vectors.hpp"
#include "test.hpp"
#include "test_suite.hpp"

namespace boost {
namespace json {

BOOST_STATIC_ASSERT( std::is_nothrow_destructible<stream_parser>::value );

class stream_parser_test
{
public:
    ::test_suite::log_type log;

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

    void
    testCtors()
    {
        // stream_parser(stream_parser const&)
        BOOST_STATIC_ASSERT(
            ! std::is_copy_constructible<stream_parser>::value);

        // operator=(stream_parser const&)
        BOOST_STATIC_ASSERT(
            ! std::is_copy_assignable<stream_parser>::value);

        // ~stream_parser()
        {
            {
                stream_parser p;
            }

            {
                stream_parser p;
                p.reset(make_shared_resource<
                    monotonic_resource>());
            }
        }

        // stream_parser(storage_ptr, parse_options, unsigned char*, size_t)
        {
            unsigned char buf[256];
            stream_parser p(
                storage_ptr(),
                parse_options(),
                &buf[0],
                sizeof(buf));
        }

        // stream_parser()
        {
            stream_parser p;
        }

        // stream_parser(storage_ptr, parse_options)
        {
            stream_parser p(storage_ptr{}, parse_options());
        }

        // stream_parser(storage_ptr)
        {
            stream_parser p(storage_ptr{});
        }

        // stream_parser(storage_ptr, parse_options, unsigned char[])
        {
            unsigned char buf[256];
            stream_parser p(
                storage_ptr(),
                parse_options(),
                buf);
        }

    #if defined(__cpp_lib_byte)
        // stream_parser(storage_ptr, parse_options, std::byte*, size_t)
        {
            std::byte buf[256];
            stream_parser p(
                storage_ptr(),
                parse_options(),
                &buf[0],
                sizeof(buf));
        }

        // stream_parser(storage_ptr, parse_options, std::byte[])
        {
            std::byte buf[256];
            stream_parser p(
                storage_ptr(),
                parse_options(),
                buf);
        }
    #endif

        // stream_parser(storage_ptr, parse_options, unsigned char[], size_t)
        {
            unsigned char buf[256];
            stream_parser p(
                storage_ptr(),
                parse_options(),
                buf,
                sizeof(buf));
        }

    #if defined(__cpp_lib_byte)
        // stream_parser(storage_ptr, parse_options, std::byte[], size_t)
        {
            std::byte buf[256];
            stream_parser p(
                storage_ptr(),
                parse_options(),
                buf,
                sizeof(buf));
        }
    #endif
    }

    void
    testMembers()
    {
        // write_some(char const*, size_t, system::error_code&)
        // write_some(char const*, size_t, std::error_code&)
        // write_some(string_view, system::error_code&)
        // write_some(string_view, std::error_code&)
        {
            {
                stream_parser p;
                system::error_code ec;
                BOOST_TEST(p.write_some(
                    "[]*", ec) == 2);
                BOOST_TEST(! ec);
            }
            {
                stream_parser p;
                std::error_code ec;
                BOOST_TEST(p.write_some(
                    "[]*", ec) == 2);
                BOOST_TEST(! ec);
            }
            {
                stream_parser p;
                system::error_code ec;
                BOOST_TEST(p.write_some(
                    "[*", ec) == 1);
                BOOST_TEST(ec);
            }
            {
                stream_parser p;
                std::error_code ec;
                BOOST_TEST(p.write_some(
                    "[*", ec) == 1);
                BOOST_TEST(ec);
            }
        }

        // write_some(char const*, size_t)
        // write_some(string_view)
        {
            {
                stream_parser p;
                BOOST_TEST(
                    p.write_some("[]*") == 2);
            }
            {
                stream_parser p;
                BOOST_TEST_THROWS_WITH_LOCATION( p.write_some("[*") );
            }
        }

        // write(char const*, size_t, system::error_code&)
        // write(char const*, size_t, std::error_code&)
        // write(string_view, system::error_code&)
        // write(string_view, std::error_code&)
        {
            {
                stream_parser p;
                system::error_code ec;
                BOOST_TEST(p.write(
                    "null", ec) == 4);
                BOOST_TEST(! ec);
            }
            {
                stream_parser p;
                std::error_code ec;
                BOOST_TEST(p.write(
                    "null", ec) == 4);
                BOOST_TEST(! ec);
            }
            {
                stream_parser p;
                system::error_code ec;
                p.write("[]*", ec),
                BOOST_TEST(
                    ec == error::extra_data);
                BOOST_TEST(ec.has_location());
            }
            {
                stream_parser p;
                std::error_code ec;
                p.write("[]*", ec),
                BOOST_TEST(ec == error::extra_data);
            }
        }

        // write(char const*, size_t)
        // write(string_view)
        {
            {
                stream_parser p;
                BOOST_TEST(p.write(
                    "null") == 4);
            }
            {
                stream_parser p;
                BOOST_TEST_THROWS_WITH_LOCATION( p.write("[]*") );
            }
        }

        // finish()
        // finish(error_code&)
        // finish(std::error_code&)
        {
            {
                stream_parser p;
                p.write("1");
                BOOST_TEST(! p.done());
                p.finish();
                BOOST_TEST(p.done());
            }
            {
                stream_parser p;
                BOOST_TEST(! p.done());
                p.write("1.");
                BOOST_TEST_THROWS_WITH_LOCATION( p.finish() );
            }
            {
                stream_parser p;
                p.write("[1,2");
                system::error_code ec;
                p.finish(ec);
                BOOST_TEST(
                    ec == error::incomplete);
                BOOST_TEST(ec.has_location());
            }
            {
                stream_parser p;
                p.write("[1,2");
                system::error_code ec;
                p.finish(ec);
                BOOST_TEST_THROWS_WITH_LOCATION( p.finish() );
            }
            {
                stream_parser p;
                p.write("[1,2");
                std::error_code ec;
                p.finish(ec);
                BOOST_TEST(ec == error::incomplete);
            }
            {
                stream_parser p;
                p.write("[1,2");
                std::error_code ec;
                p.finish(ec);
                BOOST_TEST_THROWS_WITH_LOCATION( p.finish() );
            }
        }

        // release()
        {
            {
                stream_parser p;
                BOOST_TEST(
                    p.write_some("[") == 1);
                BOOST_TEST(! p.done());
                BOOST_TEST_THROWS_WITH_LOCATION( p.release() );
            }
            {
                stream_parser p;
                BOOST_TEST(
                    p.write_some("[]*") == 2);
                BOOST_TEST(p.done());
                p.release();
            }
            {
                stream_parser p;
                p.write("[");
                BOOST_TEST(! p.done());
                BOOST_TEST_THROWS_WITH_LOCATION( p.release() );
            }
            {
                stream_parser p;
                system::error_code ec;
                p.write("[]*", ec);
                BOOST_TEST(
                    ec == error::extra_data);
                BOOST_TEST(ec.has_location());
                BOOST_TEST(! p.done());
                BOOST_TEST_THROWS_WITH_LOCATION( p.release() );
            }
        }
    }

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

    static
    value
    from_string_test(
        string_view s,
        storage_ptr sp = {},
        const parse_options& po = parse_options())
    {
        stream_parser p(storage_ptr(), po);
        system::error_code ec;
        p.reset(std::move(sp));
        p.write(s.data(), s.size(), ec);
        if(BOOST_TEST(! ec))
            p.finish(ec);
        BOOST_TEST(! ec);
        return p.release();
    }

    void
    static
    check_round_trip(value const& jv1,
        const parse_options& po = parse_options())
    {
        auto const s2 =
            //to_string_test(jv1); // use this if serializer is broken
            serialize(jv1);
        auto jv2 =
            from_string_test(s2, {}, po);
        BOOST_TEST(equal(jv1, jv2));
    }

    template<class F>
    void
    static
    grind_one(
        string_view s,
        storage_ptr sp,
        F const& f,
        const parse_options& po = parse_options())
    {
        auto const jv =
            from_string_test(s, sp, po);
        f(jv, po);
    }

    static
    void
    grind_one(string_view s)
    {
        auto const jv =
            from_string_test(s);
        check_round_trip(jv);
    }

    template<class F>
    static
    void
    grind(string_view s, F const& f,
        const parse_options& po = parse_options())
    {
        try
        {
            grind_one(s, {}, f, po);

            fail_loop([&](storage_ptr const& sp)
            {
                grind_one(s, sp, f, po);
            });

            if(s.size() > 1)
            {
                // Destroy the stream_parser at every
                // split point to check leaks.
                for(std::size_t i = 1;
                    i < s.size(); ++i)
                {
                    fail_resource mr;
                    mr.fail_max = 0;
                    stream_parser p(storage_ptr(), po);
                    system::error_code ec;
                    p.reset(&mr);
                    p.write(s.data(), i, ec);
                    if(BOOST_TEST(! ec))
                        p.write(
                            s.data() + i,
                            s.size() - i, ec);
                    if(BOOST_TEST(! ec))
                        p.finish(ec);
                    if(BOOST_TEST(! ec))
                        f(p.release(), po);
                }
            }
        }
        catch(std::exception const&)
        {
            BOOST_TEST_FAIL();
        }
    }

    static
    void
    grind(string_view s,
        const parse_options& po = parse_options())
    {
        grind(s,
            [](value const& jv, const parse_options& po)
            {
                check_round_trip(jv, po);
            }, po);
    }

    static
    void
    grind_int64(string_view s, int64_t v)
    {
        grind(s,
            [v](value const& jv, const parse_options&)
            {
                if(! BOOST_TEST(jv.is_int64()))
                    return;
                BOOST_TEST(jv.get_int64() == v);
            });
    }

    static
    void
    grind_uint64(string_view s, uint64_t v)
    {
        grind(s,
            [v](value const& jv, const parse_options&)
            {
                if(! BOOST_TEST(jv.is_uint64()))
                    return;
                BOOST_TEST(jv.get_uint64() == v);
            });
    }

    static
    void
    grind_double( string_view s, double v, parse_options const& po = {} )
    {
        grind(s,
            [v](value const& jv, const parse_options&)
            {
                if(! BOOST_TEST(jv.is_double()))
                    return;
                if( std::isnan(v) )
                    BOOST_TEST( std::isnan(jv.get_double()) );
                else
                    BOOST_TEST( jv.get_double() == v );
            },
            po);
    }

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

    void
    testNull()
    {
        grind("null");
        grind(" null");
        grind("  null");
        grind("null\n");
        grind("null\n\n");
        grind("\r null\t ");
    }

    void
    testBool()
    {
        grind("true");
        grind(" true");
        grind("  true");
        grind("true\n");
        grind("true\n\n");
        grind("\r true\t ");

        grind("false");
        grind(" false");
        grind("  false");
        grind("false\n");
        grind("false\n\n");
        grind("\r false\t ");
    }

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

    void
    testString()
    {
        grind("\"\"");
        grind("\"x\"");
        grind(" \"x\"");
        grind("  \"x\"");
        grind("\"x\"\n");
        grind("\"x\"\n\n");
        grind("\r \"x\"\t ");

        grind("\"abcdefghij\"");
        grind("\""
            "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
            "\"");
        grind("\""
            "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
            "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
            "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
            "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
            "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
            "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
            "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
            "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
            "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
            "\"");

        // escapes
        grind("\"\\\"\"");
        grind("\"\\\\\"");
        grind("\"\\/\"");
        grind("\"\\b\"");
        grind("\"\\f\"");
        grind("\"\\n\"");
        grind("\"\\r\"");
        grind("\"\\t\"");

        // unicode
        grind("\"\\u0000\"");
        grind("\"\\ud7fF\"");
        grind("\"\\ue000\"");
        grind("\"\\ufFfF\"");
        grind("\"\\ud800\\udc00\"");
        grind("\"\\udbff\\udffF\"");

        // big string
        {
            std::string const big(4000, '*');
            {
                std::string js;
                js = "\"" + big + "\"";
                auto const N = js.size() / 2;
                system::error_code ec;
                stream_parser p;
                p.write(js.data(), N, ec);
                if(BOOST_TEST(! ec))
                {
                    p.write(js.data() + N,
                        js.size() - N, ec);
                    if(BOOST_TEST(! ec))
                        p.finish(ec);
                    if(BOOST_TEST(! ec))
                        check_round_trip(
                            p.release());
                }
            }
        }
    }

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

    struct f_boost
    {
        static
        string_view
        name() noexcept
        {
            return "boost";
        }

        double
        operator()(string_view s) const
        {
            BOOST_TEST_CHECKPOINT();
            system::error_code ec;
            stream_parser p;
            p.write(s.data(), s.size(), ec);
            if(BOOST_TEST(! ec))
                p.finish(ec);
            if(! BOOST_TEST(! ec))
                return 0;
            auto const jv = p.release();
            double const d = jv.as_double();
            grind_double(s, d);
            return d;
        }
    };

    bool
    within_1ulp(double x, double y)
    {
        std::uint64_t bx, by;
        std::memcpy(&bx, &x, sizeof(x));
        std::memcpy(&by, &y, sizeof(y));

        auto diff = bx - by;
        switch (diff)
        {
        case 0:
        case 1:
        case 0xffffffffffffffff:
            return true;
        default:
            break;
        }
        return false;
    }

    // Verify that f converts to the
    // same double produced by `strtod`.
    // Requires `s` is not represented by an integral type.
    template<class F>
    void
    fc(std::string const& s, F const& f)
    {
        char* str_end;
        double const need =
            std::strtod(s.c_str(), &str_end);
        // BOOST_TEST(str_end == &s.back() + 1);
        double const got = f(s);
        auto same = got == need;
        auto close = same ?
            true : within_1ulp(got, need);

        if( !BOOST_TEST(close) )
        {
            std::cerr << "Failure on '" << s << "': " << got << " != " << need << "\n";
        }
    }

    void
    fc(std::string const& s)
    {
        fc(s, f_boost{});
        fc(s + std::string( 64, ' ' ), f_boost{});
    }

    void
    testNumber()
    {
        grind("0");
        grind(" 0");
        grind("  0");
        grind("0\n");
        grind("0\n\n");
        grind("\r 0\t ");

        grind_int64( "-9223372036854775808", INT64_MIN);
        grind_int64( "-9223372036854775807", -9223372036854775807);
        grind_int64( "-999999999999999999", -999999999999999999);
        grind_int64( "-99999999999999999", -99999999999999999);
        grind_int64( "-9999999999999999", -9999999999999999);
        grind_int64( "-999999999999999", -999999999999999);
        grind_int64( "-99999999999999", -99999999999999);
        grind_int64( "-9999999999999", -9999999999999);
        grind_int64( "-999999999999", -999999999999);
        grind_int64( "-99999999999", -99999999999);
        grind_int64( "-9999999999", -9999999999);
        grind_int64( "-999999999", -999999999);
        grind_int64( "-99999999", -99999999);
        grind_int64( "-9999999", -9999999);
        grind_int64( "-999999", -999999);
        grind_int64( "-99999", -99999);
        grind_int64( "-9999", -9999);
        grind_int64( "-999", -999);
        grind_int64( "-99", -99);
        grind_int64( "-9", -9);
        grind_int64( "-0", 0);
        grind_int64(  "0", 0);
        grind_int64(  "1", 1);
        grind_int64(  "9", 9);
        grind_int64(  "99", 99);
        grind_int64(  "999", 999);
        grind_int64(  "9999", 9999);
        grind_int64(  "99999", 99999);
        grind_int64(  "999999", 999999);
        grind_int64(  "9999999", 9999999);
        grind_int64(  "99999999", 99999999);
        grind_int64(  "999999999", 999999999);
        grind_int64(  "9999999999", 9999999999);
        grind_int64(  "99999999999", 99999999999);
        grind_int64(  "999999999999", 999999999999);
        grind_int64(  "9999999999999", 9999999999999);
        grind_int64(  "99999999999999", 99999999999999);
        grind_int64(  "999999999999999", 999999999999999);
        grind_int64(  "9999999999999999", 9999999999999999);
        grind_int64(  "99999999999999999", 99999999999999999);
        grind_int64(  "999999999999999999", 999999999999999999);
        grind_int64(  "9223372036854775807", INT64_MAX);

        grind_uint64( "9223372036854775808", 9223372036854775808ULL);
        grind_uint64( "9999999999999999999", 9999999999999999999ULL);
        grind_uint64( "18446744073709551615", UINT64_MAX);

        grind_double("-1.010", -1.01);
        grind_double("-0.010", -0.01);
        grind_double("-0.0", -0.0);
        grind_double("-0e0", -0.0);
        grind_double( "18446744073709551616",  1.8446744073709552e+19);
        grind_double("-18446744073709551616", -1.8446744073709552e+19);
        grind_double( "18446744073709551616.0",  1.8446744073709552e+19);
        grind_double( "18446744073709551616.00009",  1.8446744073709552e+19);
        grind_double( "1844674407370955161600000",  1.8446744073709552e+24);
        grind_double("-1844674407370955161600000", -1.8446744073709552e+24);
        grind_double( "1844674407370955161600000.0",  1.8446744073709552e+24);
        grind_double( "1844674407370955161600000.00009",  1.8446744073709552e+24);

        grind_double( "1.0", 1.0);
        grind_double( "1.1", 1.1);
        grind_double( "1.11", 1.11);
        grind_double( "1.11111", 1.11111);
        grind_double( "11.1111", 11.1111);
        grind_double( "111.111", 111.111);

        fc("-999999999999999999999");
        fc("-100000000000000000009");
        fc("-10000000000000000000");
        fc("-9223372036854775809");

        fc("18446744073709551616");
        fc("99999999999999999999");
        fc("999999999999999999999");
        fc("1000000000000000000000");
        fc("9999999999999999999999");
        fc("99999999999999999999999");

        fc("-0.9999999999999999999999");
        fc("-0.9999999999999999");
        fc("-0.9007199254740991");
        fc("-0.999999999999999");
        fc("-0.99999999999999");
        fc("-0.9999999999999");
        fc("-0.999999999999");
        fc("-0.99999999999");
        fc("-0.9999999999");
        fc("-0.999999999");
        fc("-0.99999999");
        fc("-0.9999999");
        fc("-0.999999");
        fc("-0.99999");
        fc("-0.9999");
        fc("-0.8125");
        fc("-0.999");
        fc("-0.99");
        fc("-1.0");
        fc("-0.9");
        fc("-0.0");
        fc("0.0");
        fc("0.9");
        fc("0.99");
        fc("0.999");
        fc("0.8125");
        fc("0.9999");
        fc("0.99999");
        fc("0.999999");
        fc("0.9999999");
        fc("0.99999999");
        fc("0.999999999");
        fc("0.9999999999");
        fc("0.99999999999");
        fc("0.999999999999");
        fc("0.9999999999999");
        fc("0.99999999999999");
        fc("0.999999999999999");
        fc("0.9007199254740991");
        fc("0.9999999999999999");
        fc("0.9999999999999999999999");
        fc("0.999999999999999999999999999");

        fc("-1e308");
        fc("-1e-308");
        fc("-9999e300");
        fc("-999e100");
        fc("-99e10");
        fc("-9e1");
        fc("9e1");
        fc("99e10");
        fc("999e100");
        fc("9999e300");
        fc("999999999999999999.0");
        fc("999999999999999999999.0");
        fc("999999999999999999999e5");
        fc("999999999999999999999.0e5");

        fc("0.00000000000000001");

        fc("-1e-1");
        fc("-1e0");
        fc("-1e1");
        fc("0e0");
        fc("1e0");
        fc("1e10");

        fc("0."
           "00000000000000000000000000000000000000000000000000" // 50 zeroes
           "1e50");
        fc("-0."
           "00000000000000000000000000000000000000000000000000" // 50 zeroes
           "1e50");

        fc("0."
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000" // 500 zeroes
           "1e600");
        fc("-0."
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000" // 500 zeroes
           "1e600");

        fc("0e"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000"
           "00000000000000000000000000000000000000000000000000" // 500 zeroes
        );
    }

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

    void
    testArray()
    {
        grind("[]");
        grind(" []");
        grind("[] ");
        grind(" [] ");
        grind(" [ ] ");
        grind("[1]");
        grind("[ 1]");
        grind("[1 ]");
        grind("[ 1 ]");
        grind("[1,2]");
        grind("[ 1,2]");
        grind("[1 ,2]");
        grind("[1, 2]");
        grind("[1,2 ]");
        grind("[ 1 ,2]");
        grind("[1 , 2]");
        grind("[1, 2 ]");

        grind("[[]]");
        grind("[[],[]]");
        grind("[[],[],[]]");
        grind("[[[]],[[],[]],[[],[],[]]]");
        grind("[{},[],\"x\",1,-1,1.0,true,null]");

        // depth
        {
            system::error_code ec;
            parse_options opt;
            opt.max_depth = 0;
            stream_parser p(storage_ptr(), opt);
            p.write("[]", 2, ec);
            BOOST_TEST(
                ec == error::too_deep);
            BOOST_TEST(ec.has_location());
        }
    }

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

    void
    testObject()
    {
        grind("{}");
        grind(" {}");
        grind("{} ");
        grind(" {} ");
        grind(" { } ");
        grind("{\"1\":1}");
        grind("{ \"1\":1}");
        grind("{\"1\" :1}");
        grind("{\"1\": 1}");
        grind("{\"1\":1 }");
        grind("{ \"1\" :1 }");
        grind("{\"1\" : 1 }");
        grind("{\"1\":1,\"2\":2}");
        grind("{\"1\":1, \"2\":2}");
        grind("{\"1\":1, \"2\" : 2 }");

        grind("{\"\":[]}");
        grind("{\"1\":[],\"2\":[]}");

        grind(
            "{\"1\":{\"2\":{}},\"3\":{\"4\":{},\"5\":{}},"
            "\"6\":{\"7\":{},\"8\":{},\"9\":{}}}");

        grind(
            "{\"1\":{},\"2\":[],\"3\":\"x\",\"4\":1,"
            "\"5\":-1,\"6\":1.0,\"7\":false,\"8\":null}");

        // big keys
        {
            std::string const big(4000, '*');
            {
                std::string js;
                js = "{\"" + big + "\":null}";
                grind(js);
            }

            {
                std::string js;
                js = "{\"x\":\"" + big + "\"}";
                grind(js);
            }

            {
                std::string js;
                js = "{\"" + big + "\":\"" + big + "\"}";
                grind(js);
            }
        }

        // depth
        {
            system::error_code ec;
            parse_options opt;
            opt.max_depth = 0;
            stream_parser p(storage_ptr(), opt);
            p.write("{}", 2, ec);
            BOOST_TEST(
                ec == error::too_deep);
            BOOST_TEST(ec.has_location());
        }
    }

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

    void
    testFreeFunctions()
    {
        string_view const js =
            "{\"1\":{},\"2\":[],\"3\":\"x\",\"4\":1,"
            "\"5\":-1,\"6\":1.0,\"7\":false,\"8\":null}";

        // parse(string_view, system::error_code)
        {
            {
                system::error_code ec;
                auto jv = parse(js, ec);
                BOOST_TEST(! ec);
                check_round_trip(jv);
            }
            {
                system::error_code ec;
                auto jv = parse("xxx", ec);
                BOOST_TEST(ec);
                BOOST_TEST(ec.has_location());
                BOOST_TEST(jv.is_null());
            }
        }

        // parse(string_view, storage_ptr, system::error_code)
        {
            {
                system::error_code ec;
                monotonic_resource mr;
                auto jv = parse(js, ec, &mr);
                BOOST_TEST(! ec);
                //check_round_trip(jv);
            }

            {
                system::error_code ec;
                monotonic_resource mr;
                auto jv = parse("xxx", ec, &mr);
                BOOST_TEST(ec);
                BOOST_TEST(ec.has_location());
                BOOST_TEST(jv.is_null());
            }
        }

        // parse(string_view)
        {
            {
                check_round_trip(
                    parse(js));
            }

            {
                value jv;
                BOOST_TEST_THROWS_WITH_LOCATION( jv = parse("{,") );
            }
        }

        // parse(string_view, storage_ptr)
        {
            {
                monotonic_resource mr;
                check_round_trip(parse(js, &mr));
            }

            {
                monotonic_resource mr;
                value jv;
                BOOST_TEST_THROWS_WITH_LOCATION( jv = parse("xxx", &mr) );
            }
        }
    }

    void
    testSampleJson()
    {
        string_view in =
R"xx({
    "glossary": {
        "title": "example glossary",
		"GlossDiv": {
            "title": "S",
			"GlossList": {
                "GlossEntry": {
                    "ID": "SGML",
					"SortAs": "SGML",
					"GlossTerm": "Standard Generalized Markup Language",
					"Acronym": "SGML",
					"Abbrev": "ISO 8879:1986",
					"GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
						"GlossSeeAlso": ["GML", "XML"]
                    },
					"GlossSee": "markup"
                }
            }
        }
    }
})xx";
        string_view out =
            "{\"glossary\":{\"title\":\"example glossary\",\"GlossDiv\":"
            "{\"title\":\"S\",\"GlossList\":{\"GlossEntry\":{\"ID\":\"SGML\","
            "\"SortAs\":\"SGML\",\"GlossTerm\":\"Standard Generalized Markup "
            "Language\",\"Acronym\":\"SGML\",\"Abbrev\":\"ISO 8879:1986\","
            "\"GlossDef\":{\"para\":\"A meta-markup language, used to create "
            "markup languages such as DocBook.\",\"GlossSeeAlso\":[\"GML\",\"XML\"]},"
            "\"GlossSee\":\"markup\"}}}}}";
        storage_ptr sp =
            make_shared_resource<monotonic_resource>();
        stream_parser p(sp);
        system::error_code ec;
        p.write(in.data(), in.size(), ec);
        if(BOOST_TEST(! ec))
            p.finish(ec);
        if(BOOST_TEST(! ec))
            BOOST_TEST(serialize(p.release()) == out);
    }

    void
    testTrailingCommas()
    {
        parse_options enabled;
        enabled.allow_trailing_commas = true;

        grind("[1,]", enabled);
        grind("[1, 2,]", enabled);
        grind("{\"a\": 1,}", enabled);
        grind("{\"a\": 1, \"b\": 2,}", enabled);
    }

    void
    testComments()
    {
        parse_options enabled;
        enabled.allow_comments = true;

        grind("[/* c */1]", enabled);
        grind("[1/* c */]", enabled);
        grind("[1] /* c */", enabled);
        grind("/* c */ [1]", enabled);
        grind("[1/* c */, 2]", enabled);
        grind("[1, /* c */ 2]", enabled);

        grind("// c++ \n[1]", enabled);
        grind("[1] // c++ \n", enabled);
        grind("[1] // c++", enabled);
        grind("[// c++ \n1]", enabled);
        grind("[1// c++ \n]", enabled);
        grind("[1// c++ \n, 2]", enabled);
        grind("[1, // c++ \n 2]", enabled);

        grind("{/* c */\"a\":1}", enabled);
        grind("{\"a\":1/* c */}", enabled);
        grind("{\"a\":1} /* c */", enabled);
        grind("/* c */ {\"a\":1}", enabled);
        grind("{\"a\":1/* c */, \"b\":1}", enabled);
        grind("{\"a\":1, /* c */ \"b\":1}", enabled);
        grind("{\"a\"/* c */:1}", enabled);
        grind("{\"a\":/* c */1}", enabled);

        grind("// c++ \n{\"a\":1}", enabled);
        grind("{\"a\":1} // c++ \n", enabled);
        grind("{\"a\":1} // c++", enabled);
        grind("{// c++ \n\"a\":1}", enabled);
        grind("{\"a\":1// c++ \n}", enabled);
        grind("{\"a\":1// c++ \n, \"b\":2}", enabled);
        grind("{\"a\":1, // c++ \n \"b\":2}", enabled);
        grind("{\"a\"// c++ \n:1}", enabled);
        grind("{\"a\":// c++ \n1}", enabled);
    }

    void
    testUnicodeStrings()
    {
        // Embedded NULL correctly converted
        {
            auto expected = string_view("Hello\x00World", 11);
            {
                auto s = string_view(R"json("Hello\u0000World")json");
                grind(s);
                BOOST_TEST(json::parse(s).as_string() == expected);
            }
            {
                auto s = string_view(R"json(["Hello\u0000World"])json");
                grind(s);
                BOOST_TEST(json::parse(s).as_array().at(0).as_string() == expected);
            }
        }

        // surrogate pairs correctly converted to UTF-8
        {
            auto expected = string_view("\xF0\x9D\x84\x9E", 4);
            {
                auto s = string_view(R"json("\uD834\uDD1E")json");
                grind(s);
                BOOST_TEST(json::parse(s).as_string() == expected);
            }
            {
                auto s = string_view(R"json(["\uD834\uDD1E"])json");
                grind(s);
                BOOST_TEST(json::parse(s).as_array().at(0).as_string() == expected);
            }
        }
    }

    void
    testDupeKeys()
    {
        {
            value jv = parse(R"({"a":1,"b":2,"a":3})");
            object& jo = jv.as_object();
            BOOST_TEST(jo.size() == 2);
            BOOST_TEST(jo.at("a").as_int64() == 3);
            BOOST_TEST(jo.at("b").as_int64() == 2);
        }
        {
            value jv = parse(R"({"a":1,"a":3,"b":2})");
            object& jo = jv.as_object();
            BOOST_TEST(jo.size() == 2);
            BOOST_TEST(jo.at("a").as_int64() == 3);
            BOOST_TEST(jo.at("b").as_int64() == 2);
        }
        {
            value jv = parse(R"({"a":1,"a":3,"b":2,"a":4})");
            object& jo = jv.as_object();
            BOOST_TEST(jo.size() == 2);
            BOOST_TEST(jo.at("a").as_int64() == 4);
            BOOST_TEST(jo.at("b").as_int64() == 2);
        }
        {
            value jv = parse(R"({"a":1,"a":3,"b":2,"a":4,"b":5})");
            object& jo = jv.as_object();
            BOOST_TEST(jo.size() == 2);
            BOOST_TEST(jo.at("a").as_int64() == 4);
            BOOST_TEST(jo.at("b").as_int64() == 5);
        }
    }

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

    // https://github.com/boostorg/json/issues/15
    void
    testIssue15()
    {
        BOOST_TEST(
            json::parse("{\"port\": 12345}")
                .as_object()
                .at("port")
                .as_int64() == 12345);
    }

    // https://github.com/boostorg/json/issues/45
    void
    testIssue45()
    {
        struct T
        {
            value jv;

            T(value jv_)
                : jv(jv_)
            {
            }
        };

        auto const jv = parse("[]");
        auto const t = T{jv};
        BOOST_TEST(serialize(t.jv) == "[]");
    }

    void
    testIssue876()
    {
        stream_parser p;
        p.write_some( R"("\u20")", 5 );

        std::string s = "19";
        for( std::size_t i = 0; i < BOOST_JSON_STACK_BUFFER_SIZE; ++i )
            s += " ";
        s += "\"";
        p.write_some( s.data(), s.size() ); // this asserted because of a bug
        BOOST_TEST( p.release().is_string() );
    }

    // https://github.com/boostorg/json/pull/814
    void
    testSentinelOverlap()
    {
        struct {
            char buffer[8];
            boost::json::stream_parser p;
        } s;
        memcpy(s.buffer, "{\"12345\"", 8);
        s.p.write(s.buffer, sizeof(s.buffer));
        s.p.write(":0}", 3);
    }

    void
    testSpecialNumbers()
    {
        parse_options with_special_numbers;
        with_special_numbers.allow_infinity_and_nan = true;

        grind_double(
            "Infinity",
            std::numeric_limits<double>::infinity(),
            with_special_numbers);

        grind_double(
            "-Infinity",
            -std::numeric_limits<double>::infinity(),
            with_special_numbers);
        grind_double(
            "-Infinity                         ", // long enough for fast path
            -std::numeric_limits<double>::infinity(),
            with_special_numbers);


        grind_double(
            "NaN",
            std::numeric_limits<double>::quiet_NaN(),
            with_special_numbers);
    }

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

    void
    testLongNumberOverlfow()
    {
#ifdef BOOST_JSON_EXPENSIVE_TESTS
        std::array<char, 1000> zeroes;
        zeroes.fill('0');

        stream_parser p;
        {
            p.write("1", 1);

            std::size_t count = 0;
            while( static_cast<std::size_t>( INT_MAX - zeroes.size() ) > count )
                count += p.write( zeroes.data(), zeroes.size() );

            system::error_code ec;
            p.write(zeroes.data(), zeroes.size(), ec);
            BOOST_TEST( ec == error::exponent_overflow );
        }

        p.reset();
        {
            p.write("0.", 2);

            std::size_t count = 0;
            while( static_cast<std::size_t>( INT_MAX - zeroes.size() ) > count )
                count += p.write( zeroes.data(), zeroes.size() );

            system::error_code ec;
            p.write(zeroes.data(), zeroes.size(), ec);
            BOOST_TEST( ec == error::exponent_overflow );
        }

        p.reset();
        {
            p.write("0.", 2);

            int count = INT_MIN;
            while( static_cast<int>( count + zeroes.size() ) < 0 )
                count += static_cast<int>(
                    p.write( zeroes.data(), zeroes.size() ));

            p.write(zeroes.data(), -2 - count);
            p.write("1e", 2);
            // at this point we've filled bias to the brim

            std::string const int_min = std::to_string(INT_MIN);
            p.write( int_min.data(), int_min.size() );

            system::error_code ec;
            p.finish(ec);
            BOOST_TEST( ec == error::exponent_overflow );
        }

        p.reset();
        {
            std::string const uint64_max
                = std::to_string(18446744073709551615U);
            p.write( uint64_max.data(), uint64_max.size() );

            std::size_t count = INT_MAX;
            while( static_cast<int>( count - zeroes.size() ) > 0 )
                count -= p.write( zeroes.data(), zeroes.size() );

            p.write(zeroes.data(), count - 1);
            // at this point we've filled bias to the brim

            p.write("e", 1);
            std::string const int_max = std::to_string(INT_MAX);
            p.write( int_max.data(), int_max.size() );

            system::error_code ec;
            p.finish(ec);
            BOOST_TEST( ec == error::exponent_overflow );
        }
#endif
    }

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

    void
    run()
    {
        testCtors();
        testMembers();

        testNull();
        testBool();
        testString();
        testNumber();
        testArray();
        testObject();

        testFreeFunctions();
        testSampleJson();
        testUnicodeStrings();
        testTrailingCommas();
        testComments();
        testDupeKeys();
        testIssue15();
        testIssue45();
        testIssue876();
        testSentinelOverlap();
        testSpecialNumbers();
        testLongNumberOverlfow();
    }
};

TEST_SUITE(stream_parser_test, "boost.json.stream_parser");

} // namespace json
} // namespace boost
