/*
 * Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved.
 * Copyright (c) 2010 JogAmp Community. 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.
 */

package jogamp.opengl.x11.glx;

import com.jogamp.nativewindow.AbstractGraphicsConfiguration;
import com.jogamp.nativewindow.AbstractGraphicsDevice;
import com.jogamp.nativewindow.AbstractGraphicsScreen;
import com.jogamp.nativewindow.CapabilitiesChooser;
import com.jogamp.nativewindow.CapabilitiesFilter;
import com.jogamp.nativewindow.CapabilitiesImmutable;
import com.jogamp.nativewindow.GraphicsConfigurationFactory;
import com.jogamp.nativewindow.VisualIDHolder;
import com.jogamp.nativewindow.VisualIDHolder.VIDType;
import com.jogamp.opengl.DefaultGLCapabilitiesChooser;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLCapabilitiesChooser;
import com.jogamp.opengl.GLCapabilitiesFilter;
import com.jogamp.opengl.GLCapabilitiesImmutable;
import com.jogamp.opengl.GLDrawableFactory;
import com.jogamp.opengl.GLException;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.GLRendererQuirks;
import com.jogamp.common.ExceptionUtils;
import com.jogamp.common.nio.Buffers;
import com.jogamp.common.nio.PointerBuffer;
import com.jogamp.nativewindow.x11.X11GraphicsDevice;
import com.jogamp.nativewindow.x11.X11GraphicsScreen;

import jogamp.nativewindow.x11.X11Lib;
import jogamp.nativewindow.x11.XVisualInfo;
import jogamp.opengl.GLGraphicsConfigurationFactory;
import jogamp.opengl.GLGraphicsConfigurationUtil;

import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


/** Subclass of GraphicsConfigurationFactory used when non-AWT toolkits
    are used on X11 platforms. Toolkits will likely need to delegate
    to this one to change the accepted and returned types of the
    GraphicsDevice and GraphicsConfiguration abstractions. */

public class X11GLXGraphicsConfigurationFactory extends GLGraphicsConfigurationFactory {
    static VisualIDHolder.VIDComparator XVisualIDComparator = new VisualIDHolder.VIDComparator(VisualIDHolder.VIDType.X11_XVISUAL);

    static GraphicsConfigurationFactory fallbackX11GraphicsConfigurationFactory = null;
    static void registerFactory() {
        final GraphicsConfigurationFactory newFactory = new X11GLXGraphicsConfigurationFactory();
        final GraphicsConfigurationFactory oldFactory = GraphicsConfigurationFactory.registerFactory(com.jogamp.nativewindow.x11.X11GraphicsDevice.class, GLCapabilitiesImmutable.class, newFactory);
        if(oldFactory == newFactory) {
            throw new InternalError("GraphicsConfigurationFactory lifecycle impl. error");
        }
        if(null != oldFactory) {
            fallbackX11GraphicsConfigurationFactory = oldFactory;
        } else {
            fallbackX11GraphicsConfigurationFactory = GraphicsConfigurationFactory.getFactory(com.jogamp.nativewindow.x11.X11GraphicsDevice.class, CapabilitiesImmutable.class);
            if( null == fallbackX11GraphicsConfigurationFactory ) {
                throw new InternalError("Missing fallback GraphicsConfigurationFactory");
            }
        }
    }
    private X11GLXGraphicsConfigurationFactory() {
    }

