package electrosphere.renderer.framebuffer;

import electrosphere.engine.Globals;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.texture.Texture;

import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;

import static org.lwjgl.opengl.GL11.GL_DEPTH_COMPONENT;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_LINEAR;
import static org.lwjgl.opengl.GL11.GL_NEAREST;
import static org.lwjgl.opengl.GL11.GL_NONE;
import static org.lwjgl.opengl.GL11.GL_RED;
import static org.lwjgl.opengl.GL11.GL_RGB;
import static org.lwjgl.opengl.GL11.GL_RGBA;
import static org.lwjgl.opengl.GL11.GL_SHORT;
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.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL12.GL_CLAMP_TO_EDGE;
import static org.lwjgl.opengl.GL13.GL_CLAMP_TO_BORDER;
import static org.lwjgl.opengl.GL30.GL_COLOR_ATTACHMENT0;
import static org.lwjgl.opengl.GL30.GL_COLOR_ATTACHMENT1;
import static org.lwjgl.opengl.GL30.GL_DEPTH24_STENCIL8;
import static org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL_ATTACHMENT;
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER;
import static org.lwjgl.opengl.GL30.GL_RENDERBUFFER;
import static org.lwjgl.opengl.GL30.GL_HALF_FLOAT;
import static org.lwjgl.opengl.GL30.glBindRenderbuffer;
import static org.lwjgl.opengl.GL30.glFramebufferRenderbuffer;
import static org.lwjgl.opengl.GL30.glGenRenderbuffers;
import static org.lwjgl.opengl.GL30.glRenderbufferStorage;
import static org.lwjgl.opengl.GL45.glNamedFramebufferDrawBuffer;
import static org.lwjgl.opengl.GL45.glNamedFramebufferDrawBuffers;
import static org.lwjgl.opengl.GL45.glNamedFramebufferReadBuffer;

/**
 * Utilities for framebuffer creation
 */
public class FramebufferUtils {

