package speiger.src.coreengine.rendering.models.frameBuffer;

import java.util.List;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;

import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.rendering.utils.GLStateTracker;

public class FrameBuffer
{
	int id;
	int width;
	int height;
	int samples;
	boolean finished = false;
	List<IFrameAttachment> colors = new ObjectArrayList<>();
	IFrameAttachment depth;
	IFrameAttachment stencil;
	IFrameAttachment stencil_depth;
	
	public FrameBuffer(int width, int height, int samples)
	{
		this.width = width;
		this.height = height;
		this.samples = samples;
	}
	
	public FrameBuffer addColorAttachment(IFrameAttachment attachment)
	{
		if(colors.size() > 32)
		{
			throw new IllegalStateException("Color Attachments are full");
		}
		colors.add(attachment);
		return this;
	}
	
	public FrameBuffer setColorAttachment(int index, IFrameAttachment attachment)
	{
		if(colors.size() <= index)
		{
			throw new IllegalStateException("No Attachment Found!");
		}
		colors.set(index, attachment);
		return this;
	}
	
	public FrameBuffer setDepthAttachment(IFrameAttachment attachment)
	{
		depth = attachment;
		return this;
	}
	
	public FrameBuffer finished()
	{
		if(finished)
		{
			return this;
		}
		finished = true;
		id = GL30.glGenFramebuffers();
		GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, id);
		for(int i = 0,m=colors.size();i<m;i++)
		{
			colors.get(i).init(GL30.GL_COLOR_ATTACHMENT0 + i, width, height, samples);
		}
		if(depth != null)
		{
			depth.init(GL30.GL_DEPTH_ATTACHMENT, width, height, samples);
		}
		if(stencil != null)
		{
			stencil.init(GL30.GL_STENCIL_ATTACHMENT, width, height, samples);
		}
		if(stencil_depth != null)
		{
			stencil_depth.init(GL30.GL_DEPTH_STENCIL_ATTACHMENT, width, height, samples);
		}
		return this;
	}
	
	public int getWidth()
	{
		return width;
	}
	
	public int getHeight()
	{
		return height;
	}
	
	public IFrameAttachment getColorAttachment()
	{
		return colors.get(0);
	}
	
	public IFrameAttachment getColorAttachment(int index)
	{
		return colors.get(index);
	}
	
	public IFrameAttachment getDepthAttachment()
	{
		return depth;
	}
	
	public IFrameAttachment getStencilAttachment()
	{
		return stencil;
	}
	
	public IFrameAttachment getDepthStencilAttachment()
	{
		return stencil_depth;
	}
	
	public void delete()
	{
		delete(true);
	}
	
	public void delete(boolean attachments)
	{
		if(id == 0)
		{
			return;
		}
		if(attachments)
		{
			for(int i = 0,m=colors.size();i<m;i++)
			{
				colors.get(i).delete();
			}
			if(depth != null)
			{
				depth.delete();
			}
			if(stencil != null)
			{
				stencil.delete();
			}
			if(stencil_depth != null)
			{
				stencil_depth.delete();
			}
		}
		GL30.glDeleteFramebuffers(id);
		id = 0;
	}
	
	public boolean isComplete()
	{
		return GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER) == GL30.GL_FRAMEBUFFER_COMPLETE;
	}
	
	public FrameBuffer clearBuffer()
	{
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
		return this;
	}
	
	public FrameBuffer bind()
	{
		GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, id);
		GLStateTracker.instance().viewPort.push(0, 0, width, height);
		return this;
	}
	
	public FrameBuffer bindToRead()
	{
		GL30.glReadBuffer(GL30.GL_COLOR_ATTACHMENT0);
		return this;
	}
	
	public FrameBuffer bindToDraw()
	{
		GL30.glDrawBuffer(GL30.GL_COLOR_ATTACHMENT0);
		return this;
	}
	
	public FrameBuffer unbind()
	{
		GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
		GLStateTracker.instance().viewPort.pop();
		return this;
	}
	
	public FrameBuffer unbindAll()
	{
		GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
		GL30.glDrawBuffer(GL30.GL_COLOR_ATTACHMENT0);
		GL30.glReadBuffer(GL30.GL_COLOR_ATTACHMENT0);
		return this;
	}
	
	public FrameBuffer copyTo(FrameBuffer target, int bits, boolean nearest)
	{
		GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, id);
		GL30.glDrawBuffer(GL30.GL_COLOR_ATTACHMENT0);
		
		GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, target.id);
		GL30.glReadBuffer(GL30.GL_COLOR_ATTACHMENT0);
		
	    GL30.glBlitFramebuffer(0, 0, width, height, 0, 0, target.width, target.height, GL11.GL_COLOR_BUFFER_BIT, nearest ? GL11.GL_NEAREST : GL11.GL_LINEAR);
		return this;
	}
	
	public FrameBuffer copyToScreen(int bits, boolean nearest, int width, int height)
	{
		GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, id);
		GL30.glReadBuffer(GL30.GL_COLOR_ATTACHMENT0);
		
		GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0);
		GL30.glDrawBuffer(GL30.GL_COLOR_ATTACHMENT0);
		
	    GL30.glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL11.GL_COLOR_BUFFER_BIT, nearest ? GL11.GL_NEAREST : GL11.GL_LINEAR);
		return this;
	}
}
