/*
 * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
 * Copyright (c) 2010-2023 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:
 *
 * - Redistribution of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution 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.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed or intended for use
 * in the design, construction, operation or maintenance of any nuclear
 * facility.
 */

package com.jogamp.nativewindow.awt;

import com.jogamp.common.ExceptionUtils;
import com.jogamp.common.os.Platform;
import com.jogamp.common.util.awt.AWTEDTExecutor;
import com.jogamp.common.util.locks.LockFactory;
import com.jogamp.common.util.locks.RecursiveLock;
import com.jogamp.nativewindow.MutableGraphicsConfiguration;

import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.GraphicsConfiguration;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;

import com.jogamp.nativewindow.AbstractGraphicsConfiguration;
import com.jogamp.nativewindow.AbstractGraphicsDevice;
import com.jogamp.nativewindow.CapabilitiesImmutable;
import com.jogamp.nativewindow.NativeSurface;
import com.jogamp.nativewindow.NativeWindow;
import com.jogamp.nativewindow.NativeWindowException;
import com.jogamp.nativewindow.OffscreenLayerOption;
import com.jogamp.nativewindow.OffscreenLayerSurface;
import com.jogamp.nativewindow.ScalableSurface;
import com.jogamp.nativewindow.SurfaceUpdatedListener;
import com.jogamp.nativewindow.util.Insets;
import com.jogamp.nativewindow.util.InsetsImmutable;
import com.jogamp.nativewindow.util.PixelRectangle;
import com.jogamp.nativewindow.util.Point;
import com.jogamp.nativewindow.util.PointImmutable;
import com.jogamp.nativewindow.util.Rectangle;
import com.jogamp.nativewindow.util.RectangleImmutable;

import jogamp.common.os.PlatformPropsImpl;
import jogamp.nativewindow.SurfaceScaleUtils;
import jogamp.nativewindow.SurfaceUpdatedHelper;
import jogamp.nativewindow.awt.AWTMisc;
import jogamp.nativewindow.jawt.JAWT;
import jogamp.nativewindow.jawt.JAWTUtil;
import jogamp.nativewindow.jawt.JAWT_Rectangle;

public abstract class JAWTWindow implements NativeWindow, OffscreenLayerSurface, OffscreenLayerOption, ScalableSurface {
  protected static final boolean DEBUG = JAWTUtil.DEBUG;

  // user properties
  protected boolean shallUseOffscreenLayer = false;

  // lifetime: forever
  protected final Component component;
  private final AppContextInfo appContextInfo; // only used for offscreen layer, i.e. MacOS only
  private final SurfaceUpdatedHelper surfaceUpdatedHelper = new SurfaceUpdatedHelper();
  private final RecursiveLock surfaceLock = LockFactory.createRecursiveLock();
  private final JAWTComponentListener jawtComponentListener;
  private volatile AWTGraphicsConfiguration awtConfig; // control access through delegation

  // lifetime: valid after lock but may change with each 1st lock, purges after invalidate
  private JAWT jawt;
  private boolean isOffscreenLayerSurface;
  protected long drawable;
  protected Rectangle jawt_surface_bounds;
  protected Insets insets;
  private volatile long offscreenSurfaceLayer;

  private final float[] hasPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
  private volatile boolean hasPixelScaleChanged = false;
  private long drawable_old;

  /**
   * Constructed by {@link jogamp.nativewindow.NativeWindowFactoryImpl#getNativeWindow(Object, AbstractGraphicsConfiguration)}
   * via this platform's specialization (X11, OSX, Windows, ..).
   *
   * @param comp
   * @param config
   */
  protected JAWTWindow(final Object comp, final AbstractGraphicsConfiguration config) {
    if (config == null) {
        throw new IllegalArgumentException("Error: AbstractGraphicsConfiguration is null");
    }
    if(! ( config instanceof AWTGraphicsConfiguration ) ) {
        throw new NativeWindowException("Error: AbstractGraphicsConfiguration is not an AWTGraphicsConfiguration: "+config);
    }
    if( JAWTUtil.isOffscreenLayerSupported() ) {
        appContextInfo = new AppContextInfo("<init>");
    } else {
        appContextInfo = null;
    }
    this.component = (Component)comp;
    this.jawtComponentListener = new JAWTComponentListener();
    this.offscreenSurfaceLayer = 0;
    invalidate();
    this.awtConfig = (AWTGraphicsConfiguration) config;
    if(DEBUG) {
        System.err.println(jawtStr2("ctor"));
    }
  }
  private static String id(final Object obj) { return ( null!=obj ? toHexString(obj.hashCode()) : "nil" ); }
  private String jawtStr1() { return "JAWTWindow["+id(JAWTWindow.this)+"]"; }
  private String jawtStr2(final String sub) { return jawtStr1()+"."+sub+" @ Thread "+getThreadName(); }

