The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#pragma once
#include <cstring>
#include <typeinfo>
#include <cxxabi.h>
#include <panda/string.h>
#include <panda/string_map.h>
#include <panda/string_set.h>
#include <panda/lib/memory.h>
#include <panda/basic_string.h>
#include <panda/lib/from_chars.h>
#include <panda/unordered_string_map.h>
#include <panda/unordered_string_set.h>

#undef seed
#define CATCH_CONFIG_RUNNER
#include <catch.hpp>
#include <panda/log.h>

using std::string_view;
using namespace panda::lib;

inline SV* error_sv (const std::exception& err) {
    dTHX;

    int status;
    char* class_name = abi::__cxa_demangle(typeid(err).name(), NULL, NULL, &status);
    if (status != 0) croak("[error_sv] !critical! abi::__cxa_demangle error");
    SV* errsv = newSVpvs("[");
    sv_catpv(errsv, class_name);
    sv_catpv(errsv, "] ");
    sv_catpv(errsv, err.what());
    free(class_name);

    return errsv;
}

#define PXS_TRY(code) {                                                                       \
    try { code; }                                                                             \
    catch (const std::exception& err) { croak_sv(error_sv(err)); }                            \
    catch (const char* err)           { croak_sv(newSVpv(err, 0)); }                          \
    catch (const std::string& err)    { croak_sv(newSVpvn(err.data(), err.length())); }       \
    catch (const panda::string& err)  { croak_sv(newSVpvn(err.data(), err.length())); }       \
    catch (...)                       { croak_sv(newSVpvs("unknown c++ exception thrown")); } \
}

#define XS_HV_ITER(hv,code) {                                                       \
    STRLEN hvmax = HvMAX(hv);                                                       \
    HE** hvarr = HvARRAY(hv);                                                       \
    if (HvUSEDKEYS(hv))                                                             \
        for (STRLEN bucket_num = 0; bucket_num <= hvmax; ++bucket_num)              \
            for (const HE* he = hvarr[bucket_num]; he; he = HeNEXT(he)) { code }    \
}
#define XS_HV_ITER_NU(hv,code) XS_HV_ITER(hv,{if(!SvOK(HeVAL(he))) continue; code})

#define XS_AV_ITER(av,code) {                                           \
    SV** list = AvARRAY(av);                                            \
    SSize_t fillp = AvFILLp(av);                                        \
    for (SSize_t i = 0; i <= fillp; ++i) { SV* elem = *list++; code }   \
}
#define XS_AV_ITER_NE(av,code) XS_AV_ITER(av,{if(!elem) continue; code})
#define XS_AV_ITER_NU(av,code) XS_AV_ITER(av,{if(!elem || !SvOK(elem)) continue; code})

namespace test {

    namespace string {

        struct AllocsStat {
            int allocated;
            int allocated_cnt;
            int deallocated;
            int deallocated_cnt;
            int reallocated;
            int reallocated_cnt;
            int ext_deallocated;
            int ext_deallocated_cnt;
            int ext_shbuf_deallocated;
        };

        static AllocsStat allocs[2];

        static const char literal_char[] = "hello world, this is a literal string";

        template <class T, int N = 0>
        struct TestAllocator {
            typedef T value_type;

            static T* allocate (size_t n) {
                //std::cout << "allocate " << n << std::endl;
                void* mem = malloc(n * sizeof(T));
                if (!mem) throw std::bad_alloc();
                allocs[N].allocated += n;
                allocs[N].allocated_cnt++;
                return (T*)mem;
            }

            static void deallocate (T* mem, size_t n) {
                //std::cout << "deallocate " << n << std::endl;
                allocs[N].deallocated += n;
                allocs[N].deallocated_cnt++;
                free(mem);
            }

            static T* reallocate (T* mem, size_t need, size_t old) {
                //std::cout << "reallocate need=" << need << " old=" << old << std::endl;
                void* new_mem = realloc(mem, need * sizeof(T));
                allocs[N].reallocated += (need - old);
                allocs[N].reallocated_cnt++;
                return (T*)new_mem;
            }
        };