    @Override
    protected AbstractGraphicsConfiguration chooseGraphicsConfigurationImpl(
            final CapabilitiesImmutable capsChosen, final CapabilitiesImmutable capsRequested, final CapabilitiesChooser chooser, final AbstractGraphicsScreen absScreen, final int nativeVisualID) {
        if (!(absScreen instanceof X11GraphicsScreen)) {
            throw new IllegalArgumentException("Only X11GraphicsScreen are allowed here");
        }

        if ( !(capsChosen instanceof GLCapabilitiesImmutable) ) {
            throw new IllegalArgumentException("This NativeWindowFactory accepts only GLCapabilities objects - chosen");
        }

        if ( !(capsRequested instanceof GLCapabilitiesImmutable)) {
            throw new IllegalArgumentException("This NativeWindowFactory accepts only GLCapabilities objects - requested");
        }

        if (chooser != null && !(chooser instanceof GLCapabilitiesChooser)) {
            throw new IllegalArgumentException("This NativeWindowFactory accepts only GLCapabilitiesChooser objects");
        }

        if(!GLXUtil.isGLXAvailableOnServer((X11GraphicsDevice)absScreen.getDevice())) {
            if(null != fallbackX11GraphicsConfigurationFactory) {
                if(DEBUG) {
                    System.err.println("No GLX available, fallback to "+fallbackX11GraphicsConfigurationFactory.getClass().getSimpleName()+" for: "+absScreen);
                }
                return fallbackX11GraphicsConfigurationFactory.chooseGraphicsConfiguration(capsChosen, capsRequested, chooser, absScreen, VisualIDHolder.VID_UNDEFINED);
            }
            throw new InternalError("No GLX and no fallback GraphicsConfigurationFactory available for: "+absScreen);
        }
        return chooseGraphicsConfigurationStatic((GLCapabilitiesImmutable)capsChosen, (GLCapabilitiesImmutable)capsRequested,
                                                 (GLCapabilitiesChooser)chooser, (X11GraphicsScreen)absScreen, nativeVisualID);
    }

    protected static List<GLCapabilitiesImmutable> getAvailableCapabilities(final X11GLXDrawableFactory factory, final AbstractGraphicsDevice device) {
        final X11GLXDrawableFactory.SharedResource sharedResource = factory.getOrCreateSharedResourceImpl(device);
        if(null == sharedResource) {
            throw new GLException("Shared resource for device n/a: "+device);
        }
        final X11GraphicsScreen sharedScreen = (X11GraphicsScreen) sharedResource.getScreen();
        final X11GraphicsDevice sharedDevice = (X11GraphicsDevice) sharedScreen.getDevice();
        final boolean isMultisampleAvailable = sharedResource.isGLXMultisampleAvailable();
        final GLProfile glp = GLProfile.getDefault(device);

        List<GLCapabilitiesImmutable> availableCaps = null;

        sharedDevice.lock();
        try {
            if( sharedResource.isGLXVersionGreaterEqualOneThree() ) {
                availableCaps = getAvailableGLCapabilitiesFBConfig(sharedScreen, glp, isMultisampleAvailable);
            }
            if( null == availableCaps || availableCaps.isEmpty() ) {
                availableCaps = getAvailableGLCapabilitiesXVisual(sharedScreen, glp, isMultisampleAvailable);
            }
        } finally {
            sharedDevice.unlock();
        }
        if( null != availableCaps && availableCaps.size() > 1 ) {
            Collections.sort(availableCaps, XVisualIDComparator);
        }
        return availableCaps;
    }

    static List<GLCapabilitiesImmutable> getAvailableGLCapabilitiesFBConfig(final X11GraphicsScreen x11Screen, final GLProfile glProfile, final boolean isMultisampleAvailable) {
        PointerBuffer fbcfgsL = null;

        // Utilizing FBConfig
        //
        final X11GraphicsDevice absDevice = (X11GraphicsDevice) x11Screen.getDevice();
        final long display = absDevice.getHandle();

        final int screen = x11Screen.getIndex();
        final IntBuffer count = Buffers.newDirectIntBuffer(1);
        count.put(0, -1);
        final ArrayList<GLCapabilitiesImmutable> availableCaps = new ArrayList<GLCapabilitiesImmutable>();

        fbcfgsL = GLX.glXChooseFBConfig(display, screen, null, count);
        if (fbcfgsL == null || fbcfgsL.limit()<=0) {
            if(DEBUG) {
                System.err.println("X11GLXGraphicsConfiguration.getAvailableGLCapabilitiesFBConfig: Failed glXChooseFBConfig ("+x11Screen+"): "+fbcfgsL+", "+count.get(0));
            }
            return null;
        }
        for (int i = 0; i < fbcfgsL.limit(); i++) {
            final GLCapabilitiesImmutable caps = X11GLXGraphicsConfiguration.GLXFBConfig2GLCapabilities(absDevice, glProfile, fbcfgsL.get(i), GLGraphicsConfigurationUtil.ALL_BITS, isMultisampleAvailable);
            if(null != caps) {
                availableCaps.add(caps);
            } else if(DEBUG) {
                System.err.println("X11GLXGraphicsConfiguration.getAvailableGLCapabilitiesFBConfig: FBConfig invalid (2): ("+x11Screen+"): fbcfg: "+toHexString(fbcfgsL.get(i)));
            }
        }
        return availableCaps;
    }