  private class JAWTComponentListener implements ComponentListener, HierarchyListener {
        private volatile boolean isShowing;

        private String str(final Object obj) {
            if( null == obj ) {
                return "0xnil: null";
            } else if( obj instanceof Component ) {
                final Component c = (Component)obj;
                return id(obj)+": "+c.getClass().getSimpleName()+"[visible "+c.isVisible()+", showing "+c.isShowing()+", valid "+c.isValid()+
                        ", displayable "+c.isDisplayable()+", "+c.getX()+"/"+c.getY()+" "+c.getWidth()+"x"+c.getHeight()+"]";
            } else {
                return id(obj)+": "+obj.getClass().getSimpleName()+"[..]";
            }
        }
        private String s(final ComponentEvent e) {
            return "visible[isShowing "+isShowing+"],"+Platform.getNewline()+
                   "    ** COMP "+str(e.getComponent())+Platform.getNewline()+
                   "    ** SOURCE "+str(e.getSource())+Platform.getNewline()+
                   "    ** THIS "+str(component)+Platform.getNewline()+
                   "    ** THREAD "+getThreadName();
        }
        private String s(final HierarchyEvent e) {
            return "visible[isShowing "+isShowing+"], changeBits 0x"+Long.toHexString(e.getChangeFlags())+Platform.getNewline()+
                   "    ** COMP "+str(e.getComponent())+Platform.getNewline()+
                   "    ** SOURCE "+str(e.getSource())+Platform.getNewline()+
                   "    ** CHANGED "+str(e.getChanged())+Platform.getNewline()+
                   "    ** CHANGEDPARENT "+str(e.getChangedParent())+Platform.getNewline()+
                   "    ** THIS "+str(component)+Platform.getNewline()+
                   "    ** THREAD "+getThreadName();
        }
        @Override
        public final String toString() {
            return "visible[isShowing "+isShowing+"],"+Platform.getNewline()+
                   "    ** THIS "+str(component)+Platform.getNewline()+
                   "    ** THREAD "+getThreadName();
        }

        private JAWTComponentListener() {
            isShowing = component.isShowing();
            AWTEDTExecutor.singleton.invoke(false, new Runnable() { // Bug 952: Avoid deadlock via AWTTreeLock acquisition ..
                @Override
                public void run() {
                    isShowing = component.isShowing(); // Bug 1161: Runnable might be deferred, hence need to update
                    if(DEBUG) {
                        System.err.println(jawtStr2("attach")+": "+JAWTComponentListener.this.toString());
                    }
                    component.addComponentListener(JAWTComponentListener.this);
                    component.addHierarchyListener(JAWTComponentListener.this);
                } } );
        }

        private final void detach() {
            AWTEDTExecutor.singleton.invoke(false, new Runnable() { // Bug 952: Avoid deadlock via AWTTreeLock acquisition ..
                @Override
                public void run() {
                    if(DEBUG) {
                        System.err.println(jawtStr2("detach")+": "+JAWTComponentListener.this.toString());
                    }
                    component.removeComponentListener(JAWTComponentListener.this);
                    component.removeHierarchyListener(JAWTComponentListener.this);
                } } );
        }

        @Override
        public final void componentResized(final ComponentEvent e) {
            if(DEBUG) {
                System.err.println(jawtStr2("componentResized")+": "+s(e));
            }
            layoutSurfaceLayerIfEnabled(isShowing);
        }

        @Override
        public final void componentMoved(final ComponentEvent e) {
            if(DEBUG) {
                System.err.println(jawtStr2("componentMoved")+": "+s(e));
            }
            layoutSurfaceLayerIfEnabled(isShowing);
        }

        @Override
        public final void componentShown(final ComponentEvent e) {
            if(DEBUG) {
                System.err.println(jawtStr2("componentShown")+": "+s(e));
            }
            layoutSurfaceLayerIfEnabled(isShowing);
        }

