/*
 * 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 com.jogamp.newt;

import java.security.PrivilegedAction;
import java.util.Arrays;

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

import com.jogamp.common.util.IOUtil;
import com.jogamp.common.util.PropertyAccess;
import com.jogamp.common.util.SecurityUtil;

import jogamp.newt.Debug;
import jogamp.newt.DisplayImpl;
import jogamp.newt.ScreenImpl;
import jogamp.newt.WindowImpl;

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

    public static final String DRIVER_DEFAULT_ROOT_PACKAGE = "jogamp.newt.driver";

    private static IOUtil.ClassResources defaultWindowIcons;
    private static String sysPaths = "jogamp/newt/assets/jogamp-16x16.png jogamp/newt/assets/jogamp-32x32.png";

    static {
        SecurityUtil.doPrivileged(new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                NativeWindowFactory.initSingleton(); // last resort ..
                {
                    /** See API Doc in {@link Window} ! */
                    final String[] paths = PropertyAccess.getProperty("newt.window.icons", true, sysPaths).split("[\\s,]");
                    if( paths.length < 2 ) {
                        throw new IllegalArgumentException("Property 'newt.window.icons' did not specify at least two PNG icons, but "+Arrays.toString(paths));
                    }
                    defaultWindowIcons = new IOUtil.ClassResources(paths, NewtFactory.class.getClassLoader(), null);
                }
                return null;
            } } );
    }

    /**
     * Returns the application window icon resources to be used.
     * <p>
     * Property <code>newt.window.icons</code> may define a list of PNG icons separated by one whitespace or one comma character.
     * Shall reference at least two PNG icons, from lower (16x16) to higher (>= 32x32) resolution.
     * </p>
     * <p>
     * Users may also specify application window icons using {@link #setWindowIcons(com.jogamp.common.util.IOUtil.ClassResources)}.
     * </p>
     */
    public static IOUtil.ClassResources getWindowIcons() { return defaultWindowIcons; }

    /**
     * Allow user to set custom window icons, only applicable at application start before creating any NEWT instance.
     * <p>
     * Shall reference at least two PNG icons, from lower (16x16) to higher (>= 32x32) resolution.
     * </p>
     */
    public static void setWindowIcons(final IOUtil.ClassResources cres) { defaultWindowIcons = cres; }

    public static Class<?> getCustomClass(final String packageName, final String classBaseName) {
        Class<?> clazz = null;
        final String clazzName;
        if(packageName!=null && classBaseName!=null) {
            if( packageName.startsWith(".") ) {
                clazzName = DRIVER_DEFAULT_ROOT_PACKAGE + packageName + "." + classBaseName ;
            } else {
                clazzName = packageName + "." + classBaseName ;
            }
            try {
                clazz = Class.forName(clazzName);
            } catch (final Throwable t) {
                throw new NativeWindowException("Failed to find or initialize class <"+packageName+"."+classBaseName+"> -> <"+clazzName+">: "+t.getMessage(), t);
            }
        } else {
            clazzName = null;
        }
        if( null == clazz ) {
            throw new NativeWindowException("Failed to determine class <"+packageName+"."+classBaseName+"> -> <"+clazzName+">");
        }
        return clazz;
    }

    private static boolean useEDT = true;

    /**
     * Toggles the usage of an EventDispatchThread while creating a Display.<br>
     * The default is enabled.<br>
     * The EventDispatchThread is thread local to the Display instance.<br>
     */
    public static synchronized void setUseEDT(final boolean onoff) {
        useEDT = onoff;
    }

    /** @see #setUseEDT(boolean) */
    public static boolean useEDT() { return useEDT; }

    /**
     * Create a Display entity.
     * <p>
     * Native creation is lazily done at usage, ie. {@link Display#addReference()}.
     * </p>
     * <p>
     * An already existing display connection of the same <code>name</code> will be reused.
     * </p>
     * @param name the display connection name which is a technical platform specific detail,
     *        see {@link AbstractGraphicsDevice#getConnection()}. Use <code>null</code> for default.
     * @return the new or reused Display instance
     */
    public static Display createDisplay(final String name) {
        return createDisplay(name, true);
    }

    /**
     * Create a Display entity.
     * <p>
     * Native creation is lazily done at usage, ie. {@link Display#addReference()}.
     * </p>
     * <p>
     * An already existing display connection of the same <code>name</code> will be reused
     * <b>if</b> <code>reuse</code> is <code>true</code>, otherwise a new instance is being created.
     * </p>
     * @param name the display connection name which is a technical platform specific detail,
     *        see {@link AbstractGraphicsDevice#getConnection()}. Use <code>null</code> for default.
     * @param reuse attempt to reuse an existing Display with same <code>name</code> if set true, otherwise create a new instance.
     * @return the new or reused Display instance
     */
    public static Display createDisplay(final String name, final boolean reuse) {
      return DisplayImpl.create(NativeWindowFactory.getNativeWindowType(true), name, 0, reuse);
    }

    /**
     * Create a Display entity.
     * <p>
     * Native creation is lazily done at usage, ie. {@link Display#addReference()}.
     * </p>
     * <p>
     * An already existing display connection of the same <code>name</code> will be reused.
     * </p>
     * @param type explicit NativeWindow type eg. {@link NativeWindowFactory#TYPE_AWT}
     * @param name the display connection name which is a technical platform specific detail,
     *        see {@link AbstractGraphicsDevice#getConnection()}. Use <code>null</code> for default.
     * @return the new or reused Display instance
     */
    public static Display createDisplay(final String type, final String name) {
        return createDisplay(type, name, true);
    }

    /**
     * Create a Display entity.
     * <p>
     * Native creation is lazily done at usage, ie. {@link Display#addReference()}.
     * </p>
     * <p>
     * An already existing display connection of the same <code>name</code> will be reused
     * <b>if</b> <code>reuse</code> is <code>true</code>, otherwise a new instance is being created.
     * </p>
     * @param type explicit NativeWindow type eg. {@link NativeWindowFactory#TYPE_AWT}
     * @param name the display connection name which is a technical platform specific detail,
     *        see {@link AbstractGraphicsDevice#getConnection()}. Use <code>null</code> for default.
     * @param reuse attempt to reuse an existing Display with same <code>name</code> if set true, otherwise create a new instance.
     * @return the new or reused Display instance
     */
    public static Display createDisplay(final String type, final String name, final boolean reuse) {
      return DisplayImpl.create(type, name, 0, reuse);
    }

    /**
     * Create a Screen entity.
     * <p>
     * Native creation is lazily done at usage, ie. {@link Screen#addReference()}.
     * </p>
     * <p>
     * The lifecycle of this Screen's Display is handled via {@link Display#addReference()}
     * and {@link Display#removeReference()}.
     * </p>
     */
    public static Screen createScreen(final Display display, final int index) {
      return ScreenImpl.create(display, index);
    }

    /**
     * Create a top level Window entity on the default Display and default Screen.
     * <p>
     * Native creation is lazily done at usage, ie. {@link Window#setVisible(boolean)}.
     * </p>
     * <p>
     * An already existing default Display will be reused.
     * </p>
     * <p>
     * The lifecycle of this Window's Screen and Display is handled via {@link Screen#addReference()}
     * and {@link Screen#removeReference()}.
     * </p>
     */
    public static Window createWindow(final CapabilitiesImmutable caps) {
        return createWindowImpl(NativeWindowFactory.getNativeWindowType(true), caps);
    }

    /**
     * Create a top level Window entity.
     * <p>
     * Native creation is lazily done at usage, ie. {@link Window#setVisible(boolean)}.
     * </p>
     * <p>
     * The lifecycle of this Window's Screen and Display is handled via {@link Screen#addReference()}
     * and {@link Screen#removeReference()}.
     * </p>
     */
    public static Window createWindow(final Screen screen, final CapabilitiesImmutable caps) {
        return WindowImpl.create(null, 0, screen, caps);
    }

    /**
     * Create a child Window entity attached to the given parent.<br>
     * The Screen and Display information is regenerated utilizing the parents information,
     * while reusing an existing Display.<br>
     * <p>
     * In case <code>parentWindowObject</code> is a {@link com.jogamp.newt.Window} instance,<br>
     * the new window is added to it's list of children.<br>
     * This assures proper handling of visibility, creation and destruction.<br>
     * {@link com.jogamp.newt.event.WindowEvent#EVENT_WINDOW_RESIZED} is not propagated to the child window for layout<br>,
     * you have to add an appropriate {@link com.jogamp.newt.event.WindowListener} for this use case.<br>
     * The parents visibility is passed to the new Window<br></p>
     * <p>
     * In case <code>parentWindowObject</code> is a different {@link com.jogamp.nativewindow.NativeWindow} implementation,<br>
     * you have to handle all events appropriate.<br></p>
     * <p>
     * <p>
     * The lifecycle of this Window's Screen and Display is handled via {@link Screen#addReference()}
     * and {@link Screen#removeReference()}.
     * </p>
     *
     * @param parentWindowObject either a NativeWindow instance
     */
    public static Window createWindow(final NativeWindow parentWindow, final CapabilitiesImmutable caps) {
        final String type = NativeWindowFactory.getNativeWindowType(true);
        if( null == parentWindow ) {
            return createWindowImpl(type, caps);
        }
        Screen screen  = null;
        Window newtParentWindow = null;

        if ( parentWindow instanceof Window ) {
            // use parent NEWT Windows Display/Screen
            newtParentWindow = (Window) parentWindow ;
            screen = newtParentWindow.getScreen();
        } else {
            // create a Display/Screen compatible to the NativeWindow
            final AbstractGraphicsConfiguration parentConfig = parentWindow.getGraphicsConfiguration();
            if(null!=parentConfig) {
                final AbstractGraphicsScreen parentScreen = parentConfig.getScreen();
                final AbstractGraphicsDevice parentDevice = parentScreen.getDevice();
                final Display display = NewtFactory.createDisplay(type, parentDevice.getHandle(), true);
                screen  = NewtFactory.createScreen(display, parentScreen.getIndex());
            } else {
                final Display display = NewtFactory.createDisplay(type, null, true); // local display
                screen  = NewtFactory.createScreen(display, 0); // screen 0
            }
        }
        final Window win = WindowImpl.create(parentWindow, 0, screen, caps);

        win.setSize(parentWindow.getWidth(), parentWindow.getHeight());
        if ( null != newtParentWindow ) {
            newtParentWindow.addChild(win);
            win.setVisible(newtParentWindow.isVisible());
        }
        return win;
    }

    private static Window createWindowImpl(final String type, final CapabilitiesImmutable caps) {
        final Display display = NewtFactory.createDisplay(type, null, true); // local display
        final Screen screen  = NewtFactory.createScreen(display, 0); // screen 0
        return WindowImpl.create(null, 0, screen, caps);
    }

    /**
     * Create a child Window entity attached to the given parent, incl native creation<br>
     *
     * @param displayConnection the parent window's display connection
     * @param screenIdx the desired screen index
     * @param parentWindowHandle the native parent window handle
     * @param caps the desired capabilities
     * @return
     */
    public static Window createWindow(final String displayConnection, final int screenIdx, final long parentWindowHandle, final CapabilitiesImmutable caps) {
        final String type = NativeWindowFactory.getNativeWindowType(true);
        final Display display = NewtFactory.createDisplay(type, displayConnection, true);
        final Screen screen  = NewtFactory.createScreen(display, screenIdx);
        return WindowImpl.create(null, parentWindowHandle, screen, caps);
    }

    /**
     * Ability to try a Window type with a constructor argument, if supported ..<p>
     * Currently only valid is <code> AWTWindow(Frame frame) </code>,
     * to support an external created AWT Frame, ie the browsers embedded frame.
     *
     * @param undecorated only impacts if the window is in top-level state, while attached to a parent window it's rendered undecorated always
     */
    public static Window createWindow(final Object[] cstrArguments, final Screen screen, final CapabilitiesImmutable caps) {
        return WindowImpl.create(cstrArguments, screen, caps);
    }

    /**
     * Instantiate a Display entity using the native handle.
     */
    public static Display createDisplay(final String type, final long handle, final boolean reuse) {
      return DisplayImpl.create(type, null, handle, reuse);
    }

    public static boolean isScreenCompatible(final NativeWindow parent, final Screen childScreen) {
      // Get parent's NativeWindow details
      final AbstractGraphicsConfiguration parentConfig = parent.getGraphicsConfiguration();
      final AbstractGraphicsScreen parentScreen = parentConfig.getScreen();
      final AbstractGraphicsDevice parentDevice = parentScreen.getDevice();

      final DisplayImpl childDisplay = (DisplayImpl) childScreen.getDisplay();
      final String parentDisplayName = childDisplay.validateDisplayName(null, parentDevice.getHandle());
      final String childDisplayName = childDisplay.getName();
      if( ! parentDisplayName.equals( childDisplayName ) ) {
        return false;
      }

      if( parentScreen.getIndex() != childScreen.getIndex() ) {
        return false;
      }
      return true;
    }

    public static Screen createCompatibleScreen(final NativeWindow parent) {
      return createCompatibleScreen(parent, null);
    }

    public static Screen createCompatibleScreen(final NativeWindow parent, final Screen childScreen) {
      // Get parent's NativeWindow details
      final AbstractGraphicsConfiguration parentConfig = parent.getGraphicsConfiguration();
      final AbstractGraphicsScreen parentScreen = parentConfig.getScreen();
      final AbstractGraphicsDevice parentDevice = parentScreen.getDevice();

      if(null != childScreen) {
        // check if child Display/Screen is compatible already
        final DisplayImpl childDisplay = (DisplayImpl) childScreen.getDisplay();
        final String parentDisplayName = childDisplay.validateDisplayName(null, parentDevice.getHandle());
        final String childDisplayName = childDisplay.getName();
        final boolean displayEqual = parentDisplayName.equals( childDisplayName );
        final boolean screenEqual = parentScreen.getIndex() == childScreen.getIndex();
        if(DEBUG_IMPLEMENTATION) {
            System.err.println("NewtFactory.createCompatibleScreen: Display: "+
                parentDisplayName+" =? "+childDisplayName+" : "+displayEqual+"; Screen: "+
                parentScreen.getIndex()+" =? "+childScreen.getIndex()+" : "+screenEqual);
        }
        if( displayEqual && screenEqual ) {
            // match: display/screen
            return childScreen;
        }
      }

      // Prep NEWT's Display and Screen according to the parent
      final String type = NativeWindowFactory.getNativeWindowType(true);
      final Display display = NewtFactory.createDisplay(type, parentDevice.getHandle(), true);
      return NewtFactory.createScreen(display, parentScreen.getIndex());
    }
}

