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

#ifdef GUI_WIN32

namespace gui {

	D3DSurface::D3DSurface(Size size, Float scale, DXDevice *device, Nat id,
						const ComPtr<ID3D10Device1> &d3d, const ComPtr<IDXGISwapChain> &swap)
		: Surface3D(size, scale), deviceId(id), device(device), d3d(d3d), swapChain(swap) {

		createTarget();
	}

	D3DSurface::~D3DSurface() {}

	Graphics3D *D3DSurface::createGraphics(Engine &e) {
		return new (e) D3DGraphics(*this, deviceId);
	}

	void D3DSurface::resize(Size size, Float scale) {
		// We need to release the target view first:
		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: "));

		createTarget();

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

	void D3DSurface::createTarget() {
		ComPtr<ID3D10Texture2D> backBuffer;
		HRESULT ok = swapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (void **)&backBuffer.v);
		verify(device->e, ok, S("Failed to get the back buffer from the swap chain: "));

		ok = d3d->CreateRenderTargetView(backBuffer.v, NULL, &renderTarget.v);
		verify(device->e, ok, S("Failed to create a render target for 3D rendering: "));

		createDepthStencil(backBuffer.v);
	}

	void D3DSurface::createDepthStencil(ID3D10Texture2D *backBuffer) {
		// Create the depth buffer texture.
		// TODO: We might want to support stencil as well as depth?
		D3D10_TEXTURE2D_DESC depthDesc;
		backBuffer->GetDesc(&depthDesc); // Get size of back buffer.
		depthDesc.MipLevels = 1;
		depthDesc.ArraySize = 1;
		depthDesc.Format = DXGI_FORMAT_D32_FLOAT;
		depthDesc.SampleDesc.Count = 1;
		depthDesc.SampleDesc.Quality = 0;
		depthDesc.Usage = D3D10_USAGE_DEFAULT;
		depthDesc.BindFlags = D3D10_BIND_DEPTH_STENCIL;
		depthDesc.CPUAccessFlags = 0;
		depthDesc.MiscFlags = 0;

		depthStencil.clear();
		depthTexture.clear();
		HRESULT ok = d3d->CreateTexture2D(&depthDesc, NULL, &depthTexture.v);
		verify(device->e, ok, S("Failed to create depth texture."));

		ok = d3d->CreateDepthStencilView(depthTexture.v, NULL, &depthStencil.v);
		verify(device->e, ok, S("Failed to create a depth stencil."));
	}

	Surface::PresentStatus D3DSurface::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 D3DSurface::setTargets() {
		D3D10_VIEWPORT viewport;
		zeroMem(viewport);
		viewport.TopLeftX = 0;
		viewport.TopLeftY = 0;
		viewport.Width = (UINT)size.w;
		viewport.Height = (UINT)size.h;
		viewport.MinDepth = 0;
		viewport.MaxDepth = 1;
		d3d->RSSetViewports(1, &viewport);

		d3d->OMSetRenderTargets(1, &renderTarget.v, depthStencil.v);
	}

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

		// We need a texture we can render to and then copy 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 = d3d->CreateTexture2D(&desc, NULL, &grabSurface.v);
		verify(device->e, r, S("Failed to create a temporary texture: "));

		renderTarget.clear();

		r = d3d->CreateRenderTargetView(grabSurface.v, NULL, &renderTarget.v);
		verify(device->e, r, S("Failed to create a render target: "));

		// We also need to resize depth and stencil buffers.
		createDepthStencil(grabSurface.v);

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

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

		// Deallocate render target in case it locks some surface for us.
		renderTarget.clear();
		depthStencil.clear();
		depthTexture.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 = d3d->CreateTexture2D(&desc, NULL, &texture.v);
		verify(e, r, S("Failed to create a temporary texture: "));

		// Copy data to the new texture.
		d3d->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());

			// For compatibility with OpenGL, we set the alpha channel to opaque.
			for (Nat x = 0; x < output->width(); x++) {
				output->buffer(x, y)[3] = 0xFF;
			}
		}

		texture->Unmap(0);

		return output;
	}

}

#endif
