/*
 * xsi_shared_library.h
 * Copyright (c) 2012, Xilinx, Inc.  All Rights Reserved.
 * Class for loading shared libraries generated by xelab
 */

#ifndef _XSI_SHARED_LIB_H
#define _XSI_SHARED_LIB_H

#if defined(_WIN32)
#  include <windows.h>
#else
#  include <dlfcn.h>
#endif

#include <string>
#include <stdexcept>

namespace Xsi {

class SharedLibrary {
public:
#if defined(_WIN32)
    typedef HINSTANCE handle_type;
#else
    typedef void * handle_type;
#endif
    typedef void * symbol_type;

    SharedLibrary() : _lib(0), _retain(false) { }
    ~SharedLibrary() { unload(); }
    operator bool() const { return (_lib != 0); }
    bool loaded() const { return (_lib != 0); }
    handle_type handle() const { return _lib; }
    const std::string& path() const { return _path; }
    const std::string& error() const { return _err; }
    bool load(const std::string& path)
    {
        unload();
        // reset the retain flag
        _retain = false;

        if (path.empty()) {
            _err = "Failed to load shared library. "
                   "Path of the shared library is not specified.";
            return false;
        }
        std::string msg;
        bool ok = load_impl(path, msg);
        if (ok) {
            _path = path;
            _err.clear();
        }
        else {
            _err = "Failed to load shared library \"" + path +
                "\". " + msg;
        }
        return ok;
    }

    bool load_impl(const std::string& path, std::string& errmsg)
    {
        bool ok = true;
#if defined(_WIN32) 
        SetLastError(0);
        _lib = LoadLibrary(path.c_str());
        if (_lib == 0) {
            translate_error_message(GetLastError(), errmsg);
            ok = false;
        }
#else
        _lib = dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL);
        char *err = dlerror();
        if (err != NULL) {
            errmsg = err;
            ok = false;
        }
#endif
        return ok;
    }

    void unload()
    {
        if (_lib) {
            if (!_retain) {
#if defined(_WIN32)
                FreeLibrary(_lib);
#else
                dlclose(_lib);
#endif
            }
            _lib = 0;
        }
        _err.clear();
    }

    void retain()
    {
        _retain = true;
    }

    bool getsymbol(const std::string& name, symbol_type& sym)
    {
        std::string msg;
        bool ok = true;

        if (_lib == 0) {
            msg = "The shared library is not loaded.";
            ok = false;
        }
        else {

#if defined(_WIN32)
            sym = (void*) GetProcAddress(_lib, name.c_str());
            if (sym == NULL) {
                translate_error_message(GetLastError(), msg);
                ok = false;
            }
#else
            dlerror(); // clear error
            sym = (void *) dlsym(_lib, name.c_str());
            char *err = dlerror();
            if (err != NULL) {
                msg = err;
                ok = false;
            }
#endif
        }

        if (ok) {
            _err.clear();
        }
        else {
            _err = "Failed to obtain symbol \"" + name +
                "\" from shared library. " + msg;
        }

        return ok;
    }

    symbol_type getfunction(const std::string& name)
    {
        symbol_type sym = NULL;
        return getsymbol(name, sym) ? sym : NULL;
    }

private:
    // shared library is non-copyable
    SharedLibrary(const SharedLibrary&);
    const SharedLibrary& operator=(const SharedLibrary&);

#if defined(_WIN32)
    static void translate_error_message(DWORD errid, std::string& msg)
    {
        LPVOID bufptr;
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                      FORMAT_MESSAGE_FROM_SYSTEM |
                      FORMAT_MESSAGE_IGNORE_INSERTS,
                      NULL,
                      errid,
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                      (LPTSTR) &bufptr,
                      0,
                      NULL);
        msg.clear();
        if (bufptr) {
            msg = reinterpret_cast<char *>(bufptr);
        }
        LocalFree(bufptr);
    }

    static const std::string& library_suffix()
    {
        static const std::string s = ".dll";
        return s;
    }
#else
    static const std::string& library_suffix()
    {
        static const std::string s = ".so";
        return s;
    }
#endif

    handle_type _lib;
    std::string _path;
    std::string _err;
    bool _retain;
};

} // namespace Xsi

#endif // _XSI_SHARED_LIB_H