        @Override
        public final void componentHidden(final ComponentEvent e) {
            if(DEBUG) {
                System.err.println(jawtStr2("componentHidden")+": "+s(e));
            }
            layoutSurfaceLayerIfEnabled(isShowing);
        }

        @Override
        public final void hierarchyChanged(final HierarchyEvent e) {
            final boolean wasShowing = isShowing;
            isShowing = component.isShowing();
            int action = 0;
            if( 0 != ( java.awt.event.HierarchyEvent.SHOWING_CHANGED & e.getChangeFlags() ) ) {
                if( e.getChanged() != component && wasShowing != isShowing ) {
                    // A parent component changed and caused a 'showing' state change,
                    // propagate to offscreen-layer!
                    layoutSurfaceLayerIfEnabled(isShowing);
                    action = 1;
                }
            }
            if(DEBUG) {
                final java.awt.Component changed = e.getChanged();
                final boolean displayable = changed.isDisplayable();
                final boolean showing = changed.isShowing();
                System.err.println(jawtStr2("hierarchyChanged")+": action "+action+", displayable "+displayable+", showing [changed "+showing+", comp "+wasShowing+" -> "+isShowing+"], "+s(e));
            }
        }
  }

  private static String getThreadName() { return Thread.currentThread().getName(); }

  protected synchronized void invalidate() {
    if(DEBUG) {
        System.err.println(jawtStr2("invalidate")+" - "+jawtComponentListener.toString());
        if( isSurfaceLayerAttached() ) {
            System.err.println("OffscreenSurfaceLayer still attached: 0x"+Long.toHexString(offscreenSurfaceLayer));
        }
        // Thread.dumpStack();
    }
    {
        final long osl = offscreenSurfaceLayer;
        offscreenSurfaceLayer = 0;
        invalidateNative(osl);
    }
    jawt = null;
    awtConfig = null;
    offscreenSurfaceLayer = 0; // Bug 1389
    isOffscreenLayerSurface = false;
    drawable= 0;
    drawable_old = 0;
    jawt_surface_bounds = new Rectangle();
    insets = new Insets();
    hasPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
    hasPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
    hasPixelScaleChanged = false;
  }
  protected abstract void invalidateNative(final long _offscreenSurfaceLayer);

  /**
   * Set a new {@link AWTGraphicsConfiguration} instance,
   * as required if {@link #getAWTComponent() upstream component}'s {@link GraphicsConfiguration} has been changed
   * due to reconfiguration, e.g. moving to a different monitor or changed capabilities.
   * <p>
   * {@link #getAWTComponent() Upstream component} shall override {@link Component#getGraphicsConfiguration()},
   * which shall call this method if detecting a reconfiguration.
   * See JOGL's GLCanvas and NewtCanvasAWT.
   * </p>
   * @param config the new {@link AWTGraphicsConfiguration}
   * @see #getAWTGraphicsConfiguration()
   */
  public final void setAWTGraphicsConfiguration(final AWTGraphicsConfiguration config) {
    if(DEBUG) {
        System.err.println(jawtStr2("setAWTGraphicsConfiguration")+": "+this.awtConfig+" -> "+config);
        // Thread.dumpStack();
    }
    if( null == awtConfig ) {
        throw new IllegalArgumentException(jawtStr2("")+": null config");
    }
    this.awtConfig = config;
  }
  /**
   * Return the current {@link AWTGraphicsConfiguration} instance,
   * which also holds its {@link #getAWTComponent() upstream component}'s {@link GraphicsConfiguration}
   * @see #setAWTGraphicsConfiguration(AWTGraphicsConfiguration)
   */
  public final AWTGraphicsConfiguration getAWTGraphicsConfiguration() {
    return awtConfig;
  }

  /**
   * {@inheritDoc}
   * <p>
   * This implementation returns false, i.e. not supporting manual change of pixel-scale.
   * </p>
   */
  @Override
  public final boolean canSetSurfaceScale() { return false; }

  /**
   * {@inheritDoc}
   * <p>
   * Ignored for an AWT widget since pixelScale is dictated by AWT mechanisms.
   * </p>
   */
  @Override
  public boolean setSurfaceScale(final float[] pixelScale) {
      return false;
  }

  /**
   * {@inheritDoc}
   * <p>
   * Returns {@link ScalableSurface#AUTOMAX_PIXELSCALE}, always.
   * </p>
   */
  @Override
  public final float[] getRequestedSurfaceScale(final float[] result) {
      result[0] = ScalableSurface.AUTOMAX_PIXELSCALE;
      result[1] = ScalableSurface.AUTOMAX_PIXELSCALE;
      return result;
  }

