/*
 * Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved.
 * Copyright (c) 2010 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.
 *
 */

package jogamp.newt.driver.awt;

import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsConfiguration;

import com.jogamp.common.os.Platform;
import com.jogamp.common.os.Platform.OSType;
import com.jogamp.nativewindow.AbstractGraphicsDevice;
import com.jogamp.nativewindow.AbstractGraphicsScreen;
import com.jogamp.nativewindow.CapabilitiesChooser;
import com.jogamp.nativewindow.CapabilitiesImmutable;
import com.jogamp.nativewindow.GraphicsConfigurationFactory;
import com.jogamp.nativewindow.NativeWindow;
import com.jogamp.nativewindow.NativeWindowException;
import com.jogamp.nativewindow.NativeWindowFactory;
import com.jogamp.nativewindow.VisualIDHolder;

import com.jogamp.nativewindow.awt.AWTGraphicsConfiguration;
import com.jogamp.nativewindow.awt.AWTGraphicsDevice;
import com.jogamp.nativewindow.awt.AWTGraphicsScreen;
import com.jogamp.nativewindow.awt.JAWTWindow;
import com.jogamp.newt.Window;

import jogamp.nativewindow.jawt.JAWTUtil;

@SuppressWarnings("serial")
public class AWTCanvas extends Canvas {
  private static JAWTUtil.BackgroundEraseControl backgroundEraseControl = new JAWTUtil.BackgroundEraseControl();

  private final WindowDriver driver;
  private final CapabilitiesImmutable capabilities;
  private final CapabilitiesChooser chooser;
  private final UpstreamScalable upstreamScale;
  private GraphicsConfiguration chosen;
  private volatile GraphicsDevice device;
  private volatile AWTGraphicsConfiguration awtConfig;
  private volatile JAWTWindow jawtWindow=null; // the JAWTWindow presentation of this AWT Canvas, bound to the 'drawable' lifecycle

  public static interface UpstreamScalable {
      float[] getReqPixelScale();
      void setHasPixelScale(final float[] pixelScale);
  }

  private boolean displayConfigChanged=false;

  public AWTCanvas(final WindowDriver driver, final CapabilitiesImmutable capabilities, final CapabilitiesChooser chooser, final UpstreamScalable upstreamScale) {
    super();
    if(null==capabilities) {
        throw new NativeWindowException("Capabilities null");
    }
    if(null==driver) {
        throw new NativeWindowException("driver null");
    }
    this.driver = driver;
    this.capabilities=capabilities;
    this.chooser=chooser;
    this.upstreamScale = upstreamScale;
  }

  public AWTGraphicsConfiguration getAWTGraphicsConfiguration() {
    return awtConfig;
  }

  /**
   * Overridden from Canvas to prevent the AWT's clearing of the
   * canvas from interfering with the OpenGL rendering.
   */
  @Override
  public void update(final Graphics g) {
    // paint(g);
  }

  /** Overridden to cause OpenGL rendering to be performed during
      repaint cycles. Subclasses which override this method must call
      super.paint() in their paint() method in order to function
      properly.
   */
  @Override
  public void paint(final Graphics g) {
  }

  public boolean hasDeviceChanged() {
    final boolean res = displayConfigChanged;
    displayConfigChanged=false;
    return res;
  }