    static List<GLCapabilitiesImmutable> getAvailableGLCapabilitiesXVisual(final X11GraphicsScreen x11Screen, final GLProfile glProfile, final boolean isMultisampleAvailable) {
        final X11GraphicsDevice absDevice = (X11GraphicsDevice) x11Screen.getDevice();
        final long display = absDevice.getHandle();

        final int screen = x11Screen.getIndex();

        final int[] count = new int[1];
        final XVisualInfo template = XVisualInfo.create();
        template.setScreen(screen);
        final XVisualInfo[] infos = X11Lib.XGetVisualInfo(display, X11Lib.VisualScreenMask, template, count, 0);
        if (infos == null || infos.length<1) {
            throw new GLException("Error while enumerating available XVisualInfos");
        }
        final ArrayList<GLCapabilitiesImmutable> availableCaps = new ArrayList<GLCapabilitiesImmutable>();
        for (int i = 0; i < infos.length; i++) {
            final GLCapabilitiesImmutable caps = X11GLXGraphicsConfiguration.XVisualInfo2GLCapabilities(absDevice, glProfile, infos[i], GLGraphicsConfigurationUtil.ALL_BITS, isMultisampleAvailable);
            if(null != caps) {
                availableCaps.add(caps);
            } if(DEBUG) {
                System.err.println("X11GLXGraphicsConfiguration.getAvailableGLCapabilitiesXVisual: XVisual invalid: ("+x11Screen+"): fbcfg: "+toHexString(infos[i].getVisualid()));
            }
        }
        return availableCaps;
    }


    static X11GLXGraphicsConfiguration chooseGraphicsConfigurationStatic(GLCapabilitiesImmutable capsChosen,
                                                                         final GLCapabilitiesImmutable capsReq,
                                                                         final GLCapabilitiesChooser chooser,
                                                                         final X11GraphicsScreen x11Screen, final int xvisualID) {
        if (x11Screen == null) {
            throw new IllegalArgumentException("AbstractGraphicsScreen is null");
        }
        if (capsChosen == null) {
            capsChosen = new GLCapabilities(null);
        }
        final X11GraphicsDevice x11Device = (X11GraphicsDevice) x11Screen.getDevice();
        final X11GLXDrawableFactory factory = (X11GLXDrawableFactory) GLDrawableFactory.getDesktopFactory();

        capsChosen = GLGraphicsConfigurationUtil.fixGLCapabilities( capsChosen, factory, x11Device);
        final boolean usePBuffer = !capsChosen.isOnscreen() && capsChosen.isPBuffer();

        X11GLXGraphicsConfiguration res = null;
        x11Device.lock();
        try {
            if( factory.isGLXVersionGreaterEqualOneThree(x11Device) ) {
                res = chooseGraphicsConfigurationFBConfig(capsChosen, capsReq, chooser, x11Screen, xvisualID);
            }
            if(null==res) {
                if(usePBuffer) {
                    throw new GLException("Error: Couldn't create X11GLXGraphicsConfiguration based on FBConfig for visualID "+toHexString(xvisualID)+", "+capsChosen);
                }
                res = chooseGraphicsConfigurationXVisual(capsChosen, capsReq, chooser, x11Screen, xvisualID);
            }
        } finally {
            x11Device.unlock();
        }
        if(null==res) {
            throw new GLException("Error: Couldn't create X11GLXGraphicsConfiguration based on FBConfig and XVisual for visualID "+toHexString(xvisualID)+", "+x11Screen+", "+capsChosen);
        }
        if(DEBUG) {
            System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationStatic(visualID "+toHexString(xvisualID)+", "+x11Screen+","+capsChosen+"): "+res);
        }
        return res;
    }

