module coneneko.rendertarget;
import
	coneneko.unit,
	coneneko.glext,
	std.string,
	coneneko.texture,
	opengl,
	coneneko.rgba,
	coneneko.scenegraph;

///
interface RenderTarget
{
	void draw(SceneGraph a); ///
}

///
class FrameBufferObject : Texture, RenderTarget
{
	private uint fbHandle;
	private Renderbuffer depthBuffer;
	private ViewPort viewPort;
	
	///
	this(uint width, uint height, uint index, bool enableDepthBuffer)
	{
		super(width, height, null, index);
		
		if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_UNSUPPORTED_EXT)
		{
			throw new Error("GL_FRAMEBUFFER_UNSUPPORTED_EXT");
		}
		else if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT)
		{
			throw new Error("GL_FRAMEBUFFER_COMPLETE_EXT failed");
		}
		
		glGenFramebuffersEXT(1, &fbHandle);
		
		if (enableDepthBuffer) depthBuffer = new Renderbuffer(this, width, height);
		
		viewPort = new ViewPort(width, height);
	}
	
	~this()
	{
		delete depthBuffer;
		glDeleteFramebuffersEXT(1, &fbHandle);
	}
	
	void draw(SceneGraph a)
	in
	{
		assert(a.root !is null, "scene graph has not root");
		assert(a.isTree(a.root), "scene graph is not tree");
		foreach (SceneGraph v; a) assert(false, "scene graph has scene graph");
	}
	body
	{
		attachFramebuffer();
		viewPort.attach();
		a.callTree(
			a.root,
			delegate void(Object obj, bool into)
			{
				auto unit = cast(Unit)obj;
				if (unit !is null) into ? unit.attach() : unit.detach();
			}
		);
		viewPort.detach();
		detachFramebuffer();
	}
	
	protected void attachFramebuffer(ubyte attachmentIndex = 0) ///
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbHandle);
		glFramebufferTexture2DEXT(
			GL_FRAMEBUFFER_EXT,
			GL_COLOR_ATTACHMENT0_EXT + attachmentIndex,
			GL_TEXTURE_2D,
			handle,
			0
		);
	}
	
	protected void detachFramebuffer() ///
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	}
	
	protected override int internalformat() /// GL_RGBA8
	{
		return GL_RGBA8;
	}
}

/// fboのdepth
class Renderbuffer
{
	invariant()
	{
		assert(emptyGlError());
	}
	
	private uint depthHandle;
	
	///
	this(FrameBufferObject owner, uint width, uint height)
	{
		glGenRenderbuffersEXT(1, &depthHandle);
		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthHandle);
		glRenderbufferStorageEXT(
			GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24_ARB, width, height
		);
		owner.attachFramebuffer();
		glFramebufferRenderbufferEXT(
			GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthHandle
		);
		owner.detachFramebuffer();
		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
	}
	
	~this()
	{
		glDeleteRenderbuffersEXT(1, &depthHandle);
	}
}

///
class FrameBufferObjectRgba32f : FrameBufferObject
{
	///
	this(uint width, uint height, uint index, bool enableDepthBuffer)
	{
		super(width, height, index, enableDepthBuffer);
	}
	
	override protected int internalformat() /// GL_RGBA32F_ARB
	{
		return GL_RGBA32F_ARB;
	}
	
	override protected uint dataType() /// GL_FLOAT
	{
		return GL_FLOAT;
	}
	
	override Rgba rgba() /// 使えなくなる、代わりはfloatPixels
	{
		throw new Error("FrameBufferObjectRgba32f.rgba");
	}
	
	/// float[4] rgbaで、4倍のサイズ
	float[] floatPixels()
	{
		float[] result = new float[width * height * 4];
		attach();
		glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, dataType, result.ptr);
		detach();
		return result;
	}
}
