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

import javax.media.nativewindow.NativeSurfaceHolder;

import jogamp.newt.Debug;

/**
 * Convenient adapter forwarding AWT events to NEWT via the event listener model.<br>
 * <p>
 * You may attach an instance of this adapter to an AWT Component. When an event happens,
 * it is converted to a NEWT event and the given NEWT listener is being called.<br></p>
 * <p>
 * This adapter fullfills three use cases. First as a plain utility to write code AWT agnostic,
 * ie write an {@link javax.media.opengl.GLEvenListener} and some appropriate NEWT {@link com.jogamp.newt.event.NEWTEventListener}.<br></p>
 * <p>
 * Attach the {@link javax.media.opengl.GLEvenListener} to a NEWT {@link javax.media.opengl.GLAutoDrawable}, e.g. {@link com.jogamp.newt.opengl.GLWindow},<br>
 * or to an AWT {@link javax.media.opengl.GLAutoDrawable}, e.g. {@link javax.media.opengl.awt.GLCanvas}.<br>
 * <br>
 * Attach the NEWT {@link com.jogamp.newt.event.NEWTEventListener} to a NEWT component, e.g. {@link com.jogamp.newt.Window},<br>
 * or to an AWT component, e.g. {@link java.awt.Component}.<br></p>
 * <p>
 * Common:<br>
 * <pre>
    // your demo/render code
    javax.media.opengl.GLEvenListener demo1 = new javax.media.opengl.GLEvenListener() { ... } ;

    // your AWT agnostic NEWT mouse listener code
    com.jogamp.newt.event.MouseListener mouseListener = new com.jogamp.newt.event.MouseAdapter() { ... } ;
 * </pre> </p>
 * <p>
 * Default NEWT use case, without using the AWTAdapter:<br>
 * <pre>
    // the NEWT GLAutoDrawable and Window
    GLWindow glWindow = GLWindow.create();

    // attach the renderer demo1
    glWindow.addGLEventListener(demo1);

    // attach the NEWT mouse event listener to glWindow
    glWindow.addMouseListener(mouseListener);
 * </pre> </p>
 * <p>
 * AWT use case, AWTAdapter used as an AWT event translator and forwarder to your NEWT listener:<br>
 * <pre>
    // the AWT GLAutoDrawable and Canvas
    GLCanvas glCanvas = new GLCanvas();

    // attach the renderer demo1
    glCanvas.addGLEventListener(demo1);

    // attach the AWTMouseAdapter to glCanvas, which translates and forwards events to the NEWT mouseListener
    new AWTMouseAdapter(mouseListener).addTo(glCanvas);
 * </pre> </p>
 * <p>
 * Previous code in detail:<br>
 * <pre>
    AWTMouseAdapter mouseAdapter = new AWTMouseAdapter(mouseListener);
    glCanvas.addMouseListener(mouseAdapter);
    glCanvas.addMouseMotionListener(mouseAdapter);
 * </pre> </p>
 *
 * <p>
 * Second use case is just a litte variation of the previous use case, where we pass a NEWT Window <br>
 * to be used as the source of the event.<br></p>
 * <p>
 * <pre>
    com.jogamp.newt.event.MouseListener mouseListener = new com.jogamp.newt.event.MouseAdapter() { ... } ;<br>
    Component comp = ... ;                 // the AWT  component<br>
    GLWindow glWindow = GLWindow.create(); // the NEWT component<br>
    <br>
    new AWTMouseAdapter(mouseListener, glWindow).addTo(comp);<br>
 * </pre> </p>
 *
 * Last but not least, the AWTAdapter maybe used as a general AWT event forwarder to NEWT.<br>
 *
 * <p>
 * <pre>
    com.jogamp.newt.event.MouseListener mouseListener = new com.jogamp.newt.event.MouseAdapter() { ... } ;<br>
    Component comp = ... ;                 // the AWT  component<br>
    GLWindow glWindow = GLWindow.create(); // the NEWT component<br>
    glWindow.addMouseListener(mouseListener); // add the custom EventListener to the NEWT component<br>
    <br>
    new AWTMouseAdapter(glWindow).addTo(comp); // forward all AWT events to glWindow, as NEWT events<br>
 * </pre> </p>
 *
 * @see #attachTo
 */