    static X11GLXGraphicsConfiguration fetchGraphicsConfigurationFBConfig(final X11GraphicsScreen x11Screen, final int fbID, final GLProfile glp) {
        final X11GraphicsDevice x11Device = (X11GraphicsDevice) x11Screen.getDevice();
        final long display = x11Device.getHandle();
        final int screen = x11Screen.getIndex();

        final long fbcfg = X11GLXGraphicsConfiguration.glXFBConfigID2FBConfig(display, screen, fbID);
        if( 0 == fbcfg || !X11GLXGraphicsConfiguration.GLXFBConfigValid( display, fbcfg ) ) {
            if(DEBUG) {
                System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationFBConfig: Failed.0 - GLX FBConfig invalid: ("+x11Screen+","+toHexString(fbID)+"): fbcfg: "+toHexString(fbcfg));
            }
            return null;
        }
        final X11GLXDrawableFactory factory = (X11GLXDrawableFactory) GLDrawableFactory.getDesktopFactory();

        final X11GLCapabilities caps = X11GLXGraphicsConfiguration.GLXFBConfig2GLCapabilities(x11Device, glp, fbcfg, GLGraphicsConfigurationUtil.ALL_BITS, factory.isGLXMultisampleAvailable(x11Device));
        if(null==caps) {
            if(DEBUG) {
                System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationFBConfig: Failed.1 - GLX FBConfig invalid: ("+x11Screen+","+toHexString(fbID)+"): fbcfg: "+toHexString(fbcfg));
            }
            return null;
        }
        return new X11GLXGraphicsConfiguration(x11Screen, caps, caps, new DefaultGLCapabilitiesChooser());
    }

