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

// External imports
import java.security.*;
import com.sun.gluegen.runtime.*;

import com.sun.opengl.impl.x11.GLX;
import com.sun.opengl.impl.x11.GLXExt;
import com.sun.opengl.impl.x11.XVisualInfo;

import javax.media.opengl.GL;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLException;

import org.eclipse.swt.internal.Platform;

// Local Imports
// None

/**
 * Internal convenience methods for the X11 environment combined into a single
 * class.
 *
 * @author Justin Couch
 * @version $Revision: 1.4 $
 */
class X11GLXUtils implements DynamicLookupHelper
{
    /** Error message when the attempt to fetch the default display fails */
    private static final String NO_DEFAULT_DISPLAY_MSG =
        "Unable to open default display, needed for visual selection and " +
        "offscreen surface handling";

    /** The maximum number of attributes that could be created */
    private static final int MAX_ATTRIBUTES = 32;

    /** The shared instance of us */
    private static X11GLXUtils instance;

    /** Indicator if this is a Linux box running AMD64 CPU */
    private static boolean isLinuxAMD64;

    /** Flag indicating if multisample capabilities have been checked yet */
    private static boolean checkedMultisample;

    /** Flag for if multisample is available for use by applications */
    private static boolean multisampleAvailable;

    /**
     * Display connection for use by visual selection algorithm and by all
     * offscreen surfaces.
     */
    private static long staticDisplay;

    /** Temporary array used in glXGetConfig() */
    private static int[] tmp;

    /**
     * Static initializer to setup the variables
     */
    static
    {
        tmp = new int[1];
        checkedMultisample = false;
        multisampleAvailable = false;

        AccessController.doPrivileged(new PrivilegedAction()
        {
            public Object run()
            {
                String os   = System.getProperty("os.name").toLowerCase();
                String arch = System.getProperty("os.arch").toLowerCase();

                if(os.startsWith("linux") && arch.equals("amd64"))
                    isLinuxAMD64 = true;

                return null;
            }
        });
    }

    /**
     * Private constructor to prevent direct instantiation.
     */
    private X11GLXUtils()
    {
    }

    //---------------------------------------------------------------
    // Methods defined by DynamicLookupHelper
    //---------------------------------------------------------------

    /**
     * Dynamically looks up the given function.
     */
    public long dynamicLookupFunction(String glFuncName)
    {
        long res = 0;
        if(!isLinuxAMD64)
        {
            res = GLX.glXGetProcAddressARB(glFuncName);
        }

        if (res == 0)
        {
            // GLU routines aren't known to the OpenGL function lookup
            res = GLX.dlsym(glFuncName);
        }

        return res;
    }

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

    /**
     * Fetch the shared instance of the lookup helper
     */
    public static DynamicLookupHelper getLookupHelper()
    {
        if(instance == null)
          instance = new X11GLXUtils();

        return instance;
    }

