// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "src/dawn/native/d3d/PlatformFunctions.h"

#include <comdef.h>
#include <versionhelpers.h>

#include <vector>

#include "src/dawn/common/SystemUtils.h"
#include "src/utils/compiler.h"

namespace dawn::native::d3d {

namespace {
// Referenced from base/win/registry.cc in Chromium
uint64_t ReadFromSZRegistryKey(HKEY registerKey, const char* registerKeyName) {
    DWORD valueType;
    DWORD returnSize;
    if (RegQueryValueExA(registerKey, registerKeyName, nullptr, &valueType, nullptr, &returnSize) !=
        ERROR_SUCCESS) {
        return 0;
    }
    std::vector<char> returnStringValue(returnSize);
    auto hr = RegQueryValueExA(registerKey, registerKeyName, nullptr, &valueType,
                               reinterpret_cast<LPBYTE>(returnStringValue.data()), &returnSize);
    if (hr != ERROR_SUCCESS || valueType != REG_SZ) {
        return 0;
    }
    constexpr int32_t kRadix = 10;
    return DAWN_UNSAFE_TODO(strtol(returnStringValue.data(), nullptr, kRadix));
}

}  // anonymous namespace

PlatformFunctions::PlatformFunctions() : mCurrentBuildNumber(0) {}

PlatformFunctions::~PlatformFunctions() = default;

MaybeError PlatformFunctions::Initialize() {
    DAWN_TRY(LoadDXGI());
    InitWindowsVersion();
    return {};
}

MaybeError PlatformFunctions::LoadDXGI() {
#if DAWN_PLATFORM_IS(WINUWP)
#if defined(_DEBUG)
    // DXGIGetDebugInterface1 is tagged as a development-only capability
    // which implies that linking to this function will cause
    // the application to fail Windows store certification
    // But we need it when debuging using VS Graphics Diagnostics or PIX
    // So we only link to it in debug build
    dxgiGetDebugInterface1 = &DXGIGetDebugInterface1;
#endif
    createDxgiFactory2 = &CreateDXGIFactory2;
#else
    std::string error;
    if (!mDXGILib.OpenSystemLibrary(L"dxgi.dll", &error) ||
        !mDXGILib.GetProc(&dxgiGetDebugInterface1, "DXGIGetDebugInterface1", &error) ||
        !mDXGILib.GetProc(&createDxgiFactory2, "CreateDXGIFactory2", &error)) {
        return DAWN_INTERNAL_ERROR(error.c_str());
    }
#endif

    return {};
}

MaybeError PlatformFunctions::EnsureFXC(std::span<const std::string> searchPaths) {
    if (mFXCompilerLib.Valid()) {
        // The library is already loaded, no need to load it again.
        return {};
    }

#if DAWN_PLATFORM_IS(WINUWP)
    d3dCompile = &D3DCompile;
    d3dDisassemble = &D3DDisassemble;
#else
    std::string error;
#if defined(DAWN_FORCE_SYSTEM_COMPONENT_LOAD)
    // https://crbug.com/399358291. Since Skia's build system is not able to locate the Windows
    // SDK tools, d3dcompiler_47.dll is not copied into the build location. Tests fail when we
    // prepend the module directory to the path. To address this, we fallback to the compiler
    // that comes with the OS. The OS compiler contains functionality and heap corruption bugs
    // on older Windows versions so care must be taken to always ship with the latest SDK
    // compiler.
    const bool loadSuccess = mFXCompilerLib.OpenSystemLibrary(L"d3dcompiler_47.dll", &error);
#else
    const bool loadSuccess = mFXCompilerLib.Open("d3dcompiler_47.dll", searchPaths, &error);
#endif
    if (!loadSuccess || !mFXCompilerLib.GetProc(&d3dCompile, "D3DCompile", &error) ||
        !mFXCompilerLib.GetProc(&d3dDisassemble, "D3DDisassemble", &error)) {
        return DAWN_INTERNAL_ERROR(error.c_str());
    }
#endif
    return {};
}

void PlatformFunctions::InitWindowsVersion() {
    // Currently we only care about the build number of Windows 10 and Windows 11.
    if (!IsWindows10OrGreater()) {
        return;
    }

    // Referenced from base/win/windows_version.cc in Chromium
    constexpr wchar_t kRegKeyWindowsNTCurrentVersion[] =
        L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";

    HKEY hKey;
    if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, kRegKeyWindowsNTCurrentVersion, 0, KEY_QUERY_VALUE,
                      &hKey) != ERROR_SUCCESS) {
        return;
    }

    mCurrentBuildNumber = ReadFromSZRegistryKey(hKey, "CurrentBuildNumber");

    RegCloseKey(hKey);
}

uint64_t PlatformFunctions::GetWindowsBuildNumber() const {
    return mCurrentBuildNumber;
}

}  // namespace dawn::native::d3d
