package com.supasulley.graphics.framebuffers;

import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_REPEAT;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glClearColor;
import static org.lwjgl.opengl.GL11.glDeleteTextures;
import static org.lwjgl.opengl.GL11.glGenTextures;
import static org.lwjgl.opengl.GL11.glTexParameteri;
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER;
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_COMPLETE;
import static org.lwjgl.opengl.GL30.GL_RENDERBUFFER;
import static org.lwjgl.opengl.GL30.glBindFramebuffer;
import static org.lwjgl.opengl.GL30.glBindRenderbuffer;
import static org.lwjgl.opengl.GL30.glCheckFramebufferStatus;
import static org.lwjgl.opengl.GL30.glDeleteFramebuffers;
import static org.lwjgl.opengl.GL30.glDeleteRenderbuffers;
import static org.lwjgl.opengl.GL30.glGenFramebuffers;
import static org.lwjgl.opengl.GL30.glGenRenderbuffers;

import org.lwjgl.opengl.GL11;

import com.supasulley.graphics.mesh.Mesh;
import com.supasulley.input.InputHandler;
import com.supasulley.input.InputHandler.ResolutionUtils;

/**
 * A framebuffer contains a texture and a renderbuffer, and implementations of this class choose which buffers to give to each.
 */
public abstract class Framebuffer implements ResizeCallback {
	
	private static Framebuffer active;
	
	protected int framebuffer, renderbuffer, texture, textureTarget;
	private int width, height;
	
	/**
	 * Call build() at the end of this method.
	 * 
	 * @param textureTarget
	 */
	protected Framebuffer(int textureTarget)
	{
		this.textureTarget = textureTarget;
		this.width = InputHandler.getResolutionUtils().getResolutionWidth();
		this.height = InputHandler.getResolutionUtils().getResolutionHeight();
		
		// Listen for when the resolution changes
		ResolutionUtils.addResolutionListener(this);
	}
	
	protected void build()
	{
		createBlank();
		checkStatus(textureTarget);
	}
	
	protected void createBlank()
	{
		framebuffer = glGenFramebuffers();
		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
		
		texture = glGenTextures();
		glBindTexture(textureTarget, texture);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		
		renderbuffer = glGenRenderbuffers();
		glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
	}
	
	private void checkStatus(int textureTarget)
	{
		if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
		{
			throw new IllegalStateException("Framebuffer is not complete: " + this);
		}
		
		// Unbind all
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		glBindTexture(textureTarget, 0);
		glBindRenderbuffer(GL_RENDERBUFFER, 0);
	}
	
	/**
	 * Binds this framebuffer to a particular target. Does not set this as the active framebuffer.
	 * 
	 * @param target the target to bind this framebuffer to
	 */
	public void bindTarget(int target)
	{
		glBindFramebuffer(target, framebuffer);
	}
	
	/**
	 * Binds this framebuffer and sets it as the active framebuffer.
	 */
	public void bind()
	{
		active = this;
		bindTarget(GL_FRAMEBUFFER);
	}
	
	public void resize(int width, int height)
	{
		// Delete old framebuffer
		destroy();
		
		this.width = width;
		this.height = height;
		
		// Create a new one
		build();
	}
	
	public void renderAsQuad()
	{
		glBindTexture(GL_TEXTURE_2D, texture);
		Mesh.renderQuad();
		glBindTexture(GL_TEXTURE_2D, 0);
	}
	
	public void destroy()
	{
		glDeleteFramebuffers(framebuffer);
		glDeleteTextures(texture);
		glDeleteRenderbuffers(renderbuffer);
	}
	
	public int getFramebuffer()
	{
		return framebuffer;
	}
	
	public int getRenderbuffer()
	{
		return renderbuffer;
	}
	
	public int getTexture()
	{
		return texture;
	}
	
	public int getWidth()
	{
		return width;
	}
	
	public int getHeight()
	{
		return height;
	}
	
	/**
	 * @return the currently bound framebuffer
	 */
	public static Framebuffer active()
	{
		return active;
	}
	
	/**
	 * Unbinds this framebuffer by binding 0 to GL_FRAMEBUFFER. Sets the active framebuffer to null.
	 */
	public static void unbind()
	{
		active = null;
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
	}
	
	@Override
	public final void onResize(int width, int height)
	{
		resize(width, height);
		
		bind();
		glClearColor(0, 0, 0, 0);
		glClear(GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
		Framebuffer.unbind();
	}
	
	@Override
	public String toString()
	{
		return this.getClass().getSimpleName() + "@" + this.hashCode() + " [" + width + ", " + height + "]";
	}
}


GLFW / LWJGL. Why does glClearColor(0, 0, 0, 0);
		glClear(GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
become required to not show tearing? Doesn't the framebuffer get completely destroyed and nothing should be retained when resized?