/*
 * Copyright (c) 2002-2011 LWJGL Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions 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 'LWJGL' nor the names of
 *   its contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.lwjgl.opengles;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.LWJGLUtil;
import org.lwjgl.opengl.PixelFormatLWJGL;

import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static org.lwjgl.opengles.EGL.*;
import static org.lwjgl.opengles.NVCoverageSample.*;
import static org.lwjgl.opengles.NVDepthNonlinear.*;
import static org.lwjgl.opengles.PixelFormat.Attrib.*;

/**
 * This class describes the configuration settings for an EGL surface. Instances
 * of this class are used as arguments to Display.create(). The attributes specified
 * in this class will be used to get EGLConfigs from an EGLDisplay. PixelFormat
 * is not the best name for this class, but it matches the corresponding class
 * in the official desktop LWJGL.
 * <p/>
 * Instances of this class are immutable. An example of the expected way to set
 * the PixelFormat property values is the following:
 * <code>PixelFormat pf = new PixelFormat().withDepth(24).withSamples(4);</code>
 * <p/>
 * Attributes that correspond to EGL extensions will be silently ignored if those
 * extensions are not supported by the EGLDisplay.
 */
public final class PixelFormat implements PixelFormatLWJGL {

	public static enum Attrib {
		// CORE ATTRIBUTES

		RED_SIZE(EGL_RED_SIZE, 0),
		GREEN_SIZE(EGL_GREEN_SIZE, 0),
		BLUE_SIZE(EGL_BLUE_SIZE, 0),
		ALPHA_SIZE(EGL_ALPHA_SIZE, 0),

		LUMINANCE_SIZE(EGL_LUMINANCE_SIZE, 0),

		DEPTH_SIZE(EGL_DEPTH_SIZE, 0),
		STENCIL_SIZE(EGL_STENCIL_SIZE, 0),

		MIN_SWAP_INTERVAL(EGL_MIN_SWAP_INTERVAL, EGL_DONT_CARE),
		MAX_SWAP_INTERVAL(EGL_MAX_SWAP_INTERVAL, EGL_DONT_CARE),

		SAMPLES(EGL_SAMPLES, 0),
		SAMPLE_BUFFERS(EGL_SAMPLE_BUFFERS, 0),

		TRANSPARENT_TYPE(EGL_TRANSPARENT_TYPE, EGL_NONE),
		TRANSPARENT_RED_VALUE(EGL_TRANSPARENT_RED_VALUE, EGL_DONT_CARE),
		TRANSPARENT_GREEN_VALUE(EGL_TRANSPARENT_GREEN_VALUE, EGL_DONT_CARE),
		TRANSPARENT_BLUE_VALUE(EGL_TRANSPARENT_BLUE_VALUE, EGL_DONT_CARE),

		// SURFACE ATTRIBUTES

		MULTISAMPLE_RESOLVE(EGL_MULTISAMPLE_RESOLVE, EGL_MULTISAMPLE_RESOLVE_DEFAULT, true),
		SWAP_BEHAVIOR(EGL_SWAP_BEHAVIOR, EGL_DONT_CARE, true),

		// EXTENSION ATTRIBUTES

		COVERAGE_SAMPLES_NV(EGL_COVERAGE_SAMPLES_NV, 0),
		COVERAGE_BUFFERS_NV(EGL_COVERAGE_BUFFERS_NV, 0),

		DEPTH_ENCODING_NONLINEAR_NV(EGL_DEPTH_ENCODING_NONLINEAR_NV, EGL_DONT_CARE);

		private final int eglAttrib;
		private final int defaultValue;

		private final boolean surfaceAttrib;

		Attrib(final int eglAttrib, final int defaultValue) {
			this(eglAttrib, defaultValue, false);
		}

		Attrib(final int eglAttrib, final int defaultValue, final boolean surfaceAttrib) {
			this.eglAttrib = eglAttrib;
			this.defaultValue = defaultValue;

			this.surfaceAttrib = surfaceAttrib;
		}

		/**
		 * Returns the EGL token that corresponds to this attribute.
		 *
		 * @return the EGL attribute token
		 */
		public int getEGLAttrib() {
			return eglAttrib;
		}

		/**
		 * Returns the default value of this attribute. Attributes
		 * with default values will be ignored when choosing the EGLConfig.
		 *
		 * @return the default value
		 */
		public int getDefaultValue() {
			return defaultValue;
		}