  @Override
  public final float[] getCurrentSurfaceScale(final float[] result) {
      System.arraycopy(hasPixelScale, 0, result, 0, 2);
      return result;
  }

  /**
   * {@inheritDoc}
   * <p>
   * Returns 1.0, always.
   * </p>
   */
  @Override
  public float[] getMinimumSurfaceScale(final float[] result) {
      result[0] = 1f;
      result[1] = 1f;
      return result;
  }

  /**
   * {@inheritDoc}
   * <p>
   * Returns {@link #getCurrentSurfaceScale(float[])}.
   * </p>
   */
  @Override
  public final float[] getMaximumSurfaceScale(final float[] result) {
      System.arraycopy(hasPixelScale, 0, result, 0, 2);
      return result;
  }

  /**
   * Updates bounds and pixelScale
   * @param gc GraphicsConfiguration for {@link #updatePixelScale(GraphicsConfiguration, boolean)}
   * @return true if bounds or pixelScale has changed, otherwise false
   */
  protected final boolean updateLockedData(final JAWT_Rectangle jawtBounds, final GraphicsConfiguration gc) {
    final Rectangle jb = new Rectangle(jawtBounds.getX(), jawtBounds.getY(), jawtBounds.getWidth(), jawtBounds.getHeight());
    final boolean changedBounds = !jawt_surface_bounds.equals(jb);

    if( changedBounds ) {
        if( DEBUG ) {
            System.err.println("JAWTWindow.updateBounds: "+jawt_surface_bounds+" -> "+jb);
        }
        jawt_surface_bounds.set(jawtBounds.getX(), jawtBounds.getY(), jawtBounds.getWidth(), jawtBounds.getHeight());

        if(component instanceof Container) {
            final java.awt.Insets contInsets = ((Container)component).getInsets();
            insets.set(contInsets.left, contInsets.right, contInsets.top, contInsets.bottom);
        }
    }

    updatePixelScale(gc, false);
    return hasPixelScaleChanged || changedBounds;
  }

  /**
   * Updates the minimum and maximum pixel-scale values
   * and returns {@code true} if they were updated.
   * @param gc pre-fetched {@link GraphicsConfiguration} instance of {@link #getAWTComponent() upstream component},
   *           caller may use cached {@link #getAWTGraphicsConfiguration()}'s {@link AWTGraphicsConfiguration#getAWTGraphicsConfiguration() GC}
   *           or a {@link Component#getGraphicsConfiguration()}.
   * @param clearFlag if {@code true}, the {@code hasPixelScaleChanged} flag will be cleared
   * @return {@code true} if values were updated, otherwise {@code false}.
   * @see #hasPixelScaleChanged()
   * @see #getAWTGraphicsConfiguration()
   * @see Component#getGraphicsConfiguration()
   */
  public final boolean updatePixelScale(final GraphicsConfiguration gc, final boolean clearFlag) {
      final float[] min = { 1, 1 };
      final float[] max = { hasPixelScale[0], hasPixelScale[1] };
      if( JAWTUtil.getPixelScale(gc, min, max) ) {
          // Enforce maxPixelScale as used by AWT
          if( DEBUG ) {
              System.err.println("JAWTWindow.updatePixelScale: ["+hasPixelScale[0]+", "+hasPixelScale[1]+"] -> ["+max[0]+", "+max[1]+"]");
          }
          hasPixelScaleChanged = true;
          System.arraycopy(max, 0, hasPixelScale, 0, 2);
      }
      if( clearFlag ) {
          final boolean r = hasPixelScaleChanged;
          hasPixelScaleChanged = false;
          return r;
      } else {
          return hasPixelScaleChanged;
      }
  }

  /**
   * Returns and clears the {@code hasPixelScaleChanged} flag, as set via {@link #lockSurface()}.
   * <p>
   * {@code hasPixelScaleChanged} is {@code true},
   * if the {@link #getMinimumSurfaceScale(float[]) minimum} or {@link #getMaximumSurfaceScale(float[]) maximum}
   * pixel scale has changed.
   * User needs to {@link #setSurfaceScale(float[]) set the current pixel scale} in this case
   * using the {@link #getRequestedSurfaceScale(float[]) requested pixel scale}
   * to update the surface pixel scale.
   * </p>
   */
  public final boolean hasPixelScaleChanged() {
      final boolean v = hasPixelScaleChanged;
      hasPixelScaleChanged = false;
      return v;
  }

