// Copyright (c) 2013-2019 Intel Corporation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#include "mfx_plugin_hive.h"
#include "mfx_library_iterator.h"
#include "mfx_dispatcher.h"
#include "mfx_dispatcher_log.h"
#include "mfx_load_dll.h"

#define TRACE_HIVE_ERROR(str, ...) DISPATCHER_LOG_ERROR((("[HIVE]: " str), __VA_ARGS__))
#define TRACE_HIVE_INFO(str, ...) DISPATCHER_LOG_INFO((("[HIVE]: " str), __VA_ARGS__))
#define TRACE_HIVE_WRN(str, ...) DISPATCHER_LOG_WRN((("[HIVE]: " str), __VA_ARGS__))

namespace
{
    const wchar_t rootPluginPath[] = L"Software\\Intel\\MediaSDK\\Plugin";
    const wchar_t rootDispatchPath[] = L"Software\\Intel\\MediaSDK\\Dispatch";
    const wchar_t pluginSubkey[] = L"Plugin";
    const wchar_t TypeKeyName[] = L"Type";
    const wchar_t CodecIDKeyName[] = L"CodecID";
    const wchar_t GUIDKeyName[] = L"GUID";
    const wchar_t PathKeyName[] = L"Path";
    const wchar_t DefaultKeyName[] = L"Default";
    const wchar_t PlgVerKeyName[] = L"PluginVersion";
    const wchar_t APIVerKeyName[] = L"APIVersion";
}

namespace
{
#ifdef _WIN64
    const wchar_t pluginFileName[] = L"FileName64";
#else
    const wchar_t pluginFileName[] = L"FileName32";
#endif // _WIN64

    //do not allow store plugin in different hierarchy
    const wchar_t pluginFileNameRestrictedCharacters[] = L"\\/";
    const wchar_t pluginCfgFileName[] = L"plugin.cfg";
    const wchar_t pluginSearchPattern[] = L"????????????????????????????????";
    const mfxU32 pluginCfgFileNameLen = 10;
    const mfxU32 pluginDirNameLen = 32;
    const mfxU32 defaultPluginNameLen = 25;
    const mfxU32 charsPermfxU8 = 2;
    const mfxU32 slashLen = 1;
    enum
    {
        MAX_PLUGIN_FILE_LINE = 4096
    };

    #define alignStr() "%-14S"
}

#if !defined(MEDIASDK_UWP_DISPATCHER)

