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

import java.lang.reflect.InvocationTargetException;

import org.eclipse.swt.SWT ;

import org.eclipse.swt.layout.FillLayout ;

import org.eclipse.swt.widgets.Composite ;
import org.eclipse.swt.widgets.Display ;
import org.eclipse.swt.widgets.Shell ;

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

import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities ;
import com.jogamp.opengl.GLProfile;

import jogamp.newt.swt.SWTEDTUtil;
import jogamp.newt.swt.event.SWTNewtEventFactory;

import com.jogamp.common.util.InterruptSource;
import com.jogamp.nativewindow.swt.SWTAccessor;
import com.jogamp.newt.NewtFactory;
import com.jogamp.newt.opengl.GLWindow ;
import com.jogamp.newt.swt.NewtCanvasSWT ;
import com.jogamp.opengl.swt.GLCanvas;
import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2;
import com.jogamp.opengl.test.junit.util.MiscUtils;
import com.jogamp.opengl.test.junit.util.NewtTestUtil;
import com.jogamp.opengl.test.junit.util.SWTTestUtil;
import com.jogamp.opengl.test.junit.util.TestUtil;
import com.jogamp.opengl.test.junit.util.UITestCase;
import com.jogamp.opengl.util.Animator;

////////////////////////////////////////////////////////////////////////////////