  /** @return the JAWT_DrawingSurfaceInfo's (JAWT_Rectangle) bounds in pixel units, updated with lock */
  public final RectangleImmutable getJAWTSurfaceBounds() { return jawt_surface_bounds; }

  /** @return the safe pixelScale value for x-direction, i.e. never negative or zero. Updated with lock. */
  protected final float getPixelScaleX() { return hasPixelScale[0]; }

  /** @return the safe pixelScale value for y-direction, i.e. never negative or zero. Updated with lock. */
  protected final float getPixelScaleY() { return hasPixelScale[1]; }

  @Override
  public final InsetsImmutable getInsets() { return insets; }

  public final Component getAWTComponent() {
    return component;
  }

  /**
   *
   * Return false since there is no more {@link java.applet.Applet} support.
   *
   * Historical: Returns true if the AWT component is parented to an {@link java.applet.Applet},
   * otherwise false. This information is valid only after {@link #lockSurface()}.
   */
  public final boolean isApplet() {
      return false; // return isApplet;
  }

  /** Returns the underlying JAWT instance created @ {@link #lockSurface()}. */
  public final JAWT getJAWT() {
      return jawt;
  }

  //
  // OffscreenLayerOption
  //

  @Override
  public void setShallUseOffscreenLayer(final boolean v) {
      shallUseOffscreenLayer = v;
  }

  @Override
  public final boolean getShallUseOffscreenLayer() {
      return shallUseOffscreenLayer;
  }

  @Override
  public final boolean isOffscreenLayerSurfaceEnabled() {
      return isOffscreenLayerSurface;
  }

  //
  // OffscreenLayerSurface
  //

  @Override
  public final void attachSurfaceLayer(final long layerHandle) throws NativeWindowException {
      if( null == appContextInfo ) { // !JAWTUtil.isOffscreenLayerSupported()
          throw new NativeWindowException("Offscreen layer not supported");
      }
      if( !isOffscreenLayerSurfaceEnabled() ) {
          throw new NativeWindowException("Not an offscreen layer surface");
      }
      attachSurfaceLayerImpl(layerHandle);
      offscreenSurfaceLayer = layerHandle;
      appContextInfo.invokeOnAppContextThread(false /* waitUntilDone */, repaintTask, "Repaint");
  }
  private final Runnable repaintTask = new Runnable() {
          @Override
          public void run() {
              final Component c = component;
              if( DEBUG ) {
                  System.err.println("Bug 1004: RepaintTask on "+Thread.currentThread()+": Has Comp "+(null != c));
              }
              if( null != c ) {
                  c.repaint();
              }
          } };

  protected void attachSurfaceLayerImpl(final long _offscreenSurfaceLayer) {
      throw new UnsupportedOperationException("offscreen layer not supported");
  }

  /**
   * Layout the offscreen layer according to the implementing class's constraints.
   * <p>
   * This method allows triggering a re-layout of the offscreen surface
   * in case the implementation requires it.
   * </p>
   * <p>
   * Call this method if any parent or ancestor's layout has been changed,
   * which could affects the layout of this surface.
   * </p>
   * @see #isOffscreenLayerSurfaceEnabled()
   * @throws NativeWindowException if {@link #isOffscreenLayerSurfaceEnabled()} == false
   */
  protected void layoutSurfaceLayerImpl(final boolean visible) {}

  private final void layoutSurfaceLayerIfEnabled(final boolean visible) throws NativeWindowException {
      if( isOffscreenLayerSurfaceEnabled() && 0 != offscreenSurfaceLayer ) {
          layoutSurfaceLayerImpl(visible);
      }
  }


  @Override
  public final void detachSurfaceLayer() throws NativeWindowException {
      if( 0 == offscreenSurfaceLayer) {
          throw new NativeWindowException("No offscreen layer attached: "+this);
      }
      if(DEBUG) {
        System.err.println("JAWTWindow.detachSurfaceHandle(): osh "+toHexString(offscreenSurfaceLayer)+" - "+Thread.currentThread().getName());
      }
      {
          final long osl = offscreenSurfaceLayer;
          offscreenSurfaceLayer = 0;
          detachSurfaceLayerImpl(osl);
      }
  }

  /**
   * @param detachNotify Runnable to be called before native detachment
   */
  protected void detachSurfaceLayerImpl(final long _offscreenSurfaceLayer) {
      throw new UnsupportedOperationException("offscreen layer not supported");
  }