		public boolean isSurfaceAttrib() {
			return surfaceAttrib;
		}

	}

	private final Map<Attrib, Integer> config = new HashMap<Attrib, Integer>(16);

	/**
	 * Creates a new PixelFormat with rgbSize = 8, alphaSize = 8 and depthSize = 16.
	 *
	 * @see #PixelFormat(int, int, int, int, int, int)
	 */
	public PixelFormat() {
		this(8, 16, 0);
	}

	/**
	 * Creates a new PixelFormat with rgbSize = 8 and the specified
	 * alphaSize, depthSize and stencilSize.
	 *
	 * @param alphaSize   the EGL_ALPHA_SIZE value
	 * @param depthSize   the EGL_DEPTH_SIZE value
	 * @param stencilSize the EGL_STENCIL_SIZE value
	 *
	 * @see #PixelFormat(int, int, int, int, int, int)
	 */
	public PixelFormat(int alphaSize, int depthSize, int stencilSize) {
		this(alphaSize, depthSize, stencilSize, 0);
	}

	/**
	 * Creates a new PixelFormat with rgbSize = 8 and the specified
	 * alphaSize, depthSize, stencilSize and samples.
	 *
	 * @param alphaSize   the EGL_ALPHA_SIZE value
	 * @param depthSize   the EGL_DEPTH_SIZE value
	 * @param stencilSize the EGL_STENCIL_SIZE value
	 * @param samples     the EGL_SAMPLE_SIZE value
	 *
	 * @see #PixelFormat(int, int, int, int, int, int)
	 */
	public PixelFormat(int alphaSize, int depthSize, int stencilSize, int samples) {
		this(8, alphaSize, 0, depthSize, stencilSize, samples);
	}

	/**
	 * Creates a new PixelFormat with the specified RGB sizes, EGL_ALPHA_SIZE,
	 * EGL_LUMINANCE_SIZE, EGL_DEPTH_SIZE, EGL_STENCIL_SIZE, EGL_SAMPLES.
	 * All values must be greater than or equal to 0. rgbSize and luminanceSize
	 * cannot both be greater than 0. depthSize greater than 24 and stencilSize
	 * greater than 8 are not recommended.
	 * The corresponding EGL_SAMPLE_BUFFERS value will become 0 if samples is 0,
	 * or 1 if samples is greater than 0.
	 *
	 * @param rgbSize       the RGB sizes
	 * @param alphaSize     the EGL_ALPHA_SIZE value
	 * @param luminanceSize the EGL_LUMINANCE_SIZE value
	 * @param depthSize     the EGL_DEPTH_SIZE value
	 * @param stencilSize   the EGL_STENCIL_SIZE value
	 * @param samples       the EGL_SAMPLE_SIZE value
	 */
	public PixelFormat(int rgbSize, int alphaSize, int luminanceSize, int depthSize, int stencilSize, int samples) {
		if ( rgbSize < 0 )
			throw new IllegalArgumentException("Invalid RGB size specified: " + rgbSize);

		if ( alphaSize < 0 )
			throw new IllegalArgumentException("Invalid EGL_ALPHA_SIZE specified: " + alphaSize);

		if ( luminanceSize < 0 || (0 < luminanceSize && 0 < rgbSize) )
			throw new IllegalArgumentException("Invalid EGL_LUMINANCE_SIZE specified: " + luminanceSize);

		if ( depthSize < 0 )
			throw new IllegalArgumentException("Invalid EGL_DEPTH_SIZE specified: " + depthSize);

		if ( stencilSize < 0 )
			throw new IllegalArgumentException("Invalid EGL_STENCIL_SIZE specified: " + stencilSize);

		if ( samples < 0 )
			throw new IllegalArgumentException("Invalid EGL_SAMPLES specified: " + samples);

		if ( 0 < rgbSize ) {
			setAttrib(RED_SIZE, rgbSize);
			setAttrib(GREEN_SIZE, rgbSize);
			setAttrib(BLUE_SIZE, rgbSize);
		}
		setAttrib(ALPHA_SIZE, alphaSize);

		setAttrib(LUMINANCE_SIZE, luminanceSize);

		setAttrib(DEPTH_SIZE, depthSize);
		setAttrib(STENCIL_SIZE, stencilSize);

		setAttrib(SAMPLES, samples);
		setAttrib(SAMPLE_BUFFERS, samples == 0 ? 0 : 1);
	}

