/*****************************************************************************
 *                     Yumetech, Inc Copyright (c) 2005
 *                               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.windows;

// External imports
import javax.media.opengl.*;

import java.lang.ref.WeakReference;
import java.util.ArrayList;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;

import org.eclipse.swt.internal.win32.OS;

import com.sun.opengl.impl.windows.WGL;
import com.sun.opengl.impl.windows.WindowsGLDrawable;

// Local imports
import org.j3d.opengl.swt.internal.ri.SurfaceController;

/**
 * An implementation of the {@link WindowsGLDrawable} that works with SWT
 * based implementations.
 * <p>
 *
 * @author Justin Couch
 * @version $Revision: 1.3 $
 */
public class WindowsOnscreenGLDrawable extends WindowsGLDrawable
    implements SurfaceController,
               Listener
{
    /** The component that this drawable is wrapping */
    private Composite component;

    /** Set the realisation state */
    private boolean realized;

    /*<WeakReferences of <GLContext>>*/
    private ArrayList createdContexts;

    /**
     * Create a new instance of this drawable that works within the component.
     */
    public WindowsOnscreenGLDrawable(Composite comp,
                                     GLCapabilities capabilities,
                                     GLCapabilitiesChooser chooser)
    {
        super(capabilities, chooser);
        component = comp;
        realized = false;

        createdContexts = new ArrayList();

        component.addListener(SWT.Dispose, this);
    }

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

    /**
     * Create a new context instance that may share data with the given
     * context.
     *
     * @param sharedWith The context instance to share, or null if not shared
     */
    public GLContext createContext(GLContext shareWith)
    {
        WindowsOnscreenGLContext context =
            new WindowsOnscreenGLContext(this, this, shareWith);

        // NOTE: Keep track of the contexts created using weak refs so that
        // shutdown works cleanly.
        synchronized(this)
        {
            ArrayList newContexts = new ArrayList();
            newContexts.addAll(createdContexts);
            newContexts.add(new WeakReference(context));
            createdContexts = newContexts;
        }

        return context;
    }

    /**
     * Indicates to on-screen GLDrawable implementations whether the underlying
     * window has been created and can be drawn into.
     *
     * @param state true when this has been realised by the canvas
     * @see GLDrawable#setRealized
     */
    public void setRealized(boolean state)
    {
        realized = state;

        // Assume widget was destroyed
        if(!realized)
            pixelFormatChosen = false;
    }

    /**
     * Requests a new width and height for this drawable.
     *
     * @param width The new width of the drawable
     * @param height The new height of the drawable
     */
    public void setSize(int width, int height)
    {
        component.setSize(width, height);
    }

    /**
     * Get the current width of the drawable in pixels.
     *
     * @return A value greater than or equal to zero
     */
    public int getWidth()
    {
        Point pt = component.getSize();
        return pt.x;
    }

    /**
     * Get the current height of the drawable in pixels.
     *
     * @return A value greater than or equal to zero
     */
    public int getHeight()
    {
        Point pt = component.getSize();
        return pt.y;
    }

    /**
     * Swap the front and back buffers of this drawable now.
     */
    public void swapBuffers() throws GLException
    {
        boolean didLock = false;

        if(hdc == 0)
        {
            if(lockSurface() == SURFACE_NOT_READY)
                return;

            didLock = true;
        }

        if(!WGL.SwapBuffers(hdc) && (WGL.GetLastError() != 0))
            throw new GLException("Error swapping buffers");

        if(didLock)
            unlockSurface();
    }

    //---------------------------------------------------------------
    // Methods defined by SurfaceController
    //---------------------------------------------------------------

    /**
     * Lock the surface now so that we can draw to it.
     *
     * @return One of the surface constants from this interface
     * @throws GLException The surface is already locked by someone else
     */
    public int lockSurface() throws GLException
    {
        if(!realized)
            return SURFACE_NOT_READY;

        if(hdc != 0)
            throw new GLException("Surface already locked");

        hdc = OS.GetDC(component.handle);

        int ret_val = SUCCESS;

        // If there is no pixel format yet chosen, we assume that this is the
        // start of a new surface. Unlike AWT, SWT only creates the underlying
        // window handles once, which is done at the creation of the object.
        // This is the basic way we're detecting this newly created widget.

        if(!pixelFormatChosen)
        {
            choosePixelFormat(true);
            ret_val = SURFACE_CHANGED;
        }

        return ret_val;
    }

    /**
     * Check to see if the surface is currently locked.
     *
     * @return true if the surface is locked, false otherwise
     */
    public boolean isLockedSurface()
    {
        return (hdc != 0);
    }

    /**
     * Release the surface back to the OS. This may throw an exception if the
     * surface is already unlocked at this point.
     *
     * @throws GLException The surface is already unlocked
     */
    public void unlockSurface() throws GLException
    {
        if(hdc == 0)
          throw new GLException("Surface already unlocked");

        hdc = 0;
    }

    //---------------------------------------------------------------
    // Methods defined by Listener
    //---------------------------------------------------------------

    public void handleEvent(Event event)
    {
        switch (event.type)
        {
            case SWT.Dispose:
                // Not sure if we need this. JOGL may already take care of it for us
                shutdown();
        }
    }

    //---------------------------------------------------------------
    // Local Methods
    //---------------------------------------------------------------

    /**
     * Shutdown the contents of this drawable now. Called in response to th
     * dispose event listener, so we don't really care how clean this is.
     */
    private void shutdown()
    {
        for(int i = 0; i < createdContexts.size(); i++)
        {
            WeakReference ref = (WeakReference)createdContexts.get(i);
            WindowsOnscreenGLContext ctx = (WindowsOnscreenGLContext)ref.get();

            // FIXME: clear out unreachable contexts
            if(ctx != null)
                ctx.shutdown();
        }
    }
}
