#include "stdafx.h"
#include "Graphics.h"
#include "Surface.h"
#include "Exception.h"
#include "Shader.h"
#include "Buffer.h"
#include "Texture.h"

#ifdef GUI_WIN32

namespace gui {


	D3DGraphics::D3DGraphics(D3DSurface &surface, Nat id) : Graphics3D(id), surface(surface) {
		renderParams = new (this) D3DParams(this);
	}

	void D3DGraphics::beforeRender() {
	}

	Bool D3DGraphics::afterRender() {
		return true;
	}

	void D3DGraphics::fill(Color color) {
		FLOAT c[4] = {
			color.r, color.g, color.b, color.a
		};
		surface.dev()->ClearRenderTargetView(surface.color(), c);
	}

	void D3DGraphics::clearDepth() {
		FLOAT value = 1.0f;
		surface.dev()->ClearDepthStencilView(surface.depth(), D3D10_CLEAR_DEPTH, value, 0);
	}


	Graphics3D::RenderParams *D3DGraphics::beginRender(Pipeline *pipeline) {
		renderParams->clear();
		renderParams->pipeline = (D3DPipeline *)pipeline->forGraphics(this);
		renderParams->pipeline->update(this, pipeline, DirtyRange(0));

		return renderParams;
	}

	void D3DGraphics::D3DParams::bind(Nat id, VertexBufferBase *vbuf) {
		D3DBuffer *buffer = (D3DBuffer *)vbuf->forGraphics(graphics);

		// TODO: Maybe set multiple buffers at the same time?

		ID3D10Buffer *buffers[1] = { buffer->buffer.v };
		UINT strides[1] = { UINT(vbuf->elemSize()) };
		UINT offsets[1] = { 0 };
		graphics->surface.dev()->IASetVertexBuffers(id, 1, buffers, strides, offsets);

		if (!indexed) {
			if (numBuffers == 0)
				numVertices = vbuf->count();
			else
				numVertices = min(numVertices, vbuf->count());
		}
	}

	void D3DGraphics::D3DParams::bind(IndexBufferBase *ibuf) {
		D3DBuffer *buffer = (D3DBuffer *)ibuf->forGraphics(graphics);

		graphics->surface.dev()->IASetIndexBuffer(buffer->buffer.v, DXGI_FORMAT_R32_UINT, 0);
		topology = ibuf->primitive;
		indexed = true;
		numVertices = ibuf->count();
	}

	void D3DGraphics::D3DParams::primitive(DrawPrimitive p) {
		topology = p;
	}

	void D3DGraphics::D3DParams::clear() {
		// Reset parts of the device/object here.
		pipeline = null;
		numVertices = 0;
		numBuffers = 0;
		topology = drawTriangles;
		indexed = false;
	}

	static D3D10_PRIMITIVE_TOPOLOGY d3dTopology(DrawPrimitive p) {
		switch (p) {
		case drawTriangles:
			return D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
		case drawTriangleStrip:
			return D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
		case drawLines:
			return D3D10_PRIMITIVE_TOPOLOGY_LINELIST;
		case drawLineStrip:
			return D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP;
		case drawPoints:
			return D3D10_PRIMITIVE_TOPOLOGY_POINTLIST;
		default:
			return D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
		}
	}

	void D3DGraphics::D3DParams::done() {
		graphics->surface.setTargets();

		ID3D10Device *device = graphics->surface.dev();

		device->IASetPrimitiveTopology(d3dTopology(topology));

		const vector<ComPtr<ID3D10Buffer>> &cbuffers = pipeline->constantBuffers;
		const vector<ComPtr<ID3D10ShaderResourceView>> &resources = pipeline->resourceViews;

		device->IASetInputLayout(pipeline->inputLayout.v);
		device->VSSetShader(pipeline->vertex.v);
		device->VSSetConstantBuffers(0, UINT(cbuffers.size()), &cbuffers[0].v);
		if (!resources.empty())
			device->VSSetShaderResources(0, UINT(resources.size()), &resources[0].v);

		device->PSSetShader(pipeline->pixel.v);
		device->PSSetConstantBuffers(0, UINT(cbuffers.size()), &cbuffers[0].v);
		if (!resources.empty())
			device->PSSetShaderResources(0, UINT(resources.size()), &resources[0].v);

		if (indexed) {
			device->DrawIndexed(numVertices, 0, 0);
		} else {
			device->Draw(numVertices, 0);
		}

		// Clear binding so to not accidentally keep it alive:
		device->IASetIndexBuffer(NULL, DXGI_FORMAT_R32_UINT, 0);
		// TODO: Un-bind vertex buffers and textures as well?
	}

