/*
 * 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.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Insets;

import javax.media.nativewindow.NativeSurface;
import javax.media.nativewindow.NativeWindow;
import javax.media.nativewindow.NativeWindowException;
import javax.media.nativewindow.util.Point;

import jogamp.nativewindow.awt.AWTMisc;
import jogamp.newt.WindowImpl;

import com.jogamp.common.os.Platform;
import com.jogamp.nativewindow.awt.AWTGraphicsConfiguration;
import com.jogamp.nativewindow.awt.AWTGraphicsDevice;
import com.jogamp.nativewindow.awt.AWTGraphicsScreen;
import com.jogamp.newt.event.WindowEvent;
import com.jogamp.newt.event.WindowUpdateEvent;
import com.jogamp.newt.event.awt.AWTKeyAdapter;
import com.jogamp.newt.event.awt.AWTMouseAdapter;
import com.jogamp.newt.event.awt.AWTWindowAdapter;

/** An implementation of the Newt Window class built using the
    AWT. This is provided for convenience of porting to platforms
    supporting Java SE. */

public class WindowDriver extends WindowImpl {

    public WindowDriver() {
        this(null);
    }

    public static Class<?>[] getCustomConstructorArgumentTypes() {
        return new Class<?>[] { Container.class } ;
    }

    public WindowDriver(final Container container) {
        super();
        this.awtContainer = container;
        if(container instanceof Frame) {
            awtFrame = (Frame) container;
        }
    }

    private boolean owningFrame;
    private Container awtContainer = null;
    /** same instance as container, just for impl. convenience */
    private Frame awtFrame = null;
    private AWTCanvas awtCanvas;

    @Override
    protected void requestFocusImpl(final boolean reparented) {
        awtContainer.requestFocus();
    }

    @Override
    protected void setTitleImpl(final String title) {
        if (awtFrame != null) {
            awtFrame.setTitle(title);
        }
    }

    private final AWTCanvas.UpstreamScalable upstreamScalable = new AWTCanvas.UpstreamScalable() {
        @Override
        public int[] getReqPixelScale() {
            return WindowDriver.this.reqPixelScale;
        }

        @Override
        public void setHasPixelScale(final int[] pixelScale) {
            System.arraycopy(pixelScale, 0, WindowDriver.this.hasPixelScale, 0, 2);
        }
    };

    @Override
    protected void createNativeImpl() {
        if(0!=getParentWindowHandle()) {
            throw new RuntimeException("Window parenting not supported in AWT, use AWTWindow(Frame) cstr for wrapping instead");
        }

        if(null==awtContainer) {
            awtFrame = new Frame();
            awtContainer = awtFrame;
            owningFrame=true;
        } else {
            owningFrame=false;
            defineSize(awtContainer.getWidth(), awtContainer.getHeight());
            definePosition(awtContainer.getX(), awtContainer.getY());
        }
        if(null!=awtFrame) {
            awtFrame.setTitle(getTitle());
        }
        awtContainer.setLayout(new BorderLayout());

        if( null == awtCanvas ) {
            awtCanvas = new AWTCanvas(capsRequested, WindowDriver.this.capabilitiesChooser, upstreamScalable);

            // canvas.addComponentListener(listener);
            awtContainer.add(awtCanvas, BorderLayout.CENTER);

            // via EDT ..
            new AWTMouseAdapter(this).addTo(awtCanvas); // fwd all AWT Mouse events to here
            new AWTKeyAdapter(this).addTo(awtCanvas); // fwd all AWT Key events to here

            // direct w/o EDT
            new AWTWindowAdapter(new LocalWindowListener(), this).addTo(awtCanvas); // fwd all AWT Window events to here
        }

        reconfigureWindowImpl(getX(), getY(), getWidth(), getHeight(), getReconfigureFlags(FLAG_CHANGE_VISIBILITY | FLAG_CHANGE_DECORATION, true));
        // throws exception if failed ..

        final NativeWindow nw = awtCanvas.getNativeWindow();
        if( null != nw ) {
            setGraphicsConfiguration( awtCanvas.getAWTGraphicsConfiguration() );
            setWindowHandle( nw.getWindowHandle() );
        }
    }

