/*****************************************************************************
 *                     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.osx;

// External imports
import javax.media.opengl.*;
import org.eclipse.swt.widgets.*;

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

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.GCData;
import org.eclipse.swt.internal.carbon.Rect;
import org.eclipse.swt.internal.carbon.OS;

import com.sun.opengl.impl.macosx.agl.AGL;

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

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

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

    /** Set if this is the first time the context has been made current */
    private boolean firstTime;

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

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

        component = comp;
        realized = false;
        firstTime = true;

        createdContexts = new ArrayList();

    	Shell shell = component.getShell();
    	shell.addListener(SWT.Resize, this);
    	shell.addListener(SWT.Show, this);
    	shell.addListener(SWT.Hide, this);
    	Control c = component;
    	do
    	{
    		c.addListener(SWT.Show, this);
    		c.addListener(SWT.Hide, this);
    		c = c.getParent();
    	}
    	while (c != shell);
        
        component.addListener(SWT.Resize, this);    	
    	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)
    {
        MacOnscreenGLContext context =
            new MacOnscreenGLContext(this, this, shareWith);

        // NOTE: we need to keep track of the created contexts in order to
        // implement swapBuffers() because of how Mac OS X implements its
        // OpenGL window interface
        synchronized(this)
        {
            List newContexts = new ArrayList();
            newContexts.addAll(createdContexts);
            newContexts.add(new WeakReference(context));
            createdContexts = newContexts;
        }
        fixBounds(context);
        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;
    }

    /**
     * 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);
        fixBounds();
    }

    /**
     * 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
    {
        for(int i = 0; i < createdContexts.size(); i++)
        {
            WeakReference ref = (WeakReference)createdContexts.get(i);
            MacOnscreenGLContext ctx = (MacOnscreenGLContext)ref.get();

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

    //---------------------------------------------------------------
    // 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(carbonPort != 0)
            throw new GLException("Surface already locked");

        int ret_val = SUCCESS;

        carbonWindow = OS.GetControlOwner(component.handle);
        carbonPort = OS.GetWindowPort(carbonWindow);

        if(carbonPort == 0)
            ret_val = SURFACE_NOT_READY;
        else if(firstTime)
        {
            ret_val = SURFACE_CHANGED;
            firstTime = false;
        }

        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 (carbonPort != 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(carbonPort == 0)
            throw new GLException("Surface already unlocked");

        carbonPort = 0;
    }
    
    //---------------------------------------------------------------
    // Methods defined by GLDrawable
    //---------------------------------------------------------------

	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();
                
				// AGL.aglDestroyContext (context);
				// AGL.aglDestroyPixelFormat (pixelFormat);
				// Remove listeners
				Shell shell = component.getShell();
				shell.removeListener(SWT.Resize, this);
				shell.removeListener(SWT.Show, this);
				shell.removeListener(SWT.Hide, this);
				Control c = component;
				do
				{
					c.removeListener(SWT.Show, this);
					c.removeListener(SWT.Hide, this);
					c = c.getParent();
				}
				while(c != shell);
				break;
				
			case SWT.Resize:
			case SWT.Hide:
			case SWT.Show:
				if(!component.isDisposed())
				    fixBounds();
				break;
		}
	}
  
    //---------------------------------------------------------------
    // Local Methods
    //---------------------------------------------------------------

    /**
     * Update the window to control where OpenGL for this context will be 
     * drawing. If we don't then it will take over the entire window. 
     */
    private void fixBounds()
    {
    	GCData data = new GCData ();
    	int gc = component.internal_new_GC(data);
    	Rect bounds = new Rect();
        Rect rect = new Rect();
        
    	int window = OS.GetControlOwner(component.handle);
    	int port = OS.GetWindowPort(window);

        OS.GetRegionBounds(data.visibleRgn, bounds);
    	OS.GetPortBounds(port, rect);

        int width = bounds.right - bounds.left;
        int height = bounds.bottom - bounds.top;
       
        if(width < 0 || height < 0)
            return;
        
        int x = bounds.left;
        int y = rect.bottom - rect.top - bounds.top - height;
 
    	for(int i = 0; i < createdContexts.size(); i++)
        {
            WeakReference ref = (WeakReference)createdContexts.get(i);
            MacOnscreenGLContext ctx = (MacOnscreenGLContext)ref.get();
 
            // FIXME: clear out unreachable contexts
            if(ctx != null)
            {
            	ctx.markBoundsDirty(x, y, width, height, data.visibleRgn);
            }
        }

    	component.internal_dispose_GC(gc, data);
    }

    /**
     * Same as other fixBounds, but for a single context instance this time.
     * typically called when we've just created a new context instance on this
     * surface.
     * 
     * @param ctx The context to update the bounds for
     */
    private void fixBounds(MacOnscreenGLContext ctx)
    {
        GCData data = new GCData ();
        int gc = component.internal_new_GC(data);
        Rect bounds = new Rect();
        Rect rect = new Rect();
        
        int window = OS.GetControlOwner(component.handle);
        int port = OS.GetWindowPort(window);

        OS.GetRegionBounds(data.visibleRgn, bounds);
        OS.GetPortBounds(port, rect);

        int width = bounds.right - bounds.left;
        int height = bounds.bottom - bounds.top;
       
        if(width < 0 || height < 0)
            return;

        ctx.markBoundsDirty(bounds.left,
                            rect.bottom - rect.top - bounds.top - height,
                            width,
                            height,
                            data.visibleRgn);

        component.internal_dispose_GC(gc, data);
    }
    
    /**
     * 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);
            MacOnscreenGLContext ctx = (MacOnscreenGLContext)ref.get();

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