/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS ReactX
 * FILE:            dll/directx/d3d9/d3d9_create.c
 * PURPOSE:         d3d9.dll internal create functions and data
 * PROGRAMERS:      Gregor Brunmar <gregor (dot) brunmar (at) home (dot) se>
 */

#include <d3d9.h>
#include "d3d9_create.h"
#include "d3d9_helpers.h"
#include "d3d9_caps.h"
#include <debug.h>
#include <ddrawi.h>
#include <ddrawgdi.h>

static const GUID DISPLAY_GUID = { 0x67685559, 0x3106, 0x11D0, { 0xB9, 0x71, 0x00, 0xAA, 0x00, 0x34, 0x2F, 0x9F } };

static CHAR D3D9_PrimaryDeviceName[CCHDEVICENAME];

static BOOL IsDirectDrawSupported()
{
    HDC hDC;
    DWORD Planes;
    DWORD Bpp;

    hDC = GetDC(NULL);
    Planes = GetDeviceCaps(hDC, PLANES);
    Bpp = GetDeviceCaps(hDC, BITSPIXEL);
    ReleaseDC(NULL, hDC);

    if (Planes * Bpp < 8)
        return FALSE;

    return TRUE;
}

static VOID SetAdapterInfo(IN OUT LPDIRECT3D9_DISPLAYADAPTER pDisplayAdapter, IN LPDISPLAY_DEVICEA pDisplayDevice)
{
    memcpy(&pDisplayAdapter->DisplayGuid, &DISPLAY_GUID, sizeof(GUID));

    lstrcpynA(pDisplayAdapter->szDeviceName, pDisplayDevice->DeviceName, MAX_PATH);

    pDisplayAdapter->dwStateFlags = pDisplayDevice->StateFlags;
    pDisplayAdapter->bInUseFlag = TRUE;
}

static BOOL IsGDIDriver(HDC hDC)
{
    COLORREF NearestBlack = GetNearestColor(hDC, RGB(0, 0, 0));
    COLORREF NearestWhite = GetNearestColor(hDC, RGB(255, 255, 255));

    if (NearestBlack != RGB(0, 0, 0) || NearestWhite != RGB(255, 255, 255))
        return TRUE;

    return FALSE;
}

void GetDisplayAdapterFromDevice(IN OUT LPDIRECT3D9_DISPLAYADAPTER pDisplayAdapters, IN DWORD AdapterIndex, IN LPD3D9_DEVICEDATA pDeviceData)
{
    LPDIRECT3D9_DISPLAYADAPTER pDisplayAdapter = &pDisplayAdapters[AdapterIndex];
    DWORD FormatOpIndex;
    DWORD AdapterGroupId;
    DWORD NumAdaptersInGroup;
    DWORD Index;

    memcpy(&pDisplayAdapter->DriverCaps, &pDeviceData->DriverCaps, sizeof(D3D9_DRIVERCAPS));

    for (FormatOpIndex = 0; FormatOpIndex < pDeviceData->DriverCaps.NumSupportedFormatOps; FormatOpIndex++)
    {
        LPDDSURFACEDESC pSurfaceDesc = &pDeviceData->DriverCaps.pSupportedFormatOps[FormatOpIndex];
        D3DFORMAT Format = pSurfaceDesc->ddpfPixelFormat.dwFourCC;

        if ((pSurfaceDesc->ddpfPixelFormat.dwOperations & D3DFORMAT_OP_DISPLAYMODE) != 0 &&
            (Format == D3DFMT_R5G6B5 || Format == D3DFMT_X1R5G5B5))
        {
            pDisplayAdapter->Supported16bitFormat = Format;
            break;
        }
    }

    NumAdaptersInGroup = 0;
    AdapterGroupId = pDeviceData->DriverCaps.ulUniqueAdapterGroupId;
    for (Index = 0; Index < AdapterIndex; Index++)
    {
        if (AdapterGroupId == pDisplayAdapters[Index].DriverCaps.ulUniqueAdapterGroupId)
            ++NumAdaptersInGroup;
    }
    pDisplayAdapter->MasterAdapterIndex = AdapterIndex;
    pDisplayAdapter->NumAdaptersInGroup = NumAdaptersInGroup + 1;   /* Add self */
    pDisplayAdapter->AdapterIndexInGroup = NumAdaptersInGroup;

    CreateDisplayModeList(
        pDisplayAdapter->szDeviceName,
        NULL,
        &pDisplayAdapter->NumSupportedD3DFormats,
        pDisplayAdapter->Supported16bitFormat,
        pDeviceData->pUnknown6BC
        );

    if (pDisplayAdapter->NumSupportedD3DFormats > 0)
    {
        pDisplayAdapter->pSupportedD3DFormats = HeapAlloc(GetProcessHeap(), 0, pDisplayAdapter->NumSupportedD3DFormats * sizeof(D3DDISPLAYMODE));

        CreateDisplayModeList(
            pDisplayAdapter->szDeviceName,
            pDisplayAdapter->pSupportedD3DFormats,
            &pDisplayAdapter->NumSupportedD3DFormats,
            pDisplayAdapter->Supported16bitFormat,
            pDeviceData->pUnknown6BC
            );
    }
}