	static void cleanupCOM(void *obj) {
		IUnknown *com = (IUnknown *)obj;
		::release(com);
	}

	void D3DGraphics::createResource(Resource3D *resource, void *&data, Resource3D::Cleanup &cleanup) {
		if (Pipeline *p = as<Pipeline>(resource)) {
			D3DVertexShader *vs = (D3DVertexShader *)p->vertexShader()->forGraphics(this);
			D3DPixelShader *ps = (D3DPixelShader *)p->pixelShader()->forGraphics(this);

			data = new D3DPipeline(vs, ps, p);
			cleanup = &D3DPipeline::cleanup;
		} else if (VertexShader *vs = as<VertexShader>(resource)) {
			// Note: We *could* save the compiled code somewhere device-independent...
			data = new D3DVertexShader(vs, surface);
			cleanup = &D3DVertexShader::cleanup;
		} else if (PixelShader *ps = as<PixelShader>(resource)) {
			// Note: We *could* save the compiled code somewhere device-independent...
			data = new D3DPixelShader(ps, surface);
			cleanup = &D3DPixelShader::cleanup;
		} else if (VertexBufferBase *vb = as<VertexBufferBase>(resource)) {
			// We can not create an empty vertex buffer. So instead we just ask it to resize to one
			// element and create it with one element that we never use.
			size_t elements = vb->count();
			if (elements == 0) {
				elements = 1;
				vb->reserve(1);
			}

			data = new D3DBuffer(engine(), surface.dev(), D3D10_BIND_VERTEX_BUFFER,
								vb->usage, vb->buffer(), vb->elemSize() * elements);
			cleanup = &D3DBuffer::cleanup;
		} else if (IndexBufferBase *ib = as<IndexBufferBase>(resource)) {
			// We can not create an empty vertex buffer. So instead we just ask it to resize to one
			// element and create it with one element that we never use.
			size_t elements = ib->count();
			if (elements == 0) {
				elements = 1;
				ib->reserve(1);
			}

			data = new D3DBuffer(engine(), surface.dev(), D3D10_BIND_INDEX_BUFFER,
								ib->usage, ib->buffer(), elements * sizeof(Nat));
			cleanup = &D3DBuffer::cleanup;
		} else if (Texture *t = as<Texture>(resource)) {
			data = new D3DTexture(t, surface.dev());
			cleanup = &D3DTexture::cleanup;
		} else {
			throw new (this) NotSupported(S("Can not create this resource type."));
		}
	}

	void D3DGraphics::updateResource(Resource3D *resource, void *data, const DirtyRange &dirty) {
		if (VertexBufferBase *vb = as<VertexBufferBase>(resource)) {
			if (dirty.end > dirty.begin) {
				D3DBuffer *buffer = (D3DBuffer *)data;
				size_t elemSize = vb->elemSize();
				size_t elements = max(Nat(1), vb->count());
				buffer->update(engine(), vb->usage, vb->buffer(), elemSize * elements,
							dirty.begin * elemSize, dirty.end * elemSize);
			}
		} else if (IndexBufferBase *ib = as<IndexBufferBase>(resource)) {
			if (dirty.end > dirty.begin) {
				D3DBuffer *buffer = (D3DBuffer *)data;
				size_t elements = max(Nat(1), vb->count());
				buffer->update(engine(), vb->usage, vb->buffer(), elements * sizeof(Nat),
							dirty.begin * sizeof(Nat), dirty.end * sizeof(Nat));
			}
		} else if (Pipeline *p = as<Pipeline>(resource)) {
			((D3DPipeline *)data)->update(this, p, dirty);
		} else if (Texture *t = as<Texture>(resource)) {
			((D3DTexture *)data)->update(t, dirty);
		} else {
			throw new (this) NotSupported(S("Can not update this resource type."));
		}
	}

}

#else

namespace gui {
	D3DGraphics::D3DGraphics(D3DSurface &surface, Nat id) : Graphics3D(id), surface(surface) {
		renderParams = new (this) D3DParams(this);
	}

	GRAPHICS3D_DEFINES(D3DGraphics, D3DGraphics::D3DParams, renderParams)
}

#endif
