//
// 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.
//
#include "wrappers.h"
#include "harness/errorHelpers.h"

LPCTSTR CDeviceWrapper::WINDOW_TITLE = _T( "cl_khr_dx9_media_sharing" );
const int CDeviceWrapper::WINDOW_WIDTH = 256;
const int CDeviceWrapper::WINDOW_HEIGHT = 256;
CDeviceWrapper::TAccelerationType CDeviceWrapper::accelerationType =
    CDeviceWrapper::ACCELERATION_HW;

#if defined(_WIN32)
const D3DFORMAT CDXVAWrapper::RENDER_TARGET_FORMAT = D3DFMT_X8R8G8B8;
const D3DFORMAT CDXVAWrapper::VIDEO_FORMAT = D3DFMT_X8R8G8B8;
const unsigned int CDXVAWrapper::VIDEO_FPS = 60;
#endif

#if defined(_WIN32)
static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_DESTROY: PostQuitMessage(0); return 0;
        case WM_PAINT: ValidateRect(hWnd, 0); return 0;
        default: break;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
}
#endif

CDeviceWrapper::CDeviceWrapper()
#if defined(_WIN32)
    : _hInstance(NULL), _hWnd(NULL)
#endif
{}

void CDeviceWrapper::WindowInit()
{
#if defined(_WIN32)
    _hInstance = GetModuleHandle(NULL);
    static WNDCLASSEX wc = {
        sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L,   0L,
        _hInstance,         NULL,       NULL,    NULL, NULL,
        WINDOW_TITLE,       NULL
    };

    RegisterClassEx(&wc);

    _hWnd = CreateWindow(WINDOW_TITLE, WINDOW_TITLE, WS_OVERLAPPEDWINDOW, 0, 0,
                         WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, wc.hInstance,
                         NULL);

    if (!_hWnd)
    {
        log_error("Failed to create window");
        return;
    }

    ShowWindow(_hWnd, SW_SHOWDEFAULT);
    UpdateWindow(_hWnd);
#endif
}

void CDeviceWrapper::WindowDestroy()
{
#if defined(_WIN32)
    if (_hWnd) DestroyWindow(_hWnd);
    _hWnd = NULL;
#endif
}

#if defined(_WIN32)
HWND CDeviceWrapper::WindowHandle() const { return _hWnd; }
#endif

int CDeviceWrapper::WindowWidth() const { return WINDOW_WIDTH; }

int CDeviceWrapper::WindowHeight() const { return WINDOW_HEIGHT; }

CDeviceWrapper::TAccelerationType CDeviceWrapper::AccelerationType()
{
    return accelerationType;
}

void CDeviceWrapper::AccelerationType(TAccelerationType accelerationTypeNew)
{
    accelerationType = accelerationTypeNew;
}

CDeviceWrapper::~CDeviceWrapper() { WindowDestroy(); }

#if defined(_WIN32)
CD3D9Wrapper::CD3D9Wrapper()
    : _d3d9(NULL), _d3dDevice(NULL), _status(DEVICE_PASS), _adapterIdx(0),
      _adapterFound(false)
{
    WindowInit();

    _d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
    if (!_d3d9)
    {
        log_error("Direct3DCreate9 failed\n");
        _status = DEVICE_FAIL;
    }
}

CD3D9Wrapper::~CD3D9Wrapper()
{
    Destroy();

    if (_d3d9) _d3d9->Release();
    _d3d9 = 0;
}

void CD3D9Wrapper::Destroy()
{
    if (_d3dDevice) _d3dDevice->Release();
    _d3dDevice = 0;
}

cl_int CD3D9Wrapper::Init()
{
    if (!WindowHandle())
    {
        log_error("D3D9: Window is not created\n");
        _status = DEVICE_FAIL;
        return DEVICE_FAIL;
    }

    if (!_d3d9 || DEVICE_PASS != _status || !_adapterFound) return false;

    _d3d9->GetAdapterDisplayMode(_adapterIdx - 1, &_d3ddm);

    D3DPRESENT_PARAMETERS d3dParams;
    ZeroMemory(&d3dParams, sizeof(d3dParams));

    d3dParams.Windowed = TRUE;
    d3dParams.BackBufferCount = 1;
    d3dParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dParams.hDeviceWindow = WindowHandle();
    d3dParams.BackBufferWidth = WindowWidth();
    d3dParams.BackBufferHeight = WindowHeight();
    d3dParams.BackBufferFormat = _d3ddm.Format;

    DWORD processingType = (AccelerationType() == ACCELERATION_HW)
        ? D3DCREATE_HARDWARE_VERTEXPROCESSING
        : D3DCREATE_SOFTWARE_VERTEXPROCESSING;

    if (FAILED(_d3d9->CreateDevice(_adapterIdx - 1, D3DDEVTYPE_HAL,
                                   WindowHandle(), processingType, &d3dParams,
                                   &_d3dDevice)))
    {
        log_error("CreateDevice failed\n");
        _status = DEVICE_FAIL;
        return DEVICE_FAIL;
    }

    _d3dDevice->BeginScene();
    _d3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, 0, 1.0f, 0);
    _d3dDevice->EndScene();

    return true;
}