	/**
	 * Creates a new PixelFormat that is a copy of the specified PixelFormat.
	 *
	 * @param pf the source PixelFormat
	 */
	private PixelFormat(final PixelFormat pf) {
		config.clear();
		config.putAll(pf.config);
	}

	/**
	 * Sets the value of an attribute to the current configuration.
	 * If the value matches the default attribute value, the
	 * attribute will be removed from the configuration.
	 *
	 * @param attrib the attribute
	 * @param value  the new value
	 */
	private void setAttrib(final Attrib attrib, final int value) {
		if ( attrib.defaultValue == value )
			config.remove(attrib);
		else
			config.put(attrib, value);
	}

	/**
	 * Returns an IntBuffer that can be used to get/choose EGLConfigs.
	 * The contents of the IntBuffer will be the sum of the source
	 * LWJGL attributes and the user-defined attributes from this
	 * PixelFormat's configuration.
	 * <p/>
	 * The source LWJGL attributes should not contain the EGL_SURFACE_TYPE
	 * attirube, or any attributes that are handled by PixelFormat.
	 * <p/>
	 * Attributes that correspond to EGL extensions will be checked
	 * against the extensions supported in the specified EGLDisplay.
	 * Attributes that correspond to unsupported extensions will not
	 * be included in the final EGLConfig query.
	 *
	 * @param display      the EGL display from which the EGLConfig is going to be retrieved
	 * @param lwjglAttribs the LWJGL attributes
	 *
	 * @return the IntBuffer
	 */
	public IntBuffer getAttribBuffer(final EGLDisplay display, int surfaceType, final int[] lwjglAttribs) {
		// Create a copy of the configuration attributes
		Set<Attrib> keys = new HashSet<Attrib>(config.keySet());

		// Handle surface type bits
		if ( keys.contains(MULTISAMPLE_RESOLVE) ) {
			if ( display.getMajorVersion() == 1 && display.getMinorVersion() < 4 )
				keys.remove(MULTISAMPLE_RESOLVE);
			else if ( getAttrib(MULTISAMPLE_RESOLVE) == EGL_MULTISAMPLE_RESOLVE_BOX )
				surfaceType |= EGL_MULTISAMPLE_RESOLVE_BOX_BIT;
		}

		if ( keys.contains(SWAP_BEHAVIOR) ) {
			if ( display.getMajorVersion() == 1 && display.getMinorVersion() < 4 )
				keys.remove(SWAP_BEHAVIOR);
			else if ( getAttrib(SWAP_BEHAVIOR) == EGL_BUFFER_PRESERVED )
				surfaceType |= EGL_SWAP_BEHAVIOR_PRESERVED_BIT;
		}

		// Check NV_coverage_sample
		if ( keys.contains(COVERAGE_BUFFERS_NV) && !display.isExtensionSupported("EGL_NV_coverage_sample") ) {
			keys.remove(COVERAGE_BUFFERS_NV);
			keys.remove(COVERAGE_SAMPLES_NV);
		}

		// Check NV_depth_nonlinear
		if ( keys.contains(DEPTH_ENCODING_NONLINEAR_NV) && !display.isExtensionSupported("EGL_NV_depth_nonlinear") )
			keys.remove(DEPTH_ENCODING_NONLINEAR_NV);

		// Create IntBuffer and insert the attributes
		final IntBuffer attribs = BufferUtils.createIntBuffer(
			2                       // SURFACE_TYPE
			+ lwjglAttribs.length   // Source LWJGL attributes
			+ (keys.size() * 2)     // PixelFormat attributes
			+ 1                     // Termination
		);

		attribs.put(EGL_SURFACE_TYPE).put(surfaceType);
		attribs.put(lwjglAttribs);

		for ( Attrib key : keys ) {
			if ( !key.isSurfaceAttrib() )
				attribs.put(key.eglAttrib).put(config.get(key));
		}

		// Finish the attribute list
		attribs.put(EGL_NONE);
		attribs.flip();

		return attribs;
	}

