#pragma once
#ifndef TEST_FUZZ_COMMON_H
#define TEST_FUZZ_COMMON_H

#ifdef RYML_SINGLE_HEADER
#include <ryml_all.hpp>
#else
#include <c4/yml/std/std.hpp>
#include <c4/yml/parse.hpp>
#include <c4/yml/error.def.hpp>
#include <c4/yml/emit.hpp>
#include <c4/yml/event_handler_tree.hpp>
#include <c4/yml/parse_engine.def.hpp>
#endif
#include <testsuite/testsuite_events.hpp>
#include <c4/yml/extra/event_handler_ints.hpp>
#include <c4/yml/extra/event_handler_testsuite.hpp>
#include <cstdio>

#ifdef C4_EXCEPTIONS
#include <stdexcept>
#else
#include <csetjmp>
std::jmp_buf jmp_env = {};
c4::csubstr jmp_msg = {};
#endif


#ifdef RYML_DBG
#define _if_dbg(...) __VA_ARGS__
bool report_errors = true;
#else
#define _if_dbg(...)
bool report_errors = false;
#endif

// watchout: VS2022 requires C4_NORETURN to come before inline
[[noreturn]] void throwerr(c4::csubstr msg)
{
    C4_IF_EXCEPTIONS(
        throw std::runtime_error({msg.str, msg.len});
        ,
        jmp_msg.assign(msg.str, msg.len);
        std::longjmp(jmp_env, 1);
        );
    C4_UNREACHABLE_AFTER_ERR();
}

void dump2stderr(c4::csubstr s)
{
    if(s.len)
    {
        fwrite(s.str, 1, s.len, stderr);
        fflush(stderr);
    }
}

inline c4::yml::Callbacks create_custom_callbacks()
{
    c4::set_error_flags(c4::ON_ERROR_CALLBACK);
    c4::set_error_callback([](const char *msg, size_t msg_len){
        throwerr(c4::csubstr{msg, msg_len});
    });
    return c4::yml::Callbacks{}
        .set_allocate([](size_t len, void* , void *){
            return malloc(len);
        })
        .set_free([](void* mem, size_t, void *){
            free(mem);
        })
        .set_error_basic([](c4::csubstr msg, c4::yml::ErrorDataBasic const& errdata, void *){
            if(report_errors)
                c4::yml::err_basic_format(dump2stderr, msg, errdata);
            throwerr(msg);
        })
        .set_error_parse([](c4::csubstr msg, c4::yml::ErrorDataParse const& errdata, void *){
            if(report_errors)
                c4::yml::err_parse_format(dump2stderr, msg, errdata);
            throwerr(msg);
        })
        .set_error_visit([](c4::csubstr msg, c4::yml::ErrorDataVisit const& errdata, void *){
            if(report_errors)
                c4::yml::err_visit_format(dump2stderr, msg, errdata);
            throwerr(msg);
        });
}

namespace c4 {
namespace yml {

inline int fuzztest_parse_emit(uint32_t case_number, csubstr src)
{
    C4_UNUSED(case_number);
    set_callbacks(create_custom_callbacks());
    Tree tree(create_custom_callbacks());
    bool parse_success = false;
    C4_IF_EXCEPTIONS_(try, if(setjmp(jmp_env) == 0))
    {
        _RYML_ASSERT_BASIC(tree.empty());
        _if_dbg(_dbg_printf("in[{}]: [{}]~~~\n{}\n~~~\n", case_number, src.len, src); fflush(NULL));
        parse_in_arena(src, &tree);
        parse_success = true;
        _if_dbg(print_tree("parsed tree", tree));
        _if_dbg(_dbg_printf("in[{}]: [{}]~~~\n{}\n~~~\n", case_number, src.len, src); fflush(NULL));
        std::string dst = emitrs_yaml<std::string>(tree);
        _if_dbg(_dbg_printf("emitted[{}]: [{}]~~~\n{}\n~~~\n", case_number, dst.size(), to_csubstr(dst)); fflush(NULL));
        C4_DONT_OPTIMIZE(dst);
        C4_DONT_OPTIMIZE(parse_success);
    }
    C4_IF_EXCEPTIONS_(catch(std::exception const&), else)
    {
        // if an exception leaks from here, it is likely because of a greedy noexcept
        _if_dbg(if(parse_success) print_tree("parsed tree", tree));
        return 1;
    }
    return 0;
}

inline int fuzztest_yaml_events(uint32_t case_number, csubstr src)
{
    C4_UNUSED(case_number);
    set_callbacks(create_custom_callbacks());
    extra::EventHandlerTestSuite::EventSink sink = {};
    extra::EventHandlerTestSuite handler(&sink, create_custom_callbacks());
    ParseEngine<extra::EventHandlerTestSuite> parser(&handler);
    std::string str(src.begin(), src.end());
    C4_IF_EXCEPTIONS_(try, if(setjmp(jmp_env) == 0))
    {
        _if_dbg(_dbg_printf("in[{}]: [{}]~~~\n{}\n~~~\n", case_number, src.len, src); fflush(NULL));
        parser.parse_in_place_ev("input", c4::to_substr(str));
        _if_dbg(_dbg_printf("evts[{}]: ~~~\n{}\n~~~\n", case_number, sink); fflush(NULL));
        C4_DONT_OPTIMIZE(sink);
    }
    C4_IF_EXCEPTIONS_(catch(std::exception const&), else)
    {
        // if an exception leaks from here, it is likely because of a greedy noexcept
        _if_dbg(fprintf(stdout, "err\n"); fflush(NULL));
        return 1;
    }
    return 0;
}

inline int fuzztest_yaml_events_ints(uint32_t case_number, csubstr src)
{
    C4_UNUSED(case_number);
    set_callbacks(create_custom_callbacks());
    using Handler = extra::EventHandlerInts;
    Handler handler{};
    ParseEngine<extra::EventHandlerInts> parser(&handler);
    std::string str(src.begin(), src.end());
    std::vector<char> arena(str.size());
    std::vector<Handler::value_type> event_ints;
    event_ints.reserve(256);
    handler.reset(to_substr(str), to_substr(arena), event_ints.data(), static_cast<Handler::value_type>(event_ints.size()));
    C4_IF_EXCEPTIONS_(try, if(setjmp(jmp_env) == 0))
    {
        _if_dbg(_dbg_printf("in[{}]: [{}]~~~\n{}\n~~~\n", case_number, src.len, src); fflush(NULL));
        parser.parse_in_place_ev("input", c4::to_substr(str));
        C4_DONT_OPTIMIZE(event_ints);
    }
    C4_IF_EXCEPTIONS_(catch(std::exception const&), else)
    {
        // if an exception leaks from here, it is likely because of a greedy noexcept
        _if_dbg(fprintf(stdout, "err\n"); fflush(NULL));
        return 1;
    }
    return 0;
}

} // namespace yml
} // namespace c4

#endif /* TEST_FUZZ_COMMON_H */
