/**
 * 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 com.jogamp.nativewindow.AbstractGraphicsConfiguration;
import com.jogamp.nativewindow.AbstractGraphicsDevice;
import com.jogamp.nativewindow.AbstractGraphicsScreen;
import com.jogamp.nativewindow.GraphicsConfigurationFactory;
import com.jogamp.nativewindow.NativeSurface;
import com.jogamp.nativewindow.NativeWindowException;
import com.jogamp.nativewindow.ProxySurface;
import com.jogamp.nativewindow.UpstreamSurfaceHook;
import com.jogamp.nativewindow.VisualIDHolder;
import com.jogamp.nativewindow.VisualIDHolder.VIDType;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GLAnimatorControl;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLCapabilitiesChooser;
import com.jogamp.opengl.GLCapabilitiesImmutable;
import com.jogamp.opengl.GLContext;
import com.jogamp.opengl.GLDrawable;
import com.jogamp.opengl.GLDrawableFactory;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.GLException;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.GLRunnable;
import com.jogamp.opengl.GLSharedContextSetter;
import com.jogamp.opengl.Threading;

import jogamp.nativewindow.macosx.OSXUtil;
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.Color;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.internal.DPIUtil;
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>
 * <p>
 * <a name="contextSharing"><h5>OpenGL Context Sharing</h5></a>
 * To share a {@link GLContext} see the following note in the documentation overview:
 * <a href="../../../../overview-summary.html#SHARING">context sharing</a>
 * as well as {@link GLSharedContextSetter}.
 * </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 clientAreaPixels, clientAreaWindow;
   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 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(final int v) { return "0x"+Integer.toHexString(v); }
   private static String toHexString(final 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, clientAreaPixels.width, clientAreaPixels.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;
             }

             GLException exceptionOnDisposeGL = null;
             if( null != context ) {
                 if( context.isCreated() ) {
                     try {
                         if( !GLCanvas.this.isDisposed() ) {
                             helper.disposeGL(GLCanvas.this, context, true);
                         } else {
                             context.destroy();
                         }
                     } catch (final GLException gle) {
                         exceptionOnDisposeGL = gle;
                     }
                 }
                 context = null;
             }

             Throwable exceptionOnUnrealize = null;
             if( null != drawable ) {
                 try {
                     drawable.setRealized(false);
                 } catch( final Throwable re ) {
                     exceptionOnUnrealize = re;
                 }
                 drawable = null;
             }

             Throwable exceptionOnDeviceClose = null;
             try {
                 if( 0 != x11Window) {
                     SWTAccessor.destroyX11Window(screen.getDevice(), x11Window);
                     x11Window = 0;
                 }
                 screen.getDevice().close();
             } catch (final Throwable re) {
                 exceptionOnDeviceClose = re;
             }

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

             // throw exception in order of occurrence ..
             if( null != exceptionOnDisposeGL ) {
                 throw exceptionOnDisposeGL;
             }
             if( null != exceptionOnUnrealize ) {
                 throw GLException.newGLException(exceptionOnUnrealize);
             }
             if( null != exceptionOnDeviceClose ) {
                 throw GLException.newGLException(exceptionOnDeviceClose);
             }
         } finally {
             _lock.unlock();
         }
      }
   };

   private class DisposeGLEventListenerAction implements Runnable {
       private GLEventListener listener;
       private final boolean remove;
       private DisposeGLEventListenerAction(final GLEventListener listener, final 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 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, final GLCapabilitiesImmutable capsReqUser,
                   final GLCapabilitiesChooser capsChooser) {
      /* 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 completely initialized

      SWTAccessor.setRealized(this, true);

      clientAreaPixels = SWTAccessor.getClientAreaInPixels(this);
      clientAreaWindow = 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 use cloned provided caps */
      if(null == capsReqUser) {
          this.capsRequested = new GLCapabilities(GLProfile.getDefault(screen.getDevice()));
      } else {
          this.capsRequested = (GLCapabilitiesImmutable) capsReqUser.cloneMutable();
      }
      this.capsChooser = capsChooser;

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

      // Bug 1362 fix or workaround: Seems SWT/GTK3 at least performs lazy initialization
      // Minimal action required: setBackground of the parent canvas before reparenting!
      //
      // SWT 3.110 (Eclipse 4.10?) would require color to be disposed, hence use pre-allocated.
      // Since at least 'Eclipse 4.26.0 Release Build: 4.26' no more disposal of Color required.
      setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_WHITE));

      final Listener listener = new Listener () {
          @Override
          public void handleEvent (final Event event) {
              switch (event.type) {
              case SWT.Paint:
                  displayIfNoAnimatorNoCheck();
                  break;
              case SWT.Resize:
                  if (DEBUG) { System.err.println(getThreadName()+": SWT.GLCanvas.Resize.0 "+toString()); }
                  updateSizeCheck();
                  break;
              case SWT.ZoomChanged:
                  if (DEBUG) { System.err.println(getThreadName()+": SWT.GLCanvas.ZoomChanged.0 "+toString()); }
                  updateSizeCheck();
                  break;
              case SWT.Dispose:
                  if (DEBUG) { System.err.println(getThreadName()+": SWT.GLCanvas.Dispose.0 "+toString()); }
                  GLCanvas.this.dispose();
                  break;
              }
          }
      };
      addListener (SWT.Resize, listener);
      addListener (SWT.Paint, listener);
      addListener (SWT.Dispose, listener);
   }

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

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

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

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

       @Override
       public final int getSurfaceWidth(final ProxySurface s) {
           return clientAreaPixels.width;
       }

       @Override
       public final int getSurfaceHeight(final ProxySurface s) {
           return clientAreaPixels.height;
       }

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

       /**
        * {@inheritDoc}
        * <p>
        * Returns <code>null</code>.
        * </p>
        */
       @Override
       public final NativeSurface getUpstreamSurface() {
           return null;
       }
   };

   protected final void updateSizeCheck() {
      final Rectangle oClientAreaPixels = clientAreaPixels;
      final Rectangle nClientAreaPixels = SWTAccessor.getClientAreaInPixels(this);

      if(DEBUG) {
          System.err.println(getThreadName()+": GLCanvas.sizeChanged: clientAreaPixels.o "+getClientArea());
          System.err.println(getThreadName()+": GLCanvas.sizeChanged: clientAreaPixels.n "+nClientAreaPixels);
      }

      if ( nClientAreaPixels != null &&
           ( nClientAreaPixels.width != oClientAreaPixels.width || nClientAreaPixels.height != oClientAreaPixels.height )
         ) {
          clientAreaPixels = nClientAreaPixels; // write back new value
          clientAreaWindow = getClientArea();

          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()+"): "+nClientAreaPixels.x+"/"+nClientAreaPixels.y+" "+nClientAreaPixels.width+"x"+nClientAreaPixels.height+" - drawableHandle "+toHexString(dh));
          }
          if( drawableOK ) {
              if( ! _drawable.getChosenGLCapabilities().isOnscreen() ) {
                  final RecursiveLock _lock = lock;
                  _lock.lock();
                  try {
                      final GLDrawableImpl _drawableNew = GLDrawableHelper.resizeOffscreenDrawable(_drawable, context, nClientAreaPixels.width, nClientAreaPixels.height);
                      if(_drawable != _drawableNew) {
                          // write back
                          drawable = _drawableNew;
                      }
                  } finally {
                      _lock.unlock();
                  }
              }
          }
          if(0 != x11Window) {
              SWTAccessor.resizeX11Window(screen.getDevice(), clientAreaPixels, x11Window);
          }
          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) {
              final boolean isDrawableNull = null == drawable;
              final boolean isDrawableRealized = !isDrawableNull ? drawable.isRealized() : false;
              System.err.println(getThreadName()+": SWT.GLCanvas.validate.X  "+toHexString(hashCode())+": "+res+", drawable[null "+isDrawableNull+", realized "+isDrawableRealized+"], has context "+(null!=context));
          }
      } finally {
          _lock.unlock();
      }
      return res;
   }
   private final void createDrawableImpl() {
       final Rectangle nClientArea = clientAreaPixels;
       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, clientAreaPixels.width, clientAreaPixels.height);
               nativeWindowHandle = x11Window;
           } else {
              throw new GLException("Could not choose valid visualID: "+toHexString(visualID)+", "+this);
           }
       } else {
           nativeWindowHandle = SWTAccessor.getWindowHandle(this);
           if( SWTAccessor.isOSX ) {
               final float reqPixelScale = DPIUtil.autoScaleUp(this, 1f);
               if( DEBUG ) {
                   System.err.println(getThreadName()+": SWT.GLCanvas.OSX "+toHexString(hashCode())+": Scaling: devZoom "+DPIUtil.getDeviceZoom()+", general "+DPIUtil.autoScaleUp(1f)+", onWidged "+reqPixelScale);
               }
               if( reqPixelScale > 1f ) {
                   OSXUtil.SetWindowPixelScale(nativeWindowHandle, reqPixelScale);
               }
           }
       }
       final GLDrawableFactory glFactory = GLDrawableFactory.getFactory(capsRequested.getGLProfile());

       // Create a NativeWindow proxy for the SWT canvas
       final 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 "+proxySurface);
               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 "+proxySurface);
               System.err.println(getThreadName()+": SWT.GLCanvas.validate "+toHexString(hashCode())+": Drawable created and realized: "+_drawable);
           }
           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 final RecursiveLock getUpstreamLock() { return lock; }

   @Override
   public int getSurfaceWidth() {
      return clientAreaPixels.width;
   }

   @Override
   public int getSurfaceHeight() {
      return clientAreaPixels.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(final int index) throws IndexOutOfBoundsException {
      return helper.getGLEventListener(index);
   }

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

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

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

   @Override
   public GLEventListener disposeGLEventListener(final GLEventListener listener, final 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(final 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) throws IllegalStateException {
      return helper.invoke(this, wait, runnable);
   }

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

   @Override
   public void flushGLRunnables() {
       helper.flushGLRunnables();
   }

   @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(final GLContext newCtx, final 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;
   }

   @Override
   public GLCapabilitiesImmutable getRequestedGLCapabilities() {
      final GLDrawable _drawable = drawable;
      return null != _drawable ? (GLCapabilitiesImmutable)_drawable.getRequestedGLCapabilities() : 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);
   }

   /**
    * {@inheritDoc}
    * <p>
    * Implementation always supports multithreading, hence method always returns <code>true</code>.
    * </p>
    */
   @Override
   public final boolean isThreadGLCapable() { return true; }

   /**
    * 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(final 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.getSurfaceWidth() : -1;
       final int dh = (null!=_drawable) ? _drawable.getSurfaceHeight() : -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 "+getSurfaceWidth()+"x"+getSurfaceHeight()+"]";
   }

   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) {
               final 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();
   }
}
