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

import java.util.List;

import javax.media.nativewindow.AbstractGraphicsConfiguration;
import javax.media.nativewindow.AbstractGraphicsDevice;
import javax.media.nativewindow.AbstractGraphicsScreen;
import javax.media.nativewindow.GraphicsConfigurationFactory;
import javax.media.nativewindow.NativeSurface;
import javax.media.nativewindow.NativeWindowException;
import javax.media.nativewindow.ProxySurface;
import javax.media.nativewindow.UpstreamSurfaceHook;
import javax.media.nativewindow.VisualIDHolder;
import javax.media.nativewindow.VisualIDHolder.VIDType;
import javax.media.opengl.GL;
import javax.media.opengl.GLAnimatorControl;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLCapabilitiesChooser;
import javax.media.opengl.GLCapabilitiesImmutable;
import javax.media.opengl.GLContext;
import javax.media.opengl.GLDrawable;
import javax.media.opengl.GLDrawableFactory;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLException;
import javax.media.opengl.GLProfile;
import javax.media.opengl.GLRunnable;
import javax.media.opengl.GLSharedContextSetter;
import javax.media.opengl.Threading;

import jogamp.nativewindow.x11.X11Util;
import jogamp.opengl.Debug;
import jogamp.opengl.GLContextImpl;
import jogamp.opengl.GLDrawableHelper;
import jogamp.opengl.GLDrawableImpl;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;

import com.jogamp.common.GlueGenVersion;
import com.jogamp.common.os.Platform;
import com.jogamp.common.util.VersionUtil;
import com.jogamp.common.util.locks.LockFactory;
import com.jogamp.common.util.locks.RecursiveLock;
import com.jogamp.nativewindow.swt.SWTAccessor;
import com.jogamp.nativewindow.x11.X11GraphicsDevice;
import com.jogamp.opengl.JoglVersion;

/**
 * Native SWT Canvas implementing GLAutoDrawable
 * <p>
 * Implementation allows use of custom {@link GLCapabilities}.
 * </p>
 */