  @Override
  public final long getAttachedSurfaceLayer() {
      return offscreenSurfaceLayer;
  }

  @Override
  public final boolean isSurfaceLayerAttached() {
      return 0 != offscreenSurfaceLayer;
  }

  @Override
  public final void setChosenCapabilities(final CapabilitiesImmutable caps) {
      ((MutableGraphicsConfiguration)getGraphicsConfiguration()).setChosenCapabilities(caps);
      awtConfig.setChosenCapabilities(caps);
  }

  @Override
  public final RecursiveLock getLock() {
      return surfaceLock;
  }

  @Override
  public final boolean setCursor(final PixelRectangle pixelrect, final PointImmutable hotSpot) {
      AWTEDTExecutor.singleton.invoke(false, new Runnable() {
          @Override
          public void run() {
              Cursor c = null;
              if( null == pixelrect || null == hotSpot ) {
                  c = Cursor.getDefaultCursor();
              } else {
                  final java.awt.Point awtHotspot = new java.awt.Point(hotSpot.getX(), hotSpot.getY());
                  try {
                      c = AWTMisc.getCursor(pixelrect, awtHotspot);
                  } catch (final Exception e) {
                      e.printStackTrace();
                  }
              }
              if( null != c ) {
                  component.setCursor(c);
              }
          } } );
      return true;
  }

  @Override
  public final boolean hideCursor() {
      AWTEDTExecutor.singleton.invoke(false, new Runnable() {
          @Override
          public void run() {
              final Cursor cursor = AWTMisc.getNullCursor();
              if( null != cursor ) {
                  component.setCursor(cursor);
              }
          } } );
      return true;
  }

  //
  // NativeSurface
  //

  /**
  private void determineIfApplet() {
    boolean isApplet = false; // dummy
    Component c = component;
    while(!isApplet && null != c) {
        isApplet = c instanceof java.applet.Applet;
        c = c.getParent();
    }
  } */

  /**
   * If JAWT offscreen layer is supported,
   * implementation shall respect {@link #getShallUseOffscreenLayer()}
   * and may respect {@link #isApplet()}.
   *
   * @return The JAWT instance reflecting offscreen layer support, etc.
   *
   * @throws NativeWindowException
   */
  protected abstract JAWT fetchJAWTImpl() throws NativeWindowException;
  protected abstract int lockSurfaceImpl(GraphicsConfiguration gc) throws NativeWindowException;

  protected void dumpJAWTInfo() {
      System.err.println(jawt2String(null).toString());
      // Thread.dumpStack();
  }

  @Override
  public final int lockSurface() throws NativeWindowException, RuntimeException  {
    surfaceLock.lock();
    int res = surfaceLock.getHoldCount() == 1 ? LOCK_SURFACE_NOT_READY : LOCK_SUCCESS; // new lock ?

    if ( LOCK_SURFACE_NOT_READY == res ) {
        if( !component.isDisplayable() ) {
            // W/o native peer, we cannot utilize JAWT for locking.
            surfaceLock.unlock();
            if(DEBUG) {
                System.err.println("JAWTWindow: Can't lock surface, component peer n/a. Component displayable "+component.isDisplayable()+", "+component);
                ExceptionUtils.dumpStack(System.err);
            }
        } else {
            final GraphicsConfiguration gc;
            if( EventQueue.isDispatchThread() || Thread.holdsLock(component.getTreeLock()) ) {
                /**
                 * Trigger detection of possible reconfiguration before 'sun.awt.SunToolkit.awtLock()',
                 * which maybe triggered via adevice.lock() below (X11).
                 * See setAWTGraphicsConfiguration(..).
                 */
                gc = component.getGraphicsConfiguration();
            } else {
                // Reuse cached instance
                gc = awtConfig.getAWTGraphicsConfiguration();
            }

            // determineIfApplet();
            try {
                final AbstractGraphicsDevice adevice = getGraphicsConfiguration().getScreen().getDevice();
                adevice.lock();
                try {
                    if(null == jawt) { // no need to re-fetch for each frame
                        jawt = fetchJAWTImpl();
                        isOffscreenLayerSurface = JAWTUtil.isJAWTUsingOffscreenLayer(jawt);
                    }
                    res = lockSurfaceImpl(gc);
                    if(LOCK_SUCCESS == res && drawable_old != drawable) {
                        res = LOCK_SURFACE_CHANGED;
                        if(DEBUG) {
                            System.err.println("JAWTWindow: surface change "+toHexString(drawable_old)+" -> "+toHexString(drawable));
                            // Thread.dumpStack();
                        }
                    }
                } finally {
                    if (LOCK_SURFACE_NOT_READY >= res) {
                        adevice.unlock();
                    }
                }
            } finally {
                if (LOCK_SURFACE_NOT_READY >= res) {
                    surfaceLock.unlock();
                }
            }
        }
    }
    return res;
  }