    private static X11GLXGraphicsConfiguration chooseGraphicsConfigurationFBConfig(final GLCapabilitiesImmutable capsChosen,
                                                                                   final GLCapabilitiesImmutable capsReq,
                                                                                   final GLCapabilitiesChooser chooser,
                                                                                   final X11GraphicsScreen x11Screen, final int xvisualID) {
        int recommendedIndex = -1;
        PointerBuffer fbcfgsL = null;
        final GLProfile glProfile = capsChosen.getGLProfile();

        // Utilizing FBConfig
        //
        final X11GraphicsDevice x11Device = (X11GraphicsDevice) x11Screen.getDevice();
        final long display = x11Device.getHandle();
        final int screen = x11Screen.getIndex();

        final X11GLXDrawableFactory factory = (X11GLXDrawableFactory) GLDrawableFactory.getDesktopFactory();
        final boolean isMultisampleAvailable = factory.isGLXMultisampleAvailable(x11Device);
        final IntBuffer attribs = X11GLXGraphicsConfiguration.GLCapabilities2AttribList(capsChosen, true, isMultisampleAvailable, display, screen);
        final IntBuffer count = Buffers.newDirectIntBuffer(1);
        count.put(0, -1);
        final int winattrmask = GLGraphicsConfigurationUtil.getExclusiveWinAttributeBits(capsChosen);
        final GLRendererQuirks glrq = factory.getRendererQuirks(x11Device, glProfile);
        ArrayList<GLCapabilitiesImmutable> availableCaps;
        // 1st choice: get GLCapabilities based on users GLCapabilities setting recommendedIndex as preferred choice,
        // skipped if xvisualID is given
        final boolean hasGLXChosenCaps;
        if( VisualIDHolder.VID_UNDEFINED == xvisualID ) {
            fbcfgsL = GLX.glXChooseFBConfig(display, screen, attribs, count);
            hasGLXChosenCaps = fbcfgsL != null && fbcfgsL.limit()>0;
        } else {
            hasGLXChosenCaps = false;
        }
        final boolean isPBufferOrBitmap = capsChosen.isBitmap() || capsChosen.isPBuffer();
        final boolean dontChooseFBConfigMatch = GLRendererQuirks.exist(glrq, GLRendererQuirks.DontChooseFBConfigBestMatch) ||
                                                ( isPBufferOrBitmap && GLRendererQuirks.exist(glrq, GLRendererQuirks.No10BitColorCompOffscreen) );
        final boolean useRecommendedIndex = !dontChooseFBConfigMatch &&
                                            hasGLXChosenCaps && capsChosen.isBackgroundOpaque();
        final boolean shallSkipCapsChooser = null == chooser && useRecommendedIndex;
        if (hasGLXChosenCaps) {
            availableCaps = X11GLXGraphicsConfiguration.GLXFBConfig2GLCapabilities(x11Device, glProfile, fbcfgsL, winattrmask, isMultisampleAvailable, shallSkipCapsChooser /* onlyFirstValid */);
            if(availableCaps.size() > 0) {
                recommendedIndex = useRecommendedIndex ? 0 : -1;
                if (DEBUG) {
                    System.err.println("glXChooseFBConfig recommended fbcfg " + toHexString(fbcfgsL.get(0)) + ", idx " + recommendedIndex);
                    System.err.println("useRecommendedIndex "+useRecommendedIndex+", shallSkipCapsChooser "+shallSkipCapsChooser);
                    System.err.println("user  caps " + capsChosen);
                    System.err.println("fbcfg caps " + fbcfgsL.limit()+", availCaps "+availableCaps.get(0));
                }
            } else if (DEBUG) {
                System.err.println("glXChooseFBConfig no caps for recommended fbcfg " + toHexString(fbcfgsL.get(0)));
                System.err.println("useRecommendedIndex "+useRecommendedIndex+", shallSkipCapsChooser "+shallSkipCapsChooser);
                System.err.println("user  caps " + capsChosen);
            }
        } else {
            availableCaps = new ArrayList<GLCapabilitiesImmutable>();
        }

        // 2nd choice: get all GLCapabilities available, no preferred recommendedIndex available
        if( 0 == availableCaps.size() ) {
            // reset ..
            recommendedIndex = -1;

            fbcfgsL = GLX.glXChooseFBConfig(display, screen, null, count);
            if (fbcfgsL == null || fbcfgsL.limit()<=0) {
                if(DEBUG) {
                    System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationFBConfig: Failed glXChooseFBConfig ("+x11Screen+","+capsChosen+"): "+fbcfgsL+", "+count.get(0));
                }
                return null;
            }
            availableCaps = X11GLXGraphicsConfiguration.GLXFBConfig2GLCapabilities(x11Device, glProfile, fbcfgsL, winattrmask, isMultisampleAvailable, false /* onlyOneValid */);
        }
        final boolean skipCapsChooser = shallSkipCapsChooser && 0 <= recommendedIndex;
        if(DEBUG) {
            System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationFBConfig: got configs: "+availableCaps.size());
            for(int i=0; i<availableCaps.size(); i++) {
                System.err.println(i+": "+availableCaps.get(i));
            }
            System.err.println("recommendedIndex "+recommendedIndex+", skipCapsChooser "+skipCapsChooser);
        }
        // Filter availableCaps
        {
            final List<GLCapabilitiesImmutable> removedCaps;
            if( !skipCapsChooser && isPBufferOrBitmap && GLRendererQuirks.exist(glrq, GLRendererQuirks.No10BitColorCompOffscreen) ) {
                removedCaps = CapabilitiesFilter.removeMoreColorCompsAndUnmatchingNativeVisualID(availableCaps, 8, xvisualID);
            } else {
                removedCaps = CapabilitiesFilter.removeUnmatchingNativeVisualID(availableCaps, xvisualID);
            }
            if( removedCaps.size() > 0 ) {
                if(DEBUG) {
                    System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationFBConfig: filtered configs: "+availableCaps.size());
                    for(int i=0; i<availableCaps.size(); i++) {
                        System.err.println(i+": "+availableCaps.get(i));
                    }
                }
            }
            if(0==availableCaps.size()) {
                if(DEBUG) {
                    System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationFBConfig: post filter visualID "+toHexString(xvisualID )+" no config found, failed - return null");
                }
                return null;
            }
        }

        final int chosenIndex;
        if( skipCapsChooser ) {
            chosenIndex = recommendedIndex;
        } else {
            chosenIndex = chooseCapabilities(chooser, capsChosen, availableCaps, recommendedIndex);
        }
        if ( 0 > chosenIndex ) {
            if (DEBUG) {
                System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationFBConfig: failed, return null");
                ExceptionUtils.dumpStack(System.err);
            }
            return null;
        }
        final X11GLCapabilities chosenCaps = (X11GLCapabilities) availableCaps.get(chosenIndex);

        return new X11GLXGraphicsConfiguration(x11Screen, chosenCaps, capsReq, chooser);
    }

