#pragma once
#include "Core/Array.h"
#include "Shader.h"
#include "ShaderBuffer.h"
#include "Texture.h"

namespace gui {

	/**
	 * A pipeline - i.e. a set of shaders that draws to the screen, and the state associated with
	 * the shaders, such as constant buffers for them.
	 *
	 * Note: Buffer #0 is special, it is assumed to always be dirty, regardless of the state of the
	 * dirty flags for it.
	 */
	class Pipeline : public ShaderBuffer {
		STORM_ABSTRACT_CLASS;
	public:
		// Create. Specify number of buffers as well as their sizes, and the nu of texture units.
		STORM_CTOR Pipeline(Array<Nat> *buffers, Nat textures);

		// Get the vertex shader.
		virtual VertexShader *STORM_FN vertexShader() ABSTRACT;

		// Get the pixel shader.
		virtual PixelShader *STORM_FN pixelShader() ABSTRACT;

		// C++ access to buffer data:
		inline size_t bufferCount() const {
			return buffers->count();
		}
		inline const GcArray<Byte> *buffer(size_t id) const {
			return buffers->v[id];
		}

		// C++ access to texture slots:
		inline size_t textureCount() const {
			return textures ? textures->count() : 0;
		}
		inline const Texture *texture(size_t slot) const {
			return textures->v[slot];
		}
		inline Texture *texture(size_t slot) {
			return textures->v[slot];
		}

	protected:
		// Access the constant buffer 'id'.
		UNKNOWN(PTR_NOGC) void *STORM_FN at(Nat id) {
			if (id >= buffers->count())
				throwBufferOutOfBounds(id);
			return buffers->v[id]->v;
		}

		// Access the constant buffer 'id', mark is at modified.
		UNKNOWN(PTR_NOGC) void *STORM_FN atMark(Nat id) {
			if (id >= buffers->count())
				throwBufferOutOfBounds(id);
			markDirty(id);
			return buffers->v[id]->v;
		}

		// Access a particular texture.
		Texture *STORM_FN textureAt(Nat slot) {
			if (!textures || slot >= textures->count())
				throwTextureOutOfBounds(slot);
			return textures->v[slot];
		}

		// Set a particular texture.
		void STORM_FN textureSet(Nat slot, Texture *t) {
			if (!textures || slot >= textures->count())
				throwTextureOutOfBounds(slot);
			textures->v[slot] = t;
			// Textures have ids above the buffers.
			markDirty(Nat(buffers->count() + slot));
		}

	private:
		// Data for constant buffers. The different constant buffers are treated as individual units
		// that are updated as a group.
		typedef GcArray<Byte> CBuffer;
		GcArray<CBuffer *> *buffers;

		// Texture units.
		GcArray<Texture *> *textures;

		// Helper to throw out of bounds error, and to keep it of the hot path.
		void throwBufferOutOfBounds(Nat index) const;
		void throwTextureOutOfBounds(Nat index) const;
	};

}
