//
// Copyright (c) 2017 The Khronos Group Inc.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#define INITGUID
#include "harness.h"

#include <tchar.h>
#include <string>
#include <vector>

/*
 * OpenCL state
 */

clGetDeviceIDsFromD3D11KHR_fn      clGetDeviceIDsFromD3D11KHR      = NULL;
clCreateFromD3D11BufferKHR_fn      clCreateFromD3D11BufferKHR      = NULL;
clCreateFromD3D11Texture2DKHR_fn   clCreateFromD3D11Texture2DKHR   = NULL;
clCreateFromD3D11Texture3DKHR_fn   clCreateFromD3D11Texture3DKHR   = NULL;
clEnqueueAcquireD3D11ObjectsKHR_fn clEnqueueAcquireD3D11ObjectsKHR = NULL;
clEnqueueReleaseD3D11ObjectsKHR_fn clEnqueueReleaseD3D11ObjectsKHR = NULL;

#define INITPFN(x) \
    x = (x ## _fn)clGetExtensionFunctionAddressForPlatform(platform, #x); NonTestRequire(x, "Failed to get function pointer for %s", #x);

void
HarnessD3D11_ExtensionCheck()
{
    cl_int result = CL_SUCCESS;
    cl_platform_id platform = NULL;

    HarnessD3D11_TestBegin("Extension query");

    bool platform_d3d11 = false; // Does platform support the extension?
    {
        std::vector< char > buffer;
        size_t size = 0;
        result = clGetPlatformIDs( 1, &platform, NULL );
            NonTestRequire( result == CL_SUCCESS, "Failed to get any platforms." );
        result = clGetPlatformInfo( platform, CL_PLATFORM_EXTENSIONS, 0, NULL, & size );
            NonTestRequire( result == CL_SUCCESS, "Failed to get size of extension string." );
        buffer.resize( size );
        result = clGetPlatformInfo( platform, CL_PLATFORM_EXTENSIONS, buffer.size(), & buffer.front(), & size );
            NonTestRequire( result == CL_SUCCESS, "Failed to get extension string." );
        std::string extensions = std::string( " " ) + & buffer.front() + " ";
        platform_d3d11 = ( extensions.find( " cl_khr_d3d11_sharing " ) != std::string::npos );
    }

    // Platform is required to report the extension only if all devices support it,
    // so let us iterate through all the devices and count devices supporting the extension.

    // Get list of all devices.
    std::vector< cl_device_id > devices;
    cl_uint num_devices = 0;
    result = clGetDeviceIDs( platform, CL_DEVICE_TYPE_ALL, 0, NULL, & num_devices );
        NonTestRequire( result == CL_SUCCESS, "Failed to get number of devices." );
    devices.resize( num_devices );
    result = clGetDeviceIDs( platform, CL_DEVICE_TYPE_ALL, devices.size(), & devices.front(), & num_devices );
        NonTestRequire( result == CL_SUCCESS, "Failed to get list of device ids." );
        NonTestRequire( num_devices == devices.size(), "Failed to get list of device ids." );

    // Iterate through the devices and count devices supporting the extension.
    cl_uint num_devices_d3d11 = 0; // Number of devices supporting cl_khr_d3d11_sharing.
    for ( cl_uint i = 0; i < devices.size(); ++ i )
    {
        std::vector< char > buffer;
        size_t size = 0;
        result = clGetDeviceInfo( devices[ i ], CL_DEVICE_EXTENSIONS, 0, NULL, & size );
            NonTestRequire( result == CL_SUCCESS, "Failed to get size of extension string." );
        buffer.resize( size );
        result = clGetDeviceInfo( devices[ i ], CL_DEVICE_EXTENSIONS, buffer.size(), & buffer.front(), & size );
            NonTestRequire( result == CL_SUCCESS, "Failed to get extension string." );
        std::string extensions = std::string( " " ) + & buffer.front() + " ";
        if ( extensions.find( " cl_khr_d3d11_sharing " ) != std::string::npos )
        {
            ++ num_devices_d3d11;
        }
    }

    OSVERSIONINFO osvi;
    osvi.dwOSVersionInfoSize = sizeof(osvi);
    GetVersionEx(&osvi);
    if (osvi.dwMajorVersion <= 5)
    {
        // Neither platform nor devices should declare support.
        TestRequire( ! platform_d3d11, "Platform should not declare extension on Windows < 6" );
        TestRequire( num_devices_d3d11 == 0, "Devices should not declare extension on Windows < 6" );
    }
    else
    {
        if ( num_devices_d3d11 == num_devices )
        {
            // All the devices declare support, so platform must declare support as well.
            TestRequire( platform_d3d11, "Extension should be exported on Windows >= 6" );
        }
        else
        {
            // Not all the devices support th eextension => platform should not declare it.
            TestRequire( ! platform_d3d11, "Extension should not be exported on Windows >= 6" );
        }
    }

Cleanup:
    HarnessD3D11_TestEnd();

    // early-out of the extension is not present
    if ( num_devices_d3d11 == 0 )
    {
        HarnessD3D11_TestStats();
    }
}

void
HarnessD3D11_Initialize(cl_platform_id platform)
{
    HarnessD3D11_ExtensionCheck();

    // extract function pointers for exported functions
    INITPFN(clGetDeviceIDsFromD3D11KHR);
    INITPFN(clCreateFromD3D11BufferKHR);
    INITPFN(clCreateFromD3D11Texture2DKHR);
    INITPFN(clCreateFromD3D11Texture3DKHR);
    INITPFN(clEnqueueAcquireD3D11ObjectsKHR);
    INITPFN(clEnqueueReleaseD3D11ObjectsKHR);
}

/*
 * Window management
 */

static IDXGISwapChain*       HarnessD3D11_pSwapChain = NULL;
static ID3D11Device*         HarnessD3D11_pDevice = NULL;
static ID3D11DeviceContext*  HarnessD3D11_pDC = NULL;
static HWND                  HarnessD3D11_hWnd = NULL;

static LRESULT WINAPI HarnessD3D11_Proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_KEYDOWN:
            return 0;
            break;
        case WM_DESTROY:
            HarnessD3D11_hWnd = NULL;
            PostQuitMessage(0);
            return 0;
        case WM_PAINT:
            ValidateRect(hWnd, NULL);
            return 0;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

static void HarnessD3D11_InteractiveLoop()
{
    MSG msg;
    while(PeekMessage(&msg,HarnessD3D11_hWnd,0,0,PM_REMOVE))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

cl_int HarnessD3D11_CreateDevice(
    IDXGIAdapter* pAdapter,
    ID3D11Device **ppDevice,
    ID3D11DeviceContext** ppDC)
{
    HRESULT hr = S_OK;
    unsigned int cuStatus = 1;

    *ppDevice = NULL;

    // create window
    static WNDCLASSEX wc =
    {
        sizeof(WNDCLASSEX),
        CS_CLASSDC,
        HarnessD3D11_Proc,
        0L,
        0L,
        GetModuleHandle(NULL),
        NULL,
        NULL,
        NULL,
        NULL,
        _T( "cl_khr_d3d11_sharing_conformance" ),
        NULL
    };
    RegisterClassEx(&wc);
    HarnessD3D11_hWnd = CreateWindow(
        _T( "cl_khr_d3d11_sharing_conformance" ),
        _T( "cl_khr_d3d11_sharing_conformance" ),
        WS_OVERLAPPEDWINDOW,
        0, 0, 256, 256,
        NULL,
        NULL,
        wc.hInstance,
        NULL);
    NonTestRequire(0 != HarnessD3D11_hWnd, "Failed to create window");

    ShowWindow(HarnessD3D11_hWnd,SW_SHOWDEFAULT);
    UpdateWindow(HarnessD3D11_hWnd);

    RECT rc;
    GetClientRect(HarnessD3D11_hWnd, &rc);
    UINT width = rc.right - rc.left;
    UINT height = rc.bottom - rc.top;

    // Create device and swapchain
    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory( &sd, sizeof(sd) );
    sd.BufferCount = 1;
    sd.BufferDesc.Width = width;
    sd.BufferDesc.Height = height;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = HarnessD3D11_hWnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;
    D3D_FEATURE_LEVEL requestedFeatureLevels[] = {D3D_FEATURE_LEVEL_10_0};
    D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0;
    hr = D3D11CreateDeviceAndSwapChain(
        NULL, // pAdapter,
        D3D_DRIVER_TYPE_HARDWARE,
        NULL,
        0,
        requestedFeatureLevels,
        1,
        D3D11_SDK_VERSION,
        &sd,
        &HarnessD3D11_pSwapChain,
        &HarnessD3D11_pDevice,
        &featureLevel,
        &HarnessD3D11_pDC);
    if (FAILED(hr) ) {
        return CL_DEVICE_NOT_FOUND;
    }

    *ppDevice = HarnessD3D11_pDevice;
    *ppDC = HarnessD3D11_pDC;
    return CL_SUCCESS;
}

void HarnessD3D11_DestroyDevice()
{
    HarnessD3D11_pSwapChain->Release();
    HarnessD3D11_pDevice->Release();
    HarnessD3D11_pDC->Release();

    if (HarnessD3D11_hWnd) DestroyWindow(HarnessD3D11_hWnd);
    HarnessD3D11_hWnd = 0;
}

/*
 *
 * texture formats
 *
 */

#define ADD_TEXTURE_FORMAT(x,y,z,a,b,g) { x, y, z, a*b/8, g, #x, #y, #z, }
TextureFormat formats[] =
{
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R32G32B32A32_FLOAT , CL_RGBA , CL_FLOAT           , 32, 4, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R32G32B32A32_UINT  , CL_RGBA , CL_UNSIGNED_INT32  , 32, 4, TextureFormat::GENERIC_UINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R32G32B32A32_SINT  , CL_RGBA , CL_SIGNED_INT32    , 32, 4, TextureFormat::GENERIC_SINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16G16B16A16_FLOAT , CL_RGBA , CL_HALF_FLOAT      , 16, 4, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16G16B16A16_UNORM , CL_RGBA , CL_UNORM_INT16     , 16, 4, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16G16B16A16_UINT  , CL_RGBA , CL_UNSIGNED_INT16  , 16, 4, TextureFormat::GENERIC_UINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16G16B16A16_SNORM , CL_RGBA , CL_SNORM_INT16     , 16, 4, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16G16B16A16_SINT  , CL_RGBA , CL_SIGNED_INT16    , 16, 4, TextureFormat::GENERIC_SINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R8G8B8A8_UNORM     , CL_RGBA , CL_UNORM_INT8      ,  8, 4, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R8G8B8A8_UINT      , CL_RGBA , CL_UNSIGNED_INT8   ,  8, 4, TextureFormat::GENERIC_UINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R8G8B8A8_SNORM     , CL_RGBA , CL_SNORM_INT8      ,  8, 4, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R8G8B8A8_SINT      , CL_RGBA , CL_SIGNED_INT8     ,  8, 4, TextureFormat::GENERIC_SINT  ),

    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R32G32_FLOAT       , CL_RG   , CL_FLOAT           , 32, 2, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R32G32_UINT        , CL_RG   , CL_UNSIGNED_INT32  , 32, 2, TextureFormat::GENERIC_UINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R32G32_SINT        , CL_RG   , CL_SIGNED_INT32    , 32, 2, TextureFormat::GENERIC_SINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16G16_FLOAT       , CL_RG   , CL_HALF_FLOAT      , 16, 2, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16G16_UNORM       , CL_RG   , CL_UNORM_INT16     , 16, 2, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16G16_UINT        , CL_RG   , CL_UNSIGNED_INT16  , 16, 2, TextureFormat::GENERIC_UINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16G16_SNORM       , CL_RG   , CL_SNORM_INT16     , 16, 2, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16G16_SINT        , CL_RG   , CL_SIGNED_INT16    , 16, 2, TextureFormat::GENERIC_SINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R8G8_UNORM         , CL_RG   , CL_UNORM_INT8      ,  8, 2, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R8G8_UINT          , CL_RG   , CL_UNSIGNED_INT8   ,  8, 2, TextureFormat::GENERIC_UINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R8G8_SNORM         , CL_RG   , CL_SNORM_INT8      ,  8, 2, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R8G8_SINT          , CL_RG   , CL_SIGNED_INT8     ,  8, 2, TextureFormat::GENERIC_SINT  ),

    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R32_FLOAT          , CL_R    , CL_FLOAT           , 32, 1, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R32_UINT           , CL_R    , CL_UNSIGNED_INT32  , 32, 1, TextureFormat::GENERIC_UINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R32_SINT           , CL_R    , CL_SIGNED_INT32    , 32, 1, TextureFormat::GENERIC_SINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16_FLOAT          , CL_R    , CL_HALF_FLOAT      , 16, 1, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16_UNORM          , CL_R    , CL_UNORM_INT16     , 16, 1, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16_UINT           , CL_R    , CL_UNSIGNED_INT16  , 16, 1, TextureFormat::GENERIC_UINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16_SNORM          , CL_R    , CL_SNORM_INT16     , 16, 1, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R16_SINT           , CL_R    , CL_SIGNED_INT16    , 16, 1, TextureFormat::GENERIC_SINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R8_UNORM           , CL_R    , CL_UNORM_INT8      ,  8, 1, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R8_UINT            , CL_R    , CL_UNSIGNED_INT8   ,  8, 1, TextureFormat::GENERIC_UINT  ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R8_SNORM           , CL_R    , CL_SNORM_INT8      ,  8, 1, TextureFormat::GENERIC_FLOAT ),
    ADD_TEXTURE_FORMAT( DXGI_FORMAT_R8_SINT            , CL_R    , CL_SIGNED_INT8     ,  8, 1, TextureFormat::GENERIC_SINT  ),
};
UINT formatCount = sizeof(formats)/sizeof(formats[0]);

