/*
 * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * - Redistribution of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed or intended for use
 * in the design, construction, operation or maintenance of any nuclear
 * facility.
 *
 * Sun gratefully acknowledges that this software was originally authored
 * and developed by Kenneth Bradley Russell and Christopher John Kline.
 */
package org.j3d.opengl.swt.internal.ri.x1164;

// External imports
import javax.media.opengl.GLContext;
import javax.media.opengl.GLDrawable;
import javax.media.opengl.GLException;
import javax.media.opengl.GLDrawableFactory;

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

import com.sun.opengl.impl.GLContextImpl;
import com.sun.opengl.impl.GLContextShareSet;
import com.sun.opengl.impl.x11.XVisualInfo;
import com.sun.opengl.impl.x11.GLX;
import com.sun.opengl.impl.x11.GLXExt;
import com.sun.opengl.impl.x11.GLXExtImpl;
import com.sun.opengl.impl.x11.GLXExtProcAddressTable;

import org.eclipse.swt.internal.Platform;

// Local imports
// None

/**
 * Context for onscreen drawable objects in the X11 environment.
 *
 * @author Justin Couch
 * @version $Revision: 1.3 $
 */
public abstract class X11GLContext extends com.sun.opengl.impl.x11.X11GLContext
{
    /** Message when the fetched shared context is no longer valid */
    private static final String INVALID_SHARED_CONTEXT_MSG =
        "GLContextShareSet returned an invalid OpenGL context";

    /** The drawable that we interact with */
    protected X11GLDrawable drawable;

    /** The ID of the underlying native GL Context */
    protected long context;


    private boolean glXQueryExtensionsStringInitialized;
    private boolean glXQueryExtensionsStringAvailable;

    private static final Map functionNameMap;

    private GLXExt glXExt;

    /**
     * Table that holds the addresses of the native C-language entry points for
     * GLX extension functions.
     */
    private GLXExtProcAddressTable glXExtProcAddressTable;

    /**
     * Cache the most recent value of the "display" variable (which we
     * only guarantee to be valid in between makeCurrent / free pairs)
     * so that we can implement displayImpl() (which must be done when
     * the context is not current).
     */
    protected long mostRecentDisplay;

    /**
     * Static initializer to pre-populate the function name conversion table.
     */
    static
    {
        functionNameMap = new HashMap();
        functionNameMap.put("glAllocateMemoryNV", "glXAllocateMemoryNV");
    }

    /**
     * Create a new abstract base GL Context instance.
     *
     * @param drawable The drawable associated with this context
     * @param shareWith The optional context to share data with
     */
    public X11GLContext(X11GLDrawable drawable, GLContext shareWith)
    {
        // NOTE:
        // Always set the dontShareWithJava2D flag to true because we are
        // running in the SWT environment. Normally Java2D will not be running
        // at all here. If it is, something oddball could be going on, so just
        // avoid any possibility of causing issues.
        super(shareWith, X11GLXUtils.getLookupHelper(), true);
        this.drawable = drawable;
    }

    //---------------------------------------------------------------
    // Methods defined by GLContext
    //---------------------------------------------------------------

    /**
     * Returns the GLDrawable to which this context may be used to draw.
     *
     * @return The associated drawable instance
     */
    public GLDrawable getGLDrawable()
    {
        return drawable;
    }

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

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

    /**
     * Maps the given "platform-independent" function name to a real function
     * name. Currently this is only used to map "glAllocateMemoryNV" and
     * associated routines to wglAllocateMemoryNV / glXAllocateMemoryNV.
     */
    protected String mapToRealGLFunctionName(String glFunctionName)
    {
        String lookup = (String)functionNameMap.get(glFunctionName);

        if(lookup != null)
            return lookup;

        return glFunctionName;
    }

    /**
     * Maps the given "platform-independent" extension name to a real function
     * name.
     */
    protected String mapToRealGLExtensionName(String glExtensionName)
    {
        return glExtensionName;
    }

    /**
     * Internal implementation of the makeCurrent call.
     */
    protected int makeCurrentImpl() throws GLException
    {
        // FIXME: in offscreen (non-pbuffer) case this is run without the
        // AWT lock held
        boolean created = false;

        if(context == 0)
        {
            create();
            created = true;
        }

        if(context != 0 &&
           !GLX.glXMakeCurrent(drawable.getDisplay(),
                               drawable.getDrawable(),
                               context))
        {
            throw new GLException("Error making context current");
        }
        else
        {
            mostRecentDisplay = drawable.getDisplay();
        }

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

        return CONTEXT_CURRENT;
    }

    /**
     * Release the current GL context so that it is no longer current.
     */
    protected void releaseImpl() throws GLException
    {
        Platform.lock.lock();

        if(!GLX.glXMakeCurrent(drawable.getDisplay(), 0, 0))
            throw new GLException("Error freeing OpenGL context");

        Platform.lock.unlock();
    }

    /**
     * Free the current GL context so that it is no longer usable.
     */
    protected void destroyImpl() throws GLException
    {
        if(context != 0)
        {
            try
            {
                Platform.lock.lock();
                GLX.glXDestroyContext(mostRecentDisplay, context);
                context = 0;
                mostRecentDisplay = 0;
                GLContextShareSet.contextDestroyed(this);
            }
            finally
            {
                Platform.lock.unlock();
            }
        }
    }

