/*
 * Copyright (c) 2002-2008 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.opengl;

/**
 * This class describes pixel format properties for an OpenGL context. Instances
 * of this class is used as arguments to Display.create(), Pbuffer.create() and
 * AWTGLCanvas, to indicate minimum required properties.
 * <p/>
 * Instants 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().withDepthBits(24).withSamples(4).withSRGB(true);</code>
 * <p/>
 * WARNING: Some pixel formats are known to cause troubles on certain buggy drivers.
 * Example: Under Windows, specifying samples != 0 will enable the ARB
 * pixel format selection path, which could trigger a crash.
 *
 * @author elias_naur@sourceforge.net
 * @version $Revision$
 */
public final class PixelFormat implements PixelFormatLWJGL {

	/**
	 * The number of bits per pixel, exluding alpha.
	 * This parameter is ignored in Display.create().
	 */
	private int bpp;
	/** The number of alpha bits. */
	private int alpha;
	/** The number of depth buffer bits */
	private int depth;
	/** The number of stencil bits */
	private int stencil;
	/**
	 * The number of samples to use in anti-aliasing.
	 * 0 means that anti-aliasing is disabled.
	 */
	private int samples;
	/**
	 * The number of COLOR_SAMPLES_NV to use for Coverage Sample Anti-aliasing (CSAA).
	 * When this number is greater than 0, the {@code samples} property will be treated
	 * as if it were the COVERAGE_SAMPLES_NV property.
	 * <p/>
	 * This property is currently a no-op for the MacOS implementation.
	 */
	private int colorSamples;
	/** The number of auxiliary buffers */
	private int num_aux_buffers;
	/** The number of bits per pixel in the accumulation buffer */
	private int accum_bpp;
	/** The number of alpha bits in the accumulation buffer */
	private int accum_alpha;
	/** Whether this format requires a stereo buffer */
	private boolean stereo;
	/** Whether this format specifies a floating point format */
	private boolean floating_point;
	/**
	 * Whether this format specifies a packed floating point format (32 bit unsigned - R11F_G11F_B10F)
	 * This property is currently a no-op for the MacOS implementation.
	 */
	private boolean floating_point_packed;
	/**
	 * Whether this format specifies an sRGB format
	 * This property is currently a no-op for the MacOS implementation.
	 */
	private boolean sRGB;

	/**
	 * Default pixel format is minimum 8 bits depth, and no alpha
	 * nor stencil requirements.
	 */
	public PixelFormat() {
		this(0, 8, 0);
	}

	public PixelFormat(int alpha, int depth, int stencil) {
		this(alpha, depth, stencil, 0);
	}

	public PixelFormat(int alpha, int depth, int stencil, int samples) {
		this(0, alpha, depth, stencil, samples);
	}

	public PixelFormat(int bpp, int alpha, int depth, int stencil, int samples) {
		this(bpp, alpha, depth, stencil, samples, 0, 0, 0, false);
	}

	public PixelFormat(int bpp, int alpha, int depth, int stencil, int samples, int num_aux_buffers, int accum_bpp, int accum_alpha, boolean stereo) {
		this(bpp, alpha, depth, stencil, samples, num_aux_buffers, accum_bpp, accum_alpha, stereo, false);
	}

	public PixelFormat(int bpp, int alpha, int depth, int stencil, int samples, int num_aux_buffers, int accum_bpp, int accum_alpha, boolean stereo, boolean floating_point) {
		this.bpp = bpp;
		this.alpha = alpha;
		this.depth = depth;
		this.stencil = stencil;

		this.samples = samples;

		this.num_aux_buffers = num_aux_buffers;

		this.accum_bpp = accum_bpp;
		this.accum_alpha = accum_alpha;

		this.stereo = stereo;

		this.floating_point = floating_point;
		this.floating_point_packed = false;
		this.sRGB = false;
	}

	private PixelFormat(final PixelFormat pf) {
		this.bpp = pf.bpp;
		this.alpha = pf.alpha;
		this.depth = pf.depth;
		this.stencil = pf.stencil;

		this.samples = pf.samples;
		this.colorSamples = pf.colorSamples;

		this.num_aux_buffers = pf.num_aux_buffers;

		this.accum_bpp = pf.accum_bpp;
		this.accum_alpha = pf.accum_alpha;

		this.stereo = pf.stereo;

		this.floating_point = pf.floating_point;
		this.floating_point_packed = pf.floating_point_packed;
		this.sRGB = pf.sRGB;
	}