BOOL CreateD3D9DeviceData(IN LPDIRECT3D9_DISPLAYADAPTER pDisplayAdapter, IN LPD3D9_DEVICEDATA pDeviceData)
{
    HDC hDC;

    /* Test DC creation for the display device */
    if (NULL == (hDC = CreateDCA(NULL, pDisplayAdapter->szDeviceName, NULL, NULL)))
    {
        DPRINT1("Could not create dc for display adapter: %s", pDisplayAdapter->szDeviceName);
        return FALSE;
    }

    pDeviceData->hDC = hDC;
    pDeviceData->DisplayGuid = pDisplayAdapter->DisplayGuid;
    pDeviceData->DeviceType = D3DDEVTYPE_HAL;
    lstrcpynA(pDeviceData->szDeviceName, pDisplayAdapter->szDeviceName, CCHDEVICENAME);
    pDeviceData->szDeviceName[CCHDEVICENAME-1] = '\0';

    if (pDisplayAdapter->bInUseFlag)
    {
        pDeviceData->D3D9Callbacks.DeviceType = D3DDEVTYPE_HAL;
    }
    else if (IsGDIDriver(hDC))
    {
        pDeviceData->D3D9Callbacks.DeviceType = D3DDEVTYPE_REF;
    }

    if (FALSE == GetDeviceData(pDeviceData))
    {
        DPRINT1("Could not get device data for display adapter: %s", pDisplayAdapter->szDeviceName);
        return FALSE;
    }

    return TRUE;
}

VOID DestroyD3D9DeviceData(IN LPD3D9_DEVICEDATA pDeviceData)
{
    DeleteDC(pDeviceData->hDC);
    HeapFree(GetProcessHeap(), 0, pDeviceData->pUnknown6BC);
}

static BOOL GetDirect3D9AdapterInfo(IN OUT LPDIRECT3D9_DISPLAYADAPTER pDisplayAdapters, IN DWORD AdapterIndex)
{
    LPD3D9_DEVICEDATA pDeviceData;
    
    pDeviceData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(D3D9_DEVICEDATA));
    if (NULL == pDeviceData)
    {
        DPRINT1("Out of memory, could not initialize Direct3D adapter");
        return FALSE;
    }

    if (FALSE == CreateD3D9DeviceData(&pDisplayAdapters[AdapterIndex], pDeviceData))
    {
        DPRINT1("Could not create device data for adapter: %d", AdapterIndex);
        return FALSE;
    }

    GetDisplayAdapterFromDevice(pDisplayAdapters, AdapterIndex, pDeviceData);

    DestroyD3D9DeviceData(pDeviceData);
    HeapFree(GetProcessHeap(), 0, pDeviceData);

    return TRUE;
}