public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextSetter {
  private static final boolean DEBUG = Debug.debug("GLCanvas");

   /*
    * Flag for whether the SWT thread should be used for OpenGL calls when in single-threaded mode. This is controlled
    * by the setting of the threading mode to worker (do not use SWT thread), awt (use SWT thread), or false (always use
    * calling thread).
    *
    * @see Threading
    *
    * Now done dynamically to avoid early loading of gluegen library.
    */
   //private static final boolean useSWTThread = ThreadingImpl.getMode() != ThreadingImpl.WORKER;

   /* GL Stuff */
   private final RecursiveLock lock = LockFactory.createRecursiveLock();
   private final GLDrawableHelper helper = new GLDrawableHelper();

   private final GLCapabilitiesImmutable capsRequested;
   private final GLCapabilitiesChooser capsChooser;

   private volatile Rectangle clientArea;
   private volatile GLDrawableImpl drawable; // volatile: avoid locking for read-only access
   private volatile GLContextImpl context; // volatile: avoid locking for read-only access

   /* Native window surface */
   private final boolean useX11GTK;
   private volatile long gdkWindow; // either GDK child window ..
   private volatile long x11Window; // .. or X11 child window (for GL rendering)
   private final AbstractGraphicsScreen screen;

   /* Construction parameters stored for GLAutoDrawable accessor methods */
   private int additionalCtxCreationFlags = 0;


   /* Flag indicating whether an unprocessed reshape is pending. */
   private volatile boolean sendReshape; // volatile: maybe written by WindowManager thread w/o locking

   private static String getThreadName() { return Thread.currentThread().getName(); }
   private static String toHexString(int v) { return "0x"+Integer.toHexString(v); }
   private static String toHexString(long v) { return "0x"+Long.toHexString(v); }

   /*
    * Invokes init(...) on all GLEventListeners. Assumes context is current when run.
    */
   private final Runnable initAction = new Runnable() {
      @Override
      public void run() {
         helper.init(GLCanvas.this, !sendReshape);
      }
   };

   /*
    * Action to handle display in OpenGL, also processes reshape since they should be done at the same time.
    *
    * Assumes GLContext is current when run.
    */
   private final Runnable displayAction = new Runnable() {
      @Override
      public void run() {
         if (sendReshape) {
            helper.reshape(GLCanvas.this, 0, 0, clientArea.width, clientArea.height);
            sendReshape = false;
         }
         helper.display(GLCanvas.this);
      }
   };

   /* Action to make specified context current prior to running displayAction */
   private final Runnable makeCurrentAndDisplayOnGLAction = new Runnable() {
      @Override
      public void run() {
        final RecursiveLock _lock = lock;
        _lock.lock();
        try {
            if( !GLCanvas.this.isDisposed() ) {
                helper.invokeGL(drawable, context, displayAction, initAction);
            }
        } finally {
            _lock.unlock();
        }
      }
   };

   /* Swaps buffers, assuming the GLContext is current */
   private final Runnable swapBuffersOnGLAction = new Runnable() {
      @Override
      public void run() {
        final RecursiveLock _lock = lock;
        _lock.lock();
        try {
            final boolean drawableOK = null != drawable && drawable.isRealized();
            if( drawableOK && !GLCanvas.this.isDisposed() ) {
                drawable.swapBuffers();
            }
        } finally {
            _lock.unlock();
        }
      }
   };

   /*
    * Disposes of OpenGL resources
    */
   private final Runnable disposeOnEDTGLAction = new Runnable() {
      @Override
      public void run() {
         final RecursiveLock _lock = lock;
         _lock.lock();
         try {
             final GLAnimatorControl animator = getAnimator();
             final boolean animatorPaused;
             if(null!=animator) {
                 // can't remove us from animator for recreational addNotify()
                 animatorPaused = animator.pause();
             } else {
                 animatorPaused = false;
             }

             if ( null != context ) {
                 if( context.isCreated() ) {
                     // Catch dispose GLExceptions by GLEventListener, just 'print' them
                     // so we can continue with the destruction.
                     try {
                         if( !GLCanvas.this.isDisposed() ) {
                             helper.disposeGL(GLCanvas.this, context, true);
                         } else {
                             context.destroy();
                         }
                     } catch (GLException gle) {
                         gle.printStackTrace();
                     }
                 }
                 context = null;
             }
             if ( null != drawable ) {
                 drawable.setRealized(false);
                 drawable = null;
             }
             if( 0 != x11Window) {
                 SWTAccessor.destroyX11Window(screen.getDevice(), x11Window);
                 x11Window = 0;
             } else if( 0 != gdkWindow) {
                 SWTAccessor.destroyGDKWindow(gdkWindow);
                 gdkWindow = 0;
             }
             screen.getDevice().close();

             if (animatorPaused) {
                 animator.resume();
             }

         } finally {
             _lock.unlock();
         }
      }
   };

   private class DisposeGLEventListenerAction implements Runnable {
       private GLEventListener listener;
       private final boolean remove;
       private DisposeGLEventListenerAction(GLEventListener listener, boolean remove) {
           this.listener = listener;
           this.remove = remove;
       }

       @Override
       public void run() {
           final RecursiveLock _lock = lock;
           _lock.lock();
           try {
               if( !GLCanvas.this.isDisposed() ) {
                   listener = helper.disposeGLEventListener(GLCanvas.this, drawable, context, listener, remove);
               }
           } finally {
               _lock.unlock();
           }
       }
   };

   /**
    * Creates an instance using {@link #GLCanvas(Composite, int, GLCapabilitiesImmutable, GLCapabilitiesChooser)}
    * on the SWT thread.
    *
    * @param parent
    *           Required (non-null) parent Composite.
    * @param style
    *           Optional SWT style bit-field. The {@link SWT#NO_BACKGROUND} bit is set before passing this up to the
    *           Canvas constructor, so OpenGL handles the background.
    * @param caps
    *           Optional GLCapabilities. If not provided, the default capabilities for the default GLProfile for the
    *           graphics device determined by the parent Composite are used. Note that the GLCapabilities that are
    *           actually used may differ based on the capabilities of the graphics device.
    * @param chooser
    *           Optional GLCapabilitiesChooser to customize the selection of the used GLCapabilities based on the
    *           requested GLCapabilities, and the available capabilities of the graphics device.
    * @return a new instance
    */
   public static GLCanvas create(final Composite parent, final int style, final GLCapabilitiesImmutable caps,
                                 final GLCapabilitiesChooser chooser) {
       final GLCanvas[] res = new GLCanvas[] { null };
       parent.getDisplay().syncExec(new Runnable() {
           @Override
           public void run() {
               res[0] = new GLCanvas( parent, style, caps, chooser );
           }
       });
       return res[0];
   }

   /**
    * Creates an instance using {@link #GLCanvas(Composite, int, GLCapabilitiesImmutable, GLCapabilitiesChooser, GLContext)}
    * on the SWT thread.
    *
    * @param parent
    *           Required (non-null) parent Composite.
    * @param style
    *           Optional SWT style bit-field. The {@link SWT#NO_BACKGROUND} bit is set before passing this up to the
    *           Canvas constructor, so OpenGL handles the background.
    * @param caps
    *           Optional GLCapabilities. If not provided, the default capabilities for the default GLProfile for the
    *           graphics device determined by the parent Composite are used. Note that the GLCapabilities that are
    *           actually used may differ based on the capabilities of the graphics device.
    * @param chooser
    *           Optional GLCapabilitiesChooser to customize the selection of the used GLCapabilities based on the
    *           requested GLCapabilities, and the available capabilities of the graphics device.
    * @param shareWith
    *           Optional GLContext to share state (textures, vbos, shaders, etc.) with.
    * @return a new instance
    * @deprecated Use {@link #create(Composite, int, GLCapabilitiesImmutable, GLCapabilitiesChooser)}
    *             and set shared GLContext via {@link #setSharedContext(GLContext)} or {@link #setSharedAutoDrawable(GLAutoDrawable)}.
    */
   public static GLCanvas create(final Composite parent, final int style, final GLCapabilitiesImmutable caps,
                                 final GLCapabilitiesChooser chooser, final GLContext shareWith) {
       final GLCanvas[] res = new GLCanvas[] { null };
       parent.getDisplay().syncExec(new Runnable() {
           @Override
           public void run() {
               res[0] = new GLCanvas( parent, style, caps, chooser, shareWith );
           }
       });
       return res[0];
   }

   /**
    * Creates a new SWT GLCanvas.
    *
    * @param parent
    *           Required (non-null) parent Composite.
    * @param style
    *           Optional SWT style bit-field. The {@link SWT#NO_BACKGROUND} bit is set before passing this up to the
    *           Canvas constructor, so OpenGL handles the background.
    * @param capsReqUser
    *           Optional GLCapabilities. If not provided, the default capabilities for the default GLProfile for the
    *           graphics device determined by the parent Composite are used. Note that the GLCapabilities that are
    *           actually used may differ based on the capabilities of the graphics device.
    * @param capsChooser
    *           Optional GLCapabilitiesChooser to customize the selection of the used GLCapabilities based on the
    *           requested GLCapabilities, and the available capabilities of the graphics device.
    */
   public GLCanvas(final Composite parent, final int style, GLCapabilitiesImmutable capsReqUser,
                   final GLCapabilitiesChooser capsChooser) {
       this(parent, style, capsReqUser, capsChooser, null);
   }

   /**
    * Creates a new SWT GLCanvas.
    *
    * @param parent
    *           Required (non-null) parent Composite.
    * @param style
    *           Optional SWT style bit-field. The {@link SWT#NO_BACKGROUND} bit is set before passing this up to the
    *           Canvas constructor, so OpenGL handles the background.
    * @param capsReqUser
    *           Optional GLCapabilities. If not provided, the default capabilities for the default GLProfile for the
    *           graphics device determined by the parent Composite are used. Note that the GLCapabilities that are
    *           actually used may differ based on the capabilities of the graphics device.
    * @param capsChooser
    *           Optional GLCapabilitiesChooser to customize the selection of the used GLCapabilities based on the
    *           requested GLCapabilities, and the available capabilities of the graphics device.
    * @param shareWith
    *           Optional GLContext to share state (textures, vbos, shaders, etc.) with.
    * @deprecated Use {@link #GLCanvas(Composite, int, GLCapabilitiesImmutable, GLCapabilitiesChooser)}
    *             and set shared GLContext via {@link #setSharedContext(GLContext)} or {@link #setSharedAutoDrawable(GLAutoDrawable)}.
    */
   public GLCanvas(final Composite parent, final int style, GLCapabilitiesImmutable capsReqUser,
                   final GLCapabilitiesChooser capsChooser, final GLContext shareWith) {
      /* NO_BACKGROUND required to avoid clearing bg in native SWT widget (we do this in the GL display) */
      super(parent, style | SWT.NO_BACKGROUND);

      GLProfile.initSingleton(); // ensure JOGL is completly initialized

      SWTAccessor.setRealized(this, true);

      clientArea = GLCanvas.this.getClientArea();

      /* Get the nativewindow-Graphics Device associated with this control (which is determined by the parent Composite).
       * Note: SWT is owner of the native handle, hence closing operation will be a NOP. */
      final AbstractGraphicsDevice swtDevice = SWTAccessor.getDevice(this);

      useX11GTK = SWTAccessor.useX11GTK();
      if(useX11GTK) {
          // Decoupled X11 Device/Screen allowing X11 display lock-free off-thread rendering
          final long x11DeviceHandle = X11Util.openDisplay(swtDevice.getConnection());
          if( 0 == x11DeviceHandle ) {
              throw new RuntimeException("Error creating display(EDT): "+swtDevice.getConnection());
          }
          final AbstractGraphicsDevice x11Device = new X11GraphicsDevice(x11DeviceHandle, AbstractGraphicsDevice.DEFAULT_UNIT, true /* owner */);
          screen = SWTAccessor.getScreen(x11Device, -1 /* default */);
      } else {
          screen = SWTAccessor.getScreen(swtDevice, -1 /* default */);
      }

      /* Select default GLCapabilities if none was provided, otherwise clone provided caps to ensure safety */
      if(null == capsReqUser) {
          capsReqUser = new GLCapabilities(GLProfile.getDefault(screen.getDevice()));
      }

      this.capsRequested = capsReqUser;
      this.capsChooser = capsChooser;
      if( null != shareWith ) {
          helper.setSharedContext(null, shareWith);
      }

      // post create .. when ready
      gdkWindow = 0;
      x11Window = 0;
      drawable = null;
      context = null;

      final Listener listener = new Listener () {
          @Override
          public void handleEvent (Event event) {
              switch (event.type) {
              case SWT.Paint:
                  displayIfNoAnimatorNoCheck();
                  break;
              case SWT.Resize:
                  updateSizeCheck();
                  break;
              case SWT.Dispose:
                  GLCanvas.this.dispose();
                  break;
              }
          }
      };
      addListener (SWT.Resize, listener);
      addListener (SWT.Paint, listener);
      addListener (SWT.Dispose, listener);
   }

   @Override
   public final void setSharedContext(GLContext sharedContext) throws IllegalStateException {
       helper.setSharedContext(this.context, sharedContext);
   }

   @Override
   public final void setSharedAutoDrawable(GLAutoDrawable sharedAutoDrawable) throws IllegalStateException {
       helper.setSharedAutoDrawable(this, sharedAutoDrawable);
   }

   private final UpstreamSurfaceHook swtCanvasUpStreamHook = new UpstreamSurfaceHook() {
       @Override
       public final void create(ProxySurface s) { /* nop */ }

       @Override
       public final void destroy(ProxySurface s) { /* nop */ }

       @Override
       public final int getWidth(ProxySurface s) {
           return clientArea.width;
       }

       @Override
       public final int getHeight(ProxySurface s) {
           return clientArea.height;
       }

       @Override
       public String toString() {
           return "SWTCanvasUpstreamSurfaceHook[upstream: "+GLCanvas.this.toString()+", "+clientArea.width+"x"+clientArea.height+"]";
       }
   };

   protected final void updateSizeCheck() {
      final Rectangle oClientArea = clientArea;
      final Rectangle nClientArea = GLCanvas.this.getClientArea();
      if ( nClientArea != null &&
           ( nClientArea.width != oClientArea.width || nClientArea.height != oClientArea.height )
         ) {
          clientArea = nClientArea; // write back new value

          final GLDrawableImpl _drawable = drawable;
          final boolean drawableOK = null != _drawable && _drawable.isRealized();
          if(DEBUG) {
              final long dh = drawableOK ? _drawable.getHandle() : 0;
              System.err.println(getThreadName()+": GLCanvas.sizeChanged: ("+Thread.currentThread().getName()+"): "+nClientArea.x+"/"+nClientArea.y+" "+nClientArea.width+"x"+nClientArea.height+" - drawableHandle "+toHexString(dh));
          }
          if( drawableOK ) {
              if( ! _drawable.getChosenGLCapabilities().isOnscreen() ) {
                  final RecursiveLock _lock = lock;
                  _lock.lock();
                  try {
                      final GLDrawableImpl _drawableNew = GLDrawableHelper.resizeOffscreenDrawable(_drawable, context, nClientArea.width, nClientArea.height);
                      if(_drawable != _drawableNew) {
                          // write back
                          drawable = _drawableNew;
                      }
                  } finally {
                      _lock.unlock();
                  }
              }
          }
          if(0 != x11Window) {
              SWTAccessor.resizeX11Window(screen.getDevice(), clientArea, x11Window);
          } else if(0 != gdkWindow) {
              SWTAccessor.resizeGDKWindow(clientArea, gdkWindow);
          }
          sendReshape = true; // async if display() doesn't get called below, but avoiding deadlock
      }
   }

   private boolean isValidAndVisibleOnEDTActionResult;
   private final Runnable isValidAndVisibleOnEDTAction = new Runnable() {
       @Override
       public void run() {
           isValidAndVisibleOnEDTActionResult = !GLCanvas.this.isDisposed() && GLCanvas.this.isVisible();
       } };

   private final boolean isValidAndVisibleOnEDT() {
       synchronized(isValidAndVisibleOnEDTAction) {
           runOnEDTIfAvail(true, isValidAndVisibleOnEDTAction);
           return isValidAndVisibleOnEDTActionResult;
       }
   }

   /** assumes drawable == null (implying !drawable.isRealized()) !  Checks of !isDispose() and isVisible() */
   protected final boolean validateDrawableAndContextWithCheck() {
      if( !isValidAndVisibleOnEDT() ) {
          return false;
      }
      return validateDrawableAndContextPostCheck();
   }

   private final boolean isDrawableAndContextValid() {
       // drawable != null implies drawable.isRealized()==true
       return null != drawable && null != context;
   }

   /** assumes drawable == null (implying !drawable.isRealized()) || context == null ! No check of !isDispose() and isVisible() */
   private final boolean validateDrawableAndContextPostCheck() {
      boolean res;
      final RecursiveLock _lock = lock;
      _lock.lock();
      try {
          if(null == drawable) {
              // 'displayable' (isValidAndVisibleOnEDT()) must have been checked upfront if appropriate!
              createDrawableImpl(); // checks clientArea size (i.e. drawable size) and perf. realization
          }
          final GLDrawable _drawable = drawable;
          if ( null != _drawable ) {
              // drawable realization goes in-hand w/ it's construction
              if( null == context ) {
                  // re-try context creation
                  res = createContextImpl(_drawable); // pending creation.
              } else {
                  res = true;
              }
              if(res) {
                  sendReshape = true;
              }
          } else {
              if(DEBUG) {
                  System.err.println(getThreadName()+": SWT.GLCanvas.validate "+toHexString(hashCode())+": null drawable");
              }
              res = false;
          }
          if(DEBUG) {
              System.err.println(getThreadName()+": SWT.GLCanvas.validate.X  "+toHexString(hashCode())+": "+res+", drawable-realized "+drawable.isRealized()+", has context "+(null!=context));
          }
      } finally {
          _lock.unlock();
      }
      return res;
   }
   private final void createDrawableImpl() {
       final Rectangle nClientArea = clientArea;
       if(0 >= nClientArea.width || 0 >= nClientArea.height) {
          if(DEBUG) {
              System.err.println(getThreadName()+": SWT.GLCanvas.validate.X "+toHexString(hashCode())+": drawable could not be created: size < 0x0");
          }
          return; // early out
       }
       final AbstractGraphicsDevice device = screen.getDevice();
       device.open();

       final long nativeWindowHandle;
       if( useX11GTK ) {
           final GraphicsConfigurationFactory factory = GraphicsConfigurationFactory.getFactory(device, capsRequested);
           final AbstractGraphicsConfiguration cfg = factory.chooseGraphicsConfiguration(
                   capsRequested, capsRequested, capsChooser, screen, VisualIDHolder.VID_UNDEFINED);
           if(DEBUG) {
               System.err.println(getThreadName()+": SWT.GLCanvas.X11 "+toHexString(hashCode())+": factory: "+factory+", chosen config: "+cfg);
           }
           if (null == cfg) {
               throw new NativeWindowException("Error choosing GraphicsConfiguration creating window: "+this);
           }
           final int visualID = cfg.getVisualID(VIDType.NATIVE);
           if( VisualIDHolder.VID_UNDEFINED != visualID ) {
               // gdkWindow = SWTAccessor.createCompatibleGDKChildWindow(this, visualID, clientArea.width, clientArea.height);
               // nativeWindowHandle = SWTAccessor.gdk_window_get_xwindow(gdkWindow);
               x11Window = SWTAccessor.createCompatibleX11ChildWindow(screen, this, visualID, clientArea.width, clientArea.height);
               nativeWindowHandle = x11Window;
           } else {
              throw new GLException("Could not choose valid visualID: "+toHexString(visualID)+", "+this);
           }
       } else {
           nativeWindowHandle = SWTAccessor.getWindowHandle(this);
       }
       final GLDrawableFactory glFactory = GLDrawableFactory.getFactory(capsRequested.getGLProfile());

       // Create a NativeWindow proxy for the SWT canvas
       ProxySurface proxySurface = glFactory.createProxySurface(device, screen.getIndex(), nativeWindowHandle,
                                                                capsRequested, capsChooser, swtCanvasUpStreamHook);
       // Associate a GL surface with the proxy
       final GLDrawableImpl _drawable = (GLDrawableImpl) glFactory.createGLDrawable(proxySurface);
       _drawable.setRealized(true);
       if(!_drawable.isRealized()) {
           // oops
           if(DEBUG) {
               System.err.println(getThreadName()+": SWT.GLCanvas.validate.X "+toHexString(hashCode())+": Drawable could not be realized: "+_drawable);
           }
       } else {
           if(DEBUG) {
               System.err.println(getThreadName()+": SWT.GLCanvas.validate "+toHexString(hashCode())+": Drawable created and realized");
           }
           drawable = _drawable;
       }
   }
   private boolean createContextImpl(final GLDrawable drawable) {
       final GLContext[] shareWith = { null };
       if( !helper.isSharedGLContextPending(shareWith) ) {
           context = (GLContextImpl) drawable.createContext(shareWith[0]);
           context.setContextCreationFlags(additionalCtxCreationFlags);
           if(DEBUG) {
               System.err.println(getThreadName()+": SWT.GLCanvas.validate "+toHexString(hashCode())+": Context created: has shared "+(null != shareWith[0]));
           }
           return true;
       } else {
           if(DEBUG) {
               System.err.println(getThreadName()+": SWT.GLCanvas.validate "+toHexString(hashCode())+": Context !created: pending share");
           }
           return false;
       }
   }

   @Override
   public void update() {
       // don't paint background etc .. nop avoids flickering
       // super.update();
   }

   /**
   @Override
   public boolean forceFocus() {
       final boolean r = super.forceFocus();
       if(r && 0 != gdkWindow) {
           SWTGTKUtil.focusGDKWindow(gdkWindow);
       }
       return r;
   } */

   @Override
   public void dispose() {
     runInGLThread(disposeOnEDTGLAction);
     super.dispose();
   }

   private final void displayIfNoAnimatorNoCheck() {
       if ( !helper.isAnimatorAnimatingOnOtherThread() ) {
           if( isDrawableAndContextValid() || validateDrawableAndContextPostCheck() ) {
               runInGLThread(makeCurrentAndDisplayOnGLAction);
           }
       }
   }

   //
   // GL[Auto]Drawable
   //

   @Override
   public void display() {
      if( isDrawableAndContextValid() || validateDrawableAndContextWithCheck() ) {
          runInGLThread(makeCurrentAndDisplayOnGLAction);
      }
   }

   @Override
   public final Object getUpstreamWidget() {
       return this;
   }

   @Override
   public int getWidth() {
      return clientArea.width;
   }

   @Override
   public int getHeight() {
      return clientArea.height;
   }

   @Override
   public boolean isGLOriented() {
      final GLDrawable _drawable = drawable;
      return null != _drawable ? _drawable.isGLOriented() : true;
   }

   @Override
   public void addGLEventListener(final GLEventListener listener) {
      helper.addGLEventListener(listener);
   }

   @Override
   public void addGLEventListener(final int idx, final GLEventListener listener) throws IndexOutOfBoundsException {
      helper.addGLEventListener(idx, listener);
   }

   @Override
   public int getGLEventListenerCount() {
      return helper.getGLEventListenerCount();
   }

   @Override
   public GLEventListener getGLEventListener(int index) throws IndexOutOfBoundsException {
      return helper.getGLEventListener(index);
   }

   @Override
   public boolean areAllGLEventListenerInitialized() {
      return helper.areAllGLEventListenerInitialized();
   }

   @Override
   public boolean getGLEventListenerInitState(GLEventListener listener) {
       return helper.getGLEventListenerInitState(listener);
   }

   @Override
   public void setGLEventListenerInitState(GLEventListener listener, boolean initialized) {
       helper.setGLEventListenerInitState(listener, initialized);
   }

   @Override
   public GLEventListener disposeGLEventListener(GLEventListener listener, boolean remove) {
       final DisposeGLEventListenerAction r = new DisposeGLEventListenerAction(listener, remove);
       runInGLThread(r);
       return r.listener;
   }

   @Override
   public GLEventListener removeGLEventListener(final GLEventListener listener) {
      return helper.removeGLEventListener(listener);
   }

   /**
    * {@inheritDoc}
    *
    * <p>
    * This impl. calls this class's {@link #dispose()} SWT override,
    * where the actual implementation resides.
    * </p>
    */
   @Override
   public void destroy() {
      dispose();
   }

   @Override
   public GLAnimatorControl getAnimator() {
      return helper.getAnimator();
   }

   @Override
   public final Thread setExclusiveContextThread(Thread t) throws GLException {
       return helper.setExclusiveContextThread(t, context);
   }

   @Override
   public final Thread getExclusiveContextThread() {
       return helper.getExclusiveContextThread();
   }

   @Override
   public boolean getAutoSwapBufferMode() {
      return helper.getAutoSwapBufferMode();
   }

   @Override
   public final GLDrawable getDelegatedDrawable() {
      return drawable;
   }

   @Override
   public GLContext getContext() {
      return context;
   }

   @Override
   public int getContextCreationFlags() {
      return additionalCtxCreationFlags;
   }

   @Override
   public GL getGL() {
      final GLContext _context = context;
      return (null == _context) ? null : _context.getGL();
   }

   @Override
   public boolean invoke(final boolean wait, final GLRunnable runnable) {
      return helper.invoke(this, wait, runnable);
   }

   @Override
   public boolean invoke(final boolean wait, final List<GLRunnable> runnables) {
      return helper.invoke(this, wait, runnables);
   }

   @Override
   public void setAnimator(final GLAnimatorControl arg0) throws GLException {
      helper.setAnimator(arg0);
   }

   @Override
   public void setAutoSwapBufferMode(final boolean arg0) {
      helper.setAutoSwapBufferMode(arg0);
   }

   @Override
   public GLContext setContext(GLContext newCtx, boolean destroyPrevCtx) {
      final RecursiveLock _lock = lock;
      _lock.lock();
      try {
          final GLContext oldCtx = context;
          GLDrawableHelper.switchContext(drawable, oldCtx, destroyPrevCtx, newCtx, additionalCtxCreationFlags);
          context=(GLContextImpl)newCtx;
          return oldCtx;
      } finally {
          _lock.unlock();
      }
   }

   @Override
   public void setContextCreationFlags(final int arg0) {
      additionalCtxCreationFlags = arg0;
      final GLContext _context = context;
      if(null != _context) {
        _context.setContextCreationFlags(additionalCtxCreationFlags);
      }
   }

   @Override
   public GL setGL(final GL arg0) {
       final GLContext _context = context;
      if (null != _context) {
         _context.setGL(arg0);
         return arg0;
      }
      return null;
   }

   @Override
   public GLContext createContext(final GLContext shareWith) {
     final RecursiveLock _lock = lock;
     _lock.lock();
     try {
         if(drawable != null) {
             final GLContext _ctx = drawable.createContext(shareWith);
             _ctx.setContextCreationFlags(additionalCtxCreationFlags);
             return _ctx;
         }
         return null;
     } finally {
         _lock.unlock();
     }
   }

   @Override
   public GLCapabilitiesImmutable getChosenGLCapabilities() {
      final GLDrawable _drawable = drawable;
      return null != _drawable ? (GLCapabilitiesImmutable)_drawable.getChosenGLCapabilities() : null;
   }

   /**
    * Accessor for the GLCapabilities that were requested (via the constructor parameter).
    *
    * @return Non-null GLCapabilities.
    */
   public GLCapabilitiesImmutable getRequestedGLCapabilities() {
      final GLDrawable _drawable = drawable;
      return null != _drawable ? (GLCapabilitiesImmutable)_drawable.getNativeSurface().getGraphicsConfiguration().getRequestedCapabilities() : null;
   }

   @Override
   public GLDrawableFactory getFactory() {
      final GLDrawable _drawable = drawable;
      return (_drawable != null) ? _drawable.getFactory() : null;
   }

   @Override
   public GLProfile getGLProfile() {
      return capsRequested.getGLProfile();
   }

   @Override
   public long getHandle() {
      final GLDrawable _drawable = drawable;
      return (_drawable != null) ? _drawable.getHandle() : 0;
   }

   @Override
   public NativeSurface getNativeSurface() {
      final GLDrawable _drawable = drawable;
      return (_drawable != null) ? _drawable.getNativeSurface() : null;
   }

   @Override
   public boolean isRealized() {
      final GLDrawable _drawable = drawable;
      return (_drawable != null) ? _drawable.isRealized() : false;
   }

   @Override
   public void setRealized(final boolean arg0) {
      /* Intentionally empty */
   }

   @Override
   public void swapBuffers() throws GLException {
      runInGLThread(swapBuffersOnGLAction);
   }

   /**
    * Runs the specified action in an SWT compatible thread, which is:
    * <ul>
    *   <li>Mac OSX
    *   <ul>
    *     <!--li>AWT EDT: In case AWT is available, the AWT EDT is the OSX UI main thread</li-->
    *     <!--li><i>Main Thread</i>: Run on OSX UI main thread.</li-->
    *     <li>Current thread</li>
    *   </ul></li>
    *   <li>Linux, Windows, ..
    *   <ul>
    *     <!--li>Use {@link Threading#invokeOnOpenGLThread(boolean, Runnable)}</li-->
    *     <li>Current thread</li>
    *   </ul></li>
    * </ul>
    * The current thread seems to be valid for all platforms,
    * since no SWT lifecycle tasks are being performed w/ this call.
    * Only GL task, which are independent from the SWT threading model.
    *
    * @see Platform#AWT_AVAILABLE
    * @see Platform#getOSType()
    */
   private void runInGLThread(final Runnable action) {
      /**
      if(Platform.OSType.MACOS == Platform.OS_TYPE) {
          SWTAccessor.invoke(true, action);
      } else {
          Threading.invokeOnOpenGLThread(true, action);
      } */
      /**
      if( !isDisposed() ) {
          final Display d = getDisplay();
          if( d.getThread() == Thread.currentThread() ) {
              action.run();
          } else {
              d.syncExec(action);
          }
      } */
      action.run();
   }

   private void runOnEDTIfAvail(boolean wait, final Runnable action) {
       final Display d = isDisposed() ? null : getDisplay();
       if( null == d || d.isDisposed() || d.getThread() == Thread.currentThread() ) {
           action.run();
       } else if(wait) {
           d.syncExec(action);
       } else {
           d.asyncExec(action);
       }
   }

   @Override
   public String toString() {
       final GLDrawable _drawable = drawable;
       final int dw = (null!=_drawable) ? _drawable.getWidth() : -1;
       final int dh = (null!=_drawable) ? _drawable.getHeight() : -1;

       return "SWT-GLCanvas[Realized "+isRealized()+
               ",\n\t"+((null!=_drawable)?_drawable.getClass().getName():"null-drawable")+
               ",\n\tFactory   "+getFactory()+
               ",\n\thandle    "+toHexString(getHandle())+
               ",\n\tDrawable size "+dw+"x"+dh+
               ",\n\tSWT size "+getWidth()+"x"+getHeight()+"]";
   }

   public static void main(final String[] args) {
       System.err.println(VersionUtil.getPlatformInfo());
       System.err.println(GlueGenVersion.getInstance());
       // System.err.println(NativeWindowVersion.getInstance());
       System.err.println(JoglVersion.getInstance());

       System.err.println(JoglVersion.getDefaultOpenGLInfo(null, null, true).toString());

       final GLCapabilitiesImmutable caps = new GLCapabilities( GLProfile.getDefault(GLProfile.getDefaultDevice()) );
       final Display display = new Display();
       final Shell shell = new Shell(display);
       shell.setSize(128,128);
       shell.setLayout(new FillLayout());

       final GLCanvas canvas = new GLCanvas(shell, 0, caps, null);

       canvas.addGLEventListener(new GLEventListener() {
           @Override
           public void init(final GLAutoDrawable drawable) {
               GL gl = drawable.getGL();
               System.err.println(JoglVersion.getGLInfo(gl, null));
           }
           @Override
           public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) {}
           @Override
           public void display(final GLAutoDrawable drawable) {}
           @Override
           public void dispose(final GLAutoDrawable drawable) {}
       });
       shell.open();
       canvas.display();
       display.dispose();
   }
}