void *CD3D9Wrapper::D3D() const { return _d3d9; }

void *CD3D9Wrapper::Device() const { return _d3dDevice; }

D3DFORMAT CD3D9Wrapper::Format() { return _d3ddm.Format; }

D3DADAPTER_IDENTIFIER9 CD3D9Wrapper::Adapter() { return _adapter; }

TDeviceStatus CD3D9Wrapper::Status() const { return _status; }

bool CD3D9Wrapper::AdapterNext()
{
    if (DEVICE_PASS != _status) return false;

    _adapterFound = false;
    for (; _adapterIdx < _d3d9->GetAdapterCount();)
    {
        ++_adapterIdx;
        D3DCAPS9 caps;
        if (FAILED(
                _d3d9->GetDeviceCaps(_adapterIdx - 1, D3DDEVTYPE_HAL, &caps)))
            continue;

        if (FAILED(_d3d9->GetAdapterIdentifier(_adapterIdx - 1, 0, &_adapter)))
        {
            log_error("D3D9: GetAdapterIdentifier failed\n");
            _status = DEVICE_FAIL;
            return false;
        }

        _adapterFound = true;

        Destroy();
        if (!Init())
        {
            _status = DEVICE_FAIL;
            _adapterFound = false;
        }
        break;
    }

    return _adapterFound;
}

unsigned int CD3D9Wrapper::AdapterIdx() const { return _adapterIdx - 1; }


CD3D9ExWrapper::CD3D9ExWrapper()
    : _d3d9Ex(NULL), _d3dDeviceEx(NULL), _status(DEVICE_PASS), _adapterIdx(0),
      _adapterFound(false)
{
    WindowInit();

    HRESULT result = Direct3DCreate9Ex(D3D_SDK_VERSION, &_d3d9Ex);
    if (FAILED(result) || !_d3d9Ex)
    {
        log_error("Direct3DCreate9Ex failed\n");
        _status = DEVICE_FAIL;
    }
}

CD3D9ExWrapper::~CD3D9ExWrapper()
{
    Destroy();

    if (_d3d9Ex) _d3d9Ex->Release();
    _d3d9Ex = 0;
}

void *CD3D9ExWrapper::D3D() const { return _d3d9Ex; }

void *CD3D9ExWrapper::Device() const { return _d3dDeviceEx; }

D3DFORMAT CD3D9ExWrapper::Format() { return _d3ddmEx.Format; }

D3DADAPTER_IDENTIFIER9 CD3D9ExWrapper::Adapter() { return _adapter; }

cl_int CD3D9ExWrapper::Init()
{
    if (!WindowHandle())
    {
        log_error("D3D9EX: Window is not created\n");
        _status = DEVICE_FAIL;
        return DEVICE_FAIL;
    }

    if (!_d3d9Ex || DEVICE_FAIL == _status || !_adapterFound)
        return DEVICE_FAIL;

    RECT rect;
    GetClientRect(WindowHandle(), &rect);

    D3DPRESENT_PARAMETERS d3dParams;
    ZeroMemory(&d3dParams, sizeof(d3dParams));

    d3dParams.Windowed = TRUE;
    d3dParams.SwapEffect = D3DSWAPEFFECT_FLIP;
    d3dParams.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dParams.BackBufferWidth = WindowWidth();
    d3dParams.BackBufferHeight = WindowHeight();

    d3dParams.BackBufferCount = 1;
    d3dParams.hDeviceWindow = WindowHandle();

    DWORD processingType = (AccelerationType() == ACCELERATION_HW)
        ? D3DCREATE_HARDWARE_VERTEXPROCESSING
        : D3DCREATE_SOFTWARE_VERTEXPROCESSING;

    if (FAILED(_d3d9Ex->CreateDeviceEx(_adapterIdx - 1, D3DDEVTYPE_HAL,
                                       WindowHandle(), processingType,
                                       &d3dParams, NULL, &_d3dDeviceEx)))
    {
        log_error("CreateDeviceEx failed\n");
        _status = DEVICE_FAIL;
        return DEVICE_FAIL;
    }

    _d3dDeviceEx->BeginScene();
    _d3dDeviceEx->Clear(0, NULL, D3DCLEAR_TARGET, 0, 1.0f, 0);
    _d3dDeviceEx->EndScene();

    return DEVICE_PASS;
}

void CD3D9ExWrapper::Destroy()
{
    if (_d3dDeviceEx) _d3dDeviceEx->Release();
    _d3dDeviceEx = 0;
}

TDeviceStatus CD3D9ExWrapper::Status() const { return _status; }

