/**
 * Copyright 2013 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.glels;

import java.awt.Dimension;
import java.awt.Frame;

import javax.media.opengl.GLAnimatorControl;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLContext;
import javax.media.opengl.GLDrawableFactory;
import javax.media.opengl.GLOffscreenAutoDrawable;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLCanvas;

import jogamp.nativewindow.jawt.JAWTUtil;

import com.jogamp.newt.Screen;
import com.jogamp.newt.opengl.GLWindow;

import com.jogamp.opengl.GLEventListenerState;
import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2;
import com.jogamp.opengl.test.junit.util.AWTRobotUtil;
import com.jogamp.opengl.test.junit.util.GLEventListenerCounter;
import com.jogamp.opengl.test.junit.util.UITestCase;

import org.junit.Assert;
import org.junit.Assume;
import org.junit.BeforeClass;

/**
 * Test re-association of GLContext/GLDrawables,
 * here GLContext's survival of GLDrawable destruction
 * and reuse w/ new or recreated GLDrawable.
 * <p>
 * Test utilizes {@link GLEventListenerState} for preserving the
 * GLAutoDrawable state, i.e. GLContext, all GLEventListener
 * and the GLAnimatorControl association.
 * </p>
 * <p>
 * See Bug 665 - https://jogamp.org/bugzilla/show_bug.cgi?id=665.
 * </p>
 */
public abstract class GLContextDrawableSwitchBase extends UITestCase {
    static protected enum GLADType { GLCanvasOnscreen, GLCanvasOffscreen, GLWindow, GLOffscreen };

    // default period for 1 GLAD cycle
    static long duration = 1000; // ms

    static int width, height;

    static GLCapabilities getCaps(String profile) {
        if( !GLProfile.isAvailable(profile) )  {
            System.err.println("Profile "+profile+" n/a");
            return null;
        }
        return new GLCapabilities(GLProfile.get(profile));
    }

    @BeforeClass
    public static void initClass() {
        width  = 256;
        height = 256;
    }

