/**
 * 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.awt;

import com.jogamp.opengl.*;

import com.jogamp.opengl.util.Animator;

import com.jogamp.opengl.awt.GLCanvas;
import javax.swing.BoundedRangeModel;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.ScrollPaneConstants;

import com.jogamp.common.util.awt.AWTEDTExecutor;
import com.jogamp.newt.event.awt.AWTWindowAdapter;
import com.jogamp.newt.event.TraceWindowAdapter;
import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2;
import com.jogamp.opengl.test.junit.jogl.demos.es2.RedSquareES2;
import com.jogamp.opengl.test.junit.util.AWTRobotUtil;
import com.jogamp.opengl.test.junit.util.MiscUtils;
import com.jogamp.opengl.test.junit.util.UITestCase;
import com.jogamp.opengl.test.junit.util.QuitAdapter;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.GridLayout;
import java.lang.reflect.InvocationTargetException;

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

/**
 * Bug 816: OSX CALayer Positioning Bug.
 * <p>
 * Diff. OSX CALayer positioning w/ java6, [7uxx..7u40[, and >= 7u40
 * </p>
 */
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestBug816OSXCALayerPos01AWT extends UITestCase {
    public enum FrameLayout { None, Flow, DoubleBorderCenterSurrounded, Box, Split };

    static long duration = 1600; // ms
    static final int width = 640, height = 480;

    static boolean forceES2 = false;
    static boolean forceGL3 = false;
    static int swapInterval = 1;
    static java.awt.Dimension rwsize = new Dimension(800, 600);

    static void setComponentSize(final Frame frame, final Component comp1, final java.awt.Dimension new_sz1, final Component comp2, final java.awt.Dimension new_sz2) {
        try {
            AWTEDTExecutor.singleton.invoke(true /* wait */, new Runnable() {
                public void run() {
                    comp1.setMinimumSize(new_sz1);
                    comp1.setPreferredSize(new_sz1);
                    comp1.setSize(new_sz1);
                    if( null != comp2 ) {
                        comp2.setMinimumSize(new_sz2);
                        comp2.setPreferredSize(new_sz2);
                        comp2.setSize(new_sz2);
                    }
                    if( null != frame ) {
                        frame.pack();
                    }
                } } );
        } catch( final Throwable throwable ) {
            throwable.printStackTrace();
            Assume.assumeNoException( throwable );
        }
    }
    static void setFrameSize(final Frame frame, final boolean frameLayout, final java.awt.Dimension new_sz) {
        try {
            AWTEDTExecutor.singleton.invoke(true /* wait */, new Runnable() {
                public void run() {
                    frame.setSize(new_sz);
                    if( frameLayout ) {
                        frame.validate();
                    }
                } } );
        } catch( final Throwable throwable ) {
            throwable.printStackTrace();
            Assume.assumeNoException( throwable );
        }
    }

    protected void runTestGL(final GLCapabilities caps, final FrameLayout frameLayout, final boolean twoCanvas, final boolean resizeByComp) throws InterruptedException, InvocationTargetException {
        final JFrame frame = new JFrame("Bug816: "+this.getTestMethodName());
        Assert.assertNotNull(frame);
        final Container framePane = frame.getContentPane();

        final GLCanvas glCanvas1 = new GLCanvas(caps);
        Assert.assertNotNull(glCanvas1);
        final GLCanvas glCanvas2;
        if( twoCanvas ) {
            glCanvas2 = new GLCanvas(caps);
            Assert.assertNotNull(glCanvas2);
        } else {
            glCanvas2 = null;
        }

        final Dimension glcDim = new Dimension(width/2, height);
        final Dimension frameDim = new Dimension(twoCanvas ? width + 64: width/2 + 64, height + 64);

        setComponentSize(null, glCanvas1, glcDim, glCanvas2, glcDim);

        switch( frameLayout) {
            case None: {
                    framePane.add(glCanvas1);
                }
                break;
            case Flow: {
                    final Container c = new Container();
                    c.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
                    c.add(glCanvas1);
                    if( twoCanvas ) {
                        c.add(glCanvas2);
                    }
                    framePane.add(c);
                }
                break;
            case DoubleBorderCenterSurrounded: {
                    final Container c = new Container();
                    c.setLayout(new BorderLayout());
                    c.add(new Button("north"), BorderLayout.NORTH);
                    c.add(new Button("south"), BorderLayout.SOUTH);
                    c.add(new Button("east"), BorderLayout.EAST);
                    c.add(new Button("west"), BorderLayout.WEST);
                    if( twoCanvas ) {
                        final Container c2 = new Container();
                        c2.setLayout(new GridLayout(1, 2));
                        c2.add(glCanvas1);
                        c2.add(glCanvas2);
                        c.add(c2, BorderLayout.CENTER);
                    } else {
                        c.add(glCanvas1, BorderLayout.CENTER);
                    }
                    framePane.setLayout(new BorderLayout());
                    framePane.add(new Button("NORTH"), BorderLayout.NORTH);
                    framePane.add(new Button("SOUTH"), BorderLayout.SOUTH);
                    framePane.add(new Button("EAST"), BorderLayout.EAST);
                    framePane.add(new Button("WEST"), BorderLayout.WEST);
                    framePane.add(c, BorderLayout.CENTER);
                }
                break;
            case Box: {
                    final Container c = new Container();
                    c.setLayout(new BoxLayout(c, BoxLayout.X_AXIS));
                    c.add(glCanvas1);
                    if( twoCanvas ) {
                        c.add(glCanvas2);
                    }
                    framePane.add(c);
                }
                break;
            case Split: {
                    final Dimension sbDim = new Dimension(16, 16);
                    final JScrollPane vsp = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
                    {
                        final JScrollBar vsb = vsp.getVerticalScrollBar();
                        vsb.setPreferredSize(sbDim);
                        final BoundedRangeModel model = vsb.getModel();
                        model.setMinimum(0);
                        model.setMaximum(100);
                        model.setValue(50);
                        model.setExtent(1);
                        vsb.setEnabled(true);
                    }
                    final JScrollPane hsp = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
                    {
                        final JScrollBar hsb = hsp.getHorizontalScrollBar();
                        hsb.setPreferredSize(sbDim);
                        final BoundedRangeModel model = hsb.getModel();
                        model.setMinimum(0);
                        model.setMaximum(100);
                        model.setValue(50);
                        model.setExtent(1);
                        hsb.setEnabled(true);
                    }
                    final JSplitPane horizontalSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true,
                            twoCanvas ? glCanvas2 : vsp, glCanvas1 );
                    horizontalSplitPane.setResizeWeight(0.5);
                    final JSplitPane verticalSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
                        true, horizontalSplitPane, hsp);
                    verticalSplitPane.setResizeWeight(0.5);
                    framePane.add(verticalSplitPane);
                }
                break;
        }
        final GearsES2 demo1 = new GearsES2(swapInterval);
        glCanvas1.addGLEventListener(demo1);
        if( twoCanvas ) {
            final RedSquareES2 demo2 = new RedSquareES2(swapInterval);
            glCanvas2.addGLEventListener(demo2);
        }

        final Animator animator = new Animator();
        animator.add(glCanvas1);
        if( twoCanvas ) {
            animator.add(glCanvas2);
        }
        final QuitAdapter quitAdapter = new QuitAdapter();
        new AWTWindowAdapter(new TraceWindowAdapter(quitAdapter), glCanvas1).addTo(frame);

        javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
               if( resizeByComp ) {
                   frame.pack();
                } else {
                   setFrameSize(frame, true, frameDim);
                }
                frame.setVisible(true);
            }});
        Assert.assertEquals(true,  AWTRobotUtil.waitForVisible(frame, true, null));
        Assert.assertEquals(true,  AWTRobotUtil.waitForRealized(glCanvas1, true, null));
        if( twoCanvas ) {
            Assert.assertEquals(true,  AWTRobotUtil.waitForRealized(glCanvas2, true, null));
        }

        animator.start();
        Assert.assertTrue(animator.isStarted());
        Assert.assertTrue(animator.isAnimating());

        System.err.println("canvas1 pos/siz: "+glCanvas1.getX()+"/"+glCanvas1.getY()+" "+glCanvas1.getSurfaceWidth()+"x"+glCanvas1.getSurfaceHeight());
        if( twoCanvas ) {
            System.err.println("canvas2 pos/siz: "+glCanvas2.getX()+"/"+glCanvas2.getY()+" "+glCanvas2.getSurfaceWidth()+"x"+glCanvas2.getSurfaceHeight());
        }

        Thread.sleep(Math.max(1000, duration/2));
        if( null != rwsize ) {
            final Dimension compRSizeHalf = new Dimension(rwsize.width/2, rwsize.height);
            final Dimension frameRSizeHalf = new Dimension(twoCanvas ? rwsize.width + 64: rwsize.width/2 + 64, rwsize.height + 64);
            if( resizeByComp ) {
               setComponentSize(frame, glCanvas1, compRSizeHalf, glCanvas2, compRSizeHalf);
            } else {
               setFrameSize(frame, true, frameRSizeHalf);
            }
            System.err.println("resize canvas1 pos/siz: "+glCanvas1.getX()+"/"+glCanvas1.getY()+" "+glCanvas1.getSurfaceWidth()+"x"+glCanvas1.getSurfaceHeight());
            if( twoCanvas ) {
                System.err.println("resize canvas2 pos/siz: "+glCanvas2.getX()+"/"+glCanvas2.getY()+" "+glCanvas2.getSurfaceWidth()+"x"+glCanvas2.getSurfaceHeight());
            }
        }

        final long t0 = System.currentTimeMillis();
        long t1 = t0;
        while(!quitAdapter.shouldQuit() && t1 - t0 < duration) {
            Thread.sleep(100);
            t1 = System.currentTimeMillis();
        }

        Assert.assertNotNull(frame);
        Assert.assertNotNull(glCanvas1);
        if( twoCanvas ) {
            Assert.assertNotNull(glCanvas2);
        } else {
            Assert.assertNull(glCanvas2);
        }

        Assert.assertNotNull(animator);
        animator.stop();
        Assert.assertFalse(animator.isAnimating());
        Assert.assertFalse(animator.isStarted());

        javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                frame.setVisible(false);
            }});
        Assert.assertEquals(false, frame.isVisible());
        javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                frame.remove(glCanvas1);
                if( twoCanvas ) {
                    frame.remove(glCanvas2);
                }
                frame.dispose();
            }});
    }

    static GLProfile getGLP() {
        return GLProfile.getMaxProgrammableCore(true);
    }

    @Test
    public void test00_Compo_None_One() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 0 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.None, false /* twoCanvas */, true /* resizeByComp */);
    }

    @Test
    public void test01_Compo_Flow_One() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 1 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.Flow, false /* twoCanvas */, true /* resizeByComp */);
    }

    @Test
    public void test02_Compo_DblBrd_One() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 2 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.DoubleBorderCenterSurrounded, false /* twoCanvas */, true /* resizeByComp */);
    }

    @Test
    public void test03_Compo_Box_One() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 3 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.Box, false /* twoCanvas */, true /* resizeByComp */);
    }

    @Test
    public void test04_Compo_Split_One() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 4 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.Split, false /* twoCanvas */, true /* resizeByComp */);
    }

    @Test
    public void test05_Compo_Flow_Two() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 5 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.Flow, true/* twoCanvas */, true /* resizeByComp */);
    }

    @Test
    public void test06_Compo_DblBrd_Two() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 6 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.DoubleBorderCenterSurrounded, true/* twoCanvas */, true /* resizeByComp */);
    }

    @Test
    public void test07_Compo_Box_Two() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 7 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.Box, true/* twoCanvas */, true /* resizeByComp */);
    }

    @Test
    public void test08_Compo_Split_Two() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 8 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.Split, true/* twoCanvas */, true /* resizeByComp */);
    }

    @Test
    public void test10_Frame_None_One() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 10 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.None, false /* twoCanvas */, false /* resizeByComp */);
    }

    @Test
    public void test11_Frame_Flow_One() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 11 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.Flow, false /* twoCanvas */, false /* resizeByComp */);
    }

    @Test
    public void test12_Frame_DblBrd_One() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 12 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.DoubleBorderCenterSurrounded, false /* twoCanvas */, false /* resizeByComp */);
    }

    @Test
    public void test13_Frame_Box_One() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 13 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.Box, false /* twoCanvas */, false /* resizeByComp */);
    }

    @Test
    public void test14_Frame_Split_One() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 14) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.Split, false /* twoCanvas */, false /* resizeByComp */);
    }

    @Test
    public void test15_Frame_Flow_Two() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 15 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.Flow, true/* twoCanvas */, false /* resizeByComp */);
    }

    @Test
    public void test16_Frame_DblBrd_Two() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 16 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.DoubleBorderCenterSurrounded, true/* twoCanvas */, false /* resizeByComp */);
    }

    @Test
    public void test17_Frame_Box_Two() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 17 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.Box, true/* twoCanvas */, false /* resizeByComp */);
    }

    @Test
    public void test18_Frame_Split_Two() throws InterruptedException, InvocationTargetException {
        if( testNum != -1 && testNum != 18 ) { return ; }
        final GLCapabilities caps = new GLCapabilities(getGLP());
        runTestGL(caps, FrameLayout.Split, true/* twoCanvas */, false /* resizeByComp */);
    }

    static int testNum = -1;

    public static void main(final String args[]) {
        for(int i=0; i<args.length; i++) {
            if(args[i].equals("-time")) {
                i++;
                duration = MiscUtils.atol(args[i], duration);
            } else if(args[i].equals("-test")) {
                i++;
                testNum = MiscUtils.atoi(args[i], 0);
            } else if(args[i].equals("-noresize")) {
                rwsize = null;
            } else if(args[i].equals("-es2")) {
                forceES2 = true;
            } else if(args[i].equals("-gl3")) {
                forceGL3 = true;
            } else if(args[i].equals("-vsync")) {
                i++;
                swapInterval = MiscUtils.atoi(args[i], swapInterval);
            }
        }

        System.err.println("resize "+rwsize);
        System.err.println("forceES2 "+forceES2);
        System.err.println("forceGL3 "+forceGL3);
        System.err.println("swapInterval "+swapInterval);

        org.junit.runner.JUnitCore.main(TestBug816OSXCALayerPos01AWT.class.getName());
    }
}