	/**
	 * Returns true if the requested attribute matches the attribute in the specified EGL config.
	 *
	 * @param attrib the requested attribute
	 * @param config the EGL config
	 *
	 * @return true if the two attributes match
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	private boolean matches(final Attrib attrib, final EGLConfig config) throws LWJGLException {
		return getAttrib(attrib) == config.getAttribute(attrib.getEGLAttrib());
	}

	/**
	 * Returns true if the requested attribute matches the attribute in the specified EGL config.
	 * If the requested attribute is equal to 1, then it will match with any EGL config attribute
	 * that is greater than 0.
	 *
	 * @param attrib the requested attribute
	 * @param config the EGL config
	 *
	 * @return true if the two attributes match
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	private boolean matchesNonZero(final Attrib attrib, final EGLConfig config) throws LWJGLException {
		final int reqValue = getAttrib(attrib);
		final int cfgValue = config.getAttribute(attrib.getEGLAttrib());

		return reqValue == cfgValue || (reqValue == 1 && cfgValue > 0);
	}

	/**
	 * Returns the EGL config from the specified array that best matches this PixelFormat.
	 *
	 * @param configs the EGL configs
	 *
	 * @return the best match
	 */
	public EGLConfig getBestMatch(final EGLConfig[] configs) throws LWJGLException {
		if ( configs == null || configs.length == 0 )
			throw new IllegalArgumentException("No EGLConfigs specified.");

		if ( configs.length == 1 )
			return configs[0];

		/*System.out.println("\nASKED FOR:");
		for ( Attrib attrib : config.keySet() ) {
			System.out.println("EGL_" + attrib.name() + ": " + getAttrib(attrib));
		}

		for ( EGLConfig config : configs ) {
			if ( config == null )
				continue;

			System.out.println("\n----");
			System.out.println(config);
		}*/

		for ( EGLConfig config : configs ) {
			if ( config == null )
				continue;

			if ( !(matches(ALPHA_SIZE, config) && matchesNonZero(DEPTH_SIZE, config) && matchesNonZero(STENCIL_SIZE, config)) )
				continue;

			final int luminance = getAttrib(LUMINANCE_SIZE);
			if ( 0 < luminance && !matches(LUMINANCE_SIZE, config) )
				continue;

			if ( luminance == 0 && !(matches(RED_SIZE, config) && matches(GREEN_SIZE, config) && matches(BLUE_SIZE, config)) )
				continue;

			if ( !(matches(SAMPLE_BUFFERS, config) && matches(SAMPLES, config)) )
				continue;

			// TODO: Add more? NV's Tegra SDK checks a hardcoded 5 value for COVERAGE_SAMPLES_NV (nv_main.c, line: 1823)

			return config;
		}

		// No match found, use the one recommended by the EGL implementation.
		LWJGLUtil.log("Could not find an exact EGLConfig match for the PixelFormat requested, using first returned.");
		return configs[0];
	}

	/**
	 * Applies this PixelFormat's surface attributes to the specified EGL surface.
	 *
	 * @param surface the EGL surface
	 */
	public void setSurfaceAttribs(final EGLSurface surface) throws LWJGLException {
		setSurfaceAttrib(surface, SWAP_BEHAVIOR);
		setSurfaceAttrib(surface, MULTISAMPLE_RESOLVE);
	}

	private void setSurfaceAttrib(final EGLSurface surface, final Attrib attrib) throws LWJGLException {
		final int value = getAttrib(attrib);
		if ( value != attrib.getDefaultValue() )
			surface.setAttribute(attrib.getEGLAttrib(), value);
	}

	/**
	 * Returns the value of the specified attribute.
	 *
	 * @param attrib the attribute to retrieve
	 *
	 * @return the attribute's value
	 */
	public int getAttrib(final Attrib attrib) {
		final Integer value = config.get(attrib);
		if ( value == null )
			return attrib.defaultValue;

		return value;
	}

	/* -----------------------------------------
					CORE ATTRIBUTES
	----------------------------------------- */

	/**
	 * Returns a new PixelFormat with the specified RGB sizes.
	 *
	 * @param rgb the new EGL_RED_SIZE, EGL_GREEN_SIZE and EGL_BLUE_SIZE
	 *
	 * @return the new PixelFormat
	 *
	 * @see #withRGBSize(int, int, int)
	 */
	public PixelFormat withRGBSize(final int rgb) {
		return withRGBSize(rgb, rgb, rgb);
	}