    static void setGLCanvasSize(final GLCanvas glc, final Dimension new_sz) {
        try {
            javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    glc.setMinimumSize(new_sz);
                    glc.setPreferredSize(new_sz);
                    glc.setSize(new_sz);
                } } );
        } catch( Throwable throwable ) {
            throwable.printStackTrace();
            Assume.assumeNoException( throwable );
        }
    }

    static void setFrameVisible(final Frame frame) throws InterruptedException {
        try {
        javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                frame.pack();
                frame.setVisible(true);
            }});
        } catch( Throwable throwable ) {
            throwable.printStackTrace();
            Assume.assumeNoException( throwable );
        }
    }

    static void destroyFrame(final Frame frame) throws InterruptedException {
        try {
        javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                frame.dispose();
            }});
        } catch( Throwable throwable ) {
            throwable.printStackTrace();
            Assume.assumeNoException( throwable );
        }
    }

    private GLOffscreenAutoDrawable createGLOffscreenAutoDrawable(GLCapabilities caps, int width, int height) throws InterruptedException {
        final GLDrawableFactory factory = GLDrawableFactory.getFactory(caps.getGLProfile());
        return factory.createOffscreenAutoDrawable(null, caps, null, width, height);
    }

    protected static boolean validateOnOffscreenLayer(GLADType gladType1, GLADType gladType2) {
        final boolean useOffscreenLayer = GLADType.GLCanvasOffscreen == gladType1 || GLADType.GLCanvasOffscreen == gladType2 ;
        final boolean useOnscreenLayer = GLADType.GLCanvasOnscreen == gladType1 || GLADType.GLCanvasOnscreen == gladType2 ;
        if( useOffscreenLayer ) {
            if( !JAWTUtil.isOffscreenLayerSupported() ) {
                System.err.println("Platform doesn't support offscreen rendering.");
                return false;
            }
        } else if( useOnscreenLayer ) {
            if( JAWTUtil.isOffscreenLayerRequired() ) {
                System.err.println("Platform requires offscreen rendering.");
                return false;
            }
        }
        return true;
    }

    protected void testGLADOneLifecycle(Screen screen, GLCapabilities caps, GLADType gladType, int width,
                                        int height, GLEventListenerCounter glelTracker,
                                        SnapshotGLEventListener snapshotGLEventListener, final GLEventListenerState glelsIn, final GLEventListenerState glelsOut[], GLAnimatorControl animator)
            throws InterruptedException {

        System.err.println("GLAD Lifecycle.0 "+gladType+", restoring "+((null!=glelsIn)?true:false)+", preserving "+((null!=glelsOut)?true:false));
        final Frame frame;
        final GLAutoDrawable glad;
        if( GLADType.GLCanvasOnscreen == gladType ) {
            if( jogamp.nativewindow.jawt.JAWTUtil.isOffscreenLayerRequired() ) {
                throw new InternalError("Platform requires offscreen rendering, but onscreen requested: "+gladType);
            }
            frame = new Frame("AWT GLCanvas");

            glad = new GLCanvas(caps);
            setGLCanvasSize((GLCanvas)glad, new Dimension(width, height));
            frame.add((GLCanvas)glad);
        } else if( GLADType.GLCanvasOffscreen == gladType ) {
            if( !jogamp.nativewindow.jawt.JAWTUtil.isOffscreenLayerSupported() ) {
                throw new InternalError("Platform doesn't support offscreen rendering: "+gladType);
            }
            frame = new Frame("AWT GLCanvas");

            glad = new GLCanvas(caps);
            ((GLCanvas)glad).setShallUseOffscreenLayer(true);
            setGLCanvasSize((GLCanvas)glad, new Dimension(width, height));
            frame.add((GLCanvas)glad);
        } else if( GLADType.GLWindow == gladType ) {
            frame = null;

            if( null != screen ) {
                glad = GLWindow.create(screen, caps);
            } else {
                glad = GLWindow.create(caps);
            }
            ((GLWindow)glad).setTitle("Newt GLWindow");
            ((GLWindow)glad).setSize(width, height);
        } else if( GLADType.GLOffscreen == gladType ) {
            frame = null;

            glad = this.createGLOffscreenAutoDrawable(caps, width, height);
        } else {
            throw new InternalError("Unsupported: "+gladType);
        }

        if( null == glelsIn ) {
            if( null != animator ) {
                animator.add(glad);
            }
            glad.addGLEventListener(glelTracker);
            glad.addGLEventListener(new GearsES2(1));
            glad.addGLEventListener(snapshotGLEventListener);
        }
        snapshotGLEventListener.setMakeSnapshot();

        if( GLADType.GLCanvasOnscreen == gladType || GLADType.GLCanvasOffscreen == gladType ) {
            setFrameVisible(frame);
            Assert.assertEquals(true,  AWTRobotUtil.waitForVisible(frame, true));
        } else if( GLADType.GLWindow == gladType ) {
            ((GLWindow)glad).setVisible(true);
        }
        Assert.assertEquals(true,  AWTRobotUtil.waitForRealized(glad, true));
        Assert.assertNotNull(glad.getContext());
        Assert.assertTrue(glad.isRealized());

        if( null != glelsIn ) {
            Assert.assertEquals(0, glad.getGLEventListenerCount());
            System.err.println(".. restoring.0");
            glelsIn.moveTo(glad);
            System.err.println(".. restoring.X");

            Assert.assertEquals(1, glelTracker.initCount);
            Assert.assertTrue(1 <= glelTracker.reshapeCount);
            Assert.assertTrue(1 <= glelTracker.displayCount);
            Assert.assertEquals(0, glelTracker.disposeCount);
            Assert.assertEquals(3, glad.getGLEventListenerCount());

            Assert.assertEquals(glelsIn.context, glad.getContext());
            Assert.assertEquals(glelsIn.listenerCount(), glad.getGLEventListenerCount());
            Assert.assertEquals(glelsIn.context.getGLReadDrawable(), glad.getDelegatedDrawable());
            Assert.assertEquals(glelsIn.context.getGLDrawable(), glad.getDelegatedDrawable());
            Assert.assertEquals(false, glelsIn.isOwner());
        }

        for (int wait=0; wait<AWTRobotUtil.POLL_DIVIDER &&
                         ( 1 > glelTracker.initCount || 1 > glelTracker.reshapeCount || 1 > glelTracker.displayCount );
             wait++) {
            Thread.sleep(AWTRobotUtil.TIME_SLICE);
        }

        final long t0 = System.currentTimeMillis();
        long t1 = t0;

        while( ( t1 - t0 ) < duration ) {
            Thread.sleep(100);
            t1 = System.currentTimeMillis();
        }

        Assert.assertEquals(1, glelTracker.initCount);
        Assert.assertTrue(1 <= glelTracker.reshapeCount);
        Assert.assertTrue(1 <= glelTracker.displayCount);
        Assert.assertEquals(0, glelTracker.disposeCount);

        if( null != glelsOut ) {
            final GLContext context1 = glad.getContext();
            System.err.println(".. preserving.0");
            glelsOut[0] = GLEventListenerState.moveFrom(glad);
            System.err.println(".. preserving.X");

            Assert.assertEquals(context1, glelsOut[0].context);
            Assert.assertNull(context1.getGLReadDrawable());
            Assert.assertNull(context1.getGLDrawable());
            Assert.assertEquals(3, glelsOut[0].listenerCount());
            Assert.assertEquals(true, glelsOut[0].isOwner());
            Assert.assertEquals(null, glad.getContext());
            Assert.assertEquals(0, glad.getGLEventListenerCount());
        }
        if( GLADType.GLCanvasOnscreen == gladType || GLADType.GLCanvasOffscreen == gladType ) {
            destroyFrame(frame);
            Assert.assertEquals(true,  AWTRobotUtil.waitForVisible(frame, false));
        } else if( GLADType.GLWindow == gladType ) {
            glad.destroy();
        } else if( GLADType.GLOffscreen == gladType ) {
            glad.destroy();
        }
        Assert.assertEquals(true,  AWTRobotUtil.waitForRealized(glad, false));

        Assert.assertEquals(1, glelTracker.initCount);
        Assert.assertTrue(1 <= glelTracker.reshapeCount);
        Assert.assertTrue(1 <= glelTracker.displayCount);
        if( null != glelsOut ) {
            Assert.assertEquals(0, glelTracker.disposeCount);
        } else {
            Assert.assertEquals(1, glelTracker.disposeCount);
        }
        System.err.println("GLAD Lifecycle.X "+gladType);
    }
}
