/*
 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#ifndef __DLL_H_INCLUDED_
#define __DLL_H_INCLUDED_

#ifdef _WIN32
#include <windows.h>
#else
typedef void* HMODULE;
#endif

#include <memory>
#include "tstrings.h"
#include "ErrorHandling.h"


//
// Helper classes to dynamically load DLLs and call the libraries functions.
//

/**
 * Library loader.
 * Usage:
 * - load a library specified by full path:
 *      DLL deployLib(FileUtils::combinePath(javaHome, _T("bin\\deploy.dll"));
 *
 *  Note: library should be specified by full path (due security reasons)
 *
 * - load system library (DLLs from Windows/System32 (SysWow64) directory):
 *      DLL kernel32Lib("kernel32", Dll::System());
 */
class Dll {
public:
    struct System {};

    explicit Dll(const tstrings::any &libPath);
    explicit Dll(const tstrings::any &libName, const System &tag);

    Dll(const Dll& other);

    template <class T>
    void getFunction(const tstrings::any &name, T& addr) const {
        addr = reinterpret_cast<T>(getFunction(name.str(), true));
    }

    // returns false & sets addr to NULL if the function not found
    template <class T>
    bool getFunction(const tstrings::any &name, T& addr, const std::nothrow_t &) const {
        addr = reinterpret_cast<T>(getFunction(name.str(), false));
        return addr != NULL;
    }

    const tstring& path() const {
        return thePath;
    }

    HMODULE getHandle() const {
        return handle.get();
    }

    static void freeLibrary(HMODULE h);

    struct LibraryReleaser {
        typedef HMODULE pointer;

        void operator()(HMODULE h) {
            freeLibrary(h);
        }
    };

    typedef std::unique_ptr<HMODULE, LibraryReleaser> Handle;

private:
    void* getFunction(const std::string &name, bool throwIfNotFound) const;

    tstring thePath;
    Handle handle;
};


/**
 * DllFunction template class helps to check is a library function available and call it.
 * Usage example:
 *      // RegDeleteKeyExW function (from advapi32.dll) is available on Vista+ or on WinXP 64bit
 *      // so to avoid dependency on the OS version we have to check if it's available at runtime
 *
 *      // the function definition
 *      typedef LONG (WINAPI *RegDeleteKeyExWFunc)(HKEY hKey, const wchar_t* lpSubKey, REGSAM samDesired, DWORD Reserved);
 *
 *      DllFunction<RegDeleteKeyExWFunc> _RegDeleteKeyExW(Dll("advapi32", Dll::System()), "RegDeleteKeyExW");
 *      if (_RegDeleteKeyExW.available()) {
 *          // the function is available, call it
 *          LONG result = _RegDeleteKeyExW(hKey, subkeyName, samDesired, 0);
 *      } else {
 *          // the function is not available, handle this
 *          throw std::exception("RegDeleteKeyExW function is not available");
 *      }
 *
 *      // or we can just try to call the function.
 *      // if the function is not available, exception with the corresponding description is thrown
 *      DllFunction<RegDeleteKeyExWFunc> _RegDeleteKeyExW(Dll("advapi32", Dll::System()), "RegDeleteKeyExW");
 *      LONG result = _RegDeleteKeyExW(hKey, subkeyName, samDesired, 0);
 */
template<class funcType>
class DllFunction {
public:
    DllFunction(const Dll& library, const tstrings::any &funcName)
            : lib(library), theName(funcName.str()) {
        lib.getFunction(funcName, funcPtr);
    }

    DllFunction(const std::nothrow_t&, const Dll& library,
                                                const tstrings::any &funcName)
            : lib(library), theName(funcName.str()) {
        lib.getFunction(funcName, funcPtr, std::nothrow);
    }

    bool available() const {
        return funcPtr != NULL;
    }

    std::string name() const {
        return theName;
    }

    const tstring& libPath() const {
        return lib.path();
    }

    operator funcType() const {
        if (!available()) {
            JP_THROW(tstrings::any()    << theName
                                        << "() function is not available in "
                                        << lib.path());
        }
        return funcPtr;
    }

private:
    const Dll lib;
    funcType funcPtr;
    std::string theName;
};

#endif // __DLL_H_INCLUDED_
