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

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

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

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

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

/**
 * Representation of context management for onscreen surfaces
 *
 * @author Justin Couch
 * @version $Revision: 1.7 $
 */
class MacOnscreenGLContext extends MacGLContext
{
    private static final String NO_DELETE_ERR =
        "Unable to delete old GL context after surface changed";

    /** Handler for dealing with surface locking */
    private SurfaceController lockHandler;

    /** Flag indicating that a bounds update needs to be processed */
    private boolean sizeChangePending;

    /** If an internal shutdown is pending this is set */
    private boolean shutdownPending;

    /** Bounds in pixels of the size change to process */
    private int[] bufferRectangle;

    /** ID of the clip region to process */
    private int regionId;

    /** Int buffer used to send stuff to AGL */
    private IntBuffer buffer;

    /**
     * Create a new instance of the onscreen surface controller.
     *
     */
    MacOnscreenGLContext(MacGLDrawable drawable,
                         SurfaceController locker,
                         GLContext shareWith)
    {
        super(drawable, shareWith);

        lockHandler = locker;
        sizeChangePending = false;
        shutdownPending = false;
        bufferRectangle = new int[4];

        ByteBuffer buf = ByteBuffer.allocateDirect(16);
        buf.order(ByteOrder.nativeOrder());
        buffer = buf.asIntBuffer();
    }

    //---------------------------------------------------------------
    // Methods defined by GLContextImpl
    //---------------------------------------------------------------

    /**
     * Make the context current now.
     */
    protected int makeCurrentImpl() throws GLException
    {
        if(shutdownPending)
        {
            processShutdown();
            return CONTEXT_NOT_CURRENT;
        }

        int ret_val = 0;

        try
        {
            int lockRes = lockHandler.lockSurface();

            switch(lockRes)
            {
                case SurfaceController.SURFACE_NOT_READY:
                    ret_val = CONTEXT_NOT_CURRENT;
                    break;

                case SurfaceController.SURFACE_CHANGED:
                    destroyImpl();

                    if(shutdownPending)
                    {
                        processShutdown();
                        return CONTEXT_NOT_CURRENT;
                    }

                    ret_val = super.makeCurrentImpl();

                    if((ret_val == CONTEXT_CURRENT) ||
                       (ret_val == CONTEXT_CURRENT_NEW))
                    {
                        if(sizeChangePending)
                            updateBounds();
                        else
                            AGL.aglUpdateContext(aglContextID);

                    }
                    else
                    {
                        // View might not have been ready
                        lockHandler.unlockSurface();
                    }

                    break;

                default:
                    ret_val = super.makeCurrentImpl();
                    if(((ret_val == CONTEXT_CURRENT) ||
                        (ret_val == CONTEXT_CURRENT_NEW)) &&
                       sizeChangePending)
                        updateBounds();
            }
        }
        catch(RuntimeException re)
        {
            try
            {
                // This may also toss an exception due to something that
                // messed up in the above code, so catch and ignore here.
                lockHandler.unlockSurface();
            }
            catch(RuntimeException re2)
            {
System.out.println("Exception unlocking failed lock "  + re2);
re2.printStackTrace();
            }
        }

        if(shutdownPending)
        {
            processShutdown();
            return CONTEXT_NOT_CURRENT;
        }

        return ret_val;
    }

    /**
     * Release the underlying context implementation.
     */
    protected void releaseImpl() throws GLException
    {
        try
        {
            super.releaseImpl();
        }
        catch(GLException gle)
        {
            System.out.println("Release impl exception " + gle);
        }

        if(lockHandler.isLockedSurface())
            lockHandler.unlockSurface();

        // So this one inline because the normal processShutdown() method
        // calls this method directly, resulting in a recursive loop.
        if(shutdownPending)
        {
            destroyImpl();
            shutdownPending = false;
        }
    }

    /**
     * Local convenience method to swap the buffers right now. Makes access
     * to the local context pointer.
     */
    void swapBuffers() throws GLException
    {
        if(shutdownPending)
            processShutdown();
        else
            AGL.aglSwapBuffers(aglContextID);
    }

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

    /**
     * Tag the context as needing to handle the resize change notification.
     *
     * @param x The left coordinate of the bounding box
     * @param y The bottom coordinate of the bounding box
     * @param width The width of the bounds
     * @param height The height of the bounds in pixels
     * @param region The ID of the region to render
     */
    void markBoundsDirty(int x, int y, int width, int height, int region)
    {
        regionId = region;
        bufferRectangle[0] = x;
        bufferRectangle[1] = y;
        bufferRectangle[2] = width;
        bufferRectangle[3] = height;
        sizeChangePending = true;
    }

    /**
     * Process the bounds updates and send them to OpenGL now. Assumes that
     * the context is current right now.
     */
    void updateBounds()
    {
        if(aglContextID == 0)
            return;
        else if(shutdownPending)
        {
            processShutdown();
            return;
        }


        buffer.put(bufferRectangle);
        buffer.rewind();
        AGL.aglSetInteger(aglContextID, AGL.AGL_BUFFER_RECT, buffer);
        AGL.aglEnable(aglContextID, AGL.AGL_BUFFER_RECT);

/*
        NOTE:
        Apple recommends also setting the clip region. However, it seems that
        we do not need to do that. Also, the region ID pointer tends to cause
        SWT to crash, which seems to be a function of the way that the JOGL
        bindings are dealing with the data type conversion. The C interface
        demos convert the param to (const GLuint*) as a pointer to the
        NewRgn structure. Something is not translating that correctly to a pointer,
        resulting in a crash. Since everything seems to work OK without needing
        to set the clip region, so we'll leave it out for now.

        buffer.put(regionId);
        buffer.rewind();
        AGL.aglSetInteger(aglContextID, AGL.AGL_CLIP_REGION, buffer);
        AGL.aglSetInteger(aglContextID, AGL.AGL_CLIP_REGION, new int[] { regionId }, 0);
        AGL.aglEnable(aglContextID, AGL.AGL_CLIP_REGION);
*/
        AGL.aglUpdateContext(aglContextID);
        sizeChangePending = false;
    }

    /**
     * Close down the context uncerimoniously now. Makes the context non-current, then
     * releases all the resources.
     */
    void shutdown()
    {
        if(aglContextID == 0)
            return;

        if(!lockHandler.isLockedSurface())
            processShutdown();
        else
            shutdownPending = true;
    }

    private void processShutdown()
    {
        if(lockHandler.isLockedSurface())
            lockHandler.unlockSurface();

        setCurrent(null);
        releaseImpl();
        destroyImpl();
        shutdownPending = false;
    }
 }