    /**
     * Indicates whether the underlying OpenGL context has been created. This
     * is used to manage sharing of display lists and textures between
     * contexts.
     */
    public boolean isCreated()
    {
        return (context != 0);
    }

    public void copy(GLContext source, int mask) throws GLException
    {
        long dst = getContext();
        long src = ((X11GLContext) source).getContext();

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

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

        if(mostRecentDisplay == 0)
          throw new GLException("Connection to X display not yet set up");

        Platform.lock.lock();

        try
        {
          GLX.glXCopyContext(mostRecentDisplay, src, dst, mask);
          // Should check for X errors and raise GLException
        }
        finally
        {
            Platform.lock.unlock();
        }
    }

    /**
     * Resets the cache of which GL functions are available for calling through
     * this context. See {@link #isFunctionAvailable(String)} for more
     * information on the definition of "available".
     */
    protected void resetGLFunctionAvailability()
    {
        super.resetGLFunctionAvailability();

        resetProcAddressTable(getGLXExtProcAddressTable());
    }

    /**
     * Returns true if the specified OpenGL extension can be
     * successfully called using this GL context given the current host (OpenGL
     * <i>client</i>) and display (OpenGL <i>server</i>) configuration.
     *
     * See {@link GL#isExtensionAvailable(String)} for more details.
     *
     * @param glExtensionName the name of the OpenGL extension (e.g.,
     * "GL_VERTEX_PROGRAM_ARB").
     */
    public boolean isExtensionAvailable(String glExtensionName)
    {
        if (glExtensionName.equals("GL_ARB_pbuffer") ||
                glExtensionName.equals("GL_ARB_pixel_format"))
        {
            return GLDrawableFactory.getFactory().canCreateGLPbuffer();
        }

        return super.isExtensionAvailable(glExtensionName);
    }


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

        return glXExtProcAddressTable;
    }

    public synchronized String getPlatformExtensionsString()
    {
        if(drawable.getDisplay() == 0)
            throw new GLException("Context not current");

        if(!glXQueryExtensionsStringInitialized)
        {
            glXQueryExtensionsStringAvailable =
                (lookupHelper.dynamicLookupFunction("glXQueryExtensionsString") != 0);
            glXQueryExtensionsStringInitialized = true;
        }

        if(glXQueryExtensionsStringAvailable)
        {
            Platform.lock.lock();
            try
            {
                long display = drawable.getDisplay();
                return GLX.glXQueryExtensionsString(display,
                                                    GLX.DefaultScreen(display));
            }
            finally
            {
                Platform.lock.unlock();
            }
        }
        else
        {
            return "";
        }
    }

    /**
     * Sets the swap interval for onscreen OpenGL contexts. Has no
     * effect for offscreen contexts.
     */
    public void setSwapInterval(int interval)
    {
        Platform.lock.lock();
        try
        {
            // FIXME: make the context current first? Currently assumes that
            // will not be necessary. Make the caller do this?
            GLXExt glXExt = getGLXExt();

            if(glXExt.isExtensionAvailable("GLX_SGI_swap_control"))
                glXExt.glXSwapIntervalSGI(interval);
        }
        finally
        {
            Platform.lock.unlock();
        }
    }

    public ByteBuffer glAllocateMemoryNV(int arg0,
                                         float arg1,
                                         float arg2,
                                         float arg3)
    {
        return getGLXExt().glXAllocateMemoryNV(arg0, arg1, arg2, arg3);
    }

    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");
    }

    /**
     * Pbuffer support; given that this is a GLContext associated with a
     * pbuffer, binds this pbuffer to its texture target.
     */
    public void bindPbufferToTexture()
    {
        throw new GLException("Should not call this");
    }

    /**
     * Pbuffer support; given that this is a GLContext associated with a
     * pbuffer, releases this pbuffer from its texture target.
     */
    public void releasePbufferFromTexture()
    {
        throw new GLException("Should not call this");
    }

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

    /**
     * Helper routine which usually just turns around and calls
     * createContext (except for pbuffers, which use a different context
     * creation mechanism). Should only be called by {@link
     * makeCurrentImpl()}.
     */
    protected abstract void create();

    /**
     * Creates and initializes an appropriate OpenGL context. Should only be
     * called by {@link create()}.
     *
     * @param onscreen True if this is for an onscreen context, false otherwise
     */
    protected void createContext(boolean onscreen)
    {
        XVisualInfo vis = drawable.chooseVisual(onscreen);
        X11GLContext other =
            (X11GLContext)GLContextShareSet.getShareContext(this);
        long share = 0;

        if(other != null)
        {
            share = other.getContext();
            if(share == 0)
                throw new GLException(INVALID_SHARED_CONTEXT_MSG);
        }

        context = GLX.glXCreateContext(drawable.getDisplay(),
                                       vis,
                                       share,
                                       onscreen);

        if(context == 0)
            throw new GLException("Unable to create OpenGL context");

        GLContextShareSet.contextCreated(this);
    }

    public GLXExt getGLXExt()
    {
        if(glXExt == null)
            glXExt = new GLXExtImpl(this);

        return glXExt;

    }

    /**
     * Get the X11 ID of the context wrapped by this class
     */
    protected long getContext()
    {
        return context;
    }
}