        template <class T, int N = 0>
        void ext_free (T* mem, size_t n) {
            allocs[N].ext_deallocated += n;
            allocs[N].ext_deallocated_cnt++;
            free(mem);
        }

        template <class T>
        T* shared_buf_alloc () {
            return (T*)panda::lib::StaticMemoryPool<100>::instance()->allocate();
        }

        template <class T, int N = 0>
        void shared_buf_free (T* mem, size_t) {
            panda::lib::StaticMemoryPool<100>::instance()->deallocate(mem);
            allocs[N].ext_shbuf_deallocated++;
        }

        panda::wstring string_view_to_wstring (string_view v) {
            panda::wstring ret(v.length());
            for (size_t i = 0; i < v.length(); ++i) ret[i] = v[i];
            ret.length(v.length());
            return ret;
        }

    }

    typedef panda::basic_string<char, std::char_traits<char>, test::string::TestAllocator<char>> String;
    typedef panda::basic_string<char, std::char_traits<char>, test::string::TestAllocator<char,1>> String2;

    panda::string_map<test::String, panda::string> smap;
    const decltype(smap)* csmap = &smap;
    panda::string_multimap<test::String, panda::string> smmap;
    const decltype(smmap)* csmmap = &smmap;
    panda::unordered_string_map<test::String, panda::string> usmap;
    const decltype(usmap)* cusmap = &usmap;
    panda::unordered_string_multimap<test::String, panda::string> usmmap;
    const decltype(usmmap)* cusmmap = &usmmap;
    panda::string_set<test::String> sset;
    const decltype(sset)* csset = &sset;
    panda::string_multiset<test::String> smset;
    const decltype(smset)* csmset = &smset;
    panda::unordered_string_set<test::String> usset;
    const decltype(usset)* cusset = &usset;
    panda::unordered_string_multiset<test::String> usmset;
    const decltype(usmset)* cusmset = &usmset;
}

typedef test::String  testString;
typedef test::String2 testString2;
typedef panda::string pandaString;

class MyObject : public AllocatedObject<MyObject, false> {
public:
    int a;
    MyObject () : a(0) {}
};

class MyObjectThr : public AllocatedObject<MyObject, true> {
public:
    int a;
    MyObjectThr () : a(0) {}
};

class MyObjectDyn : public MyObject {
public:
    int b;
    MyObjectDyn () : b(0) {}
};

class MyObjectDynThr : public MyObjectThr {
public:
    int b;
    MyObjectDynThr () : b(0) {}
};

static inline uint64_t _test_on_thread_start () {
    static const int cnt = 10000;
    uint64_t ret = 0;
    for (int i = 0; i < cnt; ++i) {
        void* mem = ObjectAllocator::tls_instance()->allocate(16);
        ret += (uint64_t) mem;
        ObjectAllocator::tls_instance()->deallocate(mem, 16);
        mem = StaticMemoryPool<16>::tls_instance()->allocate();
        ret += (uint64_t) mem;
        StaticMemoryPool<16>::tls_instance()->deallocate(mem);
    }
    return ret;
}

inline bool run_all_cpp_tests() {
    struct CatchLogger : panda::logger::ILogger {
        virtual void log(panda::logger::Level l, panda::logger::CodePoint cp, const std::string& s) override {
            if (int(l) < int(panda::logger::WARNING)) {
                FAIL(cp << "\t" << s);
            } else {
                INFO(cp << "\t" << s);
            }
        }
    };

    panda::Log::logger().reset(new CatchLogger);

    std::vector<const char*> argv = {"test"};
    return Catch::Session().run(argv.size(), argv.data()) == 0;
}

#ifdef _WIN32
static inline DWORD WINAPI test_on_thread_start (LPVOID lpParameter) { return (DWORD)_test_on_thread_start(); }
#else
static inline void* test_on_thread_start (void*) { return (void*)_test_on_thread_start(); }
#endif