#include <xs/catch.h>
#include <xs/Simple.h>
#include <string>
#include <vector>
#include <cxxabi.h>
#include <exception>

using panda::string_view;

namespace xs {

static std::vector<CatchHandler>       catch_handlers;
static std::vector<ExceptionProcessor> exception_processors;

static std::string get_type_name (const std::type_info& ti) {
    int status;
    char* class_name = abi::__cxa_demangle(ti.name(), NULL, NULL, &status);
    std::string ret = "[";
    if (status == 0) {
        ret += class_name;
        free(class_name);
    }
    else ret = "<unknown type>";
    ret += "]";
    return ret;
}

static Sv _exc2sv_default (const Sub&) {
    try { throw; }
    catch (SV* err)                  { return err; }
    catch (Sv& err)                  { return err; }
    catch (const char* err)          { return Simple(string_view(err)); }
    catch (const string_view& err)   { return Simple(err); }
    catch (const panda::string& err) { return Simple(err); }
    catch (const std::string& err)   { return Simple(string_view(err.data(), err.length())); }
    catch (const PerlRuntimeException& err) {
        return err.sv;
    }
    catch (std::exception& err) {
        dTHX;
        auto tn = get_type_name(typeid(err));
        SV* errsv = newSVpv(tn.data(), 0);
        sv_catpv(errsv, " ");
        sv_catpv(errsv, err.what());
        return Sv::noinc(errsv);
    }
    catch (std::exception* err) {
        dTHX;
        auto tn = get_type_name(typeid(*err));
        SV* errsv = newSVpv(tn.data(), 0);
        sv_catpv(errsv, " ");
        sv_catpv(errsv, err->what());
        return Sv::noinc(errsv);
    }
    catch (...) {
        dTHX;
        auto tn = get_type_name(*abi::__cxa_current_exception_type());
        SV* errsv = newSVpv(tn.data(), 0);
        sv_catpv(errsv, " exception");
        return Sv::noinc(errsv);
    }
    return Sv();
}

static Sv _exc2sv_impl (const Sub& context, int i) {
    if (i < 0) return _exc2sv_default(context); // no 1 has catched the exception, apply defaults
    if (i >= (int)catch_handlers.size()) i = catch_handlers.size() - 1;
    try {
        auto ret = catch_handlers[i](context);
        if (ret) return ret;
    }
    catch (...) {}
    return _exc2sv_impl(context, i-1);
}

Sv _exc2sv (const Sub& context) {
    auto ex_sv = _exc2sv_impl(context, catch_handlers.size() - 1);
    for (auto& processor: exception_processors) {
        ex_sv = processor(ex_sv, context);
    }
    return ex_sv;
}

void add_catch_handler (CatchHandler h) {
    catch_handlers.push_back(h);
}

void add_catch_handler (CatchHandlerSimple h) {
    catch_handlers.push_back([h](const Sub&) -> Sv { return h(); });
}

void add_exception_processor(ExceptionProcessor f) {
    exception_processors.push_back(f);
}

void add_exception_processor(ExceptionProcessorSimple f) {
    exception_processors.push_back([f](Sv& ex, const Sub&) -> Sv { return f(ex); });
}


}