    private static X11GLXGraphicsConfiguration chooseGraphicsConfigurationXVisual(final GLCapabilitiesImmutable capsChosen,
                                                                                  final GLCapabilitiesImmutable capsReq,
                                                                                  GLCapabilitiesChooser chooser,
                                                                                  final X11GraphicsScreen x11Screen, final int xvisualID) {
        if (chooser == null) {
            chooser = new DefaultGLCapabilitiesChooser();
        }

        final GLProfile glProfile = capsChosen.getGLProfile();
        final int winattrmask = GLGraphicsConfigurationUtil.getExclusiveWinAttributeBits(capsChosen.isOnscreen(), capsChosen.isFBO(), false /* pbuffer */, capsChosen.isBitmap());
        final List<GLCapabilitiesImmutable> availableCaps = new ArrayList<GLCapabilitiesImmutable>();
        int recommendedIndex = -1;

        final X11GraphicsDevice absDevice = (X11GraphicsDevice) x11Screen.getDevice();
        final long display = absDevice.getHandle();
        final int screen = x11Screen.getIndex();

        final X11GLXDrawableFactory factory = (X11GLXDrawableFactory) GLDrawableFactory.getDesktopFactory();
        final boolean isMultisampleAvailable = factory.isGLXMultisampleAvailable(absDevice);
        final IntBuffer attribs = X11GLXGraphicsConfiguration.GLCapabilities2AttribList(capsChosen, false, isMultisampleAvailable, display, screen);

        XVisualInfo recommendedVis = null;
        // 1st choice: get GLCapabilities based on users GLCapabilities setting recommendedIndex as preferred choice
        // skipped if xvisualID is given
        if( VisualIDHolder.VID_UNDEFINED == xvisualID ) {
            recommendedVis = GLX.glXChooseVisual(display, screen, attribs);
            if (DEBUG) {
                System.err.print("glXChooseVisual recommended ");
                if (recommendedVis == null) {
                    System.err.println("null visual");
                } else {
                    System.err.println("visual id " + toHexString(recommendedVis.getVisualid()));
                }
            }
        }

        // 2nd choice: get all GLCapabilities available, preferred recommendedIndex might be available if 1st choice was successful
        final int[] count = new int[1];
        final XVisualInfo template = XVisualInfo.create();
        template.setScreen(screen);
        final XVisualInfo[] infos = X11Lib.XGetVisualInfo(display, X11Lib.VisualScreenMask, template, count, 0);
        if (infos == null || infos.length<1) {
            throw new GLException("Error while enumerating available XVisualInfos");
        }

        for (int i = 0; i < infos.length; i++) {
            final GLCapabilitiesImmutable caps = X11GLXGraphicsConfiguration.XVisualInfo2GLCapabilities(absDevice, glProfile, infos[i], winattrmask, isMultisampleAvailable);
            if( null != caps ) {
                availableCaps.add(caps);
                // Attempt to find the visual chosenIndex by glXChooseVisual, if not translucent
                if (capsChosen.isBackgroundOpaque() && recommendedVis != null && recommendedVis.getVisualid() == infos[i].getVisualid()) {
                    recommendedIndex = availableCaps.size() - 1;
                }
            } else if(DEBUG) {
                System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationXVisual: XVisual invalid: ("+x11Screen+"): fbcfg: "+toHexString(infos[i].getVisualid()));
            }
        }

        if(DEBUG) {
            System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationXVisual: got configs: "+availableCaps.size());
            for(int i=0; i<availableCaps.size(); i++) {
                System.err.println(i+": "+availableCaps.get(i));
            }
        }

        if( VisualIDHolder.VID_UNDEFINED != xvisualID ) {
            for(int i=0; i<availableCaps.size(); ) {
                final VisualIDHolder vidh = availableCaps.get(i);
                if(vidh.getVisualID(VIDType.X11_XVISUAL) != xvisualID ) {
                    availableCaps.remove(i);
                } else {
                    i++;
                }
            }
            if(0==availableCaps.size()) {
                if(DEBUG) {
                    System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationXVisual: post filter visualID "+toHexString(xvisualID )+" no config found, failed - return null");
                }
                return null;
            } else if(DEBUG) {
                System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationXVisual: post filter visualID "+toHexString(xvisualID)+" got configs: "+availableCaps.size());
            }
        }

        final int chosenIndex = chooseCapabilities(chooser, capsChosen, availableCaps, recommendedIndex);
        if ( 0 > chosenIndex ) {
            if (DEBUG) {
                System.err.println("X11GLXGraphicsConfiguration.chooseGraphicsConfigurationXVisual: failed, return null");
                ExceptionUtils.dumpStack(System.err);
            }
            return null;
        }
        final X11GLCapabilities chosenCaps = (X11GLCapabilities) availableCaps.get(chosenIndex);

        return new X11GLXGraphicsConfiguration(x11Screen, chosenCaps, capsReq, chooser);
    }

}