	/**
	 * Returns a new PixelFormat with the specified EGL_RED_SIZE, EGL_GREEN_SIZE and EGL_BLUE_SIZE.
	 * All 3 values must be greater than or equal to 0. If any of the 3 values is greater than 0,
	 * the luminanceSize will be set to 0.
	 *
	 * @param r the new EGL_RED_SIZE
	 * @param g the new EGL_GREEN_SIZE
	 * @param b the new EGL_BLUE_SIZE
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withRGBSize(final int r, final int g, final int b) {
		if ( r < 0 || g < 0 || b < 0 )
			throw new IllegalArgumentException("Invalid RGB sizes specified: " + r + ", " + g + ", " + b);

		final PixelFormat pf = new PixelFormat(this);
		pf.setAttrib(RED_SIZE, r);
		pf.setAttrib(GREEN_SIZE, g);
		pf.setAttrib(BLUE_SIZE, b);
		if ( 0 < r || 0 < g || 0 < b )
			pf.setAttrib(LUMINANCE_SIZE, 0);
		return pf;
	}

	/**
	 * Returns a new PixelFormat with the specified EGL_ALPHA_SIZE.
	 * The alphaSize value must be greater than or equal to 0.
	 *
	 * @param alphaSize the new EGL_ALPHA_SIZE
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withAlphaSize(final int alphaSize) {
		if ( alphaSize < 0 )
			throw new IllegalArgumentException("Invalid EGL_ALPHA_SIZE specified: " + alphaSize);

		final PixelFormat pf = new PixelFormat(this);
		pf.setAttrib(ALPHA_SIZE, alphaSize);
		return pf;
	}

	/**
	 * Returns a new PixelFormat with the specified EGL_LUMINANCE_SIZE.
	 * The luminanceSize value must be greater than or equal to 0. If
	 * luminanceSize is greater than 0, the RGB sizes will be set to 0.
	 *
	 * @param luminanceSize the new EGL_LUMINANCE_SIZE
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withLuminanceSize(final int luminanceSize) {
		if ( luminanceSize < 0 )
			throw new IllegalArgumentException("Invalid EGL_LUMINANCE_SIZE specified: " + luminanceSize);

		final PixelFormat pf = new PixelFormat(this);
		pf.setAttrib(LUMINANCE_SIZE, luminanceSize);
		if ( 0 < luminanceSize ) {
			pf.setAttrib(RED_SIZE, 0);
			pf.setAttrib(GREEN_SIZE, 0);
			pf.setAttrib(BLUE_SIZE, 0);
		}
		return pf;
	}

	/**
	 * Returns a new PixelFormat with the specified EGL_DEPTH_SIZE.
	 * The depthSize value must be greater than or equal to 0.
	 * Values greater than 24 are not recommended.
	 *
	 * @param depthSize the new EGL_DEPTH_SIZE
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withDepthSize(final int depthSize) {
		if ( depthSize < 0 )
			throw new IllegalArgumentException("Invalid EGL_DEPTH_SIZE specified: " + depthSize);

		final PixelFormat pf = new PixelFormat(this);
		pf.setAttrib(DEPTH_SIZE, depthSize);
		return pf;
	}

	/**
	 * Returns a new PixelFormat with the specified EGL_STENCIL_SIZE.
	 * The stencilSize value must be greater than or equal to 0.
	 * Values greater than 8 are not recommended.
	 *
	 * @param stencilSize the new EGL_STENCIL_SIZE
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withStencilSize(final int stencilSize) {
		if ( stencilSize < 0 )
			throw new IllegalArgumentException("Invalid EGL_STENCIL_SIZE specified: " + stencilSize);

		final PixelFormat pf = new PixelFormat(this);
		pf.setAttrib(STENCIL_SIZE, stencilSize);
		return pf;
	}

	/**
	 * Returns a new PixelFormat with the specified EGL_MIN_SWAP_INTERVAL.
	 * The minSwapInterval value must be between 0 and this PixelFormat's EGL_MAX_SWAP_INTERVAL.
	 *
	 * @param minSwapInterval the new EGL_MIN_SWAP_INTERVAL value
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withMinSwapInterval(final int minSwapInterval) {
		final int maxSwapInterval = getAttrib(MAX_SWAP_INTERVAL);
		if ( minSwapInterval != EGL_DONT_CARE && (minSwapInterval < 0 || (maxSwapInterval != EGL_DONT_CARE && maxSwapInterval < minSwapInterval)) )
			throw new IllegalArgumentException("Invalid EGL_MIN_SWAP_INTERVAL specified: " + minSwapInterval);

		final PixelFormat pf = new PixelFormat(this);
		pf.setAttrib(MIN_SWAP_INTERVAL, minSwapInterval);
		return pf;
	}

	/**
	 * Returns a new PixelFormat with the specified EGL_MAX_SWAP_INTERVAL.
	 * The maxSwapInterval value must be greater than or equal to this PixelFormat's EGL_MIN_SWAP_INTERVAL.
	 *
	 * @param maxSwapInterval the new EGL_MAX_SWAP_INTERVAL value
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withMaxSwapInterval(final int maxSwapInterval) {
		if ( maxSwapInterval < getAttrib(MIN_SWAP_INTERVAL) )
			throw new IllegalArgumentException("Invalid EGL_MAX_SWAP_INTERVAL specified: " + maxSwapInterval);

		final PixelFormat pf = new PixelFormat(this);
		pf.setAttrib(MAX_SWAP_INTERVAL, maxSwapInterval);
		return pf;
	}

	/**
	 * Returns a new PixelFormat with the specified number of EGL_SAMPLES.
	 * The samples value must be either 0 or greater than or equal to 2. The related
	 * EGL_SAMPLE_BUFFERS value will become 0 if samples is 0, or 1 if samples
	 * is greater than or equal to 2.
	 *
	 * @param samples the new EGL_SAMPLES value
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withSamples(final int samples) {
		if ( samples != 0 && samples < 2 )
			throw new IllegalArgumentException("Invalid number of EGL_SAMPLES specified: " + samples);

		final PixelFormat pf = new PixelFormat(this);
		pf.setAttrib(SAMPLES, samples);
		pf.setAttrib(SAMPLE_BUFFERS, samples == 0 ? 0 : 1);
		return pf;
	}

	private static int maxValue(final int bits) {
		return (2 << bits) - 1;
	}

	/**
	 * Returns a new PixelFormat with the specified EGL_TRANSPARENT_TYPE and
	 * the specified transparent RGB values. The transparentType must be
	 * either EGL_NONE or EGL_TRANSPARENT_RGB. When it is EGL_NONE, the RGB
	 * values are set to zero and ignored. When it is EGL_TRANSPARENT_RGB,
	 * the RGB values must be between 0 and 2^rgbSize - 1.
	 *
	 * @param transparentType the new EGL_TRANSPARENT_TYPE value
	 * @param r               the new EGL_TRANSPARENT_RED_VALUE
	 * @param g               the new EGL_TRANSPARENT_GREEN_VALUE
	 * @param b               the new EGL_TRANSPARENT_BLUE_VALUE
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withTransparentType(final int transparentType, int r, int g, int b) {
		if ( transparentType == EGL_TRANSPARENT_RGB ) {
			final int redSize = getAttrib(RED_SIZE);
			final int greenSize = getAttrib(GREEN_SIZE);
			final int blueSize = getAttrib(BLUE_SIZE);
			if ( r < 0 || (0 < redSize && maxValue(redSize) < r) )
				throw new IllegalArgumentException("Invalid EGL_TRANSPARENT_RED_VALUE specified: " + r);

			if ( r < 0 || (0 < greenSize && maxValue(greenSize) < g) )
				throw new IllegalArgumentException("Invalid EGL_TRANSPARENT_GREEN_VALUE specified: " + g);

			if ( r < 0 || (0 < blueSize && maxValue(blueSize) < b) )
				throw new IllegalArgumentException("Invalid EGL_TRANSPARENT_BLUE_VALUE specified: " + b);
		} else if ( transparentType != EGL_NONE )
			throw new IllegalArgumentException("Invalid EGL_TRANSPARENT_TYPE specified: " + transparentType);
		else
			r = g = b = EGL_DONT_CARE;

		final PixelFormat pf = new PixelFormat(this);

		pf.setAttrib(TRANSPARENT_TYPE, transparentType);

		pf.setAttrib(TRANSPARENT_RED_VALUE, r);
		pf.setAttrib(TRANSPARENT_GREEN_VALUE, g);
		pf.setAttrib(TRANSPARENT_BLUE_VALUE, b);

		return pf;
	}

	/* -----------------------------------------
				SURFACE ATTRIBUTES
	----------------------------------------- */