@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestSWTBug643AsyncExec extends UITestCase {

    static int duration = 500;
    static boolean useAnimator = false;

    ////////////////////////////////////////////////////////////////////////////////

    static void resetSWTAndNEWTEDTCounter() {
        synchronized(swtCountSync) {
            swtCount=0;
        }
        synchronized(edtCountSync) {
            edtCount=0;
        }
    }
    static int incrSWTCount() {
        synchronized(swtCountSync) {
            swtCount++;
            return swtCount;
        }
    }
    static int getSWTCount() {
        synchronized(swtCountSync) {
            return swtCount;
        }
    }
    static int incrNEWTCount() {
        synchronized(edtCountSync) {
            edtCount++;
            return edtCount;
        }
    }
    static int getNEWTCount() {
        synchronized(edtCountSync) {
            return edtCount;
        }
    }
    static Object swtCountSync = new Object();
    static int swtCount = 0;
    static Object edtCountSync = new Object();
    static int edtCount = 0;

    ////////////////////////////////////////////////////////////////////////////////

    static class AsyncExecEDTFeederThread extends InterruptSource.Thread {
        volatile boolean shallStop = false;
        private final Display swtDisplay ;
        private final jogamp.newt.DisplayImpl newtDisplay;
        private int swtN, newtN ;

        public AsyncExecEDTFeederThread( final Display swtDisplay, final com.jogamp.newt.Display newtDisplay )
        {
            this.swtDisplay = swtDisplay ;
            this.newtDisplay = (jogamp.newt.DisplayImpl)newtDisplay;
        }

        final Runnable swtAsyncAction = new Runnable() {
            public void run()
            {
                ++swtN ; incrSWTCount();
                System.err.println("[SWT A-i shallStop "+shallStop+"]: Counter[loc "+swtN+", glob: "+getSWTCount()+"]");
            }  };

        final Runnable newtAsyncAction = new Runnable() {
            public void run()
            {
                ++newtN ; incrNEWTCount();
                System.err.println("[NEWT A-i shallStop "+shallStop+"]: Counter[loc "+newtN+", glob: "+getNEWTCount()+"]");
            }  };

        public void run()
        {
            System.err.println("[A-0 shallStop "+shallStop+"]");

            while( !shallStop && !swtDisplay.isDisposed() )
            {
                try
                {
                    if( !swtDisplay.isDisposed() ) {
                        swtDisplay.asyncExec( swtAsyncAction );
                    }
                    if(null != newtDisplay && newtDisplay.isNativeValid() && newtDisplay.getEDTUtil().isRunning()) {
                        // only perform async exec on valid and already running NEWT EDT!
                        newtDisplay.runOnEDTIfAvail(false, newtAsyncAction);
                    }
                    java.lang.Thread.sleep( 50L ) ;
                } catch( final InterruptedException e ) {
                    break ;
                }
            }
            System.err.println("*R-Exit* shallStop "+shallStop);
        }
    }

    ////////////////////////////////////////////////////////////////////////////////

    private volatile boolean shallStop = false;

    static class SWT_DSC {
        Display display;
        Shell shell;
        Composite composite;

        public void init() {
            SWTAccessor.invokeOnOSTKThread(true, new Runnable() {
                public void run() {
                    display = new Display();
                    Assert.assertNotNull( display );
                }});

            display.syncExec(new Runnable() {
                public void run() {
                    shell = new Shell( display );
                    Assert.assertNotNull( shell );
                    shell.setLayout( new FillLayout() );
                    composite = new Composite( shell, SWT.NO_BACKGROUND );
                    composite.setLayout( new FillLayout() );
                    Assert.assertNotNull( composite );
                }});
        }

        public void dispose() {
            Assert.assertNotNull( display );
            Assert.assertNotNull( shell );
            Assert.assertNotNull( composite );
            try {
                display.syncExec(new Runnable() {
                   public void run() {
                    composite.dispose();
                    shell.dispose();
                   }});
                SWTAccessor.invokeOnOSTKThread(true, new Runnable() {
                   public void run() {
                    display.dispose();
                   }});
            }
            catch( final Throwable throwable ) {
                throwable.printStackTrace();
                Assume.assumeNoException( throwable );
            }
            display = null;
            shell = null;
            composite = null;
        }
    }

    private void testImpl(final boolean useJOGLGLCanvas, final boolean useNewtCanvasSWT, final boolean glWindowPreVisible) throws InterruptedException, InvocationTargetException {
        resetSWTAndNEWTEDTCounter();

        final SWT_DSC dsc = new SWT_DSC();
        dsc.init();

        final com.jogamp.newt.Display newtDisplay;
        {
            final GLProfile gl2Profile = GLProfile.get( GLProfile.GL2 ) ;
            final GLCapabilities caps = new GLCapabilities( gl2Profile ) ;

            final GLAutoDrawable glad;
            if( useJOGLGLCanvas ) {
                final GearsES2 demo = new GearsES2();
                final GLCanvas glc = GLCanvas.create(dsc.composite, 0, caps, null);
                final SWTNewtEventFactory swtNewtEventFactory = new SWTNewtEventFactory();
                dsc.display.syncExec(new Runnable() {
                    @Override
                    public void run() {
                        swtNewtEventFactory.attachDispatchListener(glc, glc, demo.gearsMouse, demo.gearsKeys);
                    } } );
                glc.addGLEventListener( demo ) ;
                glad = glc;
                newtDisplay = null;
            } else if( useNewtCanvasSWT ) {
                newtDisplay = NewtFactory.createDisplay(null, false); // no-reuse
                final com.jogamp.newt.Screen screen = NewtFactory.createScreen(newtDisplay, 0);
                final GLWindow glWindow = GLWindow.create( screen, caps ) ;
                glWindow.addGLEventListener( new GearsES2() ) ;
                if( glWindowPreVisible ) {
                    newtDisplay.setEDTUtil(new SWTEDTUtil(newtDisplay, dsc.display)); // Especially Windows requires creation access via same thread!
                    glWindow.setVisible(true);
                    NewtTestUtil.waitForRealized(glWindow, true, null);
                    Thread.sleep(120); // let it render a bit, before consumed by SWT
                }
                glad = glWindow;
                NewtCanvasSWT.create( dsc.composite, 0, glWindow ) ;
            } else {
                throw new InternalError("XXX");
            }
            if(useAnimator) {
                final Animator animator = new Animator(glad);
                animator.start();
            }
        }

        System.err.println("**** Pre Shell Open");
        dsc.display.syncExec( new Runnable() {
            public void run() {
               dsc.shell.setText( "NewtCanvasSWT Resize Bug Demo" ) ;
               dsc.shell.setSize( 400, 450 ) ;
               dsc.shell.open() ;
            } } );
        System.err.println("**** Post Shell Open");

        shallStop = false;

        final int[] counterBeforeExit = new int[] { 0 /* SWT */, 0 /* NEWT */ };

        final AsyncExecEDTFeederThread asyncExecFeeder;
        {
            asyncExecFeeder = new AsyncExecEDTFeederThread(dsc.display, newtDisplay) ;
            asyncExecFeeder.start() ;
        }

        {
            final Thread t = new InterruptSource.Thread(null, new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(duration);
                    } catch (final InterruptedException e) {}

                    counterBeforeExit[0] = getSWTCount();
                    counterBeforeExit[1] = getNEWTCount();
                    asyncExecFeeder.shallStop = true;
                    try
                    {
                        asyncExecFeeder.join();
                    } catch( final InterruptedException e ) { }
                    shallStop = true;
                    dsc.display.wake();
                } } );
            t.setDaemon(true);
            t.start();
        }

        final SWTTestUtil.WaitAction generalWaitAction = new SWTTestUtil.WaitAction(dsc.display, true, 10);

        try {
            final Display d = dsc.display;
            while( !shallStop && !d.isDisposed() ) {
                generalWaitAction.run();
            }
        } catch (final Exception e0) {
            e0.printStackTrace();
            Assert.assertTrue("Deadlock @ dispatch: "+e0, false);
        }

        // canvas is disposed implicit, due to it's disposed listener !

        dsc.dispose();

        System.err.println("EDT Counter before exit: SWT " + counterBeforeExit[0] + ", NEWT "+counterBeforeExit[1]);
        Assert.assertTrue("SWT EDT Counter not greater zero before dispose!", 0 < counterBeforeExit[0]);
        if( null != newtDisplay ) {
            Assert.assertTrue("NEWT EDT Counter not greater zero before dispose!", 0 < counterBeforeExit[1]);
        }
    }

    @Test
    public void test01JOGLGLCanvas() throws InterruptedException, InvocationTargetException {
        testImpl(true /* useJOGLGLCanvas */, false /* useNewtCanvasSWT */, false /* glWindowPreVisible */);
    }

    @Test
    public void test02NewtCanvasSWTSimple() throws InterruptedException, InvocationTargetException {
        testImpl(false /* useJOGLGLCanvas */, true /* useNewtCanvasSWT */, false /* glWindowPreVisible */);
    }

    @Test
    public void test02NewtCanvasSWTPreVisible() throws InterruptedException, InvocationTargetException {
        testImpl(false /* useJOGLGLCanvas */, true /* useNewtCanvasSWT */, true /* glWindowPreVisible */);
    }

    public static void main( final String[] args ) {
        for(int i=0; i<args.length; i++) {
            if(args[i].equals("-time")) {
                duration = MiscUtils.atoi(args[++i],  duration);
            } else if(args[i].equals("-anim")) {
                useAnimator = true;
            }
        }
        System.out.println("durationPerTest: "+duration);
        org.junit.runner.JUnitCore.main(TestSWTBug643AsyncExec.class.getName());
    }

}
