#include "dynlib.h"
#include "demangle_impl.h"
#include "platform.h"
#include "backtrace.h"

#include <util/stream/output.h>
#include <util/stream/format.h>
#include <util/generic/array_ref.h>
#include <util/generic/string.h>

#ifdef _win_
    #include "mutex.h"

    #ifndef OPTIONAL
        #define OPTIONAL
    #endif
    #include <dbghelp.h>
#endif

#if defined(_bionic_)
//TODO
#else
    #if !defined(HAVE_BACKTRACE) && defined(_cygwin_)
        #define CaptureStackBackTrace RtlCaptureStackBackTrace
extern "C" __stdcall unsigned short CaptureStackBackTrace(unsigned long FramesToSkip, unsigned long FramesToCapture, void** BackTrace, unsigned long* BackTraceHash);

        #define USE_WIN_BACKTRACE
        #define HAVE_BACKTRACE
    #endif

    #if !defined(HAVE_BACKTRACE) && defined(__IOS__)
        #define USE_GLIBC_BACKTRACE
        #define HAVE_BACKTRACE
    #endif

    #if !defined(HAVE_BACKTRACE) && defined(__GNUC__)
        #define USE_GCC_BACKTRACE
        #define HAVE_BACKTRACE
    #endif

    #if !defined(HAVE_BACKTRACE) && defined(_win_)
        #define USE_WIN_BACKTRACE
        #define HAVE_BACKTRACE
    #endif

    #if !defined(HAVE_BACKTRACE) && defined(_glibc_)
        #define USE_GLIBC_BACKTRACE
        #define HAVE_BACKTRACE
    #endif
#endif

#if defined(USE_GLIBC_BACKTRACE)
    #include <execinfo.h>

size_t BackTrace(void** p, size_t len) {
    return (size_t)backtrace(p, len);
}
#endif

#if defined(USE_GCC_BACKTRACE)
    #include <cxxabi.h>
    #include <unwind.h>

namespace {
    namespace NGCCBacktrace {
        struct TBackTraceContext {
            void** sym;
            size_t cnt;
            size_t size;
        };

        static _Unwind_Reason_Code Helper(struct _Unwind_Context* c, void* h) {
            TBackTraceContext* bt = (TBackTraceContext*)h;

            if (bt->cnt != 0) {
                bt->sym[bt->cnt - 1] = (void*)_Unwind_GetIP(c);
            }

            if (bt->cnt == bt->size) {
                return _URC_END_OF_STACK;
            }

            ++bt->cnt;

            return _URC_NO_REASON;
        }

        static inline size_t BackTrace(void** p, size_t len) {
            if (len >= 1) {
                TBackTraceContext bt = {p, 0, len};

                _Unwind_Backtrace(Helper, &bt);

                return bt.cnt - 1;
            }

            return 0;
        }
    }
}

size_t BackTrace(void** p, size_t len) {
    return NGCCBacktrace::BackTrace(p, len);
}
#endif

#if defined(USE_WIN_BACKTRACE)
size_t BackTrace(void** p, size_t len) {
    return CaptureStackBackTrace(0, len, p, nullptr);
}
#endif

#if !defined(HAVE_BACKTRACE)
size_t BackTrace(void**, size_t) {
    return 0;
}
#endif

#if defined(_unix_) && !defined(_cygwin_)
    #include <util/generic/strfcpy.h>

    #include <dlfcn.h>

    #if defined(_darwin_)
        #include <execinfo.h>
    #endif

static inline const char* CopyTo(const char* from, char* buf, size_t len) {
    strfcpy(buf, from, len);

    return buf;
}

TResolvedSymbol ResolveSymbol(void* sym, char* buf, size_t len) {
    TResolvedSymbol ret = {
        "??",
        sym,
    };

    Dl_info dli;

    Zero(dli);

    if (dladdr(sym, &dli) && dli.dli_sname) {
        ret.Name = CopyTo(NPrivate::TCppDemangler().Demangle(dli.dli_sname), buf, len);
        ret.NearestSymbol = dli.dli_saddr;
    }

    return ret;
}
#elif defined(_win_)
    #include <util/generic/singleton.h>

namespace {
    struct TWinSymbolResolverImpl {
        typedef BOOL(WINAPI* TSymInitializeFunc)(HANDLE, PCSTR, BOOL);
        typedef BOOL(WINAPI* TSymCleanupFunc)(HANDLE);
        typedef BOOL(WINAPI* TSymFromAddrFunc)(HANDLE, DWORD64, PDWORD64, PSYMBOL_INFO);

