/*
 * Copyright (C) 2018-2024 Intel Corporation
 *
 * SPDX-License-Identifier: MIT
 *
 */

#include "shared/source/os_interface/windows/sys_calls.h"

#include "shared/source/os_interface/windows/os_inc.h"
#include "shared/test/common/os_interface/windows/mock_sys_calls.h"

#include <cstdint>
#include <unordered_map>
#include <vector>

namespace NEO {

namespace SysCalls {

unsigned int getProcessId() {
    return 0xABCEDF;
}

unsigned int getCurrentProcessId() {
    return 0xABCEDF;
}

unsigned long getNumThreads() {
    return 1;
}

DWORD getLastErrorResult = 0u;

BOOL systemPowerStatusRetVal = 1;
BYTE systemPowerStatusACLineStatusOverride = 1;
const wchar_t *currentLibraryPath = L"";
uint32_t regOpenKeySuccessCount = 0u;
uint32_t regQueryValueSuccessCount = 0u;
uint64_t regQueryValueExpectedData = 0ull;
const HKEY validHkey = reinterpret_cast<HKEY>(0);
bool getNumThreadsCalled = false;
bool mmapAllowExtendedPointers = false;
const char *driverStorePath = nullptr;

size_t closeHandleCalled = 0u;

size_t getTempFileNameACalled = 0u;
UINT getTempFileNameAResult = 0u;

size_t lockFileExCalled = 0u;
BOOL lockFileExResult = TRUE;

size_t unlockFileExCalled = 0u;
BOOL unlockFileExResult = TRUE;

size_t createDirectoryACalled = 0u;
BOOL createDirectoryAResult = TRUE;

size_t createFileACalled = 0u;
extern const size_t createFileAResultsCount = 4;
HANDLE createFileAResults[createFileAResultsCount] = {nullptr, nullptr, nullptr, nullptr};

size_t deleteFileACalled = 0u;
const size_t deleteFilesCount = 4;
std::string deleteFiles[deleteFilesCount];

HRESULT shGetKnownFolderPathResult = 0;
extern const size_t shGetKnownFolderSetPathSize = 50;
wchar_t shGetKnownFolderSetPath[shGetKnownFolderSetPathSize];

bool callBaseReadFile = true;
BOOL readFileResult = TRUE;
size_t readFileCalled = 0u;
size_t readFileBufferData = 0u;

size_t writeFileCalled = 0u;
BOOL writeFileResult = true;
extern const size_t writeFileBufferSize = 10;
char writeFileBuffer[writeFileBufferSize];
DWORD writeFileNumberOfBytesWritten = 0u;

HANDLE findFirstFileAResult = nullptr;

size_t findNextFileACalled = 0u;
extern const size_t findNextFileAFileDataCount = 4;
WIN32_FIND_DATAA findNextFileAFileData[findNextFileAFileDataCount];

size_t findCloseCalled = 0u;

size_t getFileAttributesCalled = 0u;
DWORD getFileAttributesResult = INVALID_FILE_ATTRIBUTES;
std::unordered_map<std::string, DWORD> pathAttributes;

size_t setFilePointerCalled = 0u;
DWORD setFilePointerResult = 0;

size_t setProcessPowerThrottlingStateCalled = 0u;
ProcessPowerThrottlingState setProcessPowerThrottlingStateLastValue{};

size_t setThreadPriorityCalled = 0u;
ThreadPriority setThreadPriorityLastValue{};

HANDLE(*sysCallsCreateFile)
(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) = nullptr;

BOOL(*sysCallsDeviceIoControl)
(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped) = nullptr;

CONFIGRET(*sysCallsCmGetDeviceInterfaceListSize)
(PULONG pulLen, LPGUID interfaceClassGuid, DEVINSTID_W pDeviceID, ULONG ulFlags) = nullptr;

CONFIGRET(*sysCallsCmGetDeviceInterfaceList)
(LPGUID interfaceClassGuid, DEVINSTID_W pDeviceID, PZZWSTR buffer, ULONG bufferLen, ULONG ulFlags) = nullptr;

LPVOID(*sysCallsHeapAlloc)
(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes) = nullptr;

BOOL(*sysCallsHeapFree)
(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem) = nullptr;

void exit(int code) {
}

DWORD getLastError() {
    return getLastErrorResult;
}

HANDLE createEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCSTR lpName) {
    if (mockCreateEventClb) {
        return mockCreateEventClb(lpEventAttributes, bManualReset, bInitialState, lpName, mockCreateEventClbData);
    }
    return reinterpret_cast<HANDLE>(dummyHandle);
}

BOOL closeHandle(HANDLE hObject) {
    closeHandleCalled++;
    if (mockCloseHandleClb) {
        return mockCloseHandleClb(hObject, mockCloseHandleClbData);
    }
    return (reinterpret_cast<HANDLE>(dummyHandle) == hObject) ? TRUE : FALSE;
}

BOOL getSystemPowerStatus(LPSYSTEM_POWER_STATUS systemPowerStatusPtr) {
    systemPowerStatusPtr->ACLineStatus = systemPowerStatusACLineStatusOverride;
    return systemPowerStatusRetVal;
}

BOOL getModuleHandle(DWORD dwFlags, LPCWSTR lpModuleName, HMODULE *phModule) {
    constexpr auto expectedFlags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
    if (dwFlags != expectedFlags) {
        return FALSE;
    }
    *phModule = handleValue;
    return TRUE;
}

DWORD getModuleFileName(HMODULE hModule, LPWSTR lpFilename, DWORD nSize) {
    lstrcpyW(lpFilename, currentLibraryPath);
    return TRUE;
}

UINT getTempFileNameA(LPCSTR lpPathName, LPCSTR lpPrefixString, UINT uUnique, LPSTR lpTempFileName) {
    getTempFileNameACalled++;
    return getTempFileNameAResult;
}

BOOL moveFileExA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, DWORD dwFlags) {
    return TRUE;
}