MFX::MFXPluginsInHive::MFXPluginsInHive(int mfxStorageID, const wchar_t *msdkLibSubKey, mfxVersion currentAPIVersion)
    : MFXPluginStorageBase(currentAPIVersion)
{
    HKEY rootHKey;
    bool bRes;
    WinRegKey regKey;

    if (MFX_LOCAL_MACHINE_KEY != mfxStorageID && MFX_CURRENT_USER_KEY != mfxStorageID)
        return;

    // open required registry key
    rootHKey = (MFX_LOCAL_MACHINE_KEY == mfxStorageID) ? (HKEY_LOCAL_MACHINE) : (HKEY_CURRENT_USER);
    if (msdkLibSubKey) {
        //dispatch/subkey/plugin
        bRes = regKey.Open(rootHKey, rootDispatchPath, KEY_READ);
        if (bRes)
        {
            bRes = regKey.Open(regKey, msdkLibSubKey, KEY_READ);
        }
        if (bRes)
        {
            bRes = regKey.Open(regKey, pluginSubkey, KEY_READ);
        }
    }
    else
    {
        bRes = regKey.Open(rootHKey, rootPluginPath, KEY_READ);
    }

    if (false == bRes) {
        return;
    }
    DWORD index = 0;
    if (!regKey.QueryInfo(&index)) {
        return;
    }
    try
    {
        resize(index);
    }
    catch (...) {
        TRACE_HIVE_ERROR("new PluginDescriptionRecord[%d] threw an exception: \n", index);
        return;
    }

    for(index = 0; ; index++)
    {
        wchar_t   subKeyName[MFX_MAX_REGISTRY_KEY_NAME];
        DWORD     subKeyNameSize = sizeof(subKeyName) / sizeof(subKeyName[0]);
        WinRegKey subKey;

        // query next value name
        bool enumRes = regKey.EnumKey(index, subKeyName, &subKeyNameSize);
        if (!enumRes) {
            break;
        }

        // open the sub key
        bRes = subKey.Open(regKey, subKeyName, KEY_READ);
        if (!bRes) {
            continue;
        }

        if (msdkLibSubKey)
        {
            TRACE_HIVE_INFO("Found Plugin: %s\\%S\\%S\\%S\\%S\n", (MFX_LOCAL_MACHINE_KEY == mfxStorageID) ? ("HKEY_LOCAL_MACHINE") : ("HKEY_CURRENT_USER"),
                rootDispatchPath, msdkLibSubKey, pluginSubkey, subKeyName);
        }
        else
        {
            TRACE_HIVE_INFO("Found Plugin: %s\\%S\\%S\n", (MFX_LOCAL_MACHINE_KEY == mfxStorageID) ? ("HKEY_LOCAL_MACHINE") : ("HKEY_CURRENT_USER"),
                rootPluginPath, subKeyName);
        }

        PluginDescriptionRecord descriptionRecord;

        if (!QueryKey(subKey, TypeKeyName, descriptionRecord.Type))
        {
            continue;
        }
        TRACE_HIVE_INFO(alignStr()" : %d\n", TypeKeyName, descriptionRecord.Type);

        if (QueryKey(subKey, CodecIDKeyName, descriptionRecord.CodecId))
        {
            TRACE_HIVE_INFO(alignStr()" : " MFXFOURCCTYPE()" \n", CodecIDKeyName, MFXU32TOFOURCC(descriptionRecord.CodecId));
        }
        else
        {
                TRACE_HIVE_INFO(alignStr()" : \n", CodecIDKeyName, "NOT REGISTERED");
        }

        if (!QueryKey(subKey, GUIDKeyName, descriptionRecord.PluginUID))
        {
            continue;
        }
        TRACE_HIVE_INFO(alignStr()" : " MFXGUIDTYPE()"\n", GUIDKeyName, MFXGUIDTOHEX(&descriptionRecord.PluginUID));

        mfxU32 nSize = sizeof(descriptionRecord.sPath)/sizeof(*descriptionRecord.sPath);
        if (!subKey.Query(PathKeyName, descriptionRecord.sPath, nSize))
        {
            TRACE_HIVE_WRN("no value for : %S\n", PathKeyName);
            continue;
        }
        TRACE_HIVE_INFO(alignStr()" : %S\n", PathKeyName, descriptionRecord.sPath);

        if (!QueryKey(subKey, DefaultKeyName, descriptionRecord.Default))
        {
            continue;
        }
        TRACE_HIVE_INFO(alignStr()" : %s\n", DefaultKeyName, descriptionRecord.Default ? "true" : "false");

        mfxU32 version = 0;
        if (!QueryKey(subKey, PlgVerKeyName, version))
        {
            continue;
        }
        descriptionRecord.PluginVersion = static_cast<mfxU16>(version);
        if (0 == version)
        {
            TRACE_HIVE_ERROR(alignStr()" : %d, which is invalid\n", PlgVerKeyName, descriptionRecord.PluginVersion);
            continue;
        }
        else
        {
            TRACE_HIVE_INFO(alignStr()" : %d\n", PlgVerKeyName, descriptionRecord.PluginVersion);
        }

        mfxU32 APIVersion = 0;
        if (!QueryKey(subKey, APIVerKeyName, APIVersion))
        {
            continue;
        }
        ConvertAPIVersion(APIVersion, descriptionRecord);
        TRACE_HIVE_INFO(alignStr()" : %d.%d \n", APIVerKeyName, descriptionRecord.APIVersion.Major, descriptionRecord.APIVersion.Minor);

        try
        {
            operator[](index) = descriptionRecord;
        }
        catch (...) {
            TRACE_HIVE_ERROR("operator[](%d) = descriptionRecord; - threw exception \n", index);
        }
    }
}

