#include "stdafx.h"
#include "Surface.h"
#include "Graphics.h"
#include "Exception.h"

#ifdef GUI_WIN32

namespace gui {

	static ID2D1RenderTarget *createTarget(DXDevice *device, IDXGISwapChain *swapChain) {
		ID2D1RenderTarget *target;
		ComPtr<IDXGISurface> surface;

		HRESULT r = swapChain->GetBuffer(0, __uuidof(IDXGISurface), (void **)&surface.v);
		verify(device->e, r, S("Failed to get the surface: "));

		D2D1_RENDER_TARGET_PROPERTIES props = {
			D2D1_RENDER_TARGET_TYPE_DEFAULT,
			{ DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED },
			0, 0, // DPI -- TODO: We can re-create render targets depending on DPI here to avoid scaling!
			D2D1_RENDER_TARGET_USAGE_NONE,
			D2D1_FEATURE_LEVEL_10, // Use _DEFAULT?
		};
		r = device->factory->CreateDxgiSurfaceRenderTarget(surface.v, &props, &target);
		verify(device->e, r, S("Failed to create a render target: "));

		return target;
	}

	D2DSurface::D2DSurface(Size size, Float scale, DXDevice *device, const ComPtr<IDXGISwapChain> &swap, Nat id)
		: Surface2D(size, scale), device(device), deviceId(id), swapChain(swap),
		  renderTarget(gui::createTarget(device, swap.v)) {}

	D2DSurface::~D2DSurface() {}

	WindowGraphics *D2DSurface::createGraphics(Engine &e) {
		return new (e) D2DGraphics(*this, deviceId);
	}

	void D2DSurface::resize(Size size, Float scale) {
		grabSurface.clear(); // Just in case.
		renderTarget.clear();

		HRESULT r = swapChain->ResizeBuffers(1, (UINT)size.w, (UINT)size.h, DXGI_FORMAT_UNKNOWN, 0);
		verify(device->e, r, S("Failed to resize surface: "));

		renderTarget = createTarget(device, swapChain.v);

		this->size = size;
		this->scale = scale;
	}

	Surface::PresentStatus D2DSurface::present(bool waitForVSync) {
		HRESULT r = swapChain->Present(waitForVSync ? 1 : 0, 0);
		if (r == D2DERR_RECREATE_TARGET || r == DXGI_ERROR_DEVICE_RESET) {
			return pRecreate;
		} else if (FAILED(r)) {
			return pFailure;
		} else {
			return pSuccess;
		}
	}

	void D2DSurface::prepareGrab(Size size, Float scale) {
		grabSurface.clear();

		// We need a texture we can render to here, then we copy it to RAM.
		D3D10_TEXTURE2D_DESC desc;
		zeroMem(desc);
		desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		desc.Width = UINT(size.w);
		desc.Height = UINT(size.h);
		desc.ArraySize = 1;
		desc.MipLevels = 1;
		desc.BindFlags = D3D10_BIND_RENDER_TARGET;
		desc.Usage = D3D10_USAGE_DEFAULT;
		desc.SampleDesc.Count = 1;
		desc.SampleDesc.Quality = 0;

		HRESULT r = device->device->CreateTexture2D(&desc, NULL, &grabSurface.v);
		verify(device->e, r, S("Failed to create a temporary texture: "));

		// Get the GI surface and create a renderer.
		ComPtr<IDXGISurface> surface;
		r = grabSurface->QueryInterface(__uuidof(IDXGISurface), (void **)&surface.v);
		verify(device->e, r, S("Failed to get the surface for the texture: "));

		renderTarget.clear();
		D2D1_RENDER_TARGET_PROPERTIES props = {
			D2D1_RENDER_TARGET_TYPE_DEFAULT,
			{ DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED },
			0, 0, // DPI -- TODO: We can re-create render targets depending on DPI here to avoid scaling!
			D2D1_RENDER_TARGET_USAGE_NONE,
			D2D1_FEATURE_LEVEL_DEFAULT, // Use _DEFAULT?
		};
		r = device->factory->CreateDxgiSurfaceRenderTarget(surface.v, &props, &renderTarget.v);
		verify(device->e, r, S("Failed to create a render target: "));

		this->size = size;
		this->scale = scale;
	}

	Image *D2DSurface::grabImage(Engine &e) {
		if (!grabSurface.v)
			throw new (e) GuiError(S("'prepareGrab' was not called before trying to grab an image!"));

		// Deallocate the render target in case it locks some surface for us.
		renderTarget.clear();

		// Create a staging texture for transferring to the CPU:
		D3D10_TEXTURE2D_DESC desc;
		zeroMem(desc);
		desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		desc.Width = UINT(size.w);
		desc.Height = UINT(size.h);
		desc.ArraySize = 1;
		desc.MipLevels = 1;
		desc.Usage = D3D10_USAGE_STAGING;
		desc.CPUAccessFlags = D3D10_CPU_ACCESS_READ;
		desc.SampleDesc.Count = 1;
		desc.SampleDesc.Quality = 0;

		ComPtr<ID3D10Texture2D> texture;
		HRESULT r = device->device->CreateTexture2D(&desc, NULL, &texture.v);
		verify(e, r, S("Failed to create a temporary texture: "));

		// Copy data to the new texture.
		device->device->CopyResource(texture.v, grabSurface.v);
		grabSurface.clear();

		// Finally, we can move data to our Image.
		Image *output = new (e) Image(size);

		D3D10_MAPPED_TEXTURE2D mapped;
		r = texture->Map(0, D3D10_MAP_READ, 0, &mapped);
		verify(e, r, S("Failed to map the surface: "));

		for (Nat y = 0; y < output->height(); y++) {
			memcpy(output->buffer(0, y), (byte *)mapped.pData + mapped.RowPitch*y, output->stride());
		}

		texture->Unmap(0);


		return output;
	}


}

#endif
