/*****************************************************************************
 *                                         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 com.sun.opengl.impl.*;

import java.nio.ByteBuffer;
import com.sun.opengl.impl.macosx.agl.*;

/**
 * Base class for the GLDrawable implementations on OSX under AGL/SWT.
 *
 * @author Justin Couch
 * @version $Revision: 1.5 $
 */
public class MacGLContext extends GLContextImpl
{
    /** Max number of attributes that we could pass to AGL on configuration */
    private static final int MAX_ATTRIBUTES = 32;

    /** Reference to the drawable that we render to */
    protected MacGLDrawable drawable;

    /** ID of the current context that we're using at the AGL level */
    protected long aglContextID;

    private AGLExt aglExt;

    /**
     * Table that holds the addresses of the native C-language entry points
     * for AGL extension functions.
     */
    private AGLExtProcAddressTable aglExtProcAddressTable;

    /** The AGLPixelFormat pointer. Has the same lifetime as the containing drawable */
    private long pixelFormat;

    /**
     * Create a new GL context that can be shared anywhere needed
     */
    public MacGLContext(MacGLDrawable drawable, GLContext shareWith)
    {
        this(drawable, shareWith, false);
    }

    public MacGLContext(MacGLDrawable drawable,
                        GLContext shareWith,
                        boolean dontShareWithJava2D)
    {
        super(shareWith, AGLUtils.getLookupHelper(), dontShareWithJava2D);
        this.drawable = drawable;
    }

    public Object getPlatformGLExtensions()
    {
        return getAGLExt();
    }

    public AGLExt getAGLExt()
    {
        if(aglExt == null)
            aglExt = new AGLExtImpl(this);

        return aglExt;
    }

    public GLDrawable getGLDrawable()
    {
        return drawable;
    }

    protected String mapToRealGLFunctionName(String glFunctionName)
    {
        return glFunctionName;
    }

    protected String mapToRealGLExtensionName(String glExtensionName)
    {
        return glExtensionName;
    }

    protected boolean create()
    {
        // Note that we specify pbuffer support for all contexts by
        // default; workaround for problem on Mac OS X 10.4.3 where could
        // not share textures and display lists between pbuffers and
        // on-screen contexts
        return create(true, false);
    }

    /**
     * Creates and initializes an appropriate OpenGl aglContextID. Should only
     * be called by {@link makeCurrentImpl()}.
     */
    protected boolean create(boolean pbuffer, boolean floatingPoint)
    {
        MacGLContext other = (MacGLContext)GLContextShareSet.getShareContext(this);
        long share = 0;
        if(other != null)
        {
            share = other.getAGLContextID();
            if(share == 0)
                throw new GLException("GLContextShareSet returned an invalid OpenGL context");

        }

        GLCapabilities capabilities = drawable.getCapabilities();

        int pos = 0;
        int aglAttrib[] = new int[MAX_ATTRIBUTES];

        // Fixed attributes that are always set
        aglAttrib[pos++] = AGL.AGL_RGBA;

        aglAttrib[pos++] = AGL.AGL_RED_SIZE;
        aglAttrib[pos++] = capabilities.getRedBits();

        aglAttrib[pos++] = AGL.AGL_GREEN_SIZE;
        aglAttrib[pos++] = capabilities.getGreenBits();

        aglAttrib[pos++] = AGL.AGL_BLUE_SIZE;
        aglAttrib[pos++] = capabilities.getBlueBits();

        aglAttrib[pos++] = AGL.AGL_ALPHA_SIZE;
        aglAttrib[pos++] = capabilities.getAlphaBits();

        aglAttrib[pos++] = AGL.AGL_DEPTH_SIZE;
        aglAttrib[pos++] = capabilities.getDepthBits();

        if(capabilities.getDoubleBuffered())
            aglAttrib[pos++] = AGL.AGL_DOUBLEBUFFER;

        if(capabilities.getStereo())
            aglAttrib[pos++] = AGL.AGL_STEREO;

        if(capabilities.getStencilBits() > 0)
        {
            aglAttrib[pos++] = AGL.AGL_STENCIL_SIZE;
            aglAttrib[pos++] = capabilities.getStencilBits();
        }

        if(capabilities.getAccumRedBits() > 0)
        {
            aglAttrib[pos++] = AGL.AGL_ACCUM_RED_SIZE;
            aglAttrib[pos++] = capabilities.getAccumRedBits();
        }

        if(capabilities.getAccumGreenBits() > 0)
        {
            aglAttrib[pos++] = AGL.AGL_ACCUM_GREEN_SIZE;
            aglAttrib[pos++] = capabilities.getAccumGreenBits();
        }

        if(capabilities.getAccumBlueBits() > 0)
        {
            aglAttrib[pos++] = AGL.AGL_ACCUM_BLUE_SIZE;
            aglAttrib[pos++] = capabilities.getAccumBlueBits();
        }

        if(capabilities.getAccumAlphaBits() > 0)
        {
            aglAttrib[pos++] = AGL.AGL_ACCUM_ALPHA_SIZE;
            aglAttrib[pos++] = capabilities.getAccumAlphaBits();
        }

        if(capabilities.getSampleBuffers())
        {
            aglAttrib[pos++] = AGL.AGL_SAMPLE_BUFFERS_ARB;
            aglAttrib[pos++] = AGL.AGL_SAMPLES_ARB;
            aglAttrib[pos++] = capabilities.getNumSamples();
        }

        if(floatingPoint)
            aglAttrib[pos++] = AGL.AGL_COLOR_FLOAT;

        if(pbuffer)
            aglAttrib[pos++] = AGL.AGL_OFFSCREEN;

/*
  These are not useful for onscreen contexts
        if(capabilities.getOffscreenFloatingPointBuffers())
            aglAttrib[pos++] = AGL.AGL_

        if(capabilities.getOffscreenRenderToTexture())
            aglAttrib[pos++] = AGL.AGL_

        if(capabilities.getOffscreenRenderToTextureRectangle())
            aglAttrib[pos++] = AGL.AGL_
*/

        aglAttrib [pos++] = AGL.AGL_NONE;
        pixelFormat = AGL.aglChoosePixelFormat(0, 0, aglAttrib, 0);

        aglContextID = AGL.aglCreateContext(pixelFormat, share);

        if(aglContextID == 0)
            throw new GLException("Error creating aglContextID");

        AGL.aglSetDrawable(aglContextID, drawable.getCarbonPort());

        GLContextShareSet.contextCreated(this);

        return true;
    }

