#include <leatherman/dynamic_library/dynamic_library.hpp>
#include <leatherman/logging/logging.hpp>
#include <dlfcn.h>

using namespace std;
namespace lth_locale = leatherman::locale;

namespace leatherman { namespace dynamic_library {

    dynamic_library dynamic_library::find_by_pattern(std::string const& pattern)
    {
        // POSIX doesn't have this capability
        return dynamic_library();
    }

    dynamic_library dynamic_library::find_by_symbol(std::string const& symbol)
    {
        dynamic_library library;

        // Load the "null" library; this will cause dlsym to search for the symbol
        void* handle = dlopen(nullptr, RTLD_GLOBAL | RTLD_LAZY);
        if (!handle) {
            return library;
        }

        // Check to see if a search for the symbol succeeds
        if (!dlsym(handle, symbol.c_str())) {
            dlclose(handle);
            return library;
        }

        // At least one loaded module will resolve the given symbol
        // Return this handle to allow the caller to search for other symbols
        library._handle = handle;
        library._first_load = false;
        return library;
    }

    bool dynamic_library::load(string const& name, bool global)
    {
        close();

        auto load_mode = (global ? RTLD_GLOBAL : RTLD_LOCAL) | RTLD_LAZY;

        _handle = dlopen(name.c_str(), load_mode);
        if (!_handle) {
            LOG_DEBUG("library {1} not found {2} ({3}).", name.c_str(), strerror(errno), errno);
            return false;
        }
        _first_load = true;
        _name = name;
        return true;
    }

    void dynamic_library::close()
    {
        if (_handle) {
            dlclose(_handle);
            _handle = nullptr;
        }
        _name.clear();
        _first_load = false;
    }

    void* dynamic_library::find_symbol(string const& name, bool throw_if_missing, string const& alias) const
    {
        if (!_handle) {
            if (throw_if_missing) {
                throw missing_import_exception("library is not loaded.");
            } else {
                LOG_DEBUG("library {1} is not loaded when attempting to load symbol {2}.", _name.c_str(), name.c_str());
            }
            return nullptr;
        }
        void* symbol = dlsym(_handle, name.c_str());
        if (!symbol && !alias.empty()) {
            LOG_DEBUG("symbol {1} not found in library {2}, trying alias {3}.", name.c_str(), _name.c_str(), alias.c_str());
            symbol = dlsym(_handle, alias.c_str());
        }
        if (!symbol) {
            if (throw_if_missing) {
                throw missing_import_exception(lth_locale::format("symbol {1} was not found in {2}.", name, _name));
            } else {
                LOG_DEBUG("symbol {1} not found in library {2}.", name.c_str(), _name.c_str());
            }
        }
        return symbol;
    }

}}  // namespace leatherman::dynamic_library

