/*****************************************************************************
 *                       Yumetech, Inc Copyright (c) 2006
 *                              Java Source
 *
 * This source is licensed under the BSD-style License
 * Please read http://www.opensource.org/licenses/bsd-license.php for more
 * information or docs/BSD.txt in the downloaded code.
 *
 * This software comes with the standard NO WARRANTY disclaimer for any
 * purpose. Use it at your own risk. If there's a problem you get to fix it.
 *
 ****************************************************************************/

package org.j3d.opengl.swt.internal.ri;

// External imports
import java.awt.event.*;

import java.awt.Dimension;
import java.beans.PropertyChangeListener;

// Listed out here because we don't want to pull in the Threading class
import javax.media.opengl.GL;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLContext;
import javax.media.opengl.GLDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLException;
import javax.media.opengl.GLPbuffer;

import com.sun.opengl.impl.GLDrawableHelper;
import com.sun.opengl.impl.GLContextImpl;
import com.sun.opengl.impl.GLDrawableImpl;

// Local imports
import org.j3d.opengl.swt.Threading;

/**
 * Platform-independent class exposing pbuffer functionality to
 * applications.
 *
 * @author Justin Couch
 * @version $Revision: 1.2 $
 */
public class JOGLPbufferImpl implements GLPbuffer
{
    /**
     * Message when the floating point mode has not yet been set. Most probably
     * because the initialisation thread hasn't been run yet.
     */
    private static final String NO_FLOAT_MODE_SET_ERR =
        "Pbuffer not initialized, or floating-point support not requested";

    /** The drawable the pbuffer wraps */
    private GLDrawableImpl pbufferDrawable;

    /** The context instance that is used to draw to this pbuffer */
    private GLContextImpl context;

    /** Helper for dealing with the auto drawable stuff */
    private GLDrawableHelper drawableHelper;

    /** Which of the pbuffer floating point modes we're in */
    private int floatMode;

    /** Action for initialising the system in opengl */
    private InitAction initAction;

    /** Action for repainting the pbuffer on the appropriate thread */
    private DisplayAction displayAction;

    /** Action for destroying and making invalid this pbuffer */
    private DestroyAction destroyAction;

    /** Action for swapping the buffers */
    private SwapBuffersAction swapBuffersAction;

    /** Action to abstract the drawing away to a separate thread */
    private RunOnEventDispatchThreadAction displayThreadAction;

    /** Action to abstract the swap buffers away to a separate thread */
    private RunOnEventDispatchThreadAction swapBuffersThreadAction;

    /**
     * Action class that can be used to initialise the open GL drawing
     * pipeline from an external thread.
     */
    private class InitAction implements Runnable
    {
        public void run()
        {
            floatMode = context.getFloatingPointMode();
            drawableHelper.init(JOGLPbufferImpl.this);
        }
    }

    /**
     * Action class that can be used to have the pbuffer display on an
     * external thread.
     */
    private class DisplayAction implements Runnable
    {
        public void run()
        {
            drawableHelper.display(JOGLPbufferImpl.this);
        }
    }

    /**
     * Action class that can be used to swap front and rear buffers on an
     * external thread.
     */
    private class SwapBuffersAction implements Runnable
    {
        // FIXME: currently a no-op
        public void run()
        {
            pbufferDrawable.swapBuffers();
        }
    }

    /**
     * Action class that can be used to destroy and clean up native resources
     * on an external thread.
     */
    class DestroyAction implements Runnable
    {
        public void run()
        {
            GLContext current = GLContext.getCurrent();
            if(current == context)
            {
                context.release();
            }

            context.destroy();
            pbufferDrawable.destroy();
        }
    }

    /**
     * Workaround for ATI driver bugs related to multithreading issues
     * like simultaneous rendering via Animators to canvases that are
     * being resized on the AWT event dispatch thread
     */
    private class RunOnEventDispatchThreadAction implements Runnable
    {
        private Runnable runAction;

        RunOnEventDispatchThreadAction(Runnable executable)
        {
            runAction = executable;
        }

        public void run()
        {
            drawableHelper.invokeGL(pbufferDrawable,
                                    context,
                                    runAction,
                                    initAction);
        }
    }

