module coneneko.vertexbuffer;
import
	coneneko.unit,
	coneneko.glext,
	std.string,
	opengl,
	coneneko.math,
	coneneko.pcnta;

/// 使ったらdelete
class VertexBuffer : Unit
{
	invariant()
	{
		assert(emptyGlError());
	}
	
	private uint handle;
	
	///
	this(float[] data)
	{
		glGenBuffersARB(1, &handle);
		bindBuffer(); // overrideされたものを間違えて呼ばないように注意
		glBufferDataARB(
			GL_ARRAY_BUFFER_ARB,
			float.sizeof * data.length,
			data.ptr,
			GL_STATIC_DRAW_ARB
		);
	}
	
	/// dataをN次元ベクトルとして扱う、vecN, float = 1, vec2 = 2, vec3 = 3, vec4 = 4
	this(Vector[] data, ubyte vecN)
	in
	{
		assert(1 <= vecN && vecN <= 4);
	}
	body
	{
		float[] floats;
		foreach (v; data)
		{
			floats ~= v.x;
			if (2 <= vecN) floats ~= v.y;
			if (3 <= vecN) floats ~= v.z;
			if (4 == vecN) floats ~= v.w;
		}
		this(floats);
	}
	
	~this()
	{
		glDeleteBuffersARB(1, &handle);
	}
	
	void attach()
	{
		bindBuffer();
	}
	
	private void bindBuffer()
	{
		glBindBufferARB(GL_ARRAY_BUFFER_ARB, handle);
	}
	
	void detach() {}
}

///
class PositionBuffer : VertexBuffer
{
	private const uint vertexLength;
	
	/// float3
	this(float[] positions)
	{
		super(positions);
		vertexLength = positions.length / 3;
	}
	
	///
	this(Vector[] vec3Array)
	{
		super(vec3Array, 3);
		vertexLength = vec3Array.length;
	}
	
	override void attach()
	{
		super.attach();
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, null);
		
		glDrawArrays(GL_TRIANGLES, 0, vertexLength);
	}
	
	override void detach()
	{
		glDisableClientState(GL_VERTEX_ARRAY);
		super.detach();
	}
}

///
class ColorBuffer : VertexBuffer
{
	/// float4
	this(float[] rgba)
	{
		super(rgba);
	}
	
	///
	this(Vector[] vec4Array)
	{
		super(vec4Array, 4);
	}
	
	override void attach()
	{
		super.attach();
		glEnableClientState(GL_COLOR_ARRAY);
		glColorPointer(4, GL_FLOAT, 0, null);
	}
	
	override void detach()
	{
		glDisableClientState(GL_COLOR_ARRAY);
		super.detach();
	}
}

///
class NormalBuffer : VertexBuffer
{
	/// float3
	this(float[] normals)
	{
		super(normals);
	}
	
	///
	this(Vector[] vec3Array)
	{
		super(vec3Array, 3);
	}
	
	override void attach()
	{
		super.attach();
		glEnableClientState(GL_NORMAL_ARRAY);
		glNormalPointer(GL_FLOAT, 0, null);
	}
	
	override void detach()
	{
		glDisableClientState(GL_NORMAL_ARRAY);
		super.detach();
	}
}

///
class TexCoordBuffer : VertexBuffer
{
	/// float2
	this(float[] texCoords)
	{
		super(texCoords);
	}
	
	///
	this(Vector[] vec2Array)
	{
		super(vec2Array, 2);
	}
	
	override void attach()
	{
		super.attach();
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2, GL_FLOAT, 0, null);
	}
	
	override void detach()
	{
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		super.detach();
	}
}

///
class AttributeBuffer : VertexBuffer
{
	private const uint vecN;
	uint location;
	
	/// vecN, float = 1, vec2 = 2, vec3 = 3, vec4 = 4
	/// location = shader.glGetAttribLocationARB
	this(float[] data, uint vecN, uint location = 0)
	{
		super(data);
		this.vecN = vecN;
		this.location = location;
	}
	
	/// ditto
	this(Vector[] data, uint vecN, uint location = 0)
	{
		super(data, vecN);
		this.vecN = vecN;
		this.location = location;
	}
	
	override void attach()
	in
	{
		assert(location != 0);
	}
	body
	{
		super.attach();
		glEnableVertexAttribArrayARB(location);
		glVertexAttribPointerARB(location, vecN, GL_FLOAT, false, 0, null);
	}
	
	override void detach()
	{
		glDisableVertexAttribArrayARB(location);
		super.detach();
	}
}

///
class PcntaBuffer : Unit
{
	private Unit[] units;
	private AttributeBuffer attributes;
	
	///
	this(Pcnta pcnta, uint attributeLocation = 0)
	in
	{
		assert(pcnta.canUseAsPcntaBuffer);
	}
	body
	{
		units ~= new PositionBuffer(pcnta.positions);
		units ~= new ColorBuffer(pcnta.colors);
		units ~= new NormalBuffer(pcnta.normals);
		units ~= new TexCoordBuffer(pcnta.texCoords);
		attributes = pcnta.hasAttribute
			? new AttributeBuffer(pcnta.attributes, 3, attributeLocation)
			: null;
	}
	
	void attach()
	{
		if (attributes !is null) attributes.attach();
		foreach_reverse (v; units) v.attach();
		foreach (v; units) v.detach();
		if (attributes !is null) attributes.detach();
	}
	
	void detach() {}
	
	void attributeLocation(uint a)
	in
	{
		assert(attributes !is null);
	}
	body
	{
		attributes.location = a;
	}
}

void draw(Pcnta pcnta, uint attributeLocation = 0)
in
{
	assert(pcnta.canUseAsPcntaBuffer);
	if (pcnta.hasAttribute) assert(attributeLocation != 0);
}
body
{
	glBegin(GL_TRIANGLES);
	for (int i = 0; i < pcnta.positions.length; i++)
	{
		if (pcnta.hasAttribute)
		{
			auto a = pcnta.attributes[i];
			glVertexAttrib3fARB(attributeLocation, a.x, a.y, a.z);
		}
		auto t = pcnta.texCoords[i];
		auto n = pcnta.normals[i];
		auto c = pcnta.colors[i];
		auto p = pcnta.positions[i];
		glTexCoord2f(t.x, t.y);
		glNormal3f(n.x, n.y, n.z);
		glColor4f(c.x, c.y, c.z, c.w);
		glVertex3f(p.x, p.y, p.z);
	}
	glEnd();
}