  protected abstract void unlockSurfaceImpl() throws NativeWindowException;

  @Override
  public final void unlockSurface() {
    surfaceLock.validateLocked();
    drawable_old = drawable;

    if (surfaceLock.getHoldCount() == 1) {
        final AbstractGraphicsDevice adevice = getGraphicsConfiguration().getScreen().getDevice();
        try {
            if(null != jawt) {
                unlockSurfaceImpl();
            }
        } finally {
            adevice.unlock();
        }
    }
    surfaceLock.unlock();
  }

  @Override
  public final boolean isSurfaceLockedByOtherThread() {
    return surfaceLock.isLockedByOtherThread();
  }

  @Override
  public final Thread getSurfaceLockOwner() {
    return surfaceLock.getOwner();
  }

  @Override
  public boolean surfaceSwap() {
    return false;
  }

  @Override
  public void addSurfaceUpdatedListener(final SurfaceUpdatedListener l) {
      surfaceUpdatedHelper.addSurfaceUpdatedListener(l);
  }

  @Override
  public void addSurfaceUpdatedListener(final int index, final SurfaceUpdatedListener l) throws IndexOutOfBoundsException {
      surfaceUpdatedHelper.addSurfaceUpdatedListener(index, l);
  }

  @Override
  public void removeSurfaceUpdatedListener(final SurfaceUpdatedListener l) {
      surfaceUpdatedHelper.removeSurfaceUpdatedListener(l);
  }

  @Override
  public void surfaceUpdated(final Object updater, final NativeSurface ns, final long when) {
      surfaceUpdatedHelper.surfaceUpdated(updater, ns, when);
  }

  @Override
  public long getSurfaceHandle() {
    return drawable;
  }

  @Override
  public final AbstractGraphicsConfiguration getGraphicsConfiguration() {
    if( null == awtConfig ) {
        throw new NativeWindowException(jawtStr2("")+": null awtConfig, invalidated");
    }
    return awtConfig.getNativeGraphicsConfiguration();
  }

  @Override
  public final long getDisplayHandle() {
    return getGraphicsConfiguration().getScreen().getDevice().getHandle();
  }

  @Override
  public final int getScreenIndex() {
    return getGraphicsConfiguration().getScreen().getIndex();
  }

  @Override
  public final int getSurfaceWidth() {
    return SurfaceScaleUtils.scale(getWidth(), getPixelScaleX());
  }

  @Override
  public final int getSurfaceHeight() {
    return SurfaceScaleUtils.scale(getHeight(), getPixelScaleY());
  }

  @Override
  public final int[] convertToWindowUnits(final int[] pixelUnitsAndResult) {
      return SurfaceScaleUtils.scaleInv(pixelUnitsAndResult, pixelUnitsAndResult, hasPixelScale);
  }

  @Override
  public final int[] convertToPixelUnits(final int[] windowUnitsAndResult) {
      return SurfaceScaleUtils.scale(windowUnitsAndResult, windowUnitsAndResult, hasPixelScale);
  }

  @Override
  public final NativeSurface getNativeSurface() { return this; }

  //
  // NativeWindow
  //

  @Override
  public final int getX() {
      return component.getX();
  }

  @Override
  public final int getY() {
      return component.getY();
  }

  @Override
  public final int getWidth() {
      return component.getWidth();
  }

  @Override
  public final int getHeight() {
      return component.getHeight();
  }

  @Override
  public final Rectangle getBounds() {
      return new Rectangle(getX(), getY(), getWidth(), getHeight());
  }

  @Override
  public final Rectangle getSurfaceBounds() {
      return new Rectangle(SurfaceScaleUtils.scale(getX(), getPixelScaleX()),
                           SurfaceScaleUtils.scale(getY(), getPixelScaleY()),
                           getSurfaceWidth(), getSurfaceHeight());
  }

