#pragma once
#include "ShaderBuffer.h"
#include "Resource.h"
#include "Enums.h"

namespace gui {

	/**
	 * A buffer of vertices. This is the generic type that is later subclassed in Storm. It assumes
	 * that it never contains pointers.
	 *
	 * Note: This will likely inherit from Resource at some point, since we need to keep track of a
	 * corresponding buffer on the GPU.
	 *
	 * Note: Depending on how the backends look, we might eventually need to know the exact vertex
	 * format as well.
	 */
	class VertexBufferBase : public ShaderBuffer {
		STORM_CLASS;
	public:
		// Intended buffer usage.
		BufferUsage usage;

		// Reserve space for elements.
		void STORM_FN reserve(Nat count);

		// Number of elements.
		inline Nat STORM_FN count() const {
			return data ? Nat(data->filled) : 0;
		}

		// Empty/any?
		inline Bool STORM_FN empty() const { return count() == 0; }
		inline Bool STORM_FN any() const { return count() > 0; }

		// Remove.
		void STORM_FN remove(Nat pos);

		// Get the underlying data buffer.
		const void *buffer() const {
			return data ? addr(0) : null;
		}
		size_t elemSize() const {
			return vertexSize;
		}

	protected:
		// Create an empty buffer. Specify vertex size.
		STORM_CTOR VertexBufferBase(Nat vertexSize);

		// Create a copy of another buffer.
		STORM_CTOR VertexBufferBase(VertexBufferBase *orig);

		// Get the address of an element.
		UNKNOWN(PTR_NOGC) void *STORM_FN at(Nat id) {
			if (id >= count())
				throwOutOfBounds(id);
			return addr(id);
		}

		// Same as 'at' but additionally marks the element as 'dirty'.
		UNKNOWN(PTR_NOGC) void *STORM_FN atMark(Nat id) {
			if (id >= count())
				throwOutOfBounds(id);
			markDirty(id);
			return addr(id);
		}

		// Check if 'id' is out of bounds.
		void STORM_FN checkBounds(Nat id) {
			if (id >= count())
				throwOutOfBounds(id);
		}

		// Add an element. Returns an index to the element so that it can be initialized.
		Nat STORM_FN push();

		// Insert an element. Returns an index to the element so that it can be initialized.
		Nat STORM_FN insert(Nat pos);

	private:
		// Size of elements in 'data'.
		Nat vertexSize;

		// The data array. Indexed in bytes for convenience. Note that 'filled' is number of
		// *elements*, not number of *bytes*.
		GcArray<Byte> *data;

		// Helper to get exception code out of the hot path.
		void throwOutOfBounds(Nat index) const;

		// Grow storage to fit at least one more element.
		void grow();

		// Get the address of an element.
		inline Byte *addr(Nat id) const {
			return data->v + id*vertexSize;
		}
	};

}