    /**
     * Construct a new instance of this pbuffer handler
     *
     * @param drawable The drawable this buffer wraps
     * @param parentCtx The parent context of this pbuffer
     */
    public JOGLPbufferImpl(GLDrawableImpl drawable, GLContext parentCtx)
    {
        pbufferDrawable = drawable;

        context = (GLContextImpl)drawable.createContext(parentCtx);
        context.setSynchronized(true);

        drawableHelper = new GLDrawableHelper();

        initAction = new InitAction();
        displayAction = new DisplayAction();
        destroyAction = new DestroyAction();
        swapBuffersAction = new SwapBuffersAction();

        displayThreadAction =
            new RunOnEventDispatchThreadAction(displayAction);
        swapBuffersThreadAction =
            new RunOnEventDispatchThreadAction(swapBuffersAction);

    }

    //----------------------------------------------------------------------
    // Methods defined by GLPbuffer
    //----------------------------------------------------------------------

    /**
     * Binds this pbuffer to its internal texture target. Only valid to call
     * if offscreen render-to-texture has been specified in the GLCapabilities
     * for this GLPbuffer. If the render-to-texture-rectangle capability has
     * also been specified, this will use e.g. wglBindTexImageARB as its
     * implementation and cause the texture to be bound to e.g. the
     * GL_TEXTURE_RECTANGLE_NV state; otherwise, during the display()
     * phase the pixels will have been copied into an internal texture target
     * and this will cause that to be bound to the GL_TEXTURE_2D state.
     */
    public void bindTexture()
    {
        // Doesn't make much sense to try to do this on the event dispatch
        // thread given that it has to be called while the context is current
        context.bindPbufferToTexture();
    }

    /**
     * Unbinds the pbuffer from its internal texture target.
     */
    public void releaseTexture()
    {
        // Doesn't make much sense to try to do this on the event dispatch
        // thread given that it has to be called while the context is current
        context.releasePbufferFromTexture();
    }

    /**
     * Destroys the native resources associated with this pbuffer. It is not
     * valid to call display() or any other routines on this pbuffer after it
     * has been destroyed. Before destroying the pbuffer, the application must
     * destroy any additional OpenGL contexts which have been created for the
     * pbuffer via {@link #createContext}.
     */
    public void destroy()
    {
        if(Threading.isSingleThreaded() &&
           !Threading.isOpenGLThread())
        {
            Threading.invokeOnOpenGLThread(destroyAction);
        }
        else
        {
            destroyAction.run();
        }
    }

    /**
     * Indicates which vendor's extension is being used to support floating
     * point channels in this pbuffer if that capability was requested in the
     * GLCapabilities during pbuffer creation. Returns one of
     * {@link #NV_FLOAT}, {@link #ATI_FLOAT} or {@link #APPLE_FLOAT}, or
     * throws GLException if floating-point channels were not requested for
     * this pbuffer. This function may only be called once the init method for
     * this pbuffer's GLEventListener has been called.
     *
     * @return One of NV_FLOAT, ATI_FLOAT or APPLE_FLOAT
     */
    public int getFloatingPointMode()
    {
        if(floatMode == 0)
            throw new GLException(NO_FLOAT_MODE_SET_ERR);

        return floatMode;
    }

    //----------------------------------------------------------------------
    // Methods defined by GLAutoDrawable
    //----------------------------------------------------------------------

    /**
     * Causes OpenGL rendering to be performed for this GLAutoDrawable by
     * calling {@link GLEventListener#display display} for all registered
     * listeners. Called automatically by the window system toolkit upon
     * receiving a repaint() request. This routine may be called manually for
     * better control over the rendering process. It is legal to call another
     * GLAutoDrawable's display method from within the
     * {@link GLEventListener#display display} callback.
     */
    public void display()
    {
        dispatchAction(displayThreadAction,
                       displayAction,
                       false);
    }

    /**
     * Schedules a repaint of the component at some point in the future.
     */
    public void repaint()
    {
        display();
    }

    /**
     * Indicates whether automatic buffer swapping is enabled for this
     * drawable.
     *
     * @return true when automatic swapping is taking place
     * @see #setAutoSwapBufferMode.
     */
    public boolean getAutoSwapBufferMode()
    {
        return drawableHelper.getAutoSwapBufferMode();
    }