BOOL lockFileEx(HANDLE hFile, DWORD dwFlags, DWORD dwReserved, DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh, LPOVERLAPPED lpOverlapped) {
    lockFileExCalled++;
    return lockFileExResult;
}

BOOL unlockFileEx(HANDLE hFile, DWORD dwReserved, DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh, LPOVERLAPPED lpOverlapped) {
    unlockFileExCalled++;
    return unlockFileExResult;
}

BOOL getOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWORD lpNumberOfBytesTransferred, BOOL bWait) {
    return TRUE;
}

BOOL createDirectoryA(LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes) {
    createDirectoryACalled++;
    if (createDirectoryAResult) {
        pathAttributes[lpPathName] = FILE_ATTRIBUTE_DIRECTORY;
    }
    return createDirectoryAResult;
}

HANDLE createFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
    HANDLE retVal = nullptr;

    if (createFileACalled < createFileAResultsCount) {
        retVal = createFileAResults[createFileACalled];
    }

    createFileACalled++;
    return retVal;
}

BOOL deleteFileA(LPCSTR lpFileName) {
    if (deleteFileACalled < deleteFilesCount) {
        deleteFiles[deleteFileACalled] = std::string(lpFileName);
    }
    deleteFileACalled++;
    return TRUE;
}

HRESULT shGetKnownFolderPath(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPat) {
    *ppszPat = shGetKnownFolderSetPath;
    return shGetKnownFolderPathResult;
}

BOOL readFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped) {
    readFileCalled++;
    if (callBaseReadFile) {
        return ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);
    }
    if (lpBuffer) {
        *static_cast<size_t *>(lpBuffer) = readFileBufferData;
    }
    return readFileResult;
}

BOOL writeFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped) {
    writeFileCalled++;
    memcpy_s(writeFileBuffer, writeFileBufferSize, lpBuffer, nNumberOfBytesToWrite);
    if (lpNumberOfBytesWritten) {
        *lpNumberOfBytesWritten = writeFileNumberOfBytesWritten;
    }
    return writeFileResult;
}

HANDLE findFirstFileA(LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData) {
    return findFirstFileAResult;
}

BOOL findNextFileA(HANDLE hFindFile, LPWIN32_FIND_DATAA lpFindFileData) {
    BOOL retVal = TRUE;

    if (findNextFileACalled < findNextFileAFileDataCount) {
        *lpFindFileData = findNextFileAFileData[findNextFileACalled];
    } else {
        retVal = FALSE;
    }
    findNextFileACalled++;

    return retVal;
}

BOOL findClose(HANDLE hFindFile) {
    findCloseCalled++;
    return TRUE;
}