        TWinSymbolResolverImpl()
            : InitOk(FALSE)
        {
            Library = LoadLibraryA("Dbghelp.dll");
            if (!Library) {
                return;
            }

            SymInitializeFunc = (TSymInitializeFunc)GetProcAddress(Library, "SymInitialize");
            SymCleanupFunc = (TSymCleanupFunc)GetProcAddress(Library, "SymCleanup");
            SymFromAddrFunc = (TSymFromAddrFunc)GetProcAddress(Library, "SymFromAddr");
            if (SymInitializeFunc && SymCleanupFunc && SymFromAddrFunc) {
                InitOk = SymInitializeFunc(GetCurrentProcess(), nullptr, TRUE);
            }
        }

        ~TWinSymbolResolverImpl() {
            if (InitOk) {
                SymCleanupFunc(GetCurrentProcess());
            }

            if (Library) {
                FreeLibrary(Library);
            }
        }

        TResolvedSymbol Resolve(void* sym, char* buf, size_t len) {
            TGuard<TMutex> guard(Mutex);

            TResolvedSymbol ret = {
                "??",
                sym};

            if (!InitOk || (len <= 1 + sizeof(SYMBOL_INFO))) {
                return ret;
            }

            SYMBOL_INFO* symbol = (SYMBOL_INFO*)buf;
            Zero(*symbol);

            symbol->MaxNameLen = len - sizeof(SYMBOL_INFO) - 1;
            symbol->SizeOfStruct = sizeof(SYMBOL_INFO);

            DWORD64 displacement = 0;
            BOOL res = SymFromAddrFunc(GetCurrentProcess(), (DWORD64)sym, &displacement, symbol);
            if (res) {
                ret.NearestSymbol = (void*)symbol->Address;
                ret.Name = symbol->Name;
            }

            return ret;
        }

        TMutex Mutex;
        HMODULE Library;
        TSymInitializeFunc SymInitializeFunc;
        TSymCleanupFunc SymCleanupFunc;
        TSymFromAddrFunc SymFromAddrFunc;
        BOOL InitOk;
    };
}

TResolvedSymbol ResolveSymbol(void* sym, char* buf, size_t len) {
    return Singleton<TWinSymbolResolverImpl>()->Resolve(sym, buf, len);
}
#else
TResolvedSymbol ResolveSymbol(void* sym, char*, size_t) {
    TResolvedSymbol ret = {
        "??",
        sym,
    };

    return ret;
}
#endif

void FormatBackTrace(IOutputStream* out, void* const* backtrace, size_t backtraceSize) {
    char tmpBuf[1024];

    for (size_t i = 0; i < backtraceSize; ++i) {
        TResolvedSymbol rs = ResolveSymbol(backtrace[i], tmpBuf, sizeof(tmpBuf));

        *out << rs.Name << "+" << ((ptrdiff_t)backtrace[i] - (ptrdiff_t)rs.NearestSymbol) << " (" << Hex((ptrdiff_t)backtrace[i], HF_ADDX) << ')' << '\n';
    }
}

TFormatBackTraceFn FormatBackTraceFn = FormatBackTrace;

TFormatBackTraceFn SetFormatBackTraceFn(TFormatBackTraceFn f) {
    TFormatBackTraceFn prevFn = FormatBackTraceFn;
    FormatBackTraceFn = f;
    return prevFn;
}

void FormatBackTrace(IOutputStream* out) {
    void* array[300];
    const size_t s = BackTrace(array, Y_ARRAY_SIZE(array));
    FormatBackTraceFn(out, array, s);
}

TFormatBackTraceFn GetFormatBackTraceFn() {
    return FormatBackTraceFn;
}

void PrintBackTrace() {
    FormatBackTrace(&Cerr);
}

TBackTrace::TBackTrace()
    : Size(0)
{
}

void TBackTrace::Capture() {
    Size = BackTrace(Data, CAPACITY);
}

void TBackTrace::PrintTo(IOutputStream& out) const {
    FormatBackTraceFn(&out, Data, Size);
}

TString TBackTrace::PrintToString() const {
    TStringStream ss;
    PrintTo(ss);
    return ss.Str();
}

size_t TBackTrace::size() const {
    return Size;
}

const void* const* TBackTrace::data() const {
    return Data;
}

TBackTrace::operator TBackTraceView() const {
    return TBackTraceView(Data, Size);
}

TBackTrace TBackTrace::FromCurrentException() {
#ifdef _YNDX_LIBUNWIND_EXCEPTION_BACKTRACE_SIZE
    TBackTrace result;
    result.Size = __cxxabiv1::__cxa_collect_current_exception_backtrace(result.Data, CAPACITY);
    return result;
#else
    return TBackTrace();
#endif
}