	/**
	 * Returns a new PixelFormat with the specified EGL_MULTISAMPLE_RESOLVE value.
	 * Valid values for multisampleResolve are EGL_MULTISAMPLE_RESOLVE_DEFAULT
	 * and EGL_MULTISAMPLE_RESOLVE_BOX.
	 * <p/>
	 * An IllegalStateException will be thrown if EGL_SAMPLES has not been previously defined
	 * to be greater than or equal to 2.
	 *
	 * @param multisampleResolve the new EGL_MULTISAMPLE_RESOLVE value
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withMultisampleResolve(final int multisampleResolve) {
		if ( multisampleResolve != EGL_MULTISAMPLE_RESOLVE_DEFAULT && multisampleResolve != EGL_MULTISAMPLE_RESOLVE_BOX )
			throw new IllegalArgumentException("Invalid EGL_MULTISAMPLE_RESOLVE value specified: " + multisampleResolve);

		if ( getAttrib(SAMPLE_BUFFERS) == 0 )
			throw new IllegalStateException("An EGL_MULTISAMPLE_RESOLVE value cannot be specified unless EGL_SAMPLE_BUFFERS is 1.");

		final PixelFormat pf = new PixelFormat(this);
		pf.setAttrib(MULTISAMPLE_RESOLVE, multisampleResolve);
		return pf;
	}

	/**
	 * Returns a new PixelFormat with the specified EGL_SWAP_BEHAVIOR value.
	 * Valid values for swapBehavior are EGL_DONT_CARE, EGL_BUFFER_PRESERVED
	 * and EGL_BUFFER_DESTROYED.
	 *
	 * @param swapBehavior the new EGL_SWAP_BEHAVIOR value
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withSwapBehavior(final int swapBehavior) {
		switch ( swapBehavior ) {
			case EGL_DONT_CARE:
			case EGL_BUFFER_PRESERVED:
			case EGL_BUFFER_DESTROYED:
				break;
			default:
				throw new IllegalArgumentException("Invalid EGL_SWAP_BEHAVIOR value specified: " + swapBehavior);
		}

		final PixelFormat pf = new PixelFormat(this);
		pf.setAttrib(SWAP_BEHAVIOR, swapBehavior);
		return pf;
	}

	/* -----------------------------------------
				EXTENSION ATTRIBUTES
	----------------------------------------- */

