#include "./test_lib/test_case.hpp"
#ifndef RYML_SINGLE_HEADER
#include "c4/yml/common.hpp"
#include "c4/yml/error.def.hpp"
#include "c4/format.hpp"
#include "c4/span.hpp"
#include "c4/yml/std/std.hpp"
#include "c4/yml/detail/print.hpp"
#include "c4/yml/detail/checks.hpp"
#endif

#include <gtest/gtest.h>
#ifdef C4_EXCEPTIONS
#include <exception>
#else
#include <csetjmp>
#endif

#if defined(_MSC_VER)
#   pragma warning(push)
#elif defined(__clang__)
#   pragma clang diagnostic push
#   pragma clang diagnostic ignored "-Wold-style-cast"
#elif defined(__GNUC__)
#   pragma GCC diagnostic push
#   pragma GCC diagnostic ignored "-Wuseless-cast"
#   pragma GCC diagnostic ignored "-Wold-style-cast"
#   if __GNUC__ >= 6
#       pragma GCC diagnostic ignored "-Wnull-dereference"
#   endif
#   if __GNUC__ >= 7
#       pragma GCC diagnostic ignored "-Wstringop-overflow"
#   endif
#endif


namespace c4 {
namespace yml {

const bomspec bomspecs_[] = {
    //                        bare string causes problems in gcc5 and earlier
    {"NOBOM"       , UTF8   , csubstr("", size_t(0))},
    {"UTF8"        , UTF8   , csubstr("\xef\xbb\xbf", 3)},
    {"UTF16BE"     , UTF16BE, csubstr("\xfe\xff", 2)},
    {"!UTF16BE-a"  , UTF16BE, csubstr("\x00""a", 2)},
    {"!UTF16BE-b"  , UTF16BE, csubstr("\x00""b", 2)},
    {"!UTF16BE-0"  , UTF16BE, csubstr("\x00""0", 2)},
    {"UTF16LE"     , UTF16LE, csubstr("\xff\xfe", 2)},
    {"!UTF16LE-a"  , UTF16LE, csubstr("a""\x00" , 2)},
    {"!UTF16LE-b"  , UTF16LE, csubstr("b""\x00" , 2)},
    {"!UTF16LE-0"  , UTF16LE, csubstr("0""\x00" , 2)},
    {"UTF32BE"     , UTF32BE, csubstr("\x00\x00\xfe\xff", 4)},
    {"!UTF32BE-a"  , UTF32BE, csubstr("\x00\x00\x00""a" , 4)},
    {"!UTF32BE-b"  , UTF32BE, csubstr("\x00\x00\x00""b" , 4)},
    {"!UTF32BE-0"  , UTF32BE, csubstr("\x00\x00\x00""0" , 4)},
    {"UTF32LE"     , UTF32LE, csubstr("\xff\xfe\x00\x00", 4)},
    {"!UTF32LE-a"  , UTF32LE, csubstr("a""\x00\x00\x00" , 4)},
    {"!UTF32LE-b"  , UTF32LE, csubstr("b""\x00\x00\x00" , 4)},
    {"!UTF32LE-0"  , UTF32LE, csubstr("0""\x00\x00\x00" , 4)},
};
const cspan<bomspec> bomspecs = bomspecs_;


id_type _num_leaves(Tree const& t, id_type node)
{
    id_type count = 0;
    for(id_type ch = t.first_child(node); ch != NONE; ch = t.next_sibling(ch))
        count += _num_leaves(t, ch);
    return count;
}


void test_compare(Tree const& actual, Tree const& expected,
                  const char *actual_name, const char *expected_name,
                  type_bits cmp_mask)
{
    ASSERT_EQ(actual.empty(), expected.empty());
    if(actual.empty() || expected.empty())
        return;
    EXPECT_EQ(actual.size(), expected.size());
    EXPECT_EQ(_num_leaves(actual, actual.root_id()), _num_leaves(expected, expected.root_id()));
    test_compare(actual, actual.root_id(), expected, expected.root_id(), 0, actual_name, expected_name, cmp_mask);
}


void test_compare(Tree const& actual, id_type node_actual,
                  Tree const& expected, id_type node_expected,
                  id_type depth, const char *actual_name, const char *expected_name,
                  type_bits cmp_mask)
{
    RYML_TRACE_FMT("actual={}[{}] vs expected={}[{}]. depth={}", actual_name, node_actual, expected_name, node_expected, depth);

    ASSERT_NE(node_actual, (id_type)NONE);
    ASSERT_NE(node_expected, (id_type)NONE);
    ASSERT_LT(node_actual, actual.capacity());
    ASSERT_LT(node_expected, expected.capacity());

    NodeType type_actual = actual.type(node_actual) & cmp_mask;
    NodeType type_expected = expected.type(node_expected) & cmp_mask;
    RYML_COMPARE_NODE_TYPE(type_actual, type_expected, ==, EQ);

    EXPECT_EQ(actual.has_key(node_actual), expected.has_key(node_expected));
    if(actual.has_key(node_actual) && expected.has_key(node_expected))
    {
        EXPECT_EQ(actual.key(node_actual), expected.key(node_expected));
    }

    EXPECT_EQ(actual.has_val(node_actual), expected.has_val(node_expected));
    if(actual.has_val(node_actual) && expected.has_val(node_expected))
    {
        EXPECT_EQ(actual.val(node_actual), expected.val(node_expected));
    }

    EXPECT_EQ(actual.has_key_tag(node_actual), expected.has_key_tag(node_expected));
    if(actual.has_key_tag(node_actual) && expected.has_key_tag(node_expected))
    {
        EXPECT_EQ(actual.key_tag(node_actual), expected.key_tag(node_expected));
    }

    EXPECT_EQ(actual.has_val_tag(node_actual), expected.has_val_tag(node_expected));
    if(actual.has_val_tag(node_actual) && expected.has_val_tag(node_expected))
    {
        csubstr actual_tag = actual.val_tag(node_actual);
        csubstr expected_tag = expected.val_tag(node_expected);
        EXPECT_EQ(actual_tag, expected_tag);
    }

    EXPECT_EQ(actual.has_key_anchor(node_actual), expected.has_key_anchor(node_expected));
    if(actual.has_key_anchor(node_actual) && expected.has_key_anchor(node_expected))
    {
        EXPECT_EQ(actual.key_anchor(node_actual), expected.key_anchor(node_expected));
    }

    EXPECT_EQ(actual.has_val_anchor(node_actual), expected.has_val_anchor(node_expected));
    if(actual.has_val_anchor(node_actual) && expected.has_val_anchor(node_expected))
    {
        EXPECT_EQ(actual.val_anchor(node_actual), expected.val_anchor(node_expected));
    }

    EXPECT_EQ(actual.num_children(node_actual), expected.num_children(node_expected));
    for(id_type ia = actual.first_child(node_actual), ib = expected.first_child(node_expected);
        ia != NONE && ib != NONE;
        ia = actual.next_sibling(ia), ib = expected.next_sibling(ib))
    {
        test_compare(actual, ia, expected, ib, depth+1, actual_name, expected_name);
    }

}

void test_arena_not_shared(Tree const& a, Tree const& b)
{
    for(NodeData const* n = a.m_buf, *e = a.m_buf + a.m_cap; n != e; ++n)
    {
        EXPECT_FALSE(b.in_arena(n->m_key.scalar)) << n - a.m_buf;
        EXPECT_FALSE(b.in_arena(n->m_key.tag   )) << n - a.m_buf;
        EXPECT_FALSE(b.in_arena(n->m_key.anchor)) << n - a.m_buf;
        EXPECT_FALSE(b.in_arena(n->m_val.scalar)) << n - a.m_buf;
        EXPECT_FALSE(b.in_arena(n->m_val.tag   )) << n - a.m_buf;
        EXPECT_FALSE(b.in_arena(n->m_val.anchor)) << n - a.m_buf;
    }
    for(NodeData const* n = b.m_buf, *e = b.m_buf + b.m_cap; n != e; ++n)
    {
        EXPECT_FALSE(a.in_arena(n->m_key.scalar)) << n - b.m_buf;
        EXPECT_FALSE(a.in_arena(n->m_key.tag   )) << n - b.m_buf;
        EXPECT_FALSE(a.in_arena(n->m_key.anchor)) << n - b.m_buf;
        EXPECT_FALSE(a.in_arena(n->m_val.scalar)) << n - b.m_buf;
        EXPECT_FALSE(a.in_arena(n->m_val.tag   )) << n - b.m_buf;
        EXPECT_FALSE(a.in_arena(n->m_val.anchor)) << n - b.m_buf;
    }
    for(TagDirective const& td : a.m_tag_directives)
    {
        EXPECT_FALSE(b.in_arena(td.handle));
        EXPECT_FALSE(b.in_arena(td.prefix));
    }
    for(TagDirective const& td : b.m_tag_directives)
    {
        EXPECT_FALSE(a.in_arena(td.handle));
        EXPECT_FALSE(a.in_arena(td.prefix));
    }
}


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

// ensure coverage of the default callback report
#ifndef RYML_NO_DEFAULT_CALLBACKS
extern void report_error_parse_impl(csubstr msg, Location loc, FILE *file);
#endif

std::string format_error_basic(csubstr msg, ErrorDataBasic const& errdata)
{
    std::string out;
    err_basic_format([&](csubstr s){
        out.append(s.str, s.len);
    }, msg, errdata);
    return out;
}

std::string format_error_parse(csubstr msg, ErrorDataParse const& errdata)
{
    std::string out;
    err_parse_format([&](csubstr s){
        out.append(s.str, s.len);
    }, msg, errdata);
    return out;
}

std::string format_error_visit(csubstr msg, ErrorDataVisit const& errdata)
{
    std::string out;
    err_visit_format([&](csubstr s){
        out.append(s.str, s.len);
    }, msg, errdata);
    return out;
}

struct ExpectedErrorBasic C4_IF_EXCEPTIONS_( : public std::exception, )
{
    std::string smsg;
    C4_IF_EXCEPTIONS_(const char *what() const noexcept override { return smsg.c_str(); }, )
    ExpectedErrorBasic() = default;
    ExpectedErrorBasic(csubstr msg, ErrorDataBasic const& errdata_)
        :
        C4_IF_EXCEPTIONS_(std::exception() C4_COMMA,)
        smsg(format_error_basic(msg, errdata_))
    {
    }
    ExpectedErrorBasic(std::string s)
        :
        C4_IF_EXCEPTIONS_(std::exception() C4_COMMA,)
        smsg(std::move(s))
    {
    }
};

struct ExpectedErrorParse : public ExpectedErrorBasic
{
    ErrorDataParse errdata;
    ExpectedErrorParse() = default;
    ExpectedErrorParse(csubstr msg, ErrorDataParse const& errdata_)
        : ExpectedErrorBasic(format_error_parse(msg, errdata_))
        , errdata(errdata_)
    {
    }
};

struct ExpectedErrorVisit : public ExpectedErrorBasic
{
    ErrorDataVisit errdata;
    ExpectedErrorVisit() = default;
    ExpectedErrorVisit(csubstr msg, ErrorDataVisit const& errdata_)
        : ExpectedErrorBasic(format_error_visit(msg, errdata_))
        , errdata(errdata_)
    {
    }
};


//-----------------------------------------------------------------------------
namespace {

C4_IF_EXCEPTIONS_(
    ,
    std::jmp_buf s_jmp_env_expect_error;
    ExpectedErrorBasic s_jmp_err_basic = {};
    ExpectedErrorParse s_jmp_err_parse = {};
    ExpectedErrorVisit s_jmp_err_visit = {};
    )

[[noreturn]] void experror_callback_basic(csubstr msg, ErrorDataBasic const& errdata, void *this_)
{
    _c4dbgp("called basic error callback! from here:");
    #ifdef RYML_DBG
    _dbg_printf("{}:{}: cpploc\n", errdata.location.name, errdata.location.line);
    _dbg_printf("{}:{}: {}\n", errdata.location.name, errdata.location.line, msg);
    #endif
    ((ExpectError*)this_)->m_error = ExpectedErrorType::err_basic; // assign in here to ensure the exception was thrown here
    C4_IF_EXCEPTIONS(
        throw ExpectedErrorBasic(msg, errdata);
        ,
        s_jmp_err_basic = ExpectedErrorBasic(msg, errdata);
        std::longjmp(s_jmp_env_expect_error, 1);
        );
    C4_UNREACHABLE_AFTER_ERR();
}

[[noreturn]] void experror_callback_parse(csubstr msg, ErrorDataParse const& errdata, void *this_)
{
    _c4dbgpf("called parse error callback! (withlocation={})", bool(errdata.ymlloc));
    #ifdef RYML_DBG
    _dbg_printf("{}:{}: cpploc\n", errdata.cpploc.name, errdata.cpploc.line);
    _dbg_printf("{}:{}: {}\n", errdata.ymlloc.name, errdata.ymlloc.line, msg);
    #endif
    ((ExpectError*)this_)->m_error = ExpectedErrorType::err_parse; // assign in here to ensure the exception was thrown here
    C4_IF_EXCEPTIONS(
        throw ExpectedErrorParse(msg, errdata);
        ,
        s_jmp_err_parse = ExpectedErrorParse(msg, errdata);
        std::longjmp(s_jmp_env_expect_error, 1);
    );
    C4_UNREACHABLE_AFTER_ERR();
}

[[noreturn]] void experror_callback_visit(csubstr msg, ErrorDataVisit const& errdata, void *this_)
{
    _c4dbgp("called visit error callback!");
    #ifdef RYML_DBG
    _dbg_printf("{}:{}: cpploc\n", errdata.cpploc.name, errdata.cpploc.line);
    _dbg_printf("{}:{}: {}\n", errdata.cpploc.name, errdata.cpploc.line, msg);
    #endif
    ((ExpectError*)this_)->m_error = ExpectedErrorType::err_visit; // assign in here to ensure the exception was thrown here
    C4_IF_EXCEPTIONS(
        throw ExpectedErrorVisit(msg, errdata);
        ,
        s_jmp_err_visit = ExpectedErrorVisit(msg, errdata);
        std::longjmp(s_jmp_env_expect_error, 1);
    );
    C4_UNREACHABLE_AFTER_ERR();
}
} // namespace

ExpectError::ExpectError(ExpectedErrorType errtype, Tree *tree, Location loc)
    : m_error(ExpectedErrorType::err_none)
    , m_expected_error(errtype)
    , m_tree(tree)
    , m_glob_prev(get_callbacks())
    , m_tree_prev(tree ? tree->callbacks() : m_glob_prev)
    , expected_location(loc)
{
    EXPECT_NE(m_glob_prev.m_allocate, nullptr);
    EXPECT_NE(m_tree_prev.m_allocate, nullptr);
    c4::yml::Callbacks tcb = m_tree_prev;
    c4::yml::Callbacks gcb = m_glob_prev;
    tcb.set_user_data((void*)this)
        .set_error_basic(experror_callback_basic)
        .set_error_parse(experror_callback_parse)
        .set_error_visit(experror_callback_visit);
    gcb.set_user_data((void*)this)
        .set_error_basic(experror_callback_basic)
        .set_error_parse(experror_callback_parse)
        .set_error_visit(experror_callback_visit);
    if(tree)
    {
        _c4dbgpf("setting error callback: tree={} err={}", c4::fmt::hex((void const*)m_tree), c4::fmt::hex((void const*)tcb.m_error_parse));
        tree->callbacks(tcb);
        EXPECT_EQ(tree->callbacks().m_error_basic, experror_callback_basic);
        EXPECT_EQ(tree->callbacks().m_error_parse, experror_callback_parse);
        EXPECT_EQ(tree->callbacks().m_error_visit, experror_callback_visit);
    }
    _c4dbgpf("setting error callback: global err={}", c4::fmt::hex((void const*)gcb.m_error_parse));
    set_callbacks(gcb);
    EXPECT_EQ(get_callbacks().m_error_basic, experror_callback_basic);
    EXPECT_EQ(get_callbacks().m_error_parse, experror_callback_parse);
    EXPECT_EQ(get_callbacks().m_error_visit, experror_callback_visit);
}

ExpectError::~ExpectError()
{
    if(m_tree)
    {
        _c4dbgp("resetting error callback: tree");
        m_tree->callbacks(m_tree_prev);
    }
    _c4dbgp("resetting error callback: global");
    set_callbacks(m_glob_prev);
    EXPECT_NE(get_callbacks().m_allocate, nullptr);
}

void ExpectError::check_error(ExpectedErrorType errtype, Tree *tree, fntestref fn, Location const& loc)
{
    switch(errtype)
    {
    case ExpectedErrorType::err_none:
        check_success(tree, std::forward<fntestref>(fn));
        break;
    case ExpectedErrorType::err_any:
        check_error_basic(tree, std::forward<fntestref>(fn), /*only_basic*/false);
        break;
    case ExpectedErrorType::err_basic:
        check_error_basic(tree, std::forward<fntestref>(fn));
        break;
    case ExpectedErrorType::err_parse:
        check_error_parse(tree, std::forward<fntestref>(fn), loc);
        break;
    case ExpectedErrorType::err_visit:
        check_error_visit(tree, std::forward<fntestref>(fn));
        break;
    }
}

void ExpectError::check_success(Tree *tree, fntestref fn)
{
    Location expected_location = {};
    auto context = ExpectError(ExpectedErrorType::err_none, tree, expected_location);
    C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
    {
        _c4dbgp("check expected success");
        fn();
        _c4dbgp("check expected success: success!");
    }
    C4_IF_EXCEPTIONS_(catch(...), else)
    {
        FAIL() << "check expected success: failed!";
    }
    ASSERT_EQ(context.m_error, ExpectedErrorType::err_none);
}

void ExpectError::check_error_basic(Tree const* tree, fntestref fn, bool only_basic)
{
    check_error_basic(const_cast<Tree*>(tree), fn, only_basic);
}

void ExpectError::check_error_parse(Tree const* tree, fntestref fn, Location const& expected_location)
{
    check_error_parse(const_cast<Tree*>(tree), fn, expected_location);
}

void ExpectError::check_error_visit(Tree const* tree, fntestref fn, id_type expected_id)
{
    check_error_visit(const_cast<Tree*>(tree), fn, expected_id);
}

void ExpectError::check_error_basic(Tree *tree, fntestref fn, bool only_basic)
{
    auto context = ExpectError(ExpectedErrorType::err_basic, tree);
    C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
    {
        _c4dbgp("check expected basic error");
        fn();
        _c4dbgp("check expected basic error: failed!");
    }
    C4_IF_EXCEPTIONS_(catch(ExpectedErrorBasic const& e), else)
    {
        C4_IF_EXCEPTIONS_( , ExpectedErrorBasic const& e = s_jmp_err_basic);
        (void)e;
        #if defined(_RYML_WITH_EXCEPTIONS)
        _c4dbgpf("---------------------\n""got an expected exception: {}""---------------------\n", e.what());
        #endif
    }
    C4_IF_EXCEPTIONS_(catch(...)
    {
        FAIL() << "got an unexpected exception";
    }, )
    if(context.m_error == ExpectedErrorType::err_none)
    {
        FAIL() << "no error occurred";
    }
    else
    {
        EXPECT_NE(context.m_error, ExpectedErrorType::err_none);
        if(only_basic)
        {
            EXPECT_EQ(context.m_error, ExpectedErrorType::err_basic);
        }
    }
}

void ExpectError::check_error_parse(Tree *tree, fntestref fn, Location const& expected_location)
{
    ExpectError context(ExpectedErrorType::err_parse, tree, expected_location);
    C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
    {
        _c4dbgp("check expected parse error");
        fn();
        _c4dbgp("check expected parse error: failed!");
    }
    C4_IF_EXCEPTIONS_(catch(ExpectedErrorParse const& e), else)
    {
        C4_IF_EXCEPTIONS_( , ExpectedErrorParse const& e = s_jmp_err_parse);
        (void)e;
        #if defined(RYML_DBG) && defined(_RYML_WITH_EXCEPTIONS)
        std::cout << "---------------\n";
        std::cout << "got an expected parse error:\n" << e.what() << "\n";
        std::cout << "---------------\n";
        #endif
        if(expected_location)
        {
            _c4dbgp("checking expected location...");
            EXPECT_EQ(static_cast<bool>(e.errdata.ymlloc), static_cast<bool>(context.expected_location));
            if(context.expected_location.line != npos)
            {
                EXPECT_EQ(e.errdata.ymlloc.line, context.expected_location.line);
            }
            if(context.expected_location.col != npos)
            {
                EXPECT_EQ(e.errdata.ymlloc.col, context.expected_location.col);
            }
            if(context.expected_location.offset != npos)
            {
                EXPECT_EQ(e.errdata.ymlloc.offset, context.expected_location.offset);
            }
        }
    }
    C4_IF_EXCEPTIONS_(catch(std::exception const& exc)
    {
        FAIL() << "got an unexpected exception:" << exc.what() << "\n";
    }, )
    C4_IF_EXCEPTIONS_(catch(...)
    {
        FAIL() << "got an unexpected exception";
    }, )
    if(context.m_error == ExpectedErrorType::err_none)
    {
        FAIL() << "no error occurred";
    }
    else
    {
        EXPECT_EQ(context.m_error, ExpectedErrorType::err_parse);
    }
}

void ExpectError::check_error_visit(Tree *tree, fntestref fn, id_type id)
{
    auto context = ExpectError(ExpectedErrorType::err_visit, tree);
    C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
    {
        _c4dbgp("check expected visit error");
        fn();
        _c4dbgp("check expected visit error: failed!");
    }
    C4_IF_EXCEPTIONS_(catch(ExpectedErrorVisit const& e), else)
    {
        C4_IF_EXCEPTIONS_( , ExpectedErrorVisit const& e = s_jmp_err_visit);
        (void)e;
        #if defined(RYML_DBG) && defined(_RYML_WITH_EXCEPTIONS)
        std::cout << "---------------\n";
        std::cout << "got an expected visit error:\n" << e.what() << "\n";
        std::cout << "---------------\n";
        #endif
        EXPECT_EQ(e.errdata.tree, tree);
        if(id != npos)
        {
            EXPECT_EQ(e.errdata.node, id);
        }
    }
    C4_IF_EXCEPTIONS_(catch(std::exception const& exc)
    {
        FAIL() << "got an unexpected exception:" << exc.what() << "\n";
    }, )
    C4_IF_EXCEPTIONS_(catch(...)
    {
        FAIL() << "got an unexpected exception";
    }, )
    if(context.m_error == ExpectedErrorType::err_none)
    {
        FAIL() << "no error occurred";
    }
    else
    {
        EXPECT_EQ(context.m_error, ExpectedErrorType::err_visit);
    }
}

void ExpectError::check_assert(ExpectedErrorType errtype, Tree *tree, fntestref fn, Location const& loc)
{
    #if RYML_USE_ASSERT
    ExpectError::check_error(errtype, tree, fn, loc);
    #else
    C4_UNUSED(errtype);
    C4_UNUSED(tree);
    C4_UNUSED(fn);
    C4_UNUSED(loc);
    #endif
}

void ExpectError::check_assert_basic(Tree *tree, fntestref fn, bool only_basic)
{
    #if RYML_USE_ASSERT
    ExpectError::check_error_basic(tree, fn, only_basic);
    #else
    C4_UNUSED(tree);
    C4_UNUSED(fn);
    C4_UNUSED(only_basic);
    #endif
}

void ExpectError::check_assert_parse(Tree *tree, fntestref fn, Location const& expected_location)
{
    #if RYML_USE_ASSERT
    ExpectError::check_error_parse(tree, fn, expected_location);
    #else
    C4_UNUSED(tree);
    C4_UNUSED(fn);
    C4_UNUSED(expected_location);
    #endif
}

void ExpectError::check_assert_visit(Tree *tree, fntestref fn, id_type id)
{
    #if RYML_USE_ASSERT
    ExpectError::check_error_visit(tree, fn, id);
    #else
    C4_UNUSED(tree);
    C4_UNUSED(fn);
    C4_UNUSED(id);
    #endif
}


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

void print_path(ConstNodeRef const& n)
{
    size_t len = 0;
    char buf[1024];
    ConstNodeRef p = n;
    while(p.readable())
    {
        if(p.has_key())
        {
            len += 1 + p.key().len;
        }
        else
        {
            int ret = snprintf(buf, sizeof(buf), "/%zu", p.has_parent() ? (size_t)p.parent().child_pos(p) : (size_t)0);
            _RYML_ASSERT_BASIC(ret >= 0);
            len += static_cast<size_t>(ret);
        }
        p = p.parent();
    };
    C4_ASSERT(len < sizeof(buf));
    size_t pos = len;
    p = n;
    while(p.readable())
    {
        if(p.has_key())
        {
            size_t tl = p.key().len;
            int ret = snprintf(buf + pos - tl, tl, "%.*s", (int)tl, p.key().str);
            _RYML_ASSERT_BASIC(ret >= 0);
            pos -= static_cast<size_t>(ret);
        }
        else if(p.has_parent())
        {
            pos = (size_t)p.parent().child_pos(p);
            int ret = snprintf(buf, 0, "/%zu", pos);
            _RYML_ASSERT_BASIC(ret >= 0);
            size_t tl = static_cast<size_t>(ret);
            _RYML_ASSERT_BASIC(pos >= tl);
            ret = snprintf(buf + static_cast<size_t>(pos - tl), tl, "/%zu", pos);
            _RYML_ASSERT_BASIC(ret >= 0);
            pos -= static_cast<size_t>(ret);
        }
        p = p.parent();
    };
    printf("%.*s", (int)len, buf);
}



void print_test_node(TestCaseNode const& p, int level)
{
    printf("%*s%p", (2*level), "", (void const*)&p);
    if( ! p.parent)
    {
        printf(" [ROOT]");
    }
    printf(" %s:", NodeType::type_str(p.type));
    if(p.has_key())
    {
        const char code = _scalar_code_key(p.type);
        if(p.has_key_anchor())
        {
            csubstr ka = p.key_anchor.str;
            printf(" &%.*s", (int)ka.len, ka.str);
        }
        if(p.key_tag.empty())
        {
            csubstr v  = p.key;
            printf(" %c%.*s%c", code, (int)v.len, v.str, code);
        }
        else
        {
            csubstr vt = p.key_tag;
            csubstr v  = p.key;
            printf(" %.*s %c%.*s%c'", (int)vt.len, vt.str, code, (int)v.len, v.str, code);
        }
    }
    if(p.has_val())
    {
        const char code = _scalar_code_val(p.type);
        if(p.val_tag.empty())
        {
            csubstr v  = p.val;
            printf(" %c%.*s%c", code, (int)v.len, v.str, code);
        }
        else
        {
            csubstr vt = p.val_tag;
            csubstr v  = p.val;
            printf(" %.*s%c%.*s%c", (int)vt.len, vt.str, code, (int)v.len, v.str, code);
        }
    }
    else
    {
        if( ! p.val_tag.empty())
        {
            csubstr vt = p.val_tag;
            printf(" %.*s", (int)vt.len, vt.str);
        }
    }
    if(p.has_val_anchor())
    {
        auto &a = p.val_anchor.str;
        printf(" valanchor='&%.*s'", (int)a.len, a.str);
    }
    printf(" (%zd sibs)", p.parent ? p.parent->children.size() : 0);
    if(p.is_container())
    {
        printf(" %zd children:", p.children.size());
    }
    printf("\n");
}


void print_test_tree(TestCaseNode const& p, int level)
{
    print_test_node(p, level);
    for(auto const& ch : p.children)
        print_test_tree(ch, level+1);
}

void print_test_tree(const char *message, TestCaseNode const& t)
{
    printf("--------------------------------------\n");
    if(message != nullptr)
        printf("%s:\n", message);
    print_test_tree(t, 0);
    printf("#nodes: %zu\n", (size_t)t.reccount());
    printf("--------------------------------------\n");
}

void test_invariants(ConstNodeRef const& n)
{
    SCOPED_TRACE(n.id());
    if(n.is_root())
    {
        EXPECT_FALSE(n.has_other_siblings());
    }
    // vals cannot be containers
    if( ! n.empty() && ! n.is_doc())
    {
        EXPECT_NE(n.has_val(), n.is_container());
    }
    if(n.has_children())
    {
        EXPECT_TRUE(n.is_container());
        EXPECT_FALSE(n.is_val());
    }
    // check sibling reciprocity
    for(ConstNodeRef s : n.siblings())
    {
        EXPECT_TRUE(n.has_sibling(s));
        EXPECT_TRUE(s.has_sibling(n));
        if(n.has_key() && s.has_key())
        {
            EXPECT_TRUE(n.has_sibling(s.key()));
            EXPECT_TRUE(s.has_sibling(n.key()));
        }
        EXPECT_EQ(s.parent().get(), n.parent().get());
    }
    // check parent/child reciprocity
    if(n.parent().readable())
    {
        EXPECT_EQ(n.parent().num_children() > 1, n.has_other_siblings());
        EXPECT_TRUE(n.parent().has_child(n));
        EXPECT_EQ(n.parent().num_children(), n.num_siblings());
        EXPECT_EQ(n.parent().num_children(), n.num_other_siblings()+id_type(1));
        // doc parent must be a seq and a stream
        if(n.is_doc())
        {
            EXPECT_TRUE(n.parent().is_seq());
            EXPECT_TRUE(n.parent().is_stream());
        }
    }
    else
    {
        EXPECT_TRUE(n.is_root());
    }
    if(n.is_seq())
    {
        EXPECT_TRUE(n.is_container());
        EXPECT_FALSE(n.is_map());
        for(ConstNodeRef ch : n.children())
        {
            EXPECT_FALSE(ch.is_keyval());
            EXPECT_FALSE(ch.has_key());
        }
    }
    if(n.is_map())
    {
        EXPECT_TRUE(n.is_container());
        EXPECT_FALSE(n.is_seq());
        for(ConstNodeRef ch : n.children())
        {
            if(ch.type() != NOTYPE)
            {
                EXPECT_TRUE(ch.has_key());
            }
        }
    }
    if(n.has_key_anchor())
    {
        EXPECT_FALSE(n.key_anchor().empty());
        EXPECT_FALSE(n.is_key_ref());
    }
    if(n.has_val_anchor())
    {
        EXPECT_FALSE(n.val_anchor().empty());
        EXPECT_FALSE(n.is_val_ref());
    }
    if(n.is_key_ref())
    {
        EXPECT_FALSE(n.key_ref().empty());
        EXPECT_FALSE(n.has_key_anchor());
    }
    if(n.is_val_ref())
    {
        EXPECT_FALSE(n.val_ref().empty());
        EXPECT_FALSE(n.has_val_anchor());
    }
    if(n.has_key())
    {
        if(n.is_key_quoted())
        {
            EXPECT_FALSE(n.key_is_null());
        }
        if(n.key_is_null())
        {
            EXPECT_FALSE(n.is_key_quoted());
        }
    }
    if(n.has_val() && n.is_val_quoted())
    {
        if(n.is_val_quoted())
        {
            EXPECT_FALSE(n.val_is_null());
        }
        if(n.val_is_null())
        {
            EXPECT_FALSE(n.is_val_quoted());
        }
    }
    // ... add more tests here

    // now recurse into the children
    for(ConstNodeRef ch : n.children())
    {
        test_invariants(ch);
    }

    #undef _MORE_INFO
}


static size_t test_tree_invariants(ConstNodeRef const& n)
{
    ConstNodeRef parent = n.parent();

    if(n.get()->m_prev_sibling == NONE)
    {
        if(parent.readable())
        {
            EXPECT_EQ(parent.first_child().get(), n.get());
            EXPECT_EQ(parent.first_child().id(), n.id());
        }
    }

    if(n.get()->m_next_sibling == NONE)
    {
        if(parent.readable())
        {
            EXPECT_EQ(parent.last_child().get(), n.get());
            EXPECT_EQ(parent.last_child().id(), n.id());
        }
    }

    if(!parent.readable())
    {
        EXPECT_TRUE(n.is_root());
        EXPECT_EQ(n.prev_sibling().get(), nullptr);
        EXPECT_EQ(n.next_sibling().get(), nullptr);
    }

    size_t count = 1, num = 0;
    for(ConstNodeRef ch : n.children())
    {
        EXPECT_NE(ch.id(), n.id());
        count += test_tree_invariants(ch);
        ++num;
    }

    EXPECT_EQ(num, n.num_children());

    return count;
}

void test_invariants(Tree const& t)
{
    SCOPED_TRACE("tree invariants");

    ASSERT_LE(t.size(), t.capacity());
    EXPECT_EQ(t.size() + t.slack(), t.capacity());

    ASSERT_LE(t.arena_size(), t.arena_capacity());
    ASSERT_LE(t.arena_slack(), t.arena_capacity());
    EXPECT_EQ(t.arena_size() + t.arena_slack(), t.arena_capacity());

    if(t.empty())
        return;

    size_t count = test_tree_invariants(t.rootref());
    EXPECT_EQ(count, t.size());

    check_invariants(t);
    test_invariants(t.rootref());

    if(!testing::UnitTest::GetInstance()->current_test_info()->result()->Passed())
    {
        print_tree(t);
    }

    return;
#if 0 == 1
    for(size_t i = 0; i < t.m_size; ++i)
    {
        auto n = t.get(i);
        if(n->m_prev_sibling == NONE)
        {
            EXPECT_TRUE(i == t.m_head || i == t.m_free_head);
        }
        if(n->m_next_sibling == NONE)
        {
            EXPECT_TRUE(i == t.m_tail || i == t.m_free_tail);
        }
    }

    std::vector<bool> touched(t.capacity());

    for(size_t i = t.m_head; i != NONE; i = t.get(i)->m_next_sibling)
        touched[i] = true;

    size_t size = 0;
    for(bool v : touched)
        size += v;

    EXPECT_EQ(size, t.size());

    touched.clear();
    touched.resize(t.capacity());

    for(size_t i = t.m_free_head; i != NONE; i = t.get(i)->m_next_sibling)
    {
        touched[i] = true;
    }

    size_t slack = 0;
    for(auto v : touched)
    {
        slack += v;
    }

    EXPECT_EQ(slack, t.slack());
    EXPECT_EQ(size+slack, t.capacity());

    // there are more checks to be done
#endif
}


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

CaseData* get_data(csubstr name)
{
    static std::map<csubstr, CaseData> m;

    auto it = m.find(name);
    CaseData *cd;
    if(it == m.end())
    {
        cd = &m[name];
        Case const* c = get_case(name);
        _RYML_CHECK_BASIC(c->src.find("\n\r") == csubstr::npos);
        {
            std::string tmp;
            replace_all("\r", "", c->src, &tmp);
            cd->unix_style.assign(to_csubstr(tmp));
            cd->unix_style_json.assign(to_csubstr(tmp));
        }
        {
            std::string tmp;
            replace_all("\n", "\r\n", cd->unix_style.src, &tmp);
            cd->windows_style.assign(to_csubstr(tmp));
            cd->windows_style_json.assign(to_csubstr(tmp));
        }
    }
    else
    {
        cd = &it->second;
    }
    return cd;
}

} // namespace yml
} // namespace c4

#if defined(_MSC_VER)
#   pragma warning(pop)
#elif defined(__clang__)
#   pragma clang diagnostic pop
#elif defined(__GNUC__)
#   pragma GCC diagnostic pop
#endif