  @Override
  public void addNotify() {

    // before native peer is valid: X11
    if( OSType.WINDOWS != Platform.getOSType() ) {
        backgroundEraseControl.disable(this);
    }

    /**
     * 'super.addNotify()' determines the GraphicsConfiguration,
     * while calling this class's overriden 'getGraphicsConfiguration()' method
     * after which it creates the native peer.
     * Hence we have to set the 'awtConfig' before since it's GraphicsConfiguration
     * is being used in getGraphicsConfiguration().
     * This code order also allows recreation, ie re-adding the GLCanvas.
     */
    awtConfig = chooseGraphicsConfiguration(capabilities, capabilities, chooser, device);
    if(Window.DEBUG_IMPLEMENTATION) {
        System.err.println(getThreadName()+": AWTCanvas.addNotify.0: Created Config: "+awtConfig);
    }
    if(null==awtConfig) {
        throw new NativeWindowException("Error: NULL AWTGraphicsConfiguration");
    }
    chosen = awtConfig.getAWTGraphicsConfiguration();

    setAWTGraphicsConfiguration(awtConfig);

    // issues getGraphicsConfiguration() and creates the native peer
    super.addNotify();

    // after native peer is valid: Windows
    if( OSType.WINDOWS == Platform.getOSType() ) {
        backgroundEraseControl.disable(this);
    }

    {
        jawtWindow = (JAWTWindow) NativeWindowFactory.getNativeWindow(this, awtConfig);
        // trigger initialization cycle
        jawtWindow.lockSurface();
        try {
            jawtWindow.setSurfaceScale(upstreamScale.getReqPixelScale() );
            upstreamScale.setHasPixelScale(jawtWindow.getCurrentSurfaceScale(new float[2]));
        } finally {
            jawtWindow.unlockSurface();
        }
    }

    final GraphicsConfiguration gc = super.getGraphicsConfiguration();
    if(null!=gc) {
        device = gc.getDevice();
    }
    driver.localCreate();
    if(Window.DEBUG_IMPLEMENTATION) {
        System.err.println(getThreadName()+": AWTCanvas.addNotify.X");
    }
  }

  public NativeWindow getNativeWindow() {
    final JAWTWindow _jawtWindow = jawtWindow;
    return (null != _jawtWindow) ? _jawtWindow : null;
  }

  public boolean isOffscreenLayerSurfaceEnabled() {
      return null != jawtWindow ? jawtWindow.isOffscreenLayerSurfaceEnabled() : false;
  }

  private void setAWTGraphicsConfiguration(final AWTGraphicsConfiguration config) {
    // Cache awtConfig
    awtConfig = config;
    if( null != jawtWindow ) {
        // Notify JAWTWindow ..
        jawtWindow.setAWTGraphicsConfiguration(config);
    }
  }

  @Override
  public void removeNotify() {
      if(Window.DEBUG_IMPLEMENTATION) {
          System.err.println(getThreadName()+": AWTCanvas.removeNotify.0: Created Config: "+awtConfig);
      }
      try {
        driver.localDestroy();
      } finally {
        super.removeNotify();
      }
  }

  void dispose() {
    if( null != jawtWindow ) {
        jawtWindow.destroy();
        if(Window.DEBUG_IMPLEMENTATION) {
            System.err.println(getThreadName()+": AWTCanvas.disposeJAWTWindowAndAWTDeviceOnEDT(): post JAWTWindow: "+jawtWindow);
        }
        jawtWindow=null;
    }
    if(null != awtConfig) {
        final AbstractGraphicsDevice adevice = awtConfig.getNativeGraphicsConfiguration().getScreen().getDevice();
        String adeviceMsg=null;
        if(Window.DEBUG_IMPLEMENTATION) {
            adeviceMsg = adevice.toString();
        }
        final boolean closed = adevice.close();
        if(Window.DEBUG_IMPLEMENTATION) {
            System.err.println(getThreadName()+": AWTCanvas.dispose(): closed GraphicsDevice: "+adeviceMsg+", result: "+closed);
        }
    }
    awtConfig = null;
  }

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