    @Override
    protected void closeNativeImpl() {
        setWindowHandle(0);
        if(null!=awtContainer) {
            awtContainer.setVisible(false);
            awtContainer.remove(awtCanvas);
            awtContainer.setEnabled(false);
            awtCanvas.setEnabled(false);
        }
        if(owningFrame && null!=awtFrame) {
            awtFrame.dispose();
            owningFrame=false;
        }
        awtCanvas = null;
        awtFrame = null;
        awtContainer = null;
    }

    @Override
    public boolean hasDeviceChanged() {
        final boolean res = awtCanvas.hasDeviceChanged();
        if(res) {
            final AWTGraphicsConfiguration cfg = awtCanvas.getAWTGraphicsConfiguration();
            if (null == cfg) {
                throw new NativeWindowException("Error Device change null GraphicsConfiguration: "+this);
            }
            setGraphicsConfiguration(cfg);

            // propagate new info ..
            ((ScreenDriver)getScreen()).setAWTGraphicsScreen((AWTGraphicsScreen)cfg.getScreen());
            ((DisplayDriver)getScreen().getDisplay()).setAWTGraphicsDevice((AWTGraphicsDevice)cfg.getScreen().getDevice());

            ((ScreenDriver)getScreen()).updateVirtualScreenOriginAndSize();
        }
        return res;
    }

    @Override
    protected void updateInsetsImpl(final javax.media.nativewindow.util.Insets insets) {
        final Insets contInsets = awtContainer.getInsets();
        insets.set(contInsets.left, contInsets.right, contInsets.top, contInsets.bottom);
    }

    private void setCanvasSizeImpl(final int width, final int height) {
        final Dimension szClient = new Dimension(width, height);
        final java.awt.Window awtWindow = AWTMisc.getWindow(awtCanvas);
        final Container c= null != awtWindow ? awtWindow : awtContainer;
        awtCanvas.setMinimumSize(szClient);
        awtCanvas.setPreferredSize(szClient);
        if(DEBUG_IMPLEMENTATION) {
            final Insets insets = c.getInsets();
            final Dimension szContainer = new Dimension(width + insets.left + insets.right,
                                                        height + insets.top + insets.bottom);
            System.err.println(getThreadName()+": AWTWindow setCanvasSize: szClient "+szClient+", szCont "+szContainer+", insets "+insets);
        }
        awtCanvas.setSize(szClient);
        awtCanvas.invalidate();
        if(null != awtWindow) {
            awtWindow.pack();
        } else {
            awtContainer.validate();
        }
    }
    private void setFrameSizeImpl(final int width, final int height) {
        final Insets insets = awtContainer.getInsets();
        final Dimension szContainer = new Dimension(width + insets.left + insets.right,
                                                    height + insets.top + insets.bottom);
        if(DEBUG_IMPLEMENTATION) {
            final Dimension szClient = new Dimension(width, height);
            System.err.println(getThreadName()+": AWTWindow setFrameSize: szClient "+szClient+", szCont "+szContainer+", insets "+insets);
        }
        awtContainer.setSize(szContainer);
        awtCanvas.invalidate();
        awtContainer.validate();
    }

