/**
 * Copyright 2010 JogAmp Community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of JogAmp Community.
 */
package com.jogamp.opengl.test.junit.jogl.acore;

import com.jogamp.opengl.FBObject;
import com.jogamp.opengl.FBObject.TextureAttachment;
import com.jogamp.opengl.util.GLArrayDataServer;
import com.jogamp.opengl.util.GLReadBufferUtil;
import com.jogamp.opengl.util.PMVMatrix;
import com.jogamp.opengl.util.glsl.ShaderCode;
import com.jogamp.opengl.util.glsl.ShaderProgram;
import com.jogamp.opengl.util.glsl.ShaderState;
import com.jogamp.opengl.util.texture.TextureIO;
import com.jogamp.opengl.FBObject.Attachment.Type;
import com.jogamp.opengl.test.junit.jogl.demos.es2.RedSquareES2;
import com.jogamp.opengl.test.junit.util.MiscUtils;
import com.jogamp.opengl.test.junit.util.NEWTGLContext;
import com.jogamp.opengl.test.junit.util.UITestCase;

import java.io.IOException;

import com.jogamp.common.os.Platform;
import com.jogamp.junit.util.JunitTracer;
import com.jogamp.nativewindow.NativeSurface;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2ES2;
import com.jogamp.opengl.GL2GL3;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLDrawable;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.GLUniformData;
import com.jogamp.opengl.fixedfunc.GLMatrixFunc;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestFBOMRTNEWT01 extends UITestCase {
    static long durationPerTest = 10*40*2; // ms

    @Test
    public void test01() throws InterruptedException {
        final int step = 4;
        final int width = 800;
        final int height = 600;
        // preset ..
        if(!GLProfile.isAvailable(GLProfile.GL2GL3)) {
            System.err.println("Test requires GL2/GL3 profile.");
            return;
        }
        final NEWTGLContext.WindowContext winctx = NEWTGLContext.createWindow(
                new GLCapabilities(GLProfile.getGL2GL3()), width/step, height/step, true);
        final GLDrawable drawable = winctx.context.getGLDrawable();
        final GL2GL3 gl = winctx.context.getGL().getGL2GL3();
        // gl = gl.getContext().setGL( GLPipelineFactory.create("com.jogamp.opengl.Debug", null, gl, null) ).getGL2GL3();
        System.err.println(winctx.context);

        Assert.assertEquals(GL.GL_NO_ERROR, gl.glGetError());
        // test code ..
        final ShaderState st = new ShaderState();
        // st.setVerbose(true);

        final ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, RedSquareES2.class, "shader",
                "shader/bin", "fbo-mrt-1", true);
        final ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, RedSquareES2.class, "shader",
                "shader/bin", "fbo-mrt-1", true);
        vp0.defaultShaderCustomization(gl, true, true);
        fp0.defaultShaderCustomization(gl, true, true);
        final ShaderProgram sp0 = new ShaderProgram();
        sp0.add(gl, vp0, System.err);
        sp0.add(gl, fp0, System.err);
        Assert.assertTrue(0 != sp0.program());
        Assert.assertTrue(!sp0.inUse());
        Assert.assertTrue(!sp0.linked());
        Assert.assertEquals(GL.GL_NO_ERROR, gl.glGetError());
        st.attachShaderProgram(gl, sp0, false);

        final ShaderCode vp1 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, RedSquareES2.class, "shader",
                "shader/bin", "fbo-mrt-2", true);
        final ShaderCode fp1 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, RedSquareES2.class, "shader",
                "shader/bin", "fbo-mrt-2", true);
        vp1.defaultShaderCustomization(gl, true, true);
        fp1.defaultShaderCustomization(gl, true, true);
        final ShaderProgram sp1 = new ShaderProgram();
        sp1.add(gl, vp1, System.err);
        sp1.add(gl, fp1, System.err);
        Assert.assertTrue(0 != sp1.program());
        Assert.assertTrue(!sp1.inUse());
        Assert.assertTrue(!sp1.linked());
        Assert.assertEquals(GL.GL_NO_ERROR, gl.glGetError());
        st.attachShaderProgram(gl, sp1, true);

        final PMVMatrix pmvMatrix = new PMVMatrix();
        final GLUniformData pmvMatrixUniform = new GLUniformData("gcu_PMVMatrix", 4, 4, pmvMatrix.getSyncPMv());
        Assert.assertEquals(GL.GL_NO_ERROR, gl.glGetError());
        st.ownUniform(pmvMatrixUniform);
        st.uniform(gl, pmvMatrixUniform);
        Assert.assertEquals(GL.GL_NO_ERROR, gl.glGetError());

        final GLArrayDataServer vertices0 = GLArrayDataServer.createGLSL("gca_Vertices", 3, GL.GL_FLOAT, false, 4, GL.GL_STATIC_DRAW);
        // st.bindAttribLocation(gl, 0, vertices0);
        vertices0.putf(0); vertices0.putf(1);  vertices0.putf(0);
        vertices0.putf(1);  vertices0.putf(1);  vertices0.putf(0);
        vertices0.putf(0); vertices0.putf(0); vertices0.putf(0);
        vertices0.putf(1);  vertices0.putf(0); vertices0.putf(0);
        vertices0.seal(gl, true);
        st.ownAttribute(vertices0, true);
        vertices0.enableBuffer(gl, false);
        Assert.assertEquals(GL.GL_NO_ERROR, gl.glGetError());

        final GLArrayDataServer colors0 = GLArrayDataServer.createGLSL("gca_Colors", 4, GL.GL_FLOAT, false, 4, GL.GL_STATIC_DRAW);
        // st.bindAttribLocation(gl, 1, colors0);
        colors0.putf(1); colors0.putf(0);  colors0.putf(1); colors0.putf(1);
        colors0.putf(0);  colors0.putf(0);  colors0.putf(1); colors0.putf(1);
        colors0.putf(0); colors0.putf(0); colors0.putf(0); colors0.putf(1);
        colors0.putf(0);  colors0.putf(1); colors0.putf(1); colors0.putf(1);
        colors0.seal(gl, true);
        st.ownAttribute(colors0, true);
        colors0.enableBuffer(gl, false);
        Assert.assertEquals(GL.GL_NO_ERROR, gl.glGetError());

        final GLUniformData texUnit0 = new GLUniformData("gcs_TexUnit0", 0);
        st.ownUniform(texUnit0);
        st.uniform(gl, texUnit0);
        final GLUniformData texUnit1 = new GLUniformData("gcs_TexUnit1", 1);
        st.ownUniform(texUnit1);
        st.uniform(gl, texUnit1);

        final GLArrayDataServer texCoords0 = GLArrayDataServer.createGLSL("gca_TexCoords", 2, GL.GL_FLOAT, false, 4, GL.GL_STATIC_DRAW);
        // st.bindAttribLocation(gl, 2, texCoords0);
        texCoords0.putf(0f); texCoords0.putf(1f);
        texCoords0.putf(1f);  texCoords0.putf(1f);
        texCoords0.putf(0f); texCoords0.putf(0f);
        texCoords0.putf(1f);  texCoords0.putf(0f);
        texCoords0.seal(gl, true);
        st.ownAttribute(texCoords0, true);
        texCoords0.enableBuffer(gl, false);
        Assert.assertEquals(GL.GL_NO_ERROR, gl.glGetError());

        final int texA0Point = 0; // attachment point for texA0
        final int texA1Point = 1; // attachment point for texA1

        // FBO w/ 2 texture2D color buffers
        final FBObject fbo_mrt = new FBObject();
        fbo_mrt.init(gl, drawable.getSurfaceWidth(), drawable.getSurfaceHeight(), 0);
        final TextureAttachment texA0 = fbo_mrt.attachTexture2D(gl, texA0Point, true, GL.GL_NEAREST, GL.GL_NEAREST, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE);
        final TextureAttachment texA1;
        if(fbo_mrt.getMaxColorAttachments() > 1) {
            texA1 = fbo_mrt.attachTexture2D(gl, texA1Point, true, GL.GL_NEAREST, GL.GL_NEAREST, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE);
        } else {
            texA1 = null;
            System.err.println("FBO supports only one attachment, no MRT available!");
        }
        fbo_mrt.attachRenderbuffer(gl, Type.DEPTH, FBObject.CHOSEN_BITS);
        Assert.assertTrue( fbo_mrt.isStatusValid() ) ;
        fbo_mrt.unbind(gl);

        // misc GL setup
        gl.glClearColor(1, 1, 1, 1);
        gl.glEnable(GL.GL_DEPTH_TEST);
        Assert.assertEquals(GL.GL_NO_ERROR, gl.glGetError());

        // reshape
        pmvMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
        pmvMatrix.glLoadIdentity();
        pmvMatrix.glOrthof(0f, 1f, 0f, 1f, -10f, 10f);
        pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
        pmvMatrix.glLoadIdentity();
        st.uniform(gl, pmvMatrixUniform);
        Assert.assertEquals(GL.GL_NO_ERROR, gl.glGetError());

        final int[] two_buffers = new int[] { GL.GL_COLOR_ATTACHMENT0+texA0Point, GL.GL_COLOR_ATTACHMENT0+texA1Point };
        final int[] bck_buffers = new int[] { GL2GL3.GL_BACK_LEFT };

        final GLReadBufferUtil screenshot = new GLReadBufferUtil(true, false);
        int step_i = 0;
        final int[] last_snap_size = new int[] { 0, 0 };

        for(int i=0; i<durationPerTest; i+=50) {
            // pass 1 - MRT: Red -> buffer0, Green -> buffer1
            st.attachShaderProgram(gl, sp0, true);
            vertices0.enableBuffer(gl, true);
            colors0.enableBuffer(gl, true);

            fbo_mrt.bind(gl);
            gl.glDrawBuffers(2, two_buffers, 0);
            gl.glViewport(0, 0, fbo_mrt.getWidth(), fbo_mrt.getHeight());

            gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
            gl.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, 4);
            fbo_mrt.unbind(gl);
            vertices0.enableBuffer(gl, false);
            colors0.enableBuffer(gl, false);

            // pass 2 - mix buffer0, buffer1 and blue
            // rg = buffer0.rg + buffer1.rg, b = Blue - length(rg);
            st.attachShaderProgram(gl, sp1, true);
            vertices0.enableBuffer(gl, true);
            colors0.enableBuffer(gl, true);
            texCoords0.enableBuffer(gl, true);
            gl.glDrawBuffers(1, bck_buffers, 0);

            gl.glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight());

            gl.glActiveTexture(GL.GL_TEXTURE0 + texUnit0.intValue());
            fbo_mrt.use(gl, texA0);
            if(null != texA1) {
                gl.glActiveTexture(GL.GL_TEXTURE0 + texUnit1.intValue());
                fbo_mrt.use(gl, texA1);
            }
            gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
            gl.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, 4);
            fbo_mrt.unuse(gl);
            vertices0.enableBuffer(gl, false);
            colors0.enableBuffer(gl, false);
            texCoords0.enableBuffer(gl, false);

            {
                final NativeSurface ns = gl.getContext().getGLReadDrawable().getNativeSurface();
                if(last_snap_size[0] != ns.getSurfaceWidth() && last_snap_size[1] != ns.getSurfaceHeight()) {
                    gl.glFinish(); // sync .. no swap buffers yet!
                    snapshot(step_i, null, gl, screenshot, TextureIO.PNG, null); // overwrite ok
                    last_snap_size[0] = ns.getSurfaceWidth();
                    last_snap_size[1] = ns.getSurfaceHeight();
                }
            }

            drawable.swapBuffers();
            Thread.sleep(50);
            final int j = (int) ( i / (durationPerTest/step) ) + 1;
            if(j>step_i) {
                final int w = width/step * j;
                final int h = height/step * j;
                System.err.println("resize: "+step_i+" -> "+j+" - "+w+"x"+h);
                fbo_mrt.reset(gl, w, h, 0);
                winctx.window.setSize(w, h);
                step_i = j;
            }
        }

        NEWTGLContext.destroyWindow(winctx);
    }

    @BeforeClass
    public static void beforeClass() throws Exception {
        if( !manual_test ) {
            if( Platform.OSType.MACOS == Platform.getOSType() ) {
                JunitTracer.setTestSupported(false);
            }
        }
    }
    static boolean manual_test = false;

    public static void main(final String args[]) throws IOException {
        manual_test = true;
        System.err.println("main - start");
        for(int i=0; i<args.length; i++) {
            if(args[i].equals("-time")) {
                durationPerTest = MiscUtils.atoi(args[++i], (int)durationPerTest);
            }
        }
        final String tstname = TestFBOMRTNEWT01.class.getName();
        org.junit.runner.JUnitCore.main(tstname);
        System.err.println("main - end");
    }
}