  /**
   * Overridden to choose a GraphicsConfiguration on a parent container's
   * GraphicsDevice because both devices
   */
  @Override
  public GraphicsConfiguration getGraphicsConfiguration() {
    /*
     * Workaround for problems with Xinerama and java.awt.Component.checkGD
     * when adding to a container on a different graphics device than the
     * one that this Canvas is associated with.
     *
     * GC will be null unless:
     *   - A native peer has assigned it. This means we have a native
     *     peer, and are already comitted to a graphics configuration.
     *   - This canvas has been added to a component hierarchy and has
     *     an ancestor with a non-null GC, but the native peer has not
     *     yet been created. This means we can still choose the GC on
     *     all platforms since the peer hasn't been created.
     */
    final GraphicsConfiguration gc = super.getGraphicsConfiguration();
    /*
     * chosen is only non-null on platforms where the GLDrawableFactory
     * returns a non-null GraphicsConfiguration (in the GLCanvas
     * constructor).
     *
     * if gc is from this Canvas' native peer then it should equal chosen,
     * otherwise it is from an ancestor component that this Canvas is being
     * added to, and we go into this block.
     */
    if (gc != null && chosen != null && !chosen.equals(gc)) {
      /*
       * Check for compatibility with gc. If they differ by only the
       * device then return a new GCconfig with the super-class' GDevice
       * (and presumably the same visual ID in Xinerama).
       *
       */
      if (!chosen.getDevice().getIDstring().equals(gc.getDevice().getIDstring())) {
        /*
         * Here we select a GraphicsConfiguration on the alternate
         * device that is presumably identical to the chosen
         * configuration, but on the other device.
         *
         * Should really check to ensure that we select a configuration
         * with the same X visual ID for Xinerama screens, otherwise the
         * GLDrawable may have the wrong visual ID (I don't think this
         * ever gets updated). May need to add a method to
         * X11GLDrawableFactory to do this in a platform specific
         * manner.
         *
         * However, on platforms where we can actually get into this
         * block, both devices should have the same visual list, and the
         * same configuration should be selected here.
         */
        final AWTGraphicsConfiguration newConfig = chooseGraphicsConfiguration(
                awtConfig.getChosenCapabilities(), awtConfig.getRequestedCapabilities(), chooser, gc.getDevice());
        final GraphicsConfiguration compatible = (null!=newConfig)?newConfig.getAWTGraphicsConfiguration():null;
        if(Window.DEBUG_IMPLEMENTATION) {
            final Exception e = new Exception("Info: Call Stack: "+Thread.currentThread().getName());
            e.printStackTrace();
            System.err.println("Created Config (n): HAVE    GC "+chosen);
            System.err.println("Created Config (n): THIS    GC "+gc);
            System.err.println("Created Config (n): Choosen GC "+compatible);
            System.err.println("Created Config (n): HAVE    CF "+awtConfig);
            System.err.println("Created Config (n): Choosen CF "+newConfig);
            System.err.println("Created Config (n): EQUALS CAPS "+newConfig.getChosenCapabilities().equals(awtConfig.getChosenCapabilities()));
        }

        if (compatible != null) {
          /*
           * Save the new GC for equals test above, and to return to
           * any outside callers of this method.
           */
          chosen = compatible;
          if( !newConfig.getChosenCapabilities().equals(awtConfig.getChosenCapabilities())) {
              displayConfigChanged=true;
          }
          setAWTGraphicsConfiguration(newConfig);
        }
      }

      /*
       * If a compatible GC was not found in the block above, this will
       * return the GC that was selected in the constructor (and might
       * cause an exception in Component.checkGD when adding to a
       * container, but in this case that would be the desired behavior).
       *
       */
      return chosen;
    } else if (gc == null) {
      /*
       * The GC is null, which means we have no native peer, and are not
       * part of a (realized) component hierarchy. So we return the
       * desired visual that was selected in the constructor (possibly
       * null).
       */
      return chosen;
    }

    /*
     * Otherwise we have not explicitly selected a GC in the constructor, so
     * just return what Canvas would have.
     */
    return gc;
  }

  private static AWTGraphicsConfiguration chooseGraphicsConfiguration(final CapabilitiesImmutable capsChosen,
                                                                      final CapabilitiesImmutable capsRequested,
                                                                      final CapabilitiesChooser chooser,
                                                                      final GraphicsDevice device) {
    final AbstractGraphicsScreen aScreen = null != device ?
            AWTGraphicsScreen.createScreenDevice(device, AbstractGraphicsDevice.DEFAULT_UNIT):
            AWTGraphicsScreen.createDefault();
    final AWTGraphicsConfiguration config = (AWTGraphicsConfiguration)
      GraphicsConfigurationFactory.getFactory(AWTGraphicsDevice.class, capsChosen.getClass()).chooseGraphicsConfiguration(capsChosen,
                                                                                                   capsRequested,
                                                                                                   chooser, aScreen, VisualIDHolder.VID_UNDEFINED);
    if (config == null) {
      throw new NativeWindowException("Error: Couldn't fetch AWTGraphicsConfiguration");
    }

    return config;
  }
}