    /**
     * Enables or disables automatic buffer swapping for this drawable. By
     * default this property is set to true; when true, after all
     * GLEventListeners have been called for a display() event, the front and
     * back buffers are swapped, displaying the results of the render. When
     * disabled, the user is responsible for calling {@link #swapBuffers}
     * manually.
     *
     * @param onOrOff true to enabl automatic buffer swapping
     */
    public void setAutoSwapBufferMode(boolean onOrOff)
    {
        drawableHelper.setAutoSwapBufferMode(onOrOff);
    }

    /**
     * Returns the context associated with this drawable. The returned
     * context will be synchronized.
     *
     * @return The context wrapper for this pbuffer
     */
    public GLContext getContext()
    {
        return context;
    }

    /**
     * Returns the {@link GL} pipeline object this GLAutoDrawable uses.
     * If this method is called outside of the {@link GLEventListener}'s
     * callback methods (init, display, etc.) it may return null. Users should
     * not rely on the identity of the returned GL object; for example, users
     * should not maintain a hash table with the GL object as the key.
     * Additionally, the GL object should not be cached in client code, but
     * should be re-fetched from the GLAutoDrawable at the beginning of each
     * call to init, display, etc.
     *
     * @return The current GL pipeline wrapper object
     */
    public GL getGL()
    {
        return getContext().getGL();
    }

    /**
     * Sets the {@link GL} pipeline object this GLAutoDrawable uses. This
     * should only be called from within the GLEventListener's callback
     * methods, and usually only from within the init() method, in order to
     * install a composable pipeline.
     *
     * @param gl The new GL context wrapper to use
     */
    public void setGL(GL gl)
    {
        getContext().setGL(gl);
    }

    /**
     * Add a {@link GLEventListener} to this drawable. If multiple
     * listeners are added to a given drawable, they are notified of
     * events in an arbitrary order.
     *
     * @param listener The listener instance to add
     */
    public void addGLEventListener(GLEventListener listener)
    {
        drawableHelper.addGLEventListener(listener);
    }

    /**
     * Remove the listener from this drawable. Note that if this is done from
     * within a particular drawable's listener handler (reshape, display, etc.)
     * that it is not guaranteed that all other listeners will be evaluated
     * properly during this update cycle.
     *
     * @param listener The listener instance to remove
     */
    public void removeGLEventListener(GLEventListener listener)
    {
        drawableHelper.removeGLEventListener(listener);
    }

    //----------------------------------------------------------------------
    // Methods defined by GLDrawable
    //----------------------------------------------------------------------

    /**
     * Fetches the {@link GLCapabilities} corresponding to the chosen OpenGL
     * capabilities (pixel format / visual) for this drawable. Some drawables,
     * in particular on-screen drawables, may be created lazily; null is
     * returned if the drawable is not currently created or if its pixel
     * format has not been set yet.
     * <p>
     *
     * On some platforms, the pixel format is not directly associated with
     * the drawable; a best attempt is made to return a reasonable value in
     * this case.
     *
     * @return The capabilities that was chosen, or null if none yet
     */
    public GLCapabilities getChosenGLCapabilities()
    {
        GLCapabilities ret_val = null;

        if(pbufferDrawable != null)
            ret_val = pbufferDrawable.getChosenGLCapabilities();

        return ret_val;
    }

    /**
     * Creates a new context for drawing to this drawable that will
     * optionally share display lists and other server-side OpenGL
     * objects with the specified GLContext.
     * <p>
     * The GLContext <code>shareWith</code> need not be associated with this
     * GLDrawable and may be null if sharing of display lists and other
     * objects is not desired.
     *
     * @param shareWith The context to share state with, or null
     */
    public GLContext createContext(GLContext shareWith)
    {
        return pbufferDrawable.createContext(shareWith);
    }