DWORD getFileAttributesA(LPCSTR lpFileName) {
    getFileAttributesCalled++;

    std::string tempP1 = lpFileName;
    if (!tempP1.empty() && tempP1.back() == PATH_SEPARATOR) {
        tempP1.pop_back();
    }

    for (const auto &[path, attributes] : pathAttributes) {
        if (path.empty())
            continue;

        std::string tempP2 = path;
        if (tempP2.back() == PATH_SEPARATOR) {
            tempP2.pop_back();
        }

        if (tempP1 == tempP2) {
            return attributes;
        }
    }

    return getFileAttributesResult;
}

DWORD setFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod) {
    setFilePointerCalled++;
    return setFilePointerResult;
}

void coTaskMemFree(LPVOID pv) {
    return;
}

void setProcessPowerThrottlingState(ProcessPowerThrottlingState state) {
    setProcessPowerThrottlingStateCalled++;
    setProcessPowerThrottlingStateLastValue = state;
}

void setThreadPriority(ThreadPriority priority) {
    setThreadPriorityCalled++;
    setThreadPriorityLastValue = priority;
}

HANDLE createFile(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
    if (sysCallsCreateFile != nullptr) {
        return sysCallsCreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
    }
    return nullptr;
}

BOOL deviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped) {
    if (sysCallsDeviceIoControl != nullptr) {
        return sysCallsDeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped);
    }
    return false;
}

CONFIGRET cmGetDeviceInterfaceListSize(PULONG pulLen, LPGUID interfaceClassGuid, DEVINSTID_W pDeviceID, ULONG ulFlags) {
    if (sysCallsCmGetDeviceInterfaceListSize != nullptr) {
        return sysCallsCmGetDeviceInterfaceListSize(pulLen, interfaceClassGuid, pDeviceID, ulFlags);
    }
    return -1;
}

CONFIGRET cmGetDeviceInterfaceList(LPGUID interfaceClassGuid, DEVINSTID_W pDeviceID, PZZWSTR buffer, ULONG bufferLen, ULONG ulFlags) {
    if (sysCallsCmGetDeviceInterfaceList != nullptr) {
        return sysCallsCmGetDeviceInterfaceList(interfaceClassGuid, pDeviceID, buffer, bufferLen, ulFlags);
    }
    return -1;
}

LPVOID heapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes) {
    if (sysCallsHeapAlloc != nullptr) {
        return sysCallsHeapAlloc(hHeap, dwFlags, dwBytes);
    }
    return HeapAlloc(hHeap, dwFlags, dwBytes);
}

BOOL heapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem) {
    if (sysCallsHeapFree != nullptr) {
        return sysCallsHeapFree(hHeap, dwFlags, lpMem);
    }
    return HeapFree(hHeap, dwFlags, lpMem);
}

LSTATUS regOpenKeyExA(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult) {
    if (regOpenKeySuccessCount > 0) {
        regOpenKeySuccessCount--;
        if (phkResult) {
            *phkResult = validHkey;
        }
        return ERROR_SUCCESS;
    }
    return ERROR_FILE_NOT_FOUND;
};

