// adopted from https://github.com/boostorg/stacktrace/tree/develop/include/boost/stacktrace/detail
#include "exception_debug.h"
#include <windows.h>
#include <dbgeng.h>

#ifdef __CRT_UUID_DECL // for __MINGW32__
    __CRT_UUID_DECL(IDebugClient,0x27fe5639,0x8407,0x4f47,0x83,0x64,0xee,0x11,0x8f,0xb0,0x8a,0xc8)
    __CRT_UUID_DECL(IDebugControl,0x5182e668,0x105e,0x416e,0xad,0x92,0x24,0xef,0x80,0x04,0x24,0xba)
    __CRT_UUID_DECL(IDebugSymbols,0x8c31e98c,0x983a,0x48a5,0x90,0x16,0x6f,0xe5,0xd6,0x67,0xa9,0x50)
#elif defined(DEFINE_GUID) && !defined(BOOST_MSVC)
    DEFINE_GUID(IID_IDebugClient,0x27fe5639,0x8407,0x4f47,0x83,0x64,0xee,0x11,0x8f,0xb0,0x8a,0xc8);
    DEFINE_GUID(IID_IDebugControl,0x5182e668,0x105e,0x416e,0xad,0x92,0x24,0xef,0x80,0x04,0x24,0xba);
    DEFINE_GUID(IID_IDebugSymbols,0x8c31e98c,0x983a,0x48a5,0x90,0x16,0x6f,0xe5,0xd6,0x67,0xa9,0x50);
#endif

using namespace panda;

com_global_initer::com_global_initer() noexcept : _ok{false} {
    // COINIT_MULTITHREADED means that we must serialize access to the objects manually.
    // This is the fastest way to work. If user calls CoInitializeEx before us - we
    // can end up with other mode (which is OK for us).
    //
    // If we call CoInitializeEx befire user - user may end up with different mode, which is a problem.
    // So we need to call that initialization function as late as possible.
    auto res = ::CoInitializeEx(0, COINIT_MULTITHREADED);
    _ok = (res == S_OK || res == S_FALSE);
}

com_global_initer::~com_global_initer() noexcept {
    if (_ok) { ::CoUninitialize(); }
}

void debugging_symbols::try_init_com(com_holder< ::IDebugSymbols>& idebug, const com_global_initer& com) noexcept {
    com_holder< ::IDebugClient> iclient(com);
    if (S_OK != ::DebugCreate(__uuidof(IDebugClient), iclient.to_void_ptr_ptr())) {
        return;
    }

    com_holder< ::IDebugControl> icontrol(com);
    const bool res0 = (S_OK == iclient->QueryInterface(
        __uuidof(IDebugControl),
        icontrol.to_void_ptr_ptr()
    ));
    if (!res0) {
        return;
    }

    const bool res1 = (S_OK == iclient->AttachProcess(
        0,
        ::GetCurrentProcessId(),
        DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND
    ));
    if (!res1) {
        return;
    }

    if (S_OK != icontrol->WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE)) {
        return;
    }

    // No cheking: QueryInterface sets the output parameter to NULL in case of error.
    iclient->QueryInterface(__uuidof(IDebugSymbols), idebug.to_void_ptr_ptr());
}


com_holder< ::IDebugSymbols>& debugging_symbols::get_thread_local_debug_inst() noexcept {
    // [class.mfct]: A static local variable or local type in a member function always refers to the same entity, whether
    // or not the member function is inline.
    static thread_local com_global_initer com;
    static thread_local com_holder< ::IDebugSymbols> idebug(com);

    if (!idebug.is_inited()) {
        try_init_com(idebug, com);
    }

    return idebug;
}

debugging_symbols::debugging_symbols() noexcept : idebug_( get_thread_local_debug_inst() ) {}

bool debugging_symbols::is_inited() const noexcept { return idebug_.is_inited();  }