public abstract class AWTAdapter implements java.util.EventListener
{
    public static final boolean DEBUG_IMPLEMENTATION = Debug.debug("Window");

    com.jogamp.newt.event.NEWTEventListener newtListener;
    com.jogamp.newt.Window newtWindow;
    NativeSurfaceHolder nsHolder;
    boolean consumeAWTEvent;
    protected boolean isSetup;

    /**
     * Create a proxy adapter, wrapping around an NEWT EventListener, exposed as an AWT EventListener,<br>
     * where the given {@link NativeSurfaceHolder} impersonates the event's source.
     * The NEWT EventListener will be called when an event happens.<br>
     */
    protected AWTAdapter(final com.jogamp.newt.event.NEWTEventListener newtListener, final NativeSurfaceHolder nsProxy) {
        if(null==newtListener) {
            throw new IllegalArgumentException("Argument newtListener is null");
        }
        if(null==nsProxy) {
            throw new IllegalArgumentException("Argument nwProxy is null");
        }
        this.newtListener = newtListener;
        this.newtWindow = null;
        this.nsHolder = nsProxy;
        this.consumeAWTEvent = false;
        this.isSetup = true;
    }

    /**
     * Create a proxy adapter, wrapping around an NEWT EventListener, exposed as an AWT EventListener,<br>
     * where the given {@link com.jogamp.newt.Window NEWT Window}, a  {@link NativeSurfaceHolder}, impersonates the event's source.
     * The NEWT EventListener will be called when an event happens.<br>
     */
    protected AWTAdapter(final com.jogamp.newt.event.NEWTEventListener newtListener, final com.jogamp.newt.Window newtProxy) {
        if(null==newtListener) {
            throw new IllegalArgumentException("Argument newtListener is null");
        }
        if(null==newtProxy) {
            throw new IllegalArgumentException("Argument newtProxy is null");
        }
        this.newtListener = newtListener;
        this.newtWindow = newtProxy;
        this.nsHolder = newtProxy;
        this.consumeAWTEvent = false;
        this.isSetup = true;
    }

    /**
     * Create a pipeline adapter, AWT EventListener.<br>
     * Once attached to an AWT component, it sends the converted AWT events to the NEWT downstream window.<br>
     * This is only supported with EDT enabled!
     * @throws IllegalStateException if EDT is not enabled
     */
    protected AWTAdapter(final com.jogamp.newt.Window downstream) throws IllegalStateException {
        this();
        setDownstream(downstream);
    }

    public AWTAdapter() {
        clear();
        this.consumeAWTEvent = false;
    }

    /**
     * Setup a pipeline adapter, AWT EventListener.<br>
     * Once attached to an AWT component, it sends the converted AWT events to the NEWT downstream window.<br>
     * This is only supported with EDT enabled!
     * @throws IllegalStateException if EDT is not enabled
     */
    public synchronized AWTAdapter setDownstream(final com.jogamp.newt.Window downstream) throws IllegalStateException {
        if(null==downstream) {
            throw new RuntimeException("Argument downstream is null");
        }
        this.newtListener = null;
        this.newtWindow = downstream;
        this.nsHolder = downstream;
        if( null == newtWindow.getScreen().getDisplay().getEDTUtil() ) {
            throw new IllegalStateException("EDT not enabled");
        }
        this.isSetup = true;
        return this;
    }

    /**
     * Removes all references, downstream and NEWT-EventListener.
     * <p>
     * Also sets the internal <code>setup</code> flag and {@link #setConsumeAWTEvent(boolean)} to <code>false</code>.
     * </p>
     */
    public synchronized AWTAdapter clear() {
        this.newtListener = null;
        this.newtWindow = null;
        this.nsHolder = null;
        this.isSetup = false;
        this.consumeAWTEvent = false;
        return this;
    }

    public final synchronized void setConsumeAWTEvent(final boolean v) {
        this.consumeAWTEvent = v;
    }