    /**
     * Convert an XVisualInfo object into a GLCapabilities object.
     *
     * @param display The X display ID
     * @aram info The info instance to grab capabilities from
     * @return A new capabilities object with the visual info data
     */
    static GLCapabilities xvi2GLCapabilities(long display, XVisualInfo info)
    {
        int val = glXGetConfig(display, info, GLX.GLX_USE_GL);

        // Visual does not support OpenGL
        if(val == 0)
            return null;

        val = glXGetConfig(display, info, GLX.GLX_RGBA);
        // Visual does not support RGBA
        if(val == 0)
            return null;

        GLCapabilities res = new GLCapabilities();
        // Note: use of hardware acceleration is determined by
        // glXCreateContext, not by the XVisualInfo. Optimistically claim
        // that all GLCapabilities have the capability to be hardware
        // accelerated.
        res.setHardwareAccelerated(true);
        res.setDoubleBuffered(glXGetConfig(display, info, GLX.GLX_DOUBLEBUFFER) != 0);
        res.setStereo(glXGetConfig(display, info, GLX.GLX_STEREO) != 0);
        res.setDepthBits(glXGetConfig(display, info, GLX.GLX_DEPTH_SIZE));
        res.setStencilBits(glXGetConfig(display, info, GLX.GLX_STENCIL_SIZE));
        res.setRedBits(glXGetConfig(display, info, GLX.GLX_RED_SIZE));
        res.setGreenBits(glXGetConfig(display, info, GLX.GLX_GREEN_SIZE));
        res.setBlueBits(glXGetConfig(display, info, GLX.GLX_BLUE_SIZE));
        res.setAlphaBits(glXGetConfig(display, info, GLX.GLX_ALPHA_SIZE));
        res.setAccumRedBits(glXGetConfig(display, info, GLX.GLX_ACCUM_RED_SIZE));
        res.setAccumGreenBits(glXGetConfig(display, info, GLX.GLX_ACCUM_GREEN_SIZE));
        res.setAccumBlueBits(glXGetConfig(display, info, GLX.GLX_ACCUM_BLUE_SIZE));
        res.setAccumAlphaBits(glXGetConfig(display, info, GLX.GLX_ACCUM_ALPHA_SIZE));

        if(isMultisampleAvailable())
        {
            res.setSampleBuffers(glXGetConfig(display, info, GLX.GLX_SAMPLE_BUFFERS_ARB) != 0);
            res.setNumSamples(glXGetConfig(display, info, GLX.GLX_SAMPLES_ARB));
        }

        return res;
    }

    static int[] glCapabilities2AttribList(GLCapabilities caps,
                                           boolean isMultisampleAvailable,
                                           boolean pbuffer,
                                           long display,
                                           int screen)
    {
        int colorDepth = (caps.getRedBits() +
                          caps.getGreenBits() +
                          caps.getBlueBits());
        if(colorDepth < 15)
            throw new GLException("Bit depths < 15 (i.e., non-true-color) not supported");

        int[] res = new int[MAX_ATTRIBUTES];
        int idx = 0;
        if(pbuffer)
        {
          res[idx++] = GLXExt.GLX_DRAWABLE_TYPE;
          res[idx++] = GLXExt.GLX_PBUFFER_BIT;

          res[idx++] = GLXExt.GLX_RENDER_TYPE;
          res[idx++] = GLXExt.GLX_RGBA_BIT;
        }
        else
        {
            res[idx++] = GLX.GLX_RGBA;
        }

        res[idx++] = GLX.GLX_DOUBLEBUFFER;
        res[idx++] = caps.getDoubleBuffered() ? GL.GL_TRUE : GL.GL_FALSE;

        if(caps.getStereo())
            res[idx++] = GLX.GLX_STEREO;

        res[idx++] = GLX.GLX_RED_SIZE;
        res[idx++] = caps.getRedBits();
        res[idx++] = GLX.GLX_GREEN_SIZE;
        res[idx++] = caps.getGreenBits();
        res[idx++] = GLX.GLX_BLUE_SIZE;
        res[idx++] = caps.getBlueBits();
        res[idx++] = GLX.GLX_ALPHA_SIZE;
        res[idx++] = caps.getAlphaBits();
        res[idx++] = GLX.GLX_DEPTH_SIZE;
        res[idx++] = caps.getDepthBits();

        if (caps.getStencilBits() > 0)
        {
            res[idx++] = GLX.GLX_STENCIL_SIZE;
            res[idx++] = caps.getStencilBits();
        }

        if (caps.getAccumRedBits()   > 0 ||
            caps.getAccumGreenBits() > 0 ||
            caps.getAccumBlueBits()  > 0 ||
            caps.getAccumAlphaBits() > 0)
        {
            res[idx++] = GLX.GLX_ACCUM_RED_SIZE;
            res[idx++] = caps.getAccumRedBits();
            res[idx++] = GLX.GLX_ACCUM_GREEN_SIZE;
            res[idx++] = caps.getAccumGreenBits();
            res[idx++] = GLX.GLX_ACCUM_BLUE_SIZE;
            res[idx++] = caps.getAccumBlueBits();
            res[idx++] = GLX.GLX_ACCUM_ALPHA_SIZE;
            res[idx++] = caps.getAccumAlphaBits();
        }

        if(isMultisampleAvailable && caps.getSampleBuffers())
        {
            res[idx++] = GLXExt.GLX_SAMPLE_BUFFERS_ARB;
            res[idx++] = GL.GL_TRUE;
            res[idx++] = GLXExt.GLX_SAMPLES_ARB;
            res[idx++] = caps.getNumSamples();
        }

        if(pbuffer)
        {
            if(caps.getPbufferFloatingPointBuffers())
            {
                String glXExtensions = GLX.glXQueryExtensionsString(display, screen);
                if(glXExtensions == null ||
                    glXExtensions.indexOf("GLX_NV_float_buffer") < 0)
                    throw new GLException("Floating-point pbuffers on X11 currently require NVidia hardware");

                res[idx++] = GLX.GLX_FLOAT_COMPONENTS_NV;
                res[idx++] = GL.GL_TRUE;
            }
        }

        res[idx++] = 0;

        return res;
    }