bool CD3D9ExWrapper::AdapterNext()
{
    if (DEVICE_FAIL == _status) return false;

    _adapterFound = false;
    for (; _adapterIdx < _d3d9Ex->GetAdapterCount();)
    {
        ++_adapterIdx;
        D3DCAPS9 caps;
        if (FAILED(
                _d3d9Ex->GetDeviceCaps(_adapterIdx - 1, D3DDEVTYPE_HAL, &caps)))
            continue;

        if (FAILED(
                _d3d9Ex->GetAdapterIdentifier(_adapterIdx - 1, 0, &_adapter)))
        {
            log_error("D3D9EX: GetAdapterIdentifier failed\n");
            _status = DEVICE_FAIL;
            return false;
        }

        _adapterFound = true;
        Destroy();
        if (!Init())
        {
            _status = DEVICE_FAIL;
            _adapterFound = _status;
        }

        break;
    }

    return _adapterFound;
}

unsigned int CD3D9ExWrapper::AdapterIdx() const { return _adapterIdx - 1; }

CDXVAWrapper::CDXVAWrapper()
    : _dxvaDevice(NULL), _status(DEVICE_PASS), _adapterFound(false)
{
    _status = _d3d9.Status();
}

CDXVAWrapper::~CDXVAWrapper() { DXVAHDDestroy(); }

void *CDXVAWrapper::Device() const { return _dxvaDevice; }

TDeviceStatus CDXVAWrapper::Status() const
{
    if (_status == DEVICE_FAIL || _d3d9.Status() == DEVICE_FAIL)
        return DEVICE_FAIL;
    else if (_status == DEVICE_NOTSUPPORTED
             || _d3d9.Status() == DEVICE_NOTSUPPORTED)
        return DEVICE_NOTSUPPORTED;
    else
        return DEVICE_PASS;
}

bool CDXVAWrapper::AdapterNext()
{
    if (DEVICE_PASS != _status) return false;

    _adapterFound = _d3d9.AdapterNext();
    _status = _d3d9.Status();
    if (DEVICE_PASS != _status)
    {
        _adapterFound = false;
        return false;
    }

    if (!_adapterFound) return false;

    DXVAHDDestroy();
    _status = DXVAHDInit();
    if (DEVICE_PASS != _status)
    {
        _adapterFound = false;
        return false;
    }

    return true;
}

TDeviceStatus CDXVAWrapper::DXVAHDInit()
{
    if ((_status == DEVICE_FAIL) || (_d3d9.Status() == DEVICE_FAIL)
        || !_adapterFound)
        return DEVICE_FAIL;

    DXVAHD_RATIONAL fps = { VIDEO_FPS, 1 };

    DXVAHD_CONTENT_DESC desc;
    desc.InputFrameFormat = DXVAHD_FRAME_FORMAT_PROGRESSIVE;
    desc.InputFrameRate = fps;
    desc.InputWidth = WindowWidth();
    desc.InputHeight = WindowHeight();
    desc.OutputFrameRate = fps;
    desc.OutputWidth = WindowWidth();
    desc.OutputHeight = WindowHeight();

#ifdef USE_SOFTWARE_PLUGIN
    _status = DEVICE_FAIL;
    return DEVICE_FAIL;
#endif

    HRESULT hr = DXVAHD_CreateDevice(
        static_cast<IDirect3DDevice9Ex *>(_d3d9.Device()), &desc,
        DXVAHD_DEVICE_USAGE_PLAYBACK_NORMAL, NULL, &_dxvaDevice);
    if (FAILED(hr))
    {
        if (hr == E_NOINTERFACE)
        {
            log_error(
                "DXVAHD_CreateDevice skipped due to no supported devices!\n");
            _status = DEVICE_NOTSUPPORTED;
        }
        else
        {
            log_error("DXVAHD_CreateDevice failed\n");
            _status = DEVICE_FAIL;
        }
    }

    return _status;
}

void CDXVAWrapper::DXVAHDDestroy()
{
    if (_dxvaDevice) _dxvaDevice->Release();
    _dxvaDevice = 0;
}

void *CDXVAWrapper::D3D() const { return _d3d9.D3D(); }

unsigned int CDXVAWrapper::AdapterIdx() const { return _d3d9.AdapterIdx(); }

const CD3D9ExWrapper &CDXVAWrapper::D3D9() const { return _d3d9; }

CD3D9SurfaceWrapper::CD3D9SurfaceWrapper(): mMem(NULL) {}

CD3D9SurfaceWrapper::CD3D9SurfaceWrapper(IDirect3DSurface9 *mem): mMem(mem) {}

CD3D9SurfaceWrapper::~CD3D9SurfaceWrapper()
{
    if (mMem != NULL) mMem->Release();
    mMem = NULL;
}

#endif

CSurfaceWrapper::CSurfaceWrapper() {}

CSurfaceWrapper::~CSurfaceWrapper() {}