    /**
     * Returns the {@link NativeSurfaceHolder} acting {@link #AWTAdapter(com.jogamp.newt.Window) as downstream},
     * {@link #AWTAdapter(com.jogamp.newt.event.NEWTEventListener, com.jogamp.newt.Window) NEWT window proxy}
     * or as an {@link #AWTAdapter(com.jogamp.newt.event.NEWTEventListener, NativeSurfaceHolder) NativeSurfaceHolder proxy}.
     * <p>
     * Returned value is never null.
     * </p>
     */
    public final synchronized NativeSurfaceHolder getNativeSurfaceHolder() {
        return nsHolder;
    }

    /**
     * Returns the {@link com.jogamp.newt.Window NEWT Window} acting {@link #AWTAdapter(com.jogamp.newt.Window) as downstream}
     * or as a {@link #AWTAdapter(com.jogamp.newt.event.NEWTEventListener, com.jogamp.newt.Window) NEWT window proxy}.
     * <p>
     * Returned value maybe null if instance is used to be a
     * {@link #AWTAdapter(com.jogamp.newt.event.NEWTEventListener, NativeSurfaceHolder) NativeSurfaceHolder proxy}.
     * </p>
     */
    public final synchronized com.jogamp.newt.Window getNewtWindow() {
        return newtWindow;
    }

    /**
     * Returns the {@link com.jogamp.newt.event.NEWTEventListener NEWT event-listener} if instance
     * is used as an {@link #AWTAdapter(com.jogamp.newt.event.NEWTEventListener, NativeSurfaceHolder) NativeSurfaceHolder proxy}
     * or {@link #AWTAdapter(com.jogamp.newt.event.NEWTEventListener, com.jogamp.newt.Window) NEWT window proxy},
     * otherwise method returns <code>null</code>.
     */
    public final synchronized com.jogamp.newt.event.NEWTEventListener getNewtEventListener() {
        return newtListener;
    }

    /**
     * Due to the fact that some NEWT {@link com.jogamp.newt.event.NEWTEventListener}
     * are mapped to more than one {@link java.util.EventListener},
     * this method is for your convenience to use this Adapter as a listener for all types.<br>
     * E.g. {@link com.jogamp.newt.event.MouseListener} is mapped to {@link java.awt.event.MouseListener} and {@link java.awt.event.MouseMotionListener}.
     */
    public abstract AWTAdapter addTo(java.awt.Component awtComponent);

    /** @see #addTo(java.awt.Component) */
    public abstract AWTAdapter removeFrom(java.awt.Component awtComponent);

    /**
     * Return value for {@link AWTAdapter#processEvent(boolean, com.jogamp.newt.event.NEWTEvent) event processing}.
     */
    static enum EventProcRes {
        /** Event shall be dispatched appropriately */
        DISPATCH,
        /** Event has been enqueued */
        ENQUEUED,
        /** No known processing method applies */
        NOP
    }

    /**
     * Process the event.
     * <p>
     * If {@link #getNewtEventListener()} is not <code>null</code>,
     * {@link EventProcRes#DISPATCH DISPATCH} is returned and caller shall dispatch the event appropriately.
     * </p>
     * <p>
     * If {@link #getNewtWindow()} is not <code>null</code>,
     * {@link EventProcRes#ENQUEUED ENQUEUED} is returned and the event has been {@link com.jogamp.newt.Window#enqueueEvent(boolean, com.jogamp.newt.event.NEWTEvent) enqueued already}.
     * </p>
     * <p>
     * If none of the above matches, {@link EventProcRes#NOP NOP} is returned and none of the above processing method applies.
     * </p>
     *
     * @param wait In case the event will be {@link EventProcRes#ENQUEUED ENQUEUED},
     *             passing <code>true</code> will block until the event has been processed, otherwise method returns immediately.
     * @param event The {@link com.jogamp.newt.event.NEWTEvent event} to enqueue.
     * @return One of the {@link EventProcRes} values, see above.
     */
    EventProcRes processEvent(final boolean wait, final com.jogamp.newt.event.NEWTEvent event) {
        if(null != newtListener) {
            return EventProcRes.DISPATCH;
        }
        if( null != newtWindow ) {
            newtWindow.enqueueEvent(wait, event);
            return EventProcRes.ENQUEUED;
        }
        return EventProcRes.NOP;
    }
}