MFX::MFXPluginsInFS::MFXPluginsInFS( mfxVersion currentAPIVersion )
    : MFXPluginStorageBase(currentAPIVersion)
    , mIsVersionParsed()
    , mIsAPIVersionParsed()
{
    WIN32_FIND_DATAW find_data;
    wchar_t currentModuleName[MAX_PLUGIN_PATH];

    GetModuleFileNameW(NULL, currentModuleName, MAX_PLUGIN_PATH);
    if (GetLastError() != 0)
    {
        TRACE_HIVE_ERROR("GetModuleFileName() reported an error: %d\n", GetLastError());
        return;
    }
    wchar_t *lastSlashPos = wcsrchr(currentModuleName, L'\\');
    if (!lastSlashPos) {
        lastSlashPos = currentModuleName;
    }
    mfxU32 executableDirLen = (mfxU32)(lastSlashPos - currentModuleName) + slashLen;
    if (executableDirLen + pluginDirNameLen + pluginCfgFileNameLen >= MAX_PLUGIN_PATH)
    {
        TRACE_HIVE_ERROR("MAX_PLUGIN_PATH which is %d, not enough to locate plugin path\n", MAX_PLUGIN_PATH);
        return;
    }
    wcscpy_s(lastSlashPos + slashLen
        , MAX_PLUGIN_PATH - executableDirLen, pluginSearchPattern);

    HANDLE fileFirst = FindFirstFileW(currentModuleName, &find_data);
    if (INVALID_HANDLE_VALUE == fileFirst)
    {
        TRACE_HIVE_ERROR("FindFirstFileW() unable to locate any plugins folders\n", 0);
        return;
    }
    do
    {
        if (!(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
        {
            continue;
        }
        if (pluginDirNameLen != wcslen(find_data.cFileName))
        {
            continue;
        }
        //converting dirname into guid
        PluginDescriptionRecord descriptionRecord;
        descriptionRecord.APIVersion = currentAPIVersion;
        descriptionRecord.onlyVersionRegistered = true;

        mfxU32 i = 0;
        for(i = 0; i != pluginDirNameLen / charsPermfxU8; i++)
        {
            mfxU32 hexNum = 0;
            if (1 != swscanf_s(find_data.cFileName + charsPermfxU8 * i, L"%2x", &hexNum))
            {
                // it is ok to have non-plugin subdirs with length 32
                //TRACE_HIVE_INFO("folder name \"%S\" is not a valid GUID string\n", find_data.cFileName);
                break;
            }
            if (hexNum == 0 && find_data.cFileName + charsPermfxU8 * i != wcsstr(find_data.cFileName + 2*i, L"00"))
            {
                // it is ok to have non-plugin subdirs with length 32
                //TRACE_HIVE_INFO("folder name \"%S\" is not a valid GUID string\n", find_data.cFileName);
                break;
            }
            descriptionRecord.PluginUID.Data[i] = (mfxU8)hexNum;
        }
        if (i != pluginDirNameLen / charsPermfxU8) {
            continue;
        }

        wcscpy_s(currentModuleName + executableDirLen
            , MAX_PLUGIN_PATH - executableDirLen, find_data.cFileName);

        wcscpy_s(currentModuleName + executableDirLen + pluginDirNameLen
            , MAX_PLUGIN_PATH - executableDirLen - pluginDirNameLen, L"\\");

        //this is path to plugin directory
        wcscpy_s(descriptionRecord.sPath
            , sizeof(descriptionRecord.sPath) / sizeof(*descriptionRecord.sPath), currentModuleName);

        wcscpy_s(currentModuleName + executableDirLen + pluginDirNameLen + slashLen
            , MAX_PLUGIN_PATH - executableDirLen - pluginDirNameLen - slashLen, pluginCfgFileName);

        FILE *pluginCfgFile = 0;
        _wfopen_s(&pluginCfgFile, currentModuleName, L"r");
        if (!pluginCfgFile)
        {
            TRACE_HIVE_INFO("in directory \"%S\" no mandatory \"%S\"\n"
                , find_data.cFileName, pluginCfgFileName);
            continue;
        }

        if (ParseFile(pluginCfgFile, descriptionRecord))
        {
            try
            {
                push_back(descriptionRecord);
            }
            catch (...) {
                TRACE_HIVE_ERROR("mRecords.push_back(descriptionRecord); - threw exception \n", 0);
            }
        }

        fclose(pluginCfgFile);
    }while (FindNextFileW(fileFirst, &find_data));
    FindClose(fileFirst);
}

bool MFX::MFXPluginsInFS::ParseFile(FILE * f, PluginDescriptionRecord & descriptionRecord)
{
    wchar_t line[MAX_PLUGIN_FILE_LINE];

    while(NULL != fgetws(line, sizeof(line) / sizeof(*line), f))
    {
        wchar_t *delimiter = wcschr(line, L'=');
        if (0 == delimiter)
        {
            TRACE_HIVE_INFO("plugin.cfg contains line \"%S\" which is not in K=V format, skipping \n", line);
            continue;
        }
        *delimiter = 0;
        if (!ParseKVPair(line, delimiter + 1, descriptionRecord))
        {
            return false;
        }
    }

    if (!mIsVersionParsed)
    {
        TRACE_HIVE_ERROR("%S : Mandatory  key %S not found\n", pluginCfgFileName, PlgVerKeyName);
        return false;
    }

    if (!mIsAPIVersionParsed)
    {
        TRACE_HIVE_ERROR("%S : Mandatory  key %S not found\n", pluginCfgFileName, APIVerKeyName);
        return false;
    }

    if (!wcslen(descriptionRecord.sPath))
    {
        TRACE_HIVE_ERROR("%S : Mandatory  key %S not found\n", pluginCfgFileName, pluginFileName);
        return false;
    }

    return true;
}

bool MFX::MFXPluginsInFS::ParseKVPair( wchar_t * key, wchar_t* value, PluginDescriptionRecord & descriptionRecord)
{
    if (0 != wcsstr(key, PlgVerKeyName))
    {
        mfxU32 version ;
        if (0 == swscanf_s(value, L"%d", &version))
        {
            return false;
        }
        descriptionRecord.PluginVersion = (mfxU16)version;

        if (0 == descriptionRecord.PluginVersion)
        {
            TRACE_HIVE_ERROR("%S: %S = %d,  which is invalid\n", pluginCfgFileName, PlgVerKeyName, descriptionRecord.PluginVersion);
            return false;
        }

        TRACE_HIVE_INFO("%S: %S = %d \n", pluginCfgFileName, PlgVerKeyName, descriptionRecord.PluginVersion);
        mIsVersionParsed = true;
        return true;
    }

    if (0 != wcsstr(key, APIVerKeyName))
    {
        mfxU32 APIversion;
        if (0 == swscanf_s(value, L"%d", &APIversion))
        {
            return false;
        }

        ConvertAPIVersion(APIversion, descriptionRecord);
        TRACE_HIVE_INFO("%S: %S = %d.%d \n", pluginCfgFileName, APIVerKeyName, descriptionRecord.APIVersion.Major, descriptionRecord.APIVersion.Minor);

        mIsAPIVersionParsed = true;
        return true;
    }


    if (0!=wcsstr(key, pluginFileName))
    {
        wchar_t *startQuoteMark = wcschr(value, L'\"');
        if (!startQuoteMark)
        {
            TRACE_HIVE_ERROR("plugin filename not in quotes : %S\n", value);
            return false;
        }
        wchar_t *endQuoteMark = wcschr(startQuoteMark + 1, L'\"');

        if (!endQuoteMark)
        {
            TRACE_HIVE_ERROR("plugin filename not in quotes : %S\n", value);
            return false;
        }
        *endQuoteMark = 0;

        mfxU32 currentPathLen = (mfxU32)wcslen(descriptionRecord.sPath);
        if (currentPathLen + wcslen(startQuoteMark + 1) > sizeof(descriptionRecord.sPath) / sizeof(*descriptionRecord.sPath))
        {
            TRACE_HIVE_ERROR("buffer of MAX_PLUGIN_PATH characters which is %d, not enough lo store plugin path: %S%S\n"
                , MAX_PLUGIN_PATH, descriptionRecord.sPath, startQuoteMark + 1);
            return false;
        }

        size_t restrictedCharIdx = wcscspn(startQuoteMark + 1, pluginFileNameRestrictedCharacters);
        if (restrictedCharIdx != wcslen(startQuoteMark + 1))
        {
            TRACE_HIVE_ERROR("plugin filename :%S, contains one of restricted characters: %S\n", startQuoteMark + 1, pluginFileNameRestrictedCharacters);
            return false;
        }

        wcscpy_s(descriptionRecord.sPath + currentPathLen
            , sizeof(descriptionRecord.sPath) / sizeof(*descriptionRecord.sPath) - currentPathLen, startQuoteMark + 1);

        TRACE_HIVE_INFO("%S: %S = \"%S\" \n", pluginCfgFileName, pluginFileName, startQuoteMark + 1);

        return true;
    }


    return true;
}

#endif //#if !defined(MEDIASDK_UWP_DISPATCHER)

MFX::MFXDefaultPlugins::MFXDefaultPlugins(mfxVersion currentAPIVersion, MFX_DISP_HANDLE * hdl, int implType)
    : MFXPluginStorageBase(currentAPIVersion)
{
    wchar_t libModuleName[MAX_PLUGIN_PATH];

    GetModuleFileNameW((HMODULE)hdl->hModule, libModuleName, MAX_PLUGIN_PATH);
    if (GetLastError() != 0)
    {
        TRACE_HIVE_ERROR("GetModuleFileName() reported an error: %d\n", GetLastError());
        return;
    }
    wchar_t *lastSlashPos = wcsrchr(libModuleName, L'\\');
    if (!lastSlashPos) {
        lastSlashPos = libModuleName;
    }
    mfxU32 executableDirLen = (mfxU32)(lastSlashPos - libModuleName) + slashLen;
    if (executableDirLen + defaultPluginNameLen >= MAX_PLUGIN_PATH)
    {
        TRACE_HIVE_ERROR("MAX_PLUGIN_PATH which is %d, not enough to locate default plugin path\n", MAX_PLUGIN_PATH);
        return;
    }

    mfx_get_default_plugin_name(lastSlashPos + slashLen, MAX_PLUGIN_PATH - executableDirLen, (eMfxImplType)implType);

    if (-1 != GetFileAttributesW(libModuleName))
    {
        // add single default plugin description
        PluginDescriptionRecord descriptionRecord;
        descriptionRecord.APIVersion = currentAPIVersion;
        descriptionRecord.Default = true;

        wcscpy_s(descriptionRecord.sPath
            , sizeof(descriptionRecord.sPath) / sizeof(*descriptionRecord.sPath), libModuleName);

        push_back(descriptionRecord);
    }
    else
    {
        TRACE_HIVE_INFO("GetFileAttributesW() unable to locate default plugin dll named %S\n", libModuleName);
    }
}


