module coneneko.texture;
import
	coneneko.unit,
	coneneko.rgba,
	coneneko.glext,
	opengl,
	coneneko.math;

///
class Texture : Unit
{
	invariant()
	{
		assert(emptyGlError());
	}
	
	///
	protected uint handle;
	
	private int stock;
	private Unit enableTexture;
	private uint index;
	
	///
	this(string fileName, uint index)
	{
		this(correctTextureSize(new Rgba(fileName)), index);
	}
	
	///
	this(uint width, uint height, uint[] pixels, uint index)
	{
		this(new Rgba(width, height, pixels), index);
	}
	
	///
	this(Rgba rgba, uint index)
	in
	{
		assert(emptyGlError());
	}
	out
	{
		assert(emptyGlError());
	}
	body
	{
		this.index = index;
		enableTexture = new Enable(GL_TEXTURE_2D);
		if (!rgba.canUseAsTexture) throw new Error("Texture.this texture size");
		glGenTextures(1, &handle);
		
		attach();
		initializeTexParameters();
		sendTexture(rgba);
		detach();
	}
	
	~this()
	in
	{
		assert(emptyGlError());
	}
	out
	{
		assert(emptyGlError());
	}
	body
	{
		glDeleteTextures(1, &handle);
	}
	
	// rendertargetはnearestに、textureは大きく
	protected void initializeTexParameters()
	{
		// GL_REPEAT(default) or GL_CLAMP
		//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		
		// GL_NEAREST or GL_LINEAR
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	}
	
	private void sendTexture(Rgba rgba)
	{
		glTexImage2D(
			GL_TEXTURE_2D, 0, internalformat,
			rgba.width, rgba.height,
			0, GL_RGBA, dataType,
			cast(void*)rgba.pixels.ptr
		);
	}
	
	protected int internalformat() /// GL_COMPRESSED_RGBA_ARB
	{
		return GL_COMPRESSED_RGBA_ARB;
	}
	
	protected uint dataType() /// GL_UNSIGNED_BYTE
	{
		return GL_UNSIGNED_BYTE;
	}
	
	void attach()
	in
	{
		assert(emptyGlError());
	}
	out
	{
		assert(emptyGlError());
	}
	body
	{
		glActiveTextureARB(GL_TEXTURE0_ARB + index);
		glGetIntegerv(GL_TEXTURE_BINDING_2D, &stock);
		glBindTexture(GL_TEXTURE_2D, handle);
		enableTexture.attach();
	}
	
	void detach()
	in
	{
		assert(emptyGlError());
	}
	out
	{
		assert(emptyGlError());
	}
	body
	{
		glActiveTextureARB(GL_TEXTURE0_ARB + index);
		enableTexture.detach();
		glBindTexture(GL_TEXTURE_2D, stock);
	}
	
	uint width() ///
	{
		int result;
		attach();
		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &result);
		detach();
		return result;
	}
	
	uint height() ///
	{
		int result;
		attach();
		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &result);
		detach();
		return result;
	}
	
	Rgba rgba() ///
	{
		uint[] result = new uint[width *  height];
		attach();
		glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, dataType, result.ptr);
		detach();
		return new Rgba(width, height, result);
	}
	
	UnitDecorator clone() ///
	{
		return new UnitDecorator(this);
	}
	
	Vector size() ///
	{
		return vector(width, height);
	}
}

///
class NoiseTexture : Unit
{
	///
	protected uint handle;
	
	private int stock;
	private Unit enableTexture;
	private const uint index;
	
	private const int WIDTH = 128, HEIGHT = 128, DEPTH = 128;
	
	///
	this(string noiseFileName, uint index = 0)
	{
		ubyte[] pixels = cast(ubyte[])std.file.read(noiseFileName);
		ubyte[] pixelsX4 = new ubyte[pixels.length * 4];
		pixelsX4[] = ubyte.max;
		foreach (i, v; pixels) pixelsX4[4 * i] = v;
		
		this.index = index;
		enableTexture = new Enable(GL_TEXTURE_3D_EXT);
		glGenTextures(1, &handle);
		attach();
		glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_R_EXT, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexImage3DEXT(
			GL_TEXTURE_3D_EXT, 0, GL_RGBA8, WIDTH, HEIGHT, DEPTH,
			0, GL_RGBA, GL_UNSIGNED_BYTE, pixelsX4.ptr
		);
		detach();
	}
	
	~this()
	{
		glDeleteTextures(1, &handle);
	}
	
	const GL_TEXTURE_BINDING_3D = 0x806A;
	
	void attach()
	{
		glActiveTextureARB(GL_TEXTURE0_ARB + index);
		glGetIntegerv(GL_TEXTURE_BINDING_3D, &stock);
		glBindTexture(GL_TEXTURE_3D_EXT, handle);
		enableTexture.attach();
	}
	
	void detach()
	{
		glActiveTextureARB(GL_TEXTURE0_ARB + index);
		enableTexture.detach();
		glBindTexture(GL_TEXTURE_3D_EXT, stock);
	}
}