/*
 *
 * Logging and error reporting
 *
 */

static struct
{
    cl_int testCount;
    cl_int passCount;

    cl_int nonTestFailures;
    cl_int inTest;
    cl_int currentTestPass;

    char currentTestName[1024];
} HarnessD3D11_testStats = {0};

void HarnessD3D11_TestBegin(const char* fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    vsprintf(HarnessD3D11_testStats.currentTestName, fmt, ap);
    va_end(ap);

    printf("[%s] ... ", HarnessD3D11_testStats.currentTestName);

    HarnessD3D11_testStats.inTest = 1;
    HarnessD3D11_testStats.currentTestPass = 1;
}

void HarnessD3D11_TestFail()
{
    if (HarnessD3D11_testStats.inTest)
    {
        HarnessD3D11_testStats.currentTestPass = 0;
    }
    else
    {
        ++HarnessD3D11_testStats.nonTestFailures;
    }
}

void HarnessD3D11_TestEnd()
{
    HarnessD3D11_testStats.inTest = 0;

    HarnessD3D11_testStats.testCount += 1;
    HarnessD3D11_testStats.passCount += HarnessD3D11_testStats.currentTestPass;

    TestPrint("%s\n",
        HarnessD3D11_testStats.currentTestPass ? "PASSED" : "FAILED");
}

