#include "dynlib.h"

#include "guard.h"
#include "mutex.h"
#include <util/generic/singleton.h>
#include <util/generic/yexception.h>

#ifdef _win32_
#include "winint.h"

#define DLLOPEN(path, flags) LoadLibrary(path)
#define DLLCLOSE(hndl) FreeLibrary(hndl)
#define DLLSYM(hndl, name) GetProcAddress(hndl, name)
#else
#include <dlfcn.h>

#ifndef RTLD_GLOBAL
#define RTLD_GLOBAL (0)
#endif

using HINSTANCE = void*;

#define DLLOPEN(path, flags) dlopen(path, flags)
#define DLLCLOSE(hndl) dlclose(hndl)
#define DLLSYM(hndl, name) dlsym(hndl, name)
#endif

inline TString DLLERR() {
#ifdef _unix_
    return dlerror();
#endif

#ifdef _win32_
    char* msg = 0;
    DWORD cnt = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                              nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char*)&msg, 0, nullptr);
    if (!msg)
        return "DLLERR() unknown error";
    while (cnt && isspace(msg[cnt - 1]))
        --cnt;
    TString err(msg, 0, cnt);
    LocalFree(msg);
    return err;
#endif
}

class TDynamicLibrary::TImpl {
private:
    inline TImpl(const char* path, int flags)
        : Module(DLLOPEN(path, flags))
        , Unloadable(true)
    {
        (void)flags;

        if (!Module) {
            ythrow yexception() << DLLERR().data();
        }
    }

    class TCreateMutex: public TMutex {
    };

public:
    static inline TImpl* SafeCreate(const char* path, int flags) {
        auto guard = Guard(*Singleton<TCreateMutex>());

        return new TImpl(path, flags);
    }

    inline ~TImpl() {
        if (Module && Unloadable) {
            DLLCLOSE(Module);
        }
    }

    inline void* SymOptional(const char* name) noexcept {
        return (void*)DLLSYM(Module, name);
    }

    inline void* Sym(const char* name) {
        void* symbol = SymOptional(name);

        if (symbol == nullptr) {
            ythrow yexception() << DLLERR().data();
        }

        return symbol;
    }

    inline void SetUnloadable(bool unloadable) {
        Unloadable = unloadable;
    }

private:
    HINSTANCE Module;
    bool Unloadable;
};

TDynamicLibrary::TDynamicLibrary() noexcept {
}

TDynamicLibrary::TDynamicLibrary(const TString& path, int flags) {
    Open(path.data(), flags);
}

TDynamicLibrary::~TDynamicLibrary() = default;

void TDynamicLibrary::Open(const char* path, int flags) {
    Impl_.Reset(TImpl::SafeCreate(path, flags));
}

void TDynamicLibrary::Close() noexcept {
    Impl_.Destroy();
}

void* TDynamicLibrary::SymOptional(const char* name) noexcept {
    if (!IsLoaded()) {
        return nullptr;
    }

    return Impl_->SymOptional(name);
}

void* TDynamicLibrary::Sym(const char* name) {
    if (!IsLoaded()) {
        ythrow yexception() << "library not loaded";
    }

    return Impl_->Sym(name);
}

bool TDynamicLibrary::IsLoaded() const noexcept {
    return (bool)Impl_.Get();
}

void TDynamicLibrary::SetUnloadable(bool unloadable) {
    Impl_->SetUnloadable(unloadable);
}