	public int getBitsPerPixel() {
		return bpp;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new bits per pixel value.
	 *
	 * @param bpp the new bits per pixel value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withBitsPerPixel(final int bpp) {
		if ( bpp < 0 )
			throw new IllegalArgumentException("Invalid number of bits per pixel specified: " + bpp);

		final PixelFormat pf = new PixelFormat(this);
		pf.bpp = bpp;
		return pf;
	}

	public int getAlphaBits() {
		return alpha;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new alpha bits value.
	 *
	 * @param alpha the new alpha bits value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withAlphaBits(final int alpha) {
		if ( alpha < 0 )
			throw new IllegalArgumentException("Invalid number of alpha bits specified: " + alpha);

		final PixelFormat pf = new PixelFormat(this);
		pf.alpha = alpha;
		return pf;
	}

	public int getDepthBits() {
		return depth;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new depth bits value.
	 *
	 * @param depth the new depth bits value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withDepthBits(final int depth) {
		if ( depth < 0 )
			throw new IllegalArgumentException("Invalid number of depth bits specified: " + depth);

		final PixelFormat pf = new PixelFormat(this);
		pf.depth = depth;
		return pf;
	}

	public int getStencilBits() {
		return stencil;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new stencil bits value.
	 *
	 * @param stencil the new stencil bits value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withStencilBits(final int stencil) {
		if ( stencil < 0 )
			throw new IllegalArgumentException("Invalid number of stencil bits specified: " + stencil);

		final PixelFormat pf = new PixelFormat(this);
		pf.stencil = stencil;
		return pf;
	}

	public int getSamples() {
		return samples;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new samples value.
	 *
	 * @param samples the new samples value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withSamples(final int samples) {
		if ( samples < 0 )
			throw new IllegalArgumentException("Invalid number of samples specified: " + samples);

		final PixelFormat pf = new PixelFormat(this);
		pf.samples = samples;
		return pf;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new color samples values.
	 * A value greater than 0 is valid only if the {@code samples} property is also greater than 0. Additionally, the
	 * color samples value needs to be lower than or equal to the {@code samples} property.
	 *
	 * @param colorSamples    the new color samples value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withCoverageSamples(final int colorSamples) {
		return withCoverageSamples(colorSamples, samples);
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new color samples
	 * and coverage samples values.
	 *
	 * @param colorSamples    the new color samples value. This value must be lower than or equal to the coverage samples value.
	 * @param coverageSamples the new coverage samples value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withCoverageSamples(final int colorSamples, final int coverageSamples) {
		if ( coverageSamples < 0 || colorSamples < 0 || (coverageSamples == 0 && 0 < colorSamples) || coverageSamples < colorSamples  )
			throw new IllegalArgumentException("Invalid number of coverage samples specified: " + coverageSamples + " - " + colorSamples);

		final PixelFormat pf = new PixelFormat(this);
		pf.samples = coverageSamples;
		pf.colorSamples = colorSamples;
		return pf;
	}

	public int getAuxBuffers() {
		return num_aux_buffers;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new auxiliary buffers value.
	 *
	 * @param num_aux_buffers the new auxiliary buffers value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withAuxBuffers(final int num_aux_buffers) {
		if ( num_aux_buffers < 0 )
			throw new IllegalArgumentException("Invalid number of auxiliary buffers specified: " + num_aux_buffers);

		final PixelFormat pf = new PixelFormat(this);
		pf.num_aux_buffers = num_aux_buffers;
		return pf;
	}

	public int getAccumulationBitsPerPixel() {
		return accum_bpp;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new bits per pixel in the accumulation buffer value.
	 *
	 * @param accum_bpp the new bits per pixel in the accumulation buffer value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withAccumulationBitsPerPixel(final int accum_bpp) {
		if ( accum_bpp < 0 )
			throw new IllegalArgumentException("Invalid number of bits per pixel in the accumulation buffer specified: " + accum_bpp);

		final PixelFormat pf = new PixelFormat(this);
		pf.accum_bpp = accum_bpp;
		return pf;
	}

	public int getAccumulationAlpha() {
		return accum_alpha;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new alpha bits in the accumulation buffer value.
	 *
	 * @param accum_alpha the new alpha bits in the accumulation buffer value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withAccumulationAlpha(final int accum_alpha) {
		if ( accum_alpha < 0 )
			throw new IllegalArgumentException("Invalid number of alpha bits in the accumulation buffer specified: " + accum_alpha);

		final PixelFormat pf = new PixelFormat(this);
		pf.accum_alpha = accum_alpha;
		return pf;
	}

	public boolean isStereo() {
		return stereo;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new stereo value.
	 *
	 * @param stereo the new stereo value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withStereo(final boolean stereo) {
		final PixelFormat pf = new PixelFormat(this);
		pf.stereo = stereo;
		return pf;
	}

	public boolean isFloatingPoint() {
		return floating_point;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new floating point value.
	 * If floating_point is true, floating_point_packed will be reset to false.
	 *
	 * @param floating_point the new floating point value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withFloatingPoint(final boolean floating_point) {
		final PixelFormat pf = new PixelFormat(this);
		pf.floating_point = floating_point;
		if ( floating_point )
			pf.floating_point_packed = false;
		return pf;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new packed floating point value.
	 * If floating_point_packed is true, floating_point will be reset to false.
	 *
	 * @param floating_point_packed the new packed floating point value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withFloatingPointPacked(final boolean floating_point_packed) {
		final PixelFormat pf = new PixelFormat(this);
		pf.floating_point_packed = floating_point_packed;
		if ( floating_point_packed )
			pf.floating_point = false;
		return pf;
	}

	public boolean isSRGB() {
		return sRGB;
	}

	/**
	 * Returns a new PixelFormat object with the same properties as this PixelFormat and the new sRGB value.
	 *
	 * @param sRGB the new floating point value.
	 *
	 * @return the new PixelFormat
	 */
	public PixelFormat withSRGB(final boolean sRGB) {
		final PixelFormat pf = new PixelFormat(this);
		pf.sRGB = sRGB;
		return pf;
	}

}