//-----------------------------------------------------------------------------
/// Wrapped_ID3D12DescriptorHeap::SetPrivateData
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE Wrapped_ID3D12DescriptorHeap::SetPrivateData(REFGUID guid, UINT DataSize, const void* pData)
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    HRESULT result = {};

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor && interceptor->ShouldCollectTrace())
    {
        ParameterEntry parameters[] =
        {
            { PARAMETER_GUID, &guid },
            { PARAMETER_UNSIGNED_INT, &DataSize },
            { PARAMETER_POINTER, pData },
        };

        int numParameters = (sizeof(parameters) / sizeof(parameters[0]));
        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_ID3D12Object_SetPrivateData, numParameters, parameters);
        result = mRealDescriptorHeap->SetPrivateData(guid, DataSize, pData);
        interceptor->PostCall(pNewEntry, result);
    }
    else
    {
        result = mRealDescriptorHeap->SetPrivateData(guid, DataSize, pData);
    }

    return result;
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12CommandQueue::Release
//-----------------------------------------------------------------------------
ULONG STDMETHODCALLTYPE Wrapped_ID3D12CommandQueue::Release()
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    ULONG result = {};

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor && interceptor->ShouldCollectTrace())
    {
        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_IUnknown_Release, 0, nullptr);
        result = mRealCommandQueue->Release();
        interceptor->PostCall(pNewEntry, result);
    }
    else
    {
        result = mRealCommandQueue->Release();
    }

    if (result == 0)
    {
        DX12WrappedObjectDatabase* objectDatabase = (DX12WrappedObjectDatabase*)DX12ObjectDatabaseProcessor::Instance()->GetObjectDatabase();
        IDX12InstanceBase* objectMetadata = objectDatabase->GetMetadataObject(this);

        if (objectMetadata != nullptr)
        {
            objectMetadata->FlagAsDestroyed();
        }
    }

    return result;
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12CommandAllocator::SetName
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE Wrapped_ID3D12CommandAllocator::SetName(LPCWSTR Name)
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    HRESULT result = {};

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor->ShouldCollectTrace())
    {
        ParameterEntry parameters[] =
        {
            { PARAMETER_WIDE_STRING, Name },
        };

        int numParameters = (sizeof(parameters) / sizeof(parameters[0]));
        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_ID3D12Object_SetName, numParameters, parameters);
        result = mRealCommandAllocator->SetName(Name);
        interceptor->PostCall(pNewEntry, result);
    }
    else
    {
        result = mRealCommandAllocator->SetName(Name);
    }

    return result;
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12CommandQueue::SetPrivateDataInterface
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE Wrapped_ID3D12CommandQueue::SetPrivateDataInterface(REFGUID guid, const IUnknown* pData)
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    HRESULT result = {};

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor && interceptor->ShouldCollectTrace())
    {
        ParameterEntry parameters[] =
        {
            { PARAMETER_GUID, &guid },
            { PARAMETER_POINTER_SPECIAL, pData },
        };

        int numParameters = (sizeof(parameters) / sizeof(parameters[0]));
        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_ID3D12Object_SetPrivateDataInterface, numParameters, parameters);
        result = mRealCommandQueue->SetPrivateDataInterface(guid, pData);
        interceptor->PostCall(pNewEntry, result);
    }
    else
    {
        result = mRealCommandQueue->SetPrivateDataInterface(guid, pData);
    }

    return result;
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12CommandQueue::UpdateTileMappings
//-----------------------------------------------------------------------------
void STDMETHODCALLTYPE Wrapped_ID3D12CommandQueue::UpdateTileMappings(ID3D12Resource* pResource, UINT NumResourceRegions, const D3D12_TILED_RESOURCE_COORDINATE* pResourceRegionStartCoordinates, const D3D12_TILE_REGION_SIZE* pResourceRegionSizes, ID3D12Heap* pHeap, UINT NumRanges, const D3D12_TILE_RANGE_FLAGS* pRangeFlags, const UINT* pHeapRangeStartOffsets, const UINT* pRangeTileCounts, D3D12_TILE_MAPPING_FLAGS Flags)
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    ID3D12Resource* pResourceUnwrapped;
    DX12CoreDeepCopy::UnwrapInterface(pResource, &(pResourceUnwrapped));
    ID3D12Heap* pHeapUnwrapped;
    DX12CoreDeepCopy::UnwrapInterface(pHeap, &(pHeapUnwrapped));

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor && interceptor->ShouldCollectTrace())
    {
        // print variables with nullptr pointer check
        UINT heapRangeStartOffsets = 0;

        if (pHeapRangeStartOffsets != nullptr)
        {
            heapRangeStartOffsets = *pHeapRangeStartOffsets;
        }

        UINT rangeTileCounts = 0;

        if (pRangeTileCounts != nullptr)
        {
            rangeTileCounts = *pRangeTileCounts;
        }

        ParameterEntry parameters[] =
        {
            { PARAMETER_POINTER_SPECIAL, pResource },
            { PARAMETER_UNSIGNED_INT, &NumResourceRegions },
            { PARAMETER_POINTER, pResourceRegionStartCoordinates },
            { PARAMETER_POINTER, pResourceRegionSizes },
            { PARAMETER_POINTER_SPECIAL, pHeap },
            { PARAMETER_UNSIGNED_INT, &NumRanges },
            { PARAMETER_POINTER, pRangeFlags },
            { PARAMETER_UNSIGNED_INT, &heapRangeStartOffsets },
            { PARAMETER_UNSIGNED_INT, &rangeTileCounts },
            { PARAMETER_UNSIGNED_INT, &Flags },
        };

        int numParameters = (sizeof(parameters) / sizeof(parameters[0]));
        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_ID3D12CommandQueue_UpdateTileMappings, numParameters, parameters);
        mRealCommandQueue->UpdateTileMappings(pResourceUnwrapped, NumResourceRegions, pResourceRegionStartCoordinates, pResourceRegionSizes, pHeapUnwrapped, NumRanges, pRangeFlags, pHeapRangeStartOffsets, pRangeTileCounts, Flags);
        interceptor->PostCall(pNewEntry);
    }
    else
    {
        mRealCommandQueue->UpdateTileMappings(pResourceUnwrapped, NumResourceRegions, pResourceRegionStartCoordinates, pResourceRegionSizes, pHeapUnwrapped, NumRanges, pRangeFlags, pHeapRangeStartOffsets, pRangeTileCounts, Flags);
    }
}
//-----------------------------------------------------------------------------
/// Function which causes all necessary entrypoints to be hooked.
/// \return true if hooking was successful; false will unload the wrapper
//-----------------------------------------------------------------------------
GPS_PLUGIN_API bool UpdateHooks()
{
    Log(logTRACE, "DX12Server2's UpdateHooks.\n");

    if (s_bInitialized == false)
    {
        if (InitPlugin() == false)
        {
            return false;
        }

        s_bInitialized = true;

#ifdef DLL_REPLACEMENT

        // Load the real D3D12.dll and get a handle to it so that the real functions can
        // be obtained later
        if (s_hRealD3D12 == 0)
        {
            s_hRealD3D12 = DllReplacement::LoadRealLibrary("D3D12.dll");

            if (!s_hRealD3D12)
            {
                Log(logERROR, "failed to load D3D12.dll\n");
                return false;
            }
        }

#endif // DLL_REPLACEMENT
    }

    DX12LayerManager* dx12LayerManager = GetDX12LayerManager();
#ifndef USE_OLD_DXGI_HOOKING
    ConnectWithDXGI(dx12LayerManager);
#endif

    if (!dx12LayerManager->HasBeenInitialized())
    {
        if (!dx12LayerManager->InitializeLayerManager())
        {
            Log(logWARNING, "The DX12LayerManager was not initialized successfully.\n");
        }
    }

    return true;
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12QueryHeap::QueryInterface
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE Wrapped_ID3D12QueryHeap::QueryInterface(REFIID riid, void** ppvObject)
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    HRESULT result = {};

    if (riid == IID_IWrappedObject)
    {
        *ppvObject = mRealQueryHeap;
        result = S_OK;
    }
    else
    {
        DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

        if (interceptor->ShouldCollectTrace())
        {
            ParameterEntry parameters[] =
            {
                { PARAMETER_REFIID, &riid },
                { PARAMETER_POINTER, ppvObject },
            };

            int numParameters = (sizeof(parameters) / sizeof(parameters[0]));
            DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_IUnknown_QueryInterface, numParameters, parameters);
            result = mRealQueryHeap->QueryInterface(riid, ppvObject);
            interceptor->PostCall(pNewEntry, result);
        }
        else
        {
            result = mRealQueryHeap->QueryInterface(riid, ppvObject);
        }

        if (result == S_OK)
        {
            if (riid == __uuidof(ID3D12QueryHeap))
            {
                WrapD3D12QueryHeap(nullptr, (ID3D12QueryHeap**)ppvObject);
            }
        }
    }

    return result;
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12CommandQueue::ExecuteCommandLists
//-----------------------------------------------------------------------------
void STDMETHODCALLTYPE Wrapped_ID3D12CommandQueue::ExecuteCommandLists(UINT NumCommandLists, ID3D12CommandList* const* ppCommandLists)
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    ID3D12CommandList** ppCommandListsCopy = nullptr;

    if (NumCommandLists > 0)
    {
        ppCommandListsCopy = new ID3D12CommandList*[NumCommandLists];

        for (UINT index = 0; index < NumCommandLists; index++)
        {
            DX12CoreDeepCopy::UnwrapInterface(ppCommandLists[index], &(ppCommandListsCopy[index]));
        }
    }

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor && interceptor->ShouldCollectTrace())
    {
        int numParameters = NumCommandLists + 1;
        ParameterEntry* parameters = new ParameterEntry[numParameters];
        parameters[0].mType = PARAMETER_UNSIGNED_INT;
        parameters[0].mData = &NumCommandLists;

        for (UINT loop = 0; loop < NumCommandLists; loop++)
        {
            parameters[loop + 1].mType = PARAMETER_POINTER;
            parameters[loop + 1].mData = ppCommandLists[loop];
        }

        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_ID3D12CommandQueue_ExecuteCommandLists, numParameters, parameters);
        mRealCommandQueue->ExecuteCommandLists(NumCommandLists, ppCommandListsCopy);
        interceptor->PostCall(pNewEntry);
        delete[] parameters;
    }
    else
    {
        mRealCommandQueue->ExecuteCommandLists(NumCommandLists, ppCommandListsCopy);
    }

    SAFE_DELETE_ARRAY(ppCommandListsCopy);
}
//--------------------------------------------------------------------------
/// Entrypoint for the DX12Server plugin. Short rundown:
/// On DLL_PROCESS_ATTACH, initialize the DX12LayerManager.
/// On DLL_PROCESS_DETACH, kill and destroy the DX12LayerManager.
/// \param hModule The module associated with this invocation of *this* DllMain.
/// \param dwReason The reason that this function is being invoked.
/// \param pReserved Reserved mystery pointer.
/// \returns See http://msdn.microsoft.com/en-us/library/windows/desktop/ms682583(v=vs.85).aspx to understand this.
//--------------------------------------------------------------------------
BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD dwReason, VOID* pReserved)
{
    UNREFERENCED_PARAMETER(hModule);
    UNREFERENCED_PARAMETER(pReserved);

    Log(logTRACE, "DX12Server2's DllMain hit with reason '%d'\n", dwReason);

    switch (dwReason)
    {
        case DLL_PROCESS_ATTACH:
        {
            s_bHooked = s_bInitialized = false;
        }
        break;

        case DLL_PROCESS_DETACH:
        {
#ifdef DLL_REPLACEMENT
            FreeLibrary(s_hRealD3D12);
#endif // DLL_REPLACEMENT
            // Only shutdown the DX12LayerManager if it was initialized.
            DX12LayerManager* layerManager = GetDX12LayerManager();

            if (layerManager->HasBeenInitialized())
            {
                if (layerManager->ShutdownLayerManager() == false)
                {
                    Log(logWARNING, "The DX12LayerManager was not shutdown successfully.\n");
                }

                // Disconnect from the running DXGI server before we kill the plugin.
#ifndef USE_OLD_DXGI_HOOKING
                DisconnectFromDXGI(layerManager);
#endif
            }
            else
            {
                Log(logERROR, "DX12Server shutdown: The DX12LayerManager was not initialized.\n");
            }
        }
        break;
    }

    return true;
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12CommandQueue::EndEvent
//-----------------------------------------------------------------------------
void STDMETHODCALLTYPE Wrapped_ID3D12CommandQueue::EndEvent()
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor && interceptor->ShouldCollectTrace())
    {
        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_ID3D12CommandQueue_EndEvent, 0);
        mRealCommandQueue->EndEvent();
        interceptor->PostCall(pNewEntry);
    }
    else
    {
        mRealCommandQueue->EndEvent();
    }
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12CommandAllocator::Reset
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE Wrapped_ID3D12CommandAllocator::Reset()
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    HRESULT result = {};

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor->ShouldCollectTrace())
    {
        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_ID3D12CommandAllocator_Reset, 0, nullptr);
        result = mRealCommandAllocator->Reset();
        interceptor->PostCall(pNewEntry, result);
    }
    else
    {
        result = mRealCommandAllocator->Reset();
    }

    return result;
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12DescriptorHeap::GetGPUDescriptorHandleForHeapStart
//-----------------------------------------------------------------------------
D3D12_GPU_DESCRIPTOR_HANDLE STDMETHODCALLTYPE Wrapped_ID3D12DescriptorHeap::GetGPUDescriptorHandleForHeapStart()
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    D3D12_GPU_DESCRIPTOR_HANDLE result = {};

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor && interceptor->ShouldCollectTrace())
    {
        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_ID3D12DescriptorHeap_GetGPUDescriptorHandleForHeapStart, 0, nullptr);
        result = mRealDescriptorHeap->GetGPUDescriptorHandleForHeapStart();
        interceptor->PostCall(pNewEntry, static_cast<INT64>(result.ptr), RETURN_VALUE_HEX);
    }
    else
    {
        result = mRealDescriptorHeap->GetGPUDescriptorHandleForHeapStart();
    }

    return result;
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12QueryHeap::AddRef
//-----------------------------------------------------------------------------
ULONG STDMETHODCALLTYPE Wrapped_ID3D12QueryHeap::AddRef()
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    ULONG result = {};

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor->ShouldCollectTrace())
    {
        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_IUnknown_AddRef, 0, nullptr);
        result = mRealQueryHeap->AddRef();
        interceptor->PostCall(pNewEntry, result);
    }
    else
    {
        result = mRealQueryHeap->AddRef();
    }

    return result;
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12CommandList::GetType
//-----------------------------------------------------------------------------
D3D12_COMMAND_LIST_TYPE STDMETHODCALLTYPE Wrapped_ID3D12CommandList::GetType()
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    D3D12_COMMAND_LIST_TYPE result = {};

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor && interceptor->ShouldCollectTrace())
    {
        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_ID3D12CommandList_GetType, 0, nullptr);
        result = mRealCommandList->GetType();
        interceptor->PostCall(pNewEntry, result);
    }
    else
    {
        result = mRealCommandList->GetType();
    }

    return result;
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12CommandQueue::BeginEvent
//-----------------------------------------------------------------------------
void STDMETHODCALLTYPE Wrapped_ID3D12CommandQueue::BeginEvent(UINT Metadata, const void* pData, UINT Size)
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor && interceptor->ShouldCollectTrace())
    {
        ParameterEntry parameters[] =
        {
            { PARAMETER_UNSIGNED_INT, &Metadata },
            { PARAMETER_STRING, pData },
            { PARAMETER_UNSIGNED_INT, &Size },
        };

        int numParameters = (sizeof(parameters) / sizeof(parameters[0]));

        if (Metadata == DirectX::Detail::PIX_EVENT_UNICODE_VERSION)
        {
            parameters[1].mType = PARAMETER_WIDE_STRING;
        }
        else if (Metadata != DirectX::Detail::PIX_EVENT_ANSI_VERSION)
        {
            numParameters = 0;
        }

        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_ID3D12CommandQueue_BeginEvent, numParameters, parameters);
        mRealCommandQueue->BeginEvent(Metadata, pData, Size);
        interceptor->PostCall(pNewEntry);
    }
    else
    {
        mRealCommandQueue->BeginEvent(Metadata, pData, Size);
    }
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12QueryHeap::GetDevice
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE Wrapped_ID3D12QueryHeap::GetDevice(REFIID riid, void** ppvDevice)
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    HRESULT result = {};

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor->ShouldCollectTrace())
    {
        ParameterEntry parameters[] =
        {
            { PARAMETER_REFIID, &riid },
            { PARAMETER_POINTER, ppvDevice },
        };

        int numParameters = (sizeof(parameters) / sizeof(parameters[0]));
        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_ID3D12DeviceChild_GetDevice, numParameters, parameters);
        result = mRealQueryHeap->GetDevice(riid, ppvDevice);
        interceptor->PostCall(pNewEntry, result);
    }
    else
    {
        result = mRealQueryHeap->GetDevice(riid, ppvDevice);
    }

    if (ppvDevice != nullptr && *ppvDevice != nullptr)
    {
        DX12WrappedObjectDatabase* objectDatabase = (DX12WrappedObjectDatabase*)DX12ObjectDatabaseProcessor::Instance()->GetObjectDatabase();
        objectDatabase->WrappedObject((IUnknown**)ppvDevice);
    }

    return result;
}
//-----------------------------------------------------------------------------
/// Wrapped_ID3D12CommandQueue::GetDesc
//-----------------------------------------------------------------------------
D3D12_COMMAND_QUEUE_DESC STDMETHODCALLTYPE Wrapped_ID3D12CommandQueue::GetDesc()
{
#if SERIALIZE_DX12_ENTRY_POINTS
    ScopeLock lock(&s_mutex);
#endif

    D3D12_COMMAND_QUEUE_DESC result = {};

    DX12Interceptor* interceptor = GetDX12LayerManager()->GetInterceptor();

    if (interceptor && interceptor->ShouldCollectTrace())
    {
        DX12APIEntry* pNewEntry = interceptor->PreCall(this, FuncId_ID3D12CommandQueue_GetDesc, 0);
        result = mRealCommandQueue->GetDesc();
        // TODO: get return value
        interceptor->PostCall(pNewEntry);
    }
    else
    {
        result = mRealCommandQueue->GetDesc();
    }

    return result;
}
//--------------------------------------------------------------------------
/// Exported function used in stepping the message pump for the server plugin.
/// \param requestID The ID of a request that is in waiting to be processed.
/// This ID can be used to query the contents of an incoming request.
/// \return true if the request could be processed; false otherwise
//--------------------------------------------------------------------------
GPS_PLUGIN_API bool ProcessRequest(CommunicationID requestID)
{
    DX12LayerManager* dx12LayerManager = GetDX12LayerManager();
    return dx12LayerManager->ProcessRequestFromCommId(requestID);
}