  @Override
  public void destroy() {
    surfaceLock.lock();
    try {
        if(DEBUG) {
            System.err.println(jawtStr2("destroy"));
        }
        jawtComponentListener.detach();
        invalidate();
    } finally {
        surfaceLock.unlock();
    }
  }

  @Override
  public final NativeWindow getParent() {
      return null;
  }

  @Override
  public long getWindowHandle() {
    return drawable;
  }

  /**
   * {@inheritDoc}
   *
   * <p>
   * This JAWT default implementation is currently still using
   * a blocking implementation. It first attempts to retrieve the location
   * via a native implementation. If this fails, it tries the blocking AWT implementation.
   * If the latter fails due to an external AWT tree-lock, the non block
   * implementation {@link #getLocationOnScreenNonBlocking(Point, Component)} is being used.
   * The latter simply traverse up to the AWT component tree and sums the rel. position.
   * We have to determine whether the latter is good enough for all cases,
   * currently only OS X utilizes the non blocking method per default.
   * </p>
   */
  @Override
  public Point getLocationOnScreen(final Point storage) {
      Point los = getLocationOnScreenNative(storage);
      if(null == los) {
          los = AWTMisc.getLocationOnScreenSafe(storage, component, DEBUG);
      }
      return los;
  }

  protected Point getLocationOnScreenNative(final Point storage) {
      final int lockRes = lockSurface();
      if(LOCK_SURFACE_NOT_READY == lockRes) {
          if(DEBUG) {
              System.err.println("Warning: JAWT Lock couldn't be acquired: "+this);
              ExceptionUtils.dumpStack(System.err);
          }
          return null;
      }
      try {
          final Point d = getLocationOnScreenNativeImpl(0, 0);
          if(null!=d) {
            if(null!=storage) {
                storage.translate(d.getX(),d.getY());
                return storage;
            }
          }
          return d;
      } finally {
          unlockSurface();
      }
  }
  protected abstract Point getLocationOnScreenNativeImpl(int x, int y);

  @Override
  public boolean hasFocus() {
      return component.hasFocus();
  }

  protected StringBuilder jawt2String(StringBuilder sb) {
      if( null == sb ) {
          sb = new StringBuilder();
      }
      sb.append("JVM version: ").append(PlatformPropsImpl.JAVA_VERSION).append(" (").
      append(PlatformPropsImpl.JAVA_VERSION_NUMBER).
      append(" update ").append(PlatformPropsImpl.JAVA_VERSION_UPDATE).append(")").append(Platform.getNewline());
      if(null != jawt) {
          sb.append("JAWT version: ").append(toHexString(jawt.getCachedVersion())).
          append(", CA_LAYER: ").append(JAWTUtil.isJAWTUsingOffscreenLayer(jawt)).
          append(", isLayeredSurface ").append(isOffscreenLayerSurfaceEnabled()).
          append(", bounds ").append(jawt_surface_bounds).append(", insets ").append(insets).
          append(", pixelScale ").append(getPixelScaleX()).append("x").append(getPixelScaleY());
      } else {
          sb.append("JAWT n/a, bounds ").append(jawt_surface_bounds).append(", insets ").append(insets);
      }
      return sb;
  }

  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder();

    sb.append(jawtStr1()+"[");
    jawt2String(sb);
    sb.append(  ", shallUseOffscreenLayer "+shallUseOffscreenLayer+", isOffscreenLayerSurface "+isOffscreenLayerSurface+
                ", attachedSurfaceLayer "+toHexString(getAttachedSurfaceLayer())+
                ", windowHandle "+toHexString(getWindowHandle())+
                ", surfaceHandle "+toHexString(getSurfaceHandle())+
                ", bounds "+jawt_surface_bounds+", insets "+insets
                );
    sb.append(", window ["+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight()+
             "], pixels[scale "+getPixelScaleX()+", "+getPixelScaleY()+" -> "+getSurfaceWidth()+"x"+getSurfaceHeight()+"]"+
              ", visible "+component.isVisible());
    sb.append(", lockedExt "+isSurfaceLockedByOtherThread()+
              ",\n\tconfig "+awtConfig+
              ",\n\tawtComponent "+getAWTComponent()+
              ",\n\tsurfaceLock "+surfaceLock+"]");

    return sb.toString();
  }

  protected static final String toHexString(final long l) {
      return "0x"+Long.toHexString(l);
  }
  protected static final String toHexString(final int i) {
      return "0x"+Integer.toHexString(i);
  }

}
