/**
 * Copyright 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:
 *
 *    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 jogamp.newt.awt;

import com.jogamp.nativewindow.AbstractGraphicsConfiguration;
import com.jogamp.nativewindow.CapabilitiesImmutable;
import com.jogamp.nativewindow.NativeWindow;
import com.jogamp.nativewindow.NativeWindowException;
import com.jogamp.nativewindow.NativeWindowFactory;

import jogamp.nativewindow.awt.AWTMisc;
import jogamp.nativewindow.jawt.JAWTUtil;
import jogamp.nativewindow.jawt.x11.X11SunJDKReflection;
import jogamp.nativewindow.x11.X11Lib;
import jogamp.newt.Debug;

import com.jogamp.nativewindow.awt.AWTGraphicsConfiguration;
import com.jogamp.nativewindow.awt.AWTGraphicsScreen;
import com.jogamp.nativewindow.awt.JAWTWindow;
import com.jogamp.nativewindow.util.Point;
import com.jogamp.nativewindow.util.Rectangle;
import com.jogamp.nativewindow.util.RectangleImmutable;
import com.jogamp.newt.Display;
import com.jogamp.newt.MonitorDevice;
import com.jogamp.newt.NewtFactory;
import com.jogamp.newt.Screen;

public class NewtFactoryAWT extends NewtFactory {
  public static final boolean DEBUG_IMPLEMENTATION = Debug.debug("Window");

  /**
   * Wraps an AWT component into a {@link com.jogamp.nativewindow.NativeWindow} utilizing the {@link com.jogamp.nativewindow.NativeWindowFactory},<br>
   * using the given {@link AWTGraphicsConfiguration}.
   * <p>
   * The actual wrapping implementation is {@link com.jogamp.nativewindow.awt.JAWTWindow}.
   * </p>
   * <p>
   * The required {@link AWTGraphicsConfiguration} may be constructed via
   * {@link AWTGraphicsConfiguration#create(java.awt.GraphicsConfiguration, CapabilitiesImmutable, CapabilitiesImmutable)}
   * </p>
   * <p>
   * Purpose of this wrapping is to access the AWT window handle,<br>
   * not to actually render into it.
   * </p>
   *
   * @param awtComp {@link java.awt.Component}
   * @param awtConfig {@link AWTGraphicsConfiguration} reflecting the used {@link java.awt.GraphicsConfiguration}
   */
  public static JAWTWindow getNativeWindow(final java.awt.Component awtComp, final AWTGraphicsConfiguration awtConfig) {
      final NativeWindow nw = NativeWindowFactory.getNativeWindow(awtComp, awtConfig); // a JAWTWindow
      if(! ( nw instanceof JAWTWindow ) ) {
          throw new NativeWindowException("Not an AWT NativeWindow: "+nw);
      }
      if(DEBUG_IMPLEMENTATION) {
        System.err.println("NewtFactoryAWT.getNativeWindow: "+awtComp+" -> "+nw);
      }
      return (JAWTWindow)nw;
  }

  public static void destroyNativeWindow(final JAWTWindow jawtWindow) {
      final AbstractGraphicsConfiguration config = jawtWindow.getGraphicsConfiguration();
      jawtWindow.destroy();
      config.getScreen().getDevice().close();
  }

  /**
   * @param awtComp
   * @throws IllegalArgumentException if {@code awtComp} is not {@link java.awt.Component#isDisplayable() displayable}
   *                                  or has {@code null} {@link java.awt.Component#getGraphicsConfiguration() GraphicsConfiguration}.
   */
  private static java.awt.GraphicsConfiguration checkComponentValid(final java.awt.Component awtComp) throws IllegalArgumentException {
      if( !awtComp.isDisplayable() ) {
          throw new IllegalArgumentException("Given AWT-Component is not displayable: "+awtComp);
      }
      final java.awt.GraphicsConfiguration gc = awtComp.getGraphicsConfiguration();
      if( null == gc ) {
          throw new IllegalArgumentException("Given AWT-Component has no GraphicsConfiguration set: "+awtComp);
      }
      return gc;
  }

  /**
   * @param awtComp must be {@link java.awt.Component#isDisplayable() displayable}
   *        and must have a {@link java.awt.Component#getGraphicsConfiguration() GraphicsConfiguration}
   * @param reuse attempt to reuse an existing {@link Display} with same <code>name</code> if set true, otherwise create a new instance.
   * @return {@link Display} instance reflecting the {@code awtComp}
   * @throws IllegalArgumentException if {@code awtComp} is not {@link java.awt.Component#isDisplayable() displayable}
   *                                  or has {@code null} {@link java.awt.Component#getGraphicsConfiguration() GraphicsConfiguration}.
   * @see #getAbstractGraphicsScreen(java.awt.Component)
   */
  public static Display createDisplay(final java.awt.Component awtComp, final boolean reuse) throws IllegalArgumentException {
      final java.awt.GraphicsConfiguration gc = checkComponentValid(awtComp);
      final java.awt.GraphicsDevice device = gc.getDevice();

      final String displayConnection;
      final String nwt = NativeWindowFactory.getNativeWindowType(true);
      if( NativeWindowFactory.TYPE_X11 == nwt ) {
          final long displayHandleAWT = X11SunJDKReflection.graphicsDeviceGetDisplay(device);
          if( 0 == displayHandleAWT ) {
              displayConnection = null; // default
          } else {
              /**
               * Using the AWT display handle works fine with NVidia.
               * However we experienced different results w/ AMD drivers,
               * some work, but some behave erratic.
               * I.e. hangs in XQueryExtension(..) via X11GraphicsScreen.
               */
              displayConnection = X11Lib.XDisplayString(displayHandleAWT);
          }
      } else {
          displayConnection = null; // default
      }
      return NewtFactory.createDisplay(displayConnection, reuse);
  }

  /**
   * @param awtComp must be {@link java.awt.Component#isDisplayable() displayable}
   *        and must have a {@link java.awt.Component#getGraphicsConfiguration() GraphicsConfiguration}
   * @param reuse attempt to reuse an existing {@link Display} with same <code>name</code> if set true, otherwise create a new instance.
   * @return {@link Screen} instance reflecting the {@code awtComp}
   * @throws IllegalArgumentException if {@code awtComp} is not {@link java.awt.Component#isDisplayable() displayable}
   *                                  or has {@code null} {@link java.awt.Component#getGraphicsConfiguration() GraphicsConfiguration}.
   * @see #createDevice(java.awt.Component)
   */
  public static Screen createScreen(final java.awt.Component awtComp, final boolean reuse) throws IllegalArgumentException {
      final Display display = createDisplay(awtComp, reuse);
      return NewtFactory.createScreen(display, AWTGraphicsScreen.findScreenIndex(awtComp.getGraphicsConfiguration().getDevice()));
  }

  /**
   * Retrieves the {@link MonitorDevice} for the given displayable {@code awtComp}.
   * <p>
   * In case this method shall be called multiple times, it is advised to keep the given {@link Screen} instance
   * natively created during operation. This should be done via the initial {@link Screen#addReference()}.
   * After operation, user shall destroy the instance accordingly via the final {@link Screen#removeReference()}.
   * </p>
   * @param screen the {@link Screen} instance matching {@code awtComp}, i.e. via {@link #createScreen(java.awt.Component, boolean)}.
   * @param awtComp must be {@link java.awt.Component#isDisplayable() displayable}
   *        and must have a {@link java.awt.Component#getGraphicsConfiguration() GraphicsConfiguration}
   * @return {@link MonitorDevice} instance reflecting the {@code awtComp}
   * @throws IllegalArgumentException if {@code awtComp} is not {@link java.awt.Component#isDisplayable() displayable}
   *                                  or has {@code null} {@link java.awt.Component#getGraphicsConfiguration() GraphicsConfiguration}.
   * @see #createScreen(java.awt.Component, boolean)
   */
  public static MonitorDevice getMonitorDevice(final Screen screen, final java.awt.Component awtComp) throws IllegalArgumentException {
      final java.awt.GraphicsConfiguration gc = checkComponentValid(awtComp);
      MonitorDevice res = null;
      screen.addReference();
      try {
          final Integer dispID = JAWTUtil.getMonitorDisplayID( gc.getDevice() );
          if( null != dispID ) {
              res = screen.getMonitorById( dispID.intValue() );
          }
          if( null == res ) {
              // Fallback, use AWT component coverage
              final Point los = AWTMisc.getLocationOnScreenSafe(null, awtComp, false);
              final RectangleImmutable r = new Rectangle(los.getX(), los.getY(), awtComp.getWidth(), awtComp.getHeight());
              res = screen.getMainMonitor(r);
          }
      } finally {
          screen.removeReference();
      }
      return res;
  }
}