static BOOL GetDisplayDeviceInfo(IN OUT LPDIRECT3D9_INT pDirect3D9)
{
    DISPLAY_DEVICEA DisplayDevice;
    DWORD AdapterIndex;

    memset(&DisplayDevice, 0, sizeof(DISPLAY_DEVICEA));
    DisplayDevice.cb = sizeof(DISPLAY_DEVICEA);   

    pDirect3D9->NumDisplayAdapters = 0;
    D3D9_PrimaryDeviceName[0] = '\0';

    AdapterIndex = 0;
    while (EnumDisplayDevicesA(NULL, AdapterIndex, &DisplayDevice, 0) == TRUE &&
           pDirect3D9->NumDisplayAdapters < D3D9_INT_MAX_NUM_ADAPTERS)
    {
        if ((DisplayDevice.StateFlags & (DISPLAY_DEVICE_DISCONNECT | DISPLAY_DEVICE_MIRRORING_DRIVER)) == 0 &&
            (DisplayDevice.StateFlags & (DISPLAY_DEVICE_PRIMARY_DEVICE | DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) != 0)
        {
            SetAdapterInfo(&pDirect3D9->DisplayAdapters[pDirect3D9->NumDisplayAdapters], &DisplayDevice);

            if (pDirect3D9->NumDisplayAdapters == 0)
                lstrcpynA(D3D9_PrimaryDeviceName, DisplayDevice.DeviceName, sizeof(D3D9_PrimaryDeviceName));

            ++pDirect3D9->NumDisplayAdapters;
            break;
        }

        ++AdapterIndex;
    }

    AdapterIndex = 0;
    while (EnumDisplayDevicesA(NULL, AdapterIndex, &DisplayDevice, 0) == TRUE &&
           pDirect3D9->NumDisplayAdapters < D3D9_INT_MAX_NUM_ADAPTERS)
    {
        if ((DisplayDevice.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) != 0 &&
            (DisplayDevice.StateFlags & (DISPLAY_DEVICE_MIRRORING_DRIVER | DISPLAY_DEVICE_PRIMARY_DEVICE)) == 0)
        {
            SetAdapterInfo(&pDirect3D9->DisplayAdapters[pDirect3D9->NumDisplayAdapters], &DisplayDevice);
            ++pDirect3D9->NumDisplayAdapters;
        }

        ++AdapterIndex;
    }

    /* Check if minimum DirectDraw is supported */
    if (IsDirectDrawSupported() == FALSE)
        return FALSE;

    for (AdapterIndex = 0; AdapterIndex < pDirect3D9->NumDisplayAdapters; AdapterIndex++)
    {
        GetDirect3D9AdapterInfo(pDirect3D9->DisplayAdapters, AdapterIndex);
    }

    return TRUE;
}

HRESULT CreateD3D9(OUT LPDIRECT3D9 *ppDirect3D9, UINT SDKVersion)
{
    LPDIRECT3D9_INT pDirect3D9;

    if (ppDirect3D9 == 0)
        return DDERR_INVALIDPARAMS;

    if (AlignedAlloc((LPVOID *)&pDirect3D9, sizeof(DIRECT3D9_INT)) != S_OK)
        return DDERR_OUTOFMEMORY;

    if (pDirect3D9 == 0)
        return DDERR_OUTOFMEMORY;

    pDirect3D9->lpVtbl = &Direct3D9_Vtbl;
    pDirect3D9->dwProcessId = GetCurrentThreadId();
    pDirect3D9->lRefCnt = 1;

    pDirect3D9->SDKVersion = SDKVersion;

    pDirect3D9->lpInt = pDirect3D9;
    pDirect3D9->unknown000007 = 1;

    InitializeCriticalSection(&pDirect3D9->d3d9_cs);

    if (FALSE == GetDisplayDeviceInfo(pDirect3D9))
    {
        DPRINT1("Could not create Direct3D9 object");
        AlignedFree(pDirect3D9);
        return DDERR_GENERIC;
    }

    *ppDirect3D9 = (LPDIRECT3D9)&pDirect3D9->lpVtbl;

    return D3D_OK;
}