    protected int makeCurrentImpl() throws GLException
    {
        boolean created = false;
        if(aglContextID == 0)
        {
            if(!create())
                return CONTEXT_NOT_CURRENT;

            if(DEBUG)
                System.err.println("!!! Created GL aglContextID for " + getClass().getName());

            created = true;
        }

        if(!AGL.aglSetCurrentContext(aglContextID))
            throw new GLException("Error making aglContextID current");

        if(created)
        {
            resetGLFunctionAvailability();
            return CONTEXT_CURRENT_NEW;
        }

        return CONTEXT_CURRENT;
    }

    protected void releaseImpl() throws GLException
    {
        // Pass a zero (NULL) value to release the current context
        if(!AGL.aglSetCurrentContext(0))
            throw new GLException("Error freeing OpenGL aglContextID");
    }

    protected synchronized void destroyImpl() throws GLException
    {
        if(aglContextID != 0)
        {
            if(!AGL.aglDestroyContext(aglContextID))
                throw new GLException("Unable to delete OpenGL context");

            if(DEBUG)
                System.err.println("!!! Destroyed OpenGL context " + aglContextID);

            GLContextShareSet.contextDestroyed(this);
            aglContextID = 0;

            AGL.aglDestroyPixelFormat(pixelFormat);
            pixelFormat = 0;
        }
    }

    public boolean isCreated()
    {
        return (aglContextID != 0);
    }

    public void copy(GLContext source, int mask)
        throws GLException
    {
        long src = ((MacGLContext)source).getAGLContextID();
        if(src == 0)
          throw new GLException("Source OpenGL context has not been created");

        if(aglContextID == 0)
          throw new GLException("Destination OpenGL context has not been created");

        AGL.aglCopyContext(aglContextID, src, mask);
    }

    protected void resetGLFunctionAvailability()
    {
        super.resetGLFunctionAvailability();
        if(DEBUG)
            System.err.println("!!! Initializing AGL extension address table");

        resetProcAddressTable(getAGLExtProcAddressTable());
    }

    public AGLExtProcAddressTable getAGLExtProcAddressTable()
    {
        if(aglExtProcAddressTable == null)
        {
            // FIXME: cache ProcAddressTables by capability bits so we can
            // share them among contexts with the same capabilities
            aglExtProcAddressTable = new AGLExtProcAddressTable();
        }

        return aglExtProcAddressTable;
    }

    public String getPlatformExtensionsString()
    {
        return "";
    }

    public void setSwapInterval(int interval)
    {
        if(aglContextID == 0)
            throw new GLException("OpenGL context not current");

        // TODO: Need to check on this behaviour. The swap behaviour seems to
        // be either to sync to the vertical retrace of the monitor or not.
        // The JOGL design requires setting a max frame rate using millisecond
        // timing I think.
        AGL.aglSetInteger(aglContextID,
                          AGL.AGL_SWAP_INTERVAL,
                          new int[] {interval},
                          0);
    }

    public ByteBuffer glAllocateMemoryNV(int arg0, float arg1, float arg2, float arg3)
    {
        // FIXME: apparently the Apple extension doesn't require a custom memory allocator
        throw new GLException("Not yet implemented");
    }

    public boolean isExtensionAvailable(String glExtensionName)
    {
        if(glExtensionName.equals("GL_ARB_pbuffer") ||
           glExtensionName.equals("GL_ARB_pixel_format"))
        {
            return true;
        }

        return super.isExtensionAvailable(glExtensionName);
    }

    public int getOffscreenContextPixelDataType()
    {
        throw new GLException("Should not call this");
    }

    public int getOffscreenContextReadBuffer()
    {
        throw new GLException("Should not call this");
    }

    public boolean offscreenImageNeedsVerticalFlip()
    {
        throw new GLException("Should not call this");
    }

    public void bindPbufferToTexture()
    {
        throw new GLException("Should not call this");
    }

    public void releasePbufferFromTexture()
    {
        throw new GLException("Should not call this");
    }

    //----------------------------------------------------------------------
    // Internals only below this point
    //

    protected long getAGLContextID()
    {
        return aglContextID;
    }
}