    /**
     * Indicates to on-screen GLDrawable implementations whether the
     * underlying window has been created and can be drawn into. This
     * method must be called from GLDrawables obtained from the
     * GLDrawableFactory via the {@link GLDrawableFactory#getGLDrawable
     * GLDrawableFactory.getGLDrawable()} method. It must typically be
     * called with an argument of <code>true</code> in the
     * <code>addNotify</code> method of components performing OpenGL
     * rendering and with an argument of <code>false</code> in the
     * <code>removeNotify</code> method. Calling this method has no
     * other effects. For example, if <code>removeNotify</code> is
     * called on a Canvas implementation for which a GLDrawable has been
     * created, it is also necessary to destroy all OpenGL contexts
     * associated with that GLDrawable. This is not done automatically
     * by the implementation. It is not necessary to call
     * <code>setRealized</code> on a GLCanvas, a GLJPanel, or a
     * GLPbuffer, as these perform the appropriate calls on their
     * underlying GLDrawables internally..
     */
    public void setRealized(boolean realized)
    {
    }

    /**
     * Requests a new width and height for this GLDrawable. Not all drawables
     * are able to respond to this request and may silently ignore it.
     *
     * @param width The new width in pixels
     * @param height The new height in pixels
     */
    public void setSize(int width, int height)
    {
    }

    /**
     * Returns the current width of this GLDrawable.
     *
     * @return A non-negative number of pixels
     */
    public int getWidth()
    {
        return pbufferDrawable.getWidth();
    }

    /**
     * Returns the current height of this GLDrawable.
     *
     * @return A non-negative number of pixels
     */
    public int getHeight()
    {
        return pbufferDrawable.getHeight();
    }

    /**
     * Swaps the front and back buffers of this drawable. For {@link
     * GLAutoDrawable} implementations, when automatic buffer swapping is
     * enabled (as is the default), this method is called automatically and
     * should not be called by the end user.
     */
    public void swapBuffers()
    {
        dispatchAction(swapBuffersThreadAction,
                                        swapBuffersAction,
                                        false);
    }

    //----------------------------------------------------------------------
    // No-ops for ComponentEvents
    //----------------------------------------------------------------------

    public void addComponentListener(ComponentListener l) {}
    public void removeComponentListener(ComponentListener l) {}
    public void addFocusListener(FocusListener l) {}
    public void removeFocusListener(FocusListener l) {}
    public void addHierarchyBoundsListener(HierarchyBoundsListener l) {}
    public void removeHierarchyBoundsListener(HierarchyBoundsListener l) {}
    public void addHierarchyListener(HierarchyListener l) {}
    public void removeHierarchyListener(HierarchyListener l) {}
    public void addInputMethodListener(InputMethodListener l) {}
    public void removeInputMethodListener(InputMethodListener l) {}
    public void addKeyListener(KeyListener l) {}
    public void removeKeyListener(KeyListener l) {}
    public void addMouseListener(MouseListener l) {}
    public void removeMouseListener(MouseListener l) {}
    public void addMouseMotionListener(MouseMotionListener l) {}
    public void removeMouseMotionListener(MouseMotionListener l) {}
    public void addMouseWheelListener(MouseWheelListener l) {}
    public void removeMouseWheelListener(MouseWheelListener l) {}
    public void addPropertyChangeListener(PropertyChangeListener listener) {}
    public void removePropertyChangeListener(PropertyChangeListener listener) {}
    public void addPropertyChangeListener(String propertyName,
                                          PropertyChangeListener listener) {}
    public void removePropertyChangeListener(String propertyName,
                                             PropertyChangeListener listener) {}

    //----------------------------------------------------------------------
    // Local methods
    //----------------------------------------------------------------------

    /**
     * Send an action for execution. This will determine if the action needs to
     * be executed right now or sent to the OpenGL rendering thread, based on the
     * user's current configuratio.
     *
     * @param isReshape true if this is a reshape of the pbuffer action
     */
    private void dispatchAction(Runnable eventDispatchThreadAction,
                                Runnable invokeGLAction,
                                boolean isReshape)
    {
        if(Threading.isSingleThreaded() && !Threading.isOpenGLThread())
        {
            Threading.invokeOnOpenGLThread(eventDispatchThreadAction);
        }
        else
        {
            drawableHelper.invokeGL(pbufferDrawable,
                                    context,
                                    invokeGLAction,
                                    initAction);
        }
    }
}
