/**
 * Copyright 2012 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.GL;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLCapabilitiesImmutable;
import com.jogamp.opengl.GLContext;
import com.jogamp.opengl.GLDrawableFactory;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.GLFBODrawable;
import com.jogamp.opengl.GLOffscreenAutoDrawable;
import com.jogamp.opengl.GLProfile;

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

import com.jogamp.opengl.FBObject;
import com.jogamp.opengl.test.junit.jogl.demos.es2.FBOMix2DemosES2;
import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2;
import com.jogamp.opengl.test.junit.jogl.demos.es2.MultisampleDemoES2;
import com.jogamp.opengl.test.junit.util.GLTestUtil;
import com.jogamp.opengl.test.junit.util.UITestCase;

/**
 * Toolkit agnostic {@link GLOffscreenAutoDrawable.FBO} tests using the
 * {@link GLDrawableFactory#createOffscreenAutoDrawable(com.jogamp.nativewindow.AbstractGraphicsDevice, GLCapabilitiesImmutable, com.jogamp.opengl.GLCapabilitiesChooser, int, int, GLContext) factory model}.
 * <p>
 * The created {@link GLOffscreenAutoDrawable.FBO} is being used to run the {@link GLEventListener}.
 * </p>
 * <p>
 * Extensive FBO reconfiguration (size and sample buffer count) and validation are performed.
 * </p>
 */
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestFBOAutoDrawableFactoryNEWT extends UITestCase {

    static final int widthStep = 800/4;
    static final int heightStep = 600/4;
    volatile int szStep = 2;

    interface MyGLEventListener extends GLEventListener {
        void setMakeSnapshot();
    }

    @Test
    public void test01a_GL2ES2_Demo1_SingleBuffer_Normal() throws InterruptedException {
        final GLProfile glp = GLProfile.getGL2ES2();
        final GLCapabilities caps = new GLCapabilities(glp);
        caps.setDoubleBuffered(false);
        testGLFBODrawableImpl(caps, GLFBODrawable.FBOMODE_USE_TEXTURE, new GearsES2(0));
    }
    @Test
    public void test01b_GL2ES2_Demo1_SingleBuffer_NoTex() throws InterruptedException {
        final GLProfile glp = GLProfile.getGL2ES2();
        final GLCapabilities caps = new GLCapabilities(glp);
        caps.setDoubleBuffered(false);
        testGLFBODrawableImpl(caps, 0, new GearsES2(0));
    }
    @Test
    public void test01c_GL2ES2_Demo1_SingleBuffer_NoTexNoDepth() throws InterruptedException {
        final GLProfile glp = GLProfile.getGL2ES2();
        final GLCapabilities caps = new GLCapabilities(glp);
        caps.setDoubleBuffered(false);
        caps.setDepthBits(0);
        testGLFBODrawableImpl(caps, 0, new GearsES2(0));
    }

    @Test
    public void test02a_GL2ES2_Demo1_DoubleBuffer_Normal() throws InterruptedException {
        final GLProfile glp = GLProfile.getGL2ES2();
        final GLCapabilities caps = new GLCapabilities(glp);
        caps.setDoubleBuffered(true); // default
        testGLFBODrawableImpl(caps, GLFBODrawable.FBOMODE_USE_TEXTURE, new GearsES2(0));
    }

    @Test
    public void test03a_GL2ES2_Demo2MSAA4_Normal() throws InterruptedException {
        final GLProfile glp = GLProfile.getGL2ES2();
        final GLCapabilities caps = new GLCapabilities(glp);
        caps.setSampleBuffers(true);
        caps.setNumSamples(4);
        testGLFBODrawableImpl(caps, GLFBODrawable.FBOMODE_USE_TEXTURE, new MultisampleDemoES2(true));
    }
    @Test
    public void test03b_GL2ES2_Demo2MSAA4_NoTex() throws InterruptedException {
        final GLProfile glp = GLProfile.getGL2ES2();
        final GLCapabilities caps = new GLCapabilities(glp);
        caps.setSampleBuffers(true);
        caps.setNumSamples(4);
        testGLFBODrawableImpl(caps, 0, new MultisampleDemoES2(true));
    }
    @Test
    public void test03c_GL2ES2_Demo2MSAA4_NoTexNoDepth() throws InterruptedException {
        final GLProfile glp = GLProfile.getGL2ES2();
        final GLCapabilities caps = new GLCapabilities(glp);
        caps.setSampleBuffers(true);
        caps.setNumSamples(4);
        caps.setDepthBits(0);
        testGLFBODrawableImpl(caps, 0, new MultisampleDemoES2(true));
    }

    @Test
    public void test04_GL2ES2_FBODemoMSAA4_Normal() throws InterruptedException {
        final GLProfile glp = GLProfile.getGL2ES2();
        final FBOMix2DemosES2 demo = new FBOMix2DemosES2(0);
        demo.setDoRotation(false);
        final GLCapabilities caps = new GLCapabilities(glp);
        caps.setSampleBuffers(true);
        caps.setNumSamples(4);
        testGLFBODrawableImpl(caps, GLFBODrawable.FBOMODE_USE_TEXTURE, demo);
    }

    @Test
    public void test11_EGLES2_Demo0Normal() throws InterruptedException {
        if( GLProfile.isAvailable(GLProfile.GLES2) )  {
            final GLProfile glp = GLProfile.get(GLProfile.GLES2);
            final GLCapabilities caps = new GLCapabilities(glp);
            testGLFBODrawableImpl(caps, GLFBODrawable.FBOMODE_USE_TEXTURE, new GearsES2(0));
        } else {
            System.err.println("EGL ES2 n/a");
        }
    }

    @Test
    public void test13_EGLES2_Demo0MSAA4() throws InterruptedException {
        if( GLProfile.isAvailable(GLProfile.GLES2) )  {
            final GLProfile glp = GLProfile.get(GLProfile.GLES2);
            final GLCapabilities caps = new GLCapabilities(glp);
            caps.setSampleBuffers(true);
            caps.setNumSamples(4);
            testGLFBODrawableImpl(caps, GLFBODrawable.FBOMODE_USE_TEXTURE, new GearsES2(0));
        } else {
            System.err.println("EGL ES2 n/a");
        }
    }

    @Test
    public void test21_GL3_Demo0Normal() throws InterruptedException {
        if( GLProfile.isAvailable(GLProfile.GL3) )  {
            final GLProfile glp = GLProfile.get(GLProfile.GL3);
            final GLCapabilities caps = new GLCapabilities(glp);
            testGLFBODrawableImpl(caps, GLFBODrawable.FBOMODE_USE_TEXTURE, new GearsES2(0));
        } else {
            System.err.println("GL3 n/a");
        }
    }

    void testGLFBODrawableImpl(final GLCapabilities caps, final int fboMode, final GLEventListener demo) throws InterruptedException {
        caps.setFBO(true);
        final GLDrawableFactory factory = GLDrawableFactory.getFactory(caps.getGLProfile());
        final GLOffscreenAutoDrawable.FBO glad = (GLOffscreenAutoDrawable.FBO)
                factory.createOffscreenAutoDrawable(null, caps, null, widthStep*szStep, heightStep*szStep);
        Assert.assertNotNull(glad);

        System.out.println("Requested:     "+caps);
        System.out.println("Realized GLAD: "+glad);
        System.out.println("Realized GLAD: "+glad.getChosenGLCapabilities());
        Assert.assertTrue("FBO drawable is initialized before ctx creation", !glad.isInitialized());
        glad.setFBOMode(fboMode);

        glad.display(); // initial display incl. init!
        {
            final GLContext context = glad.getContext();
            Assert.assertNotNull(context);
            Assert.assertTrue(context.isCreated());
        }
        Assert.assertTrue("FBO drawable is not initialized after ctx creation", glad.isInitialized());

        final boolean expDepth = caps.getDepthBits() > 0;
        final boolean reqDepth = glad.getRequestedGLCapabilities().getDepthBits() > 0;
        final boolean hasDepth = glad.getChosenGLCapabilities().getDepthBits() > 0;
        System.out.println("Depth: exp "+expDepth+", req "+reqDepth+", has "+hasDepth);
        Assert.assertEquals("Depth: expected not passed to requested", expDepth, reqDepth);
        Assert.assertEquals("Depth: requested not passed to chosen", reqDepth, hasDepth);

        //
        // FBO incl. MSAA is fully initialized now
        //

        final GLCapabilitiesImmutable chosenCaps = glad.getChosenGLCapabilities();
        System.out.println("Init GLAD: "+glad);
        System.out.println("Init GLAD: "+chosenCaps);

        final FBObject fboFront = glad.getFBObject(GL.GL_FRONT);
        final FBObject fboBack  = glad.getFBObject(GL.GL_BACK);

        System.out.println("Init front FBO: "+fboFront);
        System.out.println("Init back  FBO: "+fboBack);

        Assert.assertTrue("FBO drawable is not initialized before ctx creation", glad.isInitialized());
        Assert.assertTrue("FBO Front is not initialized before ctx creation", fboFront.isInitialized());
        Assert.assertTrue("FBO Back  is not initialized before ctx creation", fboBack.isInitialized());

        if( chosenCaps.getDoubleBuffered() ) {
            Assert.assertNotEquals("FBO are equal: "+fboFront+" == "+fboBack, fboFront, fboBack);
            Assert.assertNotSame(fboFront, fboBack);
        } else {
            Assert.assertEquals("FBO are not equal: "+fboFront+" != "+fboBack, fboFront, fboBack);
            Assert.assertSame(fboFront, fboBack);
        }

        final FBObject.Colorbuffer color0, color1;

        color0 = glad.getColorbuffer(GL.GL_FRONT);
        if(0==glad.getNumSamples()) {
            color1 = glad.getColorbuffer(GL.GL_BACK);
        } else {
            color1 = null;
        }

        final boolean expTexture = 0 != ( GLFBODrawable.FBOMODE_USE_TEXTURE & glad.getFBOMode() );
        System.out.println("Texture: exp "+expTexture+", hasFront "+color0.isTextureAttachment());
        Assert.assertEquals("Texture: Front", expTexture, color0.isTextureAttachment());
        if(0==glad.getNumSamples()) {
            Assert.assertEquals("Texture: Back", expTexture, color1.isTextureAttachment());
        }

        final FBObject.Colorbuffer colorA, colorB;
        final FBObject.RenderAttachment depthA, depthB;

        colorA = fboFront.getColorbuffer(0);
        Assert.assertNotNull(colorA);
        colorB = fboBack.getColorbuffer(0);
        Assert.assertNotNull(colorB);

        Assert.assertEquals("Texture: Front", expTexture, colorA.isTextureAttachment());
        if(0==glad.getNumSamples()) {
            Assert.assertEquals("Texture: Back", expTexture, colorB.isTextureAttachment());
        } else {
            Assert.assertEquals("Texture: MSAA Back is Texture", false, colorB.isTextureAttachment());
        }

        if( hasDepth ) {
            depthA = fboFront.getDepthAttachment();
            Assert.assertNotNull(depthA);
            depthB = fboBack.getDepthAttachment();
            Assert.assertNotNull(depthB);
        } else {
            depthA = null;
            depthB = null;
        }

        glad.display(); // SWAP_ODD

        if( chosenCaps.getDoubleBuffered() ) {
            // double buffer or MSAA
            Assert.assertNotEquals("Color attachments are equal: "+colorB+" == "+colorA, colorA, colorB);
            Assert.assertNotSame(colorB, colorA);
            if( hasDepth ) {
                Assert.assertNotEquals("Depth attachments are equal: "+depthB+" == "+depthA, depthA, depthB);
                Assert.assertNotSame(depthB, depthA);
            }
        } else {
            // single buffer
            Assert.assertEquals(colorA, colorB);
            Assert.assertSame(colorA, colorB);
            Assert.assertEquals(depthA, depthB);
            Assert.assertSame(depthA, depthB);
        }

        Assert.assertEquals(color0, colorA);
        Assert.assertSame(color0, colorA);
        if(0==glad.getNumSamples()) {
            Assert.assertEquals(color1, colorB);
            Assert.assertSame(color1, colorB);
        }

        if( chosenCaps.getNumSamples() > 0 ) {
            // MSAA
            final FBObject _fboFront = glad.getFBObject(GL.GL_FRONT);
            final FBObject _fboBack = glad.getFBObject(GL.GL_BACK);
            Assert.assertEquals("FBO are not equal: "+fboFront+" != "+_fboFront, fboFront, _fboFront);
            Assert.assertSame(fboFront, _fboFront);
            Assert.assertEquals("FBO are not equal: "+fboBack+" != "+_fboBack, fboBack, _fboBack);
            Assert.assertSame(fboBack, _fboBack);
        } else if( chosenCaps.getDoubleBuffered() ) {
            // real double buffer
            final FBObject _fboFront = glad.getFBObject(GL.GL_FRONT);
            final FBObject _fboBack = glad.getFBObject(GL.GL_BACK);
            Assert.assertEquals("FBO are not equal: "+fboBack+" != "+_fboFront, fboBack, _fboFront);
            Assert.assertSame(fboBack, _fboFront);
            Assert.assertEquals("FBO are not equal: "+fboFront+" != "+_fboBack, fboFront, _fboBack);
            Assert.assertSame(fboFront, _fboBack);
        } else {
            // single buffer
            final FBObject _fboFront = glad.getFBObject(GL.GL_FRONT);
            final FBObject _fboBack = glad.getFBObject(GL.GL_BACK);
            Assert.assertEquals("FBO are not equal: "+fboFront+" != "+_fboFront, fboFront, _fboFront);
            Assert.assertSame(fboFront, _fboFront);
            Assert.assertEquals("FBO are not equal: "+fboBack+" != "+_fboFront, fboBack, _fboFront);
            Assert.assertSame(fboBack, _fboFront);
            Assert.assertEquals("FBO are not equal: "+fboBack+" != "+_fboBack, fboBack, _fboBack);
            Assert.assertSame(fboBack, _fboBack);
            Assert.assertEquals("FBO are not equal: "+fboFront+" != "+_fboBack, fboFront, _fboBack);
            Assert.assertSame(fboFront, _fboBack);
        }

        glad.addGLEventListener(demo);

        final SnapshotGLEventListener snapshotGLEventListener = new SnapshotGLEventListener();
        glad.addGLEventListener(snapshotGLEventListener);

        glad.display(); // - SWAP_EVEN

        // 1 - szStep = 2
        snapshotGLEventListener.setMakeSnapshot();
        glad.display(); // - SWAP_ODD

        // 2, 3 (resize + display)
        szStep = 1;
        glad.setSurfaceSize(widthStep*szStep, heightStep*szStep); // SWAP_EVEN
        Assert.assertTrue("Size not reached: Expected "+(widthStep*szStep)+"x"+(heightStep*szStep)+", Is "+glad.getSurfaceWidth()+"x"+glad.getSurfaceHeight(),
                          GLTestUtil.waitForSize(glad, widthStep*szStep, heightStep*szStep, null));
        snapshotGLEventListener.setMakeSnapshot();
        glad.display();  //  - SWAP_ODD
        glad.display();  //  - SWAP_EVEN
        {
            // Check whether the attachment reference are still valid!
            final FBObject _fboFront = glad.getFBObject(GL.GL_FRONT);
            final FBObject _fboBack = glad.getFBObject(GL.GL_BACK);
            System.out.println("Resize1.oldFront: "+fboFront);
            System.out.println("Resize1.nowFront: "+_fboFront);
            System.out.println("Resize1.oldBack : "+fboBack);
            System.out.println("Resize1.nowBack : "+_fboBack);
            Assert.assertEquals(fboFront, _fboFront);
            Assert.assertSame(fboFront, _fboFront);
            Assert.assertEquals(fboBack,  _fboBack);
            Assert.assertSame(fboBack,  _fboBack);

            FBObject.Colorbuffer _color = _fboFront.getColorbuffer(0);
            Assert.assertNotNull(_color);
            Assert.assertEquals(colorA, _color);
            Assert.assertSame(colorA, _color);

            FBObject.RenderAttachment _depth = _fboFront.getDepthAttachment();
            System.err.println("Resize1.oldDepth "+depthA);
            System.err.println("Resize1.newDepth "+_depth);
            if( hasDepth ) {
                Assert.assertNotNull(_depth);
            }

            Assert.assertEquals(depthA, _depth);
            Assert.assertSame(depthA, _depth);
            _depth = _fboBack.getDepthAttachment();
            if( hasDepth ) {
                Assert.assertNotNull(_depth);
            }
            Assert.assertEquals(depthB, _depth);
            Assert.assertSame(depthB, _depth);

            _color = _fboFront.getColorbuffer(colorA);
            Assert.assertNotNull(_color);
            Assert.assertEquals(colorA, _color);
            Assert.assertSame(colorA, _color);
        }

        // 4, 5 (resize + display)
        szStep = 4;
        glad.setSurfaceSize(widthStep*szStep, heightStep*szStep); // SWAP_ODD
        Assert.assertTrue("Size not reached: Expected "+(widthStep*szStep)+"x"+(heightStep*szStep)+", Is "+glad.getSurfaceWidth()+"x"+glad.getSurfaceHeight(),
                          GLTestUtil.waitForSize(glad, widthStep*szStep, heightStep*szStep, null));
        snapshotGLEventListener.setMakeSnapshot();
        glad.display(); //  - SWAP_EVEN
        glad.display(); //  - SWAP_ODD
        {
            // Check whether the attachment reference are still valid!
            final FBObject _fboFront = glad.getFBObject(GL.GL_FRONT);
            final FBObject _fboBack = glad.getFBObject(GL.GL_BACK);
            System.out.println("Resize2.oldFront: "+fboFront);
            System.out.println("Resize2.nowFront: "+_fboFront);
            System.out.println("Resize2.oldBack : "+fboBack);
            System.out.println("Resize2.nowBack : "+_fboBack);
            if(chosenCaps.getDoubleBuffered() && 0==chosenCaps.getNumSamples()) {
                // real double buffer
                Assert.assertEquals(fboBack,  _fboFront);
                Assert.assertEquals(fboFront, _fboBack);
            } else {
                // single or MSAA
                Assert.assertEquals(fboFront,  _fboFront);
                Assert.assertEquals(fboBack,   _fboBack);
            }

            FBObject.Colorbuffer _color = fboBack.getColorbuffer(0);
            Assert.assertNotNull(_color);
            Assert.assertEquals(colorB, _color);
            Assert.assertSame(colorB, _color);

            FBObject.RenderAttachment _depth = fboBack.getDepthAttachment();
            if( hasDepth ) {
                Assert.assertNotNull(_depth); // MSAA back w/ depth
            }
            Assert.assertEquals(depthB, _depth);
            Assert.assertSame(depthB, _depth);

            _depth = fboFront.getDepthAttachment();
            if( hasDepth ) {
                Assert.assertNotNull(_depth);
            }
            Assert.assertEquals(depthA, _depth);
            Assert.assertSame(depthA, _depth);

            _color = fboBack.getColorbuffer(colorB);
            Assert.assertNotNull(_color);
            Assert.assertEquals(colorB, _color);
            Assert.assertSame(colorB, _color);
        }

        // 6 + 7 (samples + display)
        final int oldSampleCount = chosenCaps.getNumSamples();
        final int newSampleCount = oldSampleCount > 0 ? 0 : 4;
        System.out.println("Resize3.sampleCount: "+oldSampleCount+" -> "+newSampleCount);
        glad.setNumSamples(glad.getGL(), newSampleCount); // triggers repaint
        snapshotGLEventListener.setMakeSnapshot();
        glad.display(); // actual screenshot

        // 8, 9 (resize + samples + display)
        szStep = 3;
        glad.setSurfaceSize(widthStep*szStep, heightStep*szStep);
        Assert.assertTrue("Size not reached: Expected "+(widthStep*szStep)+"x"+(heightStep*szStep)+", Is "+glad.getSurfaceWidth()+"x"+glad.getSurfaceHeight(),
                          GLTestUtil.waitForSize(glad, widthStep*szStep, heightStep*szStep, null));
        snapshotGLEventListener.setMakeSnapshot();
        glad.display();

        glad.destroy();
        System.out.println("Fin: "+glad);
    }

    public static void main(final String args[]) throws Exception {
        org.junit.runner.JUnitCore.main(TestFBOAutoDrawableFactoryNEWT.class.getName());
    }

}