LSTATUS regQueryValueExA(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData) {
    if (hKey == validHkey && regQueryValueSuccessCount > 0) {
        regQueryValueSuccessCount--;

        if (lpcbData) {
            if (strcmp(lpValueName, "settingSourceString") == 0) {
                const auto settingSource = "registry";
                if (lpData) {
                    strcpy_s(reinterpret_cast<char *>(lpData), strlen(settingSource) + 1u, settingSource);
                } else {
                    *lpcbData = static_cast<DWORD>(strlen(settingSource) + 1u);
                    if (lpType) {
                        *lpType = REG_SZ;
                    }
                }
            } else if (strcmp(lpValueName, "settingSourceInt") == 0) {
                if (lpData) {
                    *reinterpret_cast<DWORD *>(lpData) = 1;
                } else {
                    *lpcbData = sizeof(DWORD);
                }
            } else if (strcmp(lpValueName, "settingSourceInt64") == 0) {
                if (lpData) {
                    *reinterpret_cast<INT64 *>(lpData) = 0xffffffffeeeeeeee;
                } else {
                    *lpcbData = sizeof(INT64);
                }
            } else if (strcmp(lpValueName, "settingSourceBinary") == 0) {
                const auto settingSource = L"registry";
                auto size = wcslen(settingSource) * sizeof(wchar_t);
                if (lpData) {
                    memcpy(reinterpret_cast<wchar_t *>(lpData), settingSource, size);
                } else {
                    *lpcbData = static_cast<DWORD>(size);
                    if (lpType) {
                        *lpType = REG_BINARY;
                    }
                }
            } else if (strcmp(lpValueName, "boolRegistryKey") == 0) {
                if (*lpcbData == sizeof(int64_t)) {
                    if (lpData) {
                        *reinterpret_cast<uint64_t *>(lpData) = regQueryValueExpectedData;
                    }
                }
            } else if (driverStorePath && (strcmp(lpValueName, "DriverStorePathForComputeRuntime") == 0)) {
                if (lpData) {
                    strcpy_s(reinterpret_cast<char *>(lpData), strlen(driverStorePath) + 1u, driverStorePath);
                }
                if (lpcbData) {
                    *lpcbData = static_cast<DWORD>(strlen(driverStorePath) + 1u);
                    if (lpType) {
                        *lpType = REG_SZ;
                    }
                }
            }
        }

        return ERROR_SUCCESS;
    }
    return ERROR_FILE_NOT_FOUND;
};

MEMORY_BASIC_INFORMATION virtualQueryMemoryBasicInformation = {};
SIZE_T virtualQuery(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength) {
    memcpy_s(lpBuffer, sizeof(MEMORY_BASIC_INFORMATION), &virtualQueryMemoryBasicInformation, sizeof(MEMORY_BASIC_INFORMATION));
    return sizeof(MEMORY_BASIC_INFORMATION);
}

BOOL(*sysCallsGetModuleHandleExW)
(DWORD dwFlags, LPCWSTR lpModuleName, HMODULE *phModule) = nullptr;
DWORD(*sysCallsGetModuleFileNameW)
(HMODULE hModule, LPWSTR lpFilename, DWORD nSize) = nullptr;
DWORD(*sysCallsGetFileVersionInfoSizeW)
(LPCWSTR lptstrFilename, LPDWORD lpdwHandle) = nullptr;
BOOL(*sysCallsGetFileVersionInfoW)
(LPCWSTR lptstrFilename, DWORD dwHandle, DWORD dwLen, LPVOID lpData) = nullptr;
BOOL(*sysCallsVerQueryValueW)
(LPCVOID pBlock, LPCWSTR lpSubBlock, LPVOID *lplpBuffer, PUINT puLen) = nullptr;
DWORD(*sysCallsGetLastError)
() = nullptr;
BOOL getModuleHandleExW(DWORD dwFlags, LPCWSTR lpModuleName, HMODULE *phModule) {
    if (sysCallsGetModuleHandleExW) {
        return sysCallsGetModuleHandleExW(dwFlags, lpModuleName, phModule);
    }
    return FALSE;
}
DWORD getModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize) {
    if (sysCallsGetModuleFileNameW) {
        return sysCallsGetModuleFileNameW(hModule, lpFilename, nSize);
    }
    return 0;
}
DWORD getFileVersionInfoSizeW(LPCWSTR lptstrFilename, LPDWORD lpdwHandle) {
    if (sysCallsGetFileVersionInfoSizeW) {
        return sysCallsGetFileVersionInfoSizeW(lptstrFilename, lpdwHandle);
    }
    return 0;
}
BOOL getFileVersionInfoW(LPCWSTR lptstrFilename, DWORD dwHandle, DWORD dwLen, LPVOID lpData) {
    if (sysCallsGetFileVersionInfoW) {
        return sysCallsGetFileVersionInfoW(lptstrFilename, dwHandle, dwLen, lpData);
    }
    return FALSE;
}
BOOL verQueryValueW(LPCVOID pBlock, LPCWSTR lpSubBlock, LPVOID *lplpBuffer, PUINT puLen) {
    if (sysCallsVerQueryValueW) {
        return sysCallsVerQueryValueW(pBlock, lpSubBlock, lplpBuffer, puLen);
    }
    return FALSE;
}

} // namespace SysCalls

bool isShutdownInProgress() {
    return false;
}

unsigned int readEnablePreemptionRegKey() {
    return 1;
}

} // namespace NEO