    @Override
    protected boolean reconfigureWindowImpl(final int x, final int y, final int width, final int height, final int flags) {
        if(DEBUG_IMPLEMENTATION) {
            System.err.println("AWTWindow reconfig: "+x+"/"+y+" "+width+"x"+height+", "+
                               getReconfigureFlagsAsString(null, flags));
        }
        if(0 != ( FLAG_CHANGE_DECORATION & flags) && null!=awtFrame) {
            if(!awtContainer.isDisplayable()) {
                awtFrame.setUndecorated(isUndecorated());
            } else {
                if(DEBUG_IMPLEMENTATION) {
                    System.err.println(getThreadName()+": AWTWindow can't undecorate already created frame");
                }
            }
        }

        if( 0 != ( FLAG_CHANGE_VISIBILITY & flags) ) {
            if( 0 != ( FLAG_IS_VISIBLE & flags) ) {
                setCanvasSizeImpl(width, height);
                awtContainer.setVisible( true );
            } else {
                awtContainer.setVisible( false );
            }
        } else if( awtCanvas.getWidth() != width || awtCanvas.getHeight() != height ) {
            if( Platform.OSType.MACOS == Platform.getOSType() && awtCanvas.isOffscreenLayerSurfaceEnabled() ) {
                setFrameSizeImpl(width, height);
            } else {
                setCanvasSizeImpl(width, height);
            }
        }
        defineSize(width, height); // we are on AWT-EDT .. change values immediately

        if( awtContainer.getX() != x || awtContainer.getY() != y ) {
            awtContainer.setLocation(x, y);
        }

        if( 0 != ( FLAG_CHANGE_VISIBILITY & flags) ) {
            if( 0 != ( FLAG_IS_VISIBLE & flags ) ) {
                if( !hasDeviceChanged() ) {
                    // oops ??
                    final AWTGraphicsConfiguration cfg = awtCanvas.getAWTGraphicsConfiguration();
                    if(null == cfg) {
                        throw new NativeWindowException("Error: !hasDeviceChanged && null == GraphicsConfiguration: "+this);
                    }
                    setGraphicsConfiguration(cfg);
                }
            }
            visibleChanged(false, 0 != ( FLAG_IS_VISIBLE & flags));
        }

        return true;
    }

    @Override
    protected Point getLocationOnScreenImpl(final int x, final int y) {
        final java.awt.Point ap = awtCanvas.getLocationOnScreen();
        ap.translate(x, y);
        return new Point((int)(ap.getX()+0.5),(int)(ap.getY()+0.5));
    }

    @Override
    public NativeSurface getWrappedSurface() {
        return ( null != awtCanvas ) ? awtCanvas.getNativeWindow() : null;
    }

    class LocalWindowListener implements com.jogamp.newt.event.WindowListener {
        @Override
        public void windowMoved(final com.jogamp.newt.event.WindowEvent e) {
            if(null!=awtContainer) {
                WindowDriver.this.positionChanged(false, awtContainer.getX(), awtContainer.getY());
            }
        }
        @Override
        public void windowResized(final com.jogamp.newt.event.WindowEvent e) {
            if(null!=awtCanvas) {
                if(DEBUG_IMPLEMENTATION) {
                    System.err.println("Window Resized: "+awtCanvas);
                }
                WindowDriver.this.sizeChanged(false, awtCanvas.getWidth(), awtCanvas.getHeight(), true);
                WindowDriver.this.windowRepaint(false, 0, 0, getSurfaceWidth(), getSurfaceHeight());
            }
        }
        @Override
        public void windowDestroyNotify(final WindowEvent e) {
            WindowDriver.this.windowDestroyNotify(false);
        }
        @Override
        public void windowDestroyed(final WindowEvent e) {
            // Not fwd by AWTWindowAdapter, synthesized by NEWT
        }
        @Override
        public void windowGainedFocus(final WindowEvent e) {
            WindowDriver.this.focusChanged(false, true);
        }
        @Override
        public void windowLostFocus(final WindowEvent e) {
            WindowDriver.this.focusChanged(false, false);
        }
        @Override
        public void windowRepaint(final WindowUpdateEvent e) {
            if(null!=awtCanvas) {
                if(DEBUG_IMPLEMENTATION) {
                    System.err.println("Window Repaint: "+awtCanvas);
                }
                WindowDriver.this.windowRepaint(false, 0, 0, getSurfaceWidth(), getSurfaceHeight());
            }
        }
    }
}