    /**
     * Get the default display connecion. Used for reading visual
     * selection information during the configuration process.
     *
     * @return The X ID of the default display
     */
    static long getDisplayConnection()
    {
        Platform.lock.lock();
        try
        {
            staticDisplay = GLX.XOpenDisplay(null);

            if(staticDisplay == 0)
                throw new GLException(NO_DEFAULT_DISPLAY_MSG);

            return staticDisplay;
        }
        finally
        {
            Platform.lock.unlock();
        }
    }

    /**
     * Check to see whether multisample capabilities are available for the
     * generic case.
     *
     * @return true if multisamples can be used
     */
    static boolean isMultisampleAvailable()
    {
        if(!checkedMultisample)
        {
            long display = getDisplayConnection();
            String exts = GLX.glXGetClientString(display, GLX.GLX_EXTENSIONS);
            if(exts != null)
                multisampleAvailable =
                    (exts.indexOf("GLX_ARB_multisample") >= 0);

            checkedMultisample = true;
        }

        return multisampleAvailable;
    }

    /**
     * Convert a GLX error code into a useful string.
     *
     * @param err The error code to convert
     * @return A readable string error message
     */
    static String glXGetConfigErrorCode(int err)
    {
        switch (err)
        {
            case GLX.GLX_NO_EXTENSION:
                return "GLX_NO_EXTENSION";

            case GLX.GLX_BAD_SCREEN:
                return "GLX_BAD_SCREEN";

            case GLX.GLX_BAD_ATTRIBUTE:
                return "GLX_BAD_ATTRIBUTE";

            case GLX.GLX_BAD_VISUAL:
                return "GLX_BAD_VISUAL";

            default:
                return "Unknown error code " + err;
        }
    }

    /**
     * Convenience wrapper around the glXGetConfig() call.
     *
     * @param display The X display ID to get the config for
     * @param info The visual info about that display
     * @param attrib The ID of the config attribute to fetch
     * @return The value of the attribute
     * @throws GLException The display ID is zero or the call failed
     */
    static int glXGetConfig(long display, XVisualInfo info, int attrib)
        throws GLException
    {
        if(display == 0)
            throw new GLException("No display connection");

        int res = GLX.glXGetConfig(display, info, attrib, tmp, 0);

        if(res != 0)
          throw new GLException("glXGetConfig() failed: error code " +
                                 glXGetConfigErrorCode(res));

        return tmp[0];
    }

    /**
     * Convenience method to check attributes in a GLCapabilities within a
     * visual.
     *
     * @param display The X display ID to get the config for
     * @param info The visual info about that display
     * @param attribs The list of attributes to check against
     */
    static void checkCapabilities(long display, XVisualInfo info, int[] attribs)
    {
        if(display == 0)
            throw new GLException("No display connection");

        for(int i = 0; i < attribs.length; i++)
        {
            if(attribs[i] == 0)
                break;

            int res = GLX.glXGetConfig(display, info, attribs[i], tmp, 0);

            if(res != 0)
              System.out.println("glXGetConfig() failed: error code " +
                                 glXGetConfigErrorCode(res));
        }
    }
}