	/**
	 * Returns a new PixelFormat with the specified number of EGL_COVERAGE_SAMPLES_NV.
	 * The samples value must be greater than or equal to 0. The related
	 * EGL_COVERAGE_BUFFERS_NV value will become 0 if samples is 0, or 1 if samples
	 * is greater than 0.
	 *
	 * @param samples the new EGL_SAMPLES value
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withCoverageSamplesNV(final int samples) {
		if ( samples < 0 )
			throw new IllegalArgumentException("Invalid number of EGL_COVERAGE_SAMPLES_NV specified: " + samples);

		final PixelFormat pf = new PixelFormat(this);
		pf.setAttrib(COVERAGE_SAMPLES_NV, samples);
		pf.setAttrib(COVERAGE_BUFFERS_NV, samples == 0 ? 0 : 1);
		return pf;
	}

	/**
	 * Returns a new PixelFormat with the specified EGL_DEPTH_ENCODING_NONLINEAR_NV.
	 * Valid values for depthEncoding are EGL_DONT_CARE, EGL_DEPTH_ENCODING_NONE_NV
	 * and EGL_DEPTH_ENCODING_NONLINEAR_NV.
	 *
	 * @param depthEncoding the new EGL_DEPTH_ENCODING_NONLINEAR_NV value
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withDepthEncodingNonlinearNV(final int depthEncoding) {
		switch ( depthEncoding ) {
			case EGL_DONT_CARE:
			case EGL_DEPTH_ENCODING_NONE_NV:
			case EGL_DEPTH_ENCODING_NONLINEAR_NV:
				break;
			default:
				throw new IllegalArgumentException("Invalid EGL_DEPTH_ENCODING_NONLINEAR_NV value specified: " + depthEncoding);
		}

		final PixelFormat pf = new PixelFormat(this);
		pf.setAttrib(DEPTH_ENCODING_NONLINEAR_NV, depthEncoding);
		return pf;
	}

}