    public static Texture generateScreenTextureColor(OpenGLState openGLState, int width, int height){
        Texture texture = new Texture();
        texture.glTexImage2D(openGLState, width, height, GL_RGB, GL_UNSIGNED_BYTE);
        texture.setMinFilter(GL_LINEAR);
        texture.setMagFilter(GL_LINEAR);
        //these make sure the texture actually clamps to the borders of the quad
        texture.setWrap(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        texture.setWrap(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        texture.checkStatus(openGLState);
        return texture;
    }

    public static Texture generateScreenTextureColorAlpha(OpenGLState openGLState, int width, int height){
        Texture texture = new Texture();
        texture.glTexImage2D(openGLState, width, height, GL_RGBA, GL_UNSIGNED_BYTE);
        texture.setMinFilter(GL_LINEAR);
        texture.setMagFilter(GL_LINEAR);
        //these make sure the texture actually clamps to the borders of the quad
        texture.setWrap(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        texture.setWrap(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        texture.checkStatus(openGLState);
        return texture;
    }

    public static Texture generateScreenTextureDepth(OpenGLState openGLState, int width, int height){
        Texture texture = new Texture();
        texture.glTexImage2D(openGLState, width, height, GL_DEPTH_COMPONENT, GL_FLOAT);

        texture.checkStatus(openGLState);
        return texture;
    }
    
    
    public static Framebuffer generateScreenTextureFramebuffer(OpenGLState openGLState, int width, int height, Texture colorTexture, Texture depthTexture){
        Framebuffer buffer = new Framebuffer();
        //texture
        // int texture = glGenTextures();
        // glBindTexture(GL_TEXTURE_2D,texture);
        // glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
        // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        // //these make sure the texture actually clamps to the borders of the quad
        // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        //bind texture to fbo
        buffer.setMipMapLevel(0);
        buffer.attachTexture(openGLState,colorTexture);
        buffer.setDepthAttachment(openGLState,depthTexture);
        //check make sure compiled
        buffer.checkStatus();
        return buffer;
    }

    public static Framebuffer generateScreenTextureFramebuffer(OpenGLState openGLState, int width, int height, Texture colorTexture){
        Framebuffer buffer = new Framebuffer();
        //texture
        // int texture = glGenTextures();
        // glBindTexture(GL_TEXTURE_2D,texture);
        // glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
        // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        // //these make sure the texture actually clamps to the borders of the quad
        // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        //bind texture to fbo
        buffer.setMipMapLevel(0);
        buffer.attachTexture(openGLState,colorTexture);
        //check make sure compiled
        buffer.checkStatus();
        return buffer;
    }

    public static Framebuffer generateScreensizeTextureFramebuffer(OpenGLState openGLState){
        Framebuffer buffer = new Framebuffer();
        buffer.bind(openGLState);
        //texture
        Texture texture = new Texture();
        texture.glTexImage2D(openGLState, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE);
        texture.setMinFilter(GL_LINEAR);
        texture.setMagFilter(GL_LINEAR);
        //these make sure the texture actually clamps to the borders of the quad
        texture.setWrap(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        texture.setWrap(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        texture.checkStatus(openGLState);
        //bind texture to fbo
        buffer.setMipMapLevel(0);
        buffer.attachTexture(openGLState,texture);
        //renderbuffer
        int renderBuffer = glGenRenderbuffers();
        glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT); 
        //bind rbo to fbo
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderBuffer);
        //check make sure compiled
        buffer.checkStatus();
        return buffer;
    }
    
    public static Framebuffer generateTextureFramebuffer(OpenGLState openGLState, int width, int height){
        Framebuffer buffer = new Framebuffer();
        buffer.bind(openGLState);
        //texture
        Texture texture = new Texture();
        texture.glTexImage2D(openGLState, width, height, GL_RGBA, GL_UNSIGNED_BYTE);
        texture.setMinFilter(GL_LINEAR);
        texture.setMagFilter(GL_LINEAR);
        //these make sure the texture actually clamps to the borders of the quad
        texture.setWrap(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        texture.setWrap(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        texture.checkStatus(openGLState);
        //bind texture to fbo
        buffer.setMipMapLevel(0);
        buffer.attachTexture(openGLState,texture);
        //renderbuffer
        int renderBuffer = glGenRenderbuffers();
        glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); 
        //bind rbo to fbo
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderBuffer);
        //check make sure compiled
        buffer.checkStatus();
        return buffer;
    }
    
    
    
    
    
    public static Renderbuffer generateScreensizeStencilDepthRenderbuffer(OpenGLState openGLState){
        Renderbuffer buffer = new Renderbuffer();
        buffer.bind();
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT);
        Globals.renderingEngine.checkError();
        return buffer;
    }
    
    
    static int depthMapWidth = 4096;
    static int depthMapHeight = 4096;
    
    public static Framebuffer generateDepthBuffer(OpenGLState openGLState){
        Framebuffer buffer = new Framebuffer();
        buffer.bind(openGLState);
        
        
        //texture
        Texture texture = new Texture();
        texture.glTexImage2D(openGLState, depthMapWidth, depthMapHeight, GL_DEPTH_COMPONENT, GL_FLOAT);
        texture.setMinFilter(GL_NEAREST);
        texture.setMagFilter(GL_NEAREST);
        texture.setWrap(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
        texture.setWrap(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
        texture.setBorderColor(new float[]{ 1.0f, 1.0f, 1.0f, 1.0f });
        texture.checkStatus(openGLState);

        //bind texture to fbo
        buffer.setMipMapLevel(0);
        buffer.setDepthAttachment(openGLState,texture);
        glNamedFramebufferDrawBuffer(buffer.getFramebufferPointer(), GL_NONE);
        glNamedFramebufferReadBuffer(buffer.getFramebufferPointer(), GL_NONE);
        
        
        
        //check make sure compiled
        buffer.checkStatus();
        return buffer;
    }


    public static Texture generateDepthBufferTexture(OpenGLState openGLState, int width, int height){
        Texture texture = new Texture();
        texture.glTexImage2D(openGLState, width, height, GL_DEPTH_COMPONENT, GL_SHORT);
        texture.setMinFilter(GL_NEAREST);
        texture.setMagFilter(GL_NEAREST);
        texture.setWrap(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
        texture.setWrap(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
        texture.setBorderColor(new float[]{ 1.0f, 1.0f, 1.0f, 1.0f });
        texture.checkStatus(openGLState);
        return texture;
    }

    public static Framebuffer generateDepthBuffer(OpenGLState openGLState, int width, int height, Texture texture){
        Framebuffer buffer = new Framebuffer();
        buffer.bind(openGLState);
        
        
        //bind texture to fbo
        buffer.setMipMapLevel(0);
        buffer.setDepthAttachment(openGLState,texture);
        glNamedFramebufferDrawBuffer(buffer.getFramebufferPointer(), GL_NONE);
        glNamedFramebufferReadBuffer(buffer.getFramebufferPointer(), GL_NONE);
        
        
        
        //check make sure compiled
        buffer.checkStatus();
        return buffer;
    }

    public static Texture generateOITAccumulatorTexture(OpenGLState openGLState, int width, int height){
        Texture texture = new Texture();
        texture.setMinFilter(GL_LINEAR);
        texture.setMagFilter(GL_LINEAR);
        texture.glTexImage2D(openGLState, width, height, GL_RGBA, GL_HALF_FLOAT);
        texture.checkStatus(openGLState);
        return texture;
    }

    public static Texture generateOITRevealageTexture(OpenGLState openGLState, int width, int height){
        Texture texture = new Texture();
        texture.setMinFilter(GL_LINEAR);
        texture.setMagFilter(GL_LINEAR);
        texture.glTexImage2D(openGLState, width, height, GL_RED, GL_FLOAT);
        texture.checkStatus(openGLState);
        return texture;
    }

    public static Framebuffer generateOITFramebuffer(OpenGLState openGLState, int width, int height, Texture accumulatorTex, Texture revealageTex, Texture depthTexture){
        Framebuffer buffer = new Framebuffer();
        buffer.bind(openGLState);
        
                
        //bind texture to fbo
        buffer.setMipMapLevel(0);
        buffer.attachTexture(openGLState,accumulatorTex,0);
        buffer.attachTexture(openGLState,revealageTex,1);
        buffer.setDepthAttachment(openGLState,depthTexture);
        
        // const GLenum transparentDrawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
        // glDrawBuffers(2, transparentDrawBuffers);
        IntBuffer drawBuffers = BufferUtils.createIntBuffer(2);
        drawBuffers.put(GL_COLOR_ATTACHMENT0);
        drawBuffers.put(GL_COLOR_ATTACHMENT1);
        drawBuffers.flip();
        glNamedFramebufferDrawBuffers(buffer.getFramebufferPointer(),drawBuffers);

        //check make sure compiled
        buffer.checkStatus();
        return buffer;
    }


}
