#include "stdafx.h"
#include "DeviceDX.h"
#include "2D/D2D/Graphics.h"
#include "2D/D2D/TextMgr.h"
#include "2D/D2D/Surface.h"
#include "3D/D3D/Surface.h"
#include "Exception.h"
#include "Win32Dpi.h"
#include "RenderMgr.h"

#ifdef GUI_WIN32

namespace gui {

#ifdef DEBUG
	static D2D1_FACTORY_OPTIONS options = { D2D1_DEBUG_LEVEL_INFORMATION };
#else
	static D2D1_FACTORY_OPTIONS options = { D2D1_DEBUG_LEVEL_NONE };
#endif
	static UINT deviceFlags = D3D10_CREATE_DEVICE_BGRA_SUPPORT;

	static HRESULT createDevice(ID3D10Device1 **device) {
		HRESULT r;

		D3D10_DRIVER_TYPE types[] = {
			D3D10_DRIVER_TYPE_HARDWARE,
			D3D10_DRIVER_TYPE_WARP,
		};
		D3D10_FEATURE_LEVEL1 featureLevels[] = {
			D3D10_FEATURE_LEVEL_10_1,
			D3D10_FEATURE_LEVEL_10_0,
			D3D10_FEATURE_LEVEL_9_3,
			D3D10_FEATURE_LEVEL_9_2,
			D3D10_FEATURE_LEVEL_9_1,
		};

		for (nat t = 0; t < ARRAY_COUNT(types); t++) {
			for (nat f = 0; f < ARRAY_COUNT(featureLevels); f++) {
				r = D3D10CreateDevice1(NULL,
									types[t],
									NULL,
									deviceFlags,
									featureLevels[f],
									D3D10_1_SDK_VERSION,
									device);
				if (SUCCEEDED(r))
					return r;
			}
			WARNING(L"Falling back to software rendering, expect bad performance!");
		}

		return r;
	}

	DXDevice::DXDevice(Engine &e)
		: factory(null), device(null), giDevice(null), giFactory(null), e(e), myId(0) {

		// Setup DX and DirectWrite.
		HRESULT r;
		r = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), &options, (void **)&factory.v);
		verify(e, r, S("Failed to create D2D factory: "));

		r = createDevice(&device.v);
		verify(e, r, S("Failed to create a D3D device: "));

		r = device->QueryInterface(__uuidof(IDXGIDevice), (void **)&giDevice.v);
		verify(e, r, S("Failed to get the DXGI device: "));

		ComPtr<IDXGIAdapter> adapter = null;
		giDevice->GetParent(__uuidof(IDXGIAdapter), (void **)&adapter.v);
		verify(e, r, S("Failed to get the DXGIAdapter: "));

		adapter->GetParent(__uuidof(IDXGIFactory), (void **)&giFactory.v);
		verify(e, r, S("Failed to get the DXGI factory: "));
	}

	TextMgr *DXDevice::createTextMgr() {
		return new D2DText();
	}

	static void create(DXGI_SWAP_CHAIN_DESC &desc, HWND window) {
		RECT c;
		GetClientRect(window, &c);

		zeroMem(desc);
		desc.BufferCount = 1;
		desc.BufferDesc.Width = c.right - c.left;
		desc.BufferDesc.Height = c.bottom - c.top;
		desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		// Not needed in Windowed mode.
		// desc.BufferDesc.RefreshRate.Numerator = 60; // TODO: Needed?
		// desc.BufferDesc.RefreshRate.Denominator = 1;
		desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		desc.OutputWindow = window;
		desc.SampleDesc.Count = 1;
		desc.SampleDesc.Quality = 0;
		desc.Windowed = TRUE;
	}

	Surface *DXDevice::createSurface(Handle window, SurfaceType type) {
		// Create an ID if we need it.
		if (myId == 0) {
			myId = renderMgr(e)->allocId();
		}

		DXGI_SWAP_CHAIN_DESC desc;
		gui::create(desc, window.hwnd());

		// TODO: Maybe use CreateSwapChainForHwnd?
		ComPtr<IDXGISwapChain> swapChain;
		HRESULT r = giFactory->CreateSwapChain(device.v, &desc, &swapChain.v);
		verify(e, r, S("Failed to create a swap chain for a window: "));

		Size size(Float(desc.BufferDesc.Width), Float(desc.BufferDesc.Height));

		switch (type) {
		case surface2D:
			return new D2DSurface(size, dpiScale(windowDpi(window.hwnd())), this, swapChain, myId);
		case surface3D:
			// TODO: At some point we might want to create a new device for each surface. If we do
			// that, we need to allocate a new ID for each surface.
			return new D3DSurface(size, dpiScale(windowDpi(window.hwnd())), this, myId, device, swapChain);
		default:
			throw new (e) InternalError(S("Unknown surface type!"));
		}
	}

}

#endif