void HarnessD3D11_TestStats()
{
    TestPrint("PASSED %d of %d tests.\n", HarnessD3D11_testStats.passCount, HarnessD3D11_testStats.testCount);
    if (HarnessD3D11_testStats.testCount > HarnessD3D11_testStats.passCount)
    {
        TestPrint("***FAILED***\n");
        exit(1);
    }
    else
    {
        TestPrint("&&&& cl_khr_d3d11_sharing test PASSED\n");
    }
    exit(0);
}

/*
 *
 * Helper function
 *
 */

cl_int HarnessD3D11_CreateKernelFromSource(
    cl_kernel *outKernel,
    cl_device_id device,
    cl_context context,
    const char *source,
    const char *entrypoint)
{
    cl_int status;
    cl_kernel kernel = NULL;

    // compile program
    cl_program program = NULL;
    {
        const char *sourceTexts[] = {source};
        size_t sourceLengths[] = {strlen(source) };

        status = create_single_kernel_helper_create_program(context, &program, 1, &sourceTexts[0]);
        TestRequire(
            CL_SUCCESS == status,
            "clCreateProgramWithSource failed");
    }
    status = clBuildProgram(
        program,
        0,
        NULL,
        NULL,
        NULL,
        NULL);
    if (CL_SUCCESS != status)
    {
        char log[2048] = {0};
        status = clGetProgramBuildInfo(
            program,
            device,
            CL_PROGRAM_BUILD_LOG,
            sizeof(log),
            log,
            NULL);
        TestPrint("error: %s\n", log);
        TestRequire(
            CL_SUCCESS == status,
            "Compilation error log:\n%s\n", log);
    }

    kernel = clCreateKernel(
        program,
        entrypoint,
        &status);
    TestRequire(
        CL_SUCCESS == status,
        "clCreateKernel failed");

    clReleaseProgram(program);
    *outKernel = kernel;

Cleanup:

    return CL_SUCCESS;
}



