/*
 * 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.BufferChecks;
import org.lwjgl.LWJGLException;
import org.lwjgl.MemoryUtil;
import org.lwjgl.PointerBuffer;

import java.nio.IntBuffer;

/** EGL wrapper class. */
public final class EGL {

	/** EGL aliases */
	public static final int
		EGL_FALSE = 0,
		EGL_TRUE  = 1;

	/** Out-of-band handle values */
	public static final int
		EGL_DEFAULT_DISPLAY = 0,
		EGL_NO_CONTEXT      = 0,
		EGL_NO_DISPLAY      = 0,
		EGL_NO_SURFACE      = 0;

	/** Out-of-band attribute value */
	public static final int EGL_DONT_CARE = -1;

	/** Errors / GetError return values */
	public static final int
		EGL_SUCCESS             = 0x3000,
		EGL_NOT_INITIALIZED     = 0x3001,
		EGL_BAD_ACCESS          = 0x3002,
		EGL_BAD_ALLOC           = 0x3003,
		EGL_BAD_ATTRIBUTE       = 0x3004,
		EGL_BAD_CONFIG          = 0x3005,
		EGL_BAD_CONTEXT         = 0x3006,
		EGL_BAD_CURRENT_SURFACE = 0x3007,
		EGL_BAD_DISPLAY         = 0x3008,
		EGL_BAD_MATCH           = 0x3009,
		EGL_BAD_NATIVE_PIXMAP   = 0x300A,
		EGL_BAD_NATIVE_WINDOW   = 0x300B,
		EGL_BAD_PARAMETER       = 0x300C,
		EGL_BAD_SURFACE         = 0x300D,
		EGL_CONTEXT_LOST        = 0x300E;	// EGL 1.1 - IMG_power_management

	/** Reserved =0x300F;-=0x301F; for additional errors */

	/** Config attributes */
	public static final int
		EGL_BUFFER_SIZE             = 0x3020,
		EGL_ALPHA_SIZE              = 0x3021,
		EGL_BLUE_SIZE               = 0x3022,
		EGL_GREEN_SIZE              = 0x3023,
		EGL_RED_SIZE                = 0x3024,
		EGL_DEPTH_SIZE              = 0x3025,
		EGL_STENCIL_SIZE            = 0x3026,
		EGL_CONFIG_CAVEAT           = 0x3027,
		EGL_CONFIG_ID               = 0x3028,
		EGL_LEVEL                   = 0x3029,
		EGL_MAX_PBUFFER_HEIGHT      = 0x302A,
		EGL_MAX_PBUFFER_PIXELS      = 0x302B,
		EGL_MAX_PBUFFER_WIDTH       = 0x302C,
		EGL_NATIVE_RENDERABLE       = 0x302D,
		EGL_NATIVE_VISUAL_ID        = 0x302E,
		EGL_NATIVE_VISUAL_TYPE      = 0x302F,
		EGL_SAMPLES                 = 0x3031,
		EGL_SAMPLE_BUFFERS          = 0x3032,
		EGL_SURFACE_TYPE            = 0x3033,
		EGL_TRANSPARENT_TYPE        = 0x3034,
		EGL_TRANSPARENT_BLUE_VALUE  = 0x3035,
		EGL_TRANSPARENT_GREEN_VALUE = 0x3036,
		EGL_TRANSPARENT_RED_VALUE   = 0x3037,
		EGL_NONE                    = 0x3038, // Attrib list terminator
		EGL_BIND_TO_TEXTURE_RGB     = 0x3039,
		EGL_BIND_TO_TEXTURE_RGBA    = 0x303A,
		EGL_MIN_SWAP_INTERVAL       = 0x303B,
		EGL_MAX_SWAP_INTERVAL       = 0x303C,
		EGL_LUMINANCE_SIZE          = 0x303D,
		EGL_ALPHA_MASK_SIZE         = 0x303E,
		EGL_COLOR_BUFFER_TYPE       = 0x303F,
		EGL_RENDERABLE_TYPE         = 0x3040,
		EGL_MATCH_NATIVE_PIXMAP     = 0x3041, // Pseudo-attribute (not queryable)
		EGL_CONFORMANT              = 0x3042;

	/** Reserved =0x3041;-=0x304F; for additional config attributes */

	/** Config attribute values */
	public static final int
		EGL_SLOW_CONFIG           = 0x3050, // EGL_CONFIG_CAVEAT value
		EGL_NON_CONFORMANT_CONFIG = 0x3051, // EGL_CONFIG_CAVEAT value
		EGL_TRANSPARENT_RGB       = 0x3052, // EGL_TRANSPARENT_TYPE value
		EGL_RGB_BUFFER            = 0x308E, // EGL_COLOR_BUFFER_TYPE value
		EGL_LUMINANCE_BUFFER      = 0x308F;	// EGL_COLOR_BUFFER_TYPE value

	/** More config attribute values, for EGL_TEXTURE_FORMAT */
	public static final int
		EGL_NO_TEXTURE   = 0x305C,
		EGL_TEXTURE_RGB  = 0x305D,
		EGL_TEXTURE_RGBA = 0x305E,
		EGL_TEXTURE_2D   = 0x305F;

	/** EGL_SURFACE_TYPE mask bits */
	public static final int
		EGL_PBUFFER_BIT                 = 0x0001,
		EGL_PIXMAP_BIT                  = 0x0002,
		EGL_WINDOW_BIT                  = 0x0004,
		EGL_VG_COLORSPACE_LINEAR_BIT    = 0x0020,
		EGL_VG_ALPHA_FORMAT_PRE_BIT     = 0x0040,
		EGL_MULTISAMPLE_RESOLVE_BOX_BIT = 0x0200,
		EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;

	/** EGL_RENDERABLE_TYPE mask bits */
	public static final int
		EGL_OPENGL_ES_BIT  = 0x0001,
		EGL_OPENVG_BIT     = 0x0002,
		EGL_OPENGL_ES2_BIT = 0x0004,
		EGL_OPENGL_BIT     = 0x0008;

	/** QueryString targets */
	public static final int
		EGL_VENDOR      = 0x3053,
		EGL_VERSION     = 0x3054,
		EGL_EXTENSIONS  = 0x3055,
		EGL_CLIENT_APIS = 0x308D;

	/** QuerySurface / SurfaceAttrib / CreatePbufferSurface targets */
	public static final int
		EGL_HEIGHT                = 0x3056,
		EGL_WIDTH                 = 0x3057,
		EGL_LARGEST_PBUFFER       = 0x3058,
		EGL_TEXTURE_FORMAT        = 0x3080,
		EGL_TEXTURE_TARGET        = 0x3081,
		EGL_MIPMAP_TEXTURE        = 0x3082,
		EGL_MIPMAP_LEVEL          = 0x3083,
		EGL_RENDER_BUFFER         = 0x3086,
		EGL_VG_COLORSPACE         = 0x3087,
		EGL_VG_ALPHA_FORMAT       = 0x3088,
		EGL_HORIZONTAL_RESOLUTION = 0x3090,
		EGL_VERTICAL_RESOLUTION   = 0x3091,
		EGL_PIXEL_ASPECT_RATIO    = 0x3092,
		EGL_SWAP_BEHAVIOR         = 0x3093,
		EGL_MULTISAMPLE_RESOLVE   = 0x3099;

	/** EGL_RENDER_BUFFER values / BindTexImage / ReleaseTexImage buffer targets */
	public static final int
		EGL_BACK_BUFFER   = 0x3084,
		EGL_SINGLE_BUFFER = 0x3085;

	/** OpenVG color spaces */
	public static final int
		EGL_VG_COLORSPACE_sRGB   = 0x3089, // EGL_VG_COLORSPACE value
		EGL_VG_COLORSPACE_LINEAR = 0x308A;	// EGL_VG_COLORSPACE value

	/** OpenVG alpha formats */
	public static final int
		EGL_VG_ALPHA_FORMAT_NONPRE = 0x308B, // EGL_ALPHA_FORMAT value
		EGL_VG_ALPHA_FORMAT_PRE    = 0x308C;	// EGL_ALPHA_FORMAT

	/**
	 * Constant scale factor by which fractional display resolutions &
	 * aspect ratio are scaled when queried as integer values.
	 */
	public static final int EGL_DISPLAY_SCALING = 10000;

	/** Unknown display resolution/aspect ratio */
	public static final int EGL_UNKNOWN = -1;

	/** Back buffer swap behaviors */
	public static final int
		EGL_BUFFER_PRESERVED = 0x3094, // EGL_SWAP_BEHAVIOR value
		EGL_BUFFER_DESTROYED = 0x3095;	// EGL_SWAP_BEHAVIOR value

	/** CreatePbufferFromClientBuffer buffer types */
	static final int EGL_OPENVG_IMAGE = 0x3096;

	/** QueryContext targets */
	public static final int EGL_CONTEXT_CLIENT_TYPE = 0x3097;

	/** CreateContext attributes */
	public static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;

	/** Multisample resolution behaviors */
	public static final int
		EGL_MULTISAMPLE_RESOLVE_DEFAULT = 0x309A, // EGL_MULTISAMPLE_RESOLVE value
		EGL_MULTISAMPLE_RESOLVE_BOX     = 0x309B;	// EGL_MULTISAMPLE_RESOLVE value

	/** BindAPI/QueryAPI targets */
	public static final int
		EGL_OPENGL_ES_API = 0x30A0,
		EGL_OPENVG_API    = 0x30A1,
		EGL_OPENGL_API    = 0x30A2;

	/** GetCurrentSurface targets */
	public static final int
		EGL_DRAW = 0x3059,
		EGL_READ = 0x305A;

	/** WaitNative engines */
	static final int EGL_CORE_NATIVE_ENGINE = 0x305B;

	private EGL() {
	}

	public static native int eglGetError();

	/**
	 * Obtains an EGL display from the specified native display and initializes it.
	 *
	 * @param display_id the handle to the native display.
	 *
	 * @return the EGL Display
	 *
	 * @throws org.lwjgl.LWJGLException if no display is available or an EGL error occurs
	 */
	public static EGLDisplay eglGetDisplay(long display_id) throws LWJGLException {
		//LWJGLUtil.log("eglGetDisplay");
		final long pointer = neglGetDisplay(display_id);

		if ( pointer == EGL_NO_DISPLAY ) // No error is generated when this happens
			throw new LWJGLException("Failed to get EGL display from native display handle: " + display_id);

		return new EGLDisplay(pointer);
	}

	private static native long neglGetDisplay(long display_id);

	/**
	 * Initializes the specified EGL display.
	 *
	 * @param dpy     the EGL display to initialize
	 * @param version the EGL major and minor version will be returned in this buffer.
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	static void eglInitialize(EGLDisplay dpy, IntBuffer version) throws LWJGLException {
		//LWJGLUtil.log("eglInitialize");
		BufferChecks.checkBuffer(version, 2);
		if ( !neglInitialize(dpy.getPointer(), MemoryUtil.getAddress(version)) )
			throwEGLError("Failed to initialize EGL display.");
	}

	private static native boolean neglInitialize(long dpy_ptr, long version);

	/**
	 * Release the resources associated with the specified EGL display.
	 *
	 * @param dpy the EGL display to terminate
	 */
	static void eglTerminate(EGLDisplay dpy) throws LWJGLException {
		//LWJGLUtil.log("eglTerminate");
		if ( !neglTerminate(dpy.getPointer()) )
			throwEGLError("Failed to terminate EGL display.");
	}

	private static native boolean neglTerminate(long dpy_ptr);

	/**
	 * Returns a string describing some aspect of the EGL implementation running on the specified display.
	 *
	 * @param dpy  the EGL display to query
	 * @param name the value to query
	 *
	 * @return the description
	 */
	public static String eglQueryString(EGLDisplay dpy, int name) {
		//LWJGLUtil.log("eglQueryString");
		return neglQueryString(dpy.getPointer(), name);
	}

	private static native String neglQueryString(long dpy, int name);

	/**
	 * Returns the number of EGLConfigs that are available on the specified display.
	 *
	 * @param dpy the EGLDisplay
	 *
	 * @return the number of EGLConfigs available
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 * @see #eglGetConfigs(EGLDisplay, EGLConfig[], IntBuffer)
	 */
	static int eglGetConfigsNum(EGLDisplay dpy) throws LWJGLException {
		//LWJGLUtil.log("eglGetConfigsNum");
		IntBuffer num_config = APIUtil.getBufferInt();

		if ( !neglGetConfigs(dpy.getPointer(), 0L, 0, MemoryUtil.getAddress0(num_config)) )
			throwEGLError("Failed to get EGL configs.");

		return num_config.get(0);
	}

	/**
	 * Returns the available EGLConfigs on the speficied display. The number of available EGLConfigs
	 * is returned in the num_config parameter. The configs array may be null. If it is null, a new
	 * array will be allocated, with size equal to the result of {@link #eglGetConfigsNum(EGLDisplay)}  eglGetConfigsNum}.
	 * If it is not null, no more than {@code configs.length} EGLConfigs will be returned. If the array is bigger
	 * than the number of available EGLConfigs, the remaining array elements will not be affected.
	 *
	 * @param dpy        the EGLDisplay
	 * @param configs    the EGLConfigs array
	 * @param num_config the number of available EGLConfigs returned
	 *
	 * @return the available EGLConfigs
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	static EGLConfig[] eglGetConfigs(EGLDisplay dpy, EGLConfig[] configs, IntBuffer num_config) throws LWJGLException {
		//LWJGLUtil.log("eglGetConfigs");
		BufferChecks.checkBuffer(num_config, 1);

		if ( configs == null ) {
			if ( !neglGetConfigs(dpy.getPointer(), 0L, 0, MemoryUtil.getAddress(num_config)) )
				throwEGLError("Failed to get number of available EGL configs.");

			configs = new EGLConfig[num_config.get(num_config.position())];
		}

		final PointerBuffer configs_buffer = APIUtil.getBufferPointer(configs.length);
		if ( !neglGetConfigs(dpy.getPointer(), MemoryUtil.getAddress0(configs_buffer), configs.length, MemoryUtil.getAddress(num_config)) )
			throwEGLError("Failed to get EGL configs.");

		final int config_size = num_config.get(num_config.position());
		for ( int i = 0; i < config_size; i++ )
			configs[i] = new EGLConfig(dpy, configs_buffer.get(i));

		return configs;
	}

	private static native boolean neglGetConfigs(long dpy_ptr, long configs, int config_size, long num_config);

	/**
	 * Returns the number of EGLConfigs that are available on the specified display and
	 * match the speficied list of attributes.
	 *
	 * @param dpy         the EGLDisplay
	 * @param attrib_list the attribute list (may be null)
	 *
	 * @return the number of EGLConfigs available that satisft the attribute list
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 * @see #eglChooseConfig(EGLDisplay, IntBuffer, EGLConfig[], IntBuffer)
	 */
	static int eglChooseConfigNum(EGLDisplay dpy, IntBuffer attrib_list) throws LWJGLException {
		//LWJGLUtil.log("eglChooseConfigNum");
		checkAttribList(attrib_list);
		IntBuffer num_config = APIUtil.getBufferInt();

		if ( !neglChooseConfig(dpy.getPointer(), MemoryUtil.getAddressSafe(attrib_list), 0L, 0, MemoryUtil.getAddress0(num_config)) )
			throwEGLError("Failed to get EGL configs.");

		return num_config.get(0);
	}

	/**
	 * Returns the available EGLConfigs on the speficied display that satisfy the specified list of attributes.
	 * The number of available EGLConfigs is returned in the num_config parameter. The configs array may be null.
	 * If it is null, a new array will be allocated, with size equal to the result of {@link #eglGetConfigsNum(EGLDisplay)}  eglGetConfigsNum}.
	 * If it is not null, no more than {@code configs.length} EGLConfigs will be returned. If the array is bigger
	 * than the number of available EGLConfigs, the remaining array elements will not be affected.
	 *
	 * @param dpy         the EGLDisplay
	 * @param attrib_list the attribute list (may be null)
	 * @param configs     the EGLConfigs array
	 * @param num_config  the number of available EGLConfigs returned
	 *
	 * @return the available EGLConfigs that satisfy the attribute list
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	static EGLConfig[] eglChooseConfig(EGLDisplay dpy, IntBuffer attrib_list, EGLConfig[] configs, IntBuffer num_config) throws LWJGLException {
		//LWJGLUtil.log("eglChooseConfig");
		checkAttribList(attrib_list);
		BufferChecks.checkBuffer(num_config, 1);

		int config_size;
		if ( configs == null ) {
			if ( !neglChooseConfig(dpy.getPointer(), MemoryUtil.getAddressSafe(attrib_list), 0L, 0, MemoryUtil.getAddress(num_config)) )
				throwEGLError("Failed to get number of available EGL configs.");

			config_size = num_config.get(num_config.position());
		} else
			config_size = configs.length;

		//LWJGLUtil.log("config_size = " + config_size);
		PointerBuffer configs_buffer = APIUtil.getBufferPointer(config_size);
		if ( !neglChooseConfig(dpy.getPointer(), MemoryUtil.getAddressSafe(attrib_list), MemoryUtil.getAddress0(configs_buffer), config_size, MemoryUtil.getAddress(num_config)) )
			throwEGLError("Failed to choose EGL config.");

		// Get the true number of configurations (the first neglChooseConfig call may return more than the second)
		config_size = num_config.get(num_config.position());
		if ( configs == null )
			configs = new EGLConfig[config_size];
		for ( int i = 0; i < config_size; i++ )
			configs[i] = new EGLConfig(dpy, configs_buffer.get(i));

		return configs;
	}

	private static native boolean neglChooseConfig(long dpy_ptr, long attrib_list, long configs, int config_size, long num_config);

	/**
	 * Returns the value of an EGL config attribute.
	 *
	 * @param dpy       the EGL display
	 * @param config    the EGL config
	 * @param attribute the attribute
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	static int eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, int attribute) throws LWJGLException {
		//LWJGLUtil.log("eglGetConfigAttrib");
		final IntBuffer value = APIUtil.getBufferInt();

		if ( !neglGetConfigAttrib(dpy.getPointer(), config.getPointer(), attribute, MemoryUtil.getAddress(value)) )
			throwEGLError("Failed to get EGL config attribute.");

		return value.get(0);
	}

	private static native boolean neglGetConfigAttrib(long dpy_ptr, long config_ptr, int attribute, long value);

	/**
	 * Creates an on-screen rendering surface on the specified EGL display.
	 *
	 * @param dpy         the EGL display
	 * @param config      the EGL config
	 * @param win         the native window handle
	 * @param attrib_list an attribute list (may be null)
	 *
	 * @return the created EGL surface
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	static EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, long win, IntBuffer attrib_list) throws LWJGLException {
		//LWJGLUtil.log("eglCreateWindowSurface");
		checkAttribList(attrib_list);
		final long pointer = neglCreateWindowSurface(dpy.getPointer(), config.getPointer(), win, MemoryUtil.getAddressSafe(attrib_list));

		if ( pointer == EGL_NO_SURFACE )
			throwEGLError("Failed to create EGL window surface.");

		return new EGLSurface(dpy, config, pointer);
	}

	private static native long neglCreateWindowSurface(long dpy_ptr, long config_ptr, long win, long attrib_list);

	/**
	 * Creates an off-screen rendering surface on the specified EGL display.
	 *
	 * @param dpy         the EGL display
	 * @param config      the EGL config
	 * @param attrib_list an attribute list (may be null)
	 *
	 * @return the created EGL surface
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	static EGLSurface eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig config, IntBuffer attrib_list) throws LWJGLException {
		//LWJGLUtil.log("eglCreatePbufferSurface");
		checkAttribList(attrib_list);
		final long pointer = neglCreatePbufferSurface(dpy.getPointer(), config.getPointer(), MemoryUtil.getAddressSafe(attrib_list));

		if ( pointer == EGL_NO_SURFACE )
			throwEGLError("Failed to create EGL pbuffer surface.");

		return new EGLSurface(dpy, config, pointer);
	}

	private static native long neglCreatePbufferSurface(long dpy_ptr, long config_ptr, long attrib_list);

	/*
	EGLAPI EGLSurface EGLAPIENTRY eglCreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list);
	*/

	/**
	 * Sets the specified EGL surface attribute to the specified value.
	 *
	 * @param dpy       the EGL display
	 * @param surface   the EGL surface
	 * @param attribute the attribute
	 * @param value     the attribute value
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	static void eglSurfaceAttrib(EGLDisplay dpy, EGLSurface surface, int attribute, int value) throws LWJGLException {
		//LWJGLUtil.log("eglSurfaceAttrib");
		if ( !neglSurfaceAttrib(dpy.getPointer(), surface.getPointer(), attribute, value) )
			throwEGLError("Failed to set surface attribute.");
	}

	private static native boolean neglSurfaceAttrib(long dpy_ptr, long surface_ptr, int attribute, int value);

	/**
	 * Destroys the specified EGL surface.
	 *
	 * @param dpy     the EGL display
	 * @param surface the EGL surface to destroy
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	static void eglDestroySurface(EGLDisplay dpy, EGLSurface surface) throws LWJGLException {
		//LWJGLUtil.log("eglDestroySurface");
		if ( !neglDestroySurface(dpy.getPointer(), surface.getPointer()) )
			throwEGLError("Failed to destroy EGL surface.");
	}

	private static native boolean neglDestroySurface(long dpy_ptr, long surface_ptr);

	/**
	 * Returns the value of the specified EGL surface attribute in the value parameter.
	 *
	 * @param dpy       the EGL display
	 * @param surface   the EGL surface
	 * @param attribute the surface attribute
	 * @param value     the attribute value will be returned here
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	public static void eglQuerySurface(EGLDisplay dpy, EGLSurface surface, int attribute, IntBuffer value) throws LWJGLException {
		//LWJGLUtil.log("eglQuerySurface");
		BufferChecks.checkBuffer(value, 1);
		if ( !neglQuerySurface(dpy.getPointer(), surface.getPointer(), attribute, MemoryUtil.getAddress(value)) )
			throwEGLError("Failed to query surface attribute.");
	}

	private static native boolean neglQuerySurface(long dpy_ptr, long surface_ptr, int attribute, long value);

	/**
	 * Binds the specified rendering API to the current thread.
	 *
	 * @param api the API to bind
	 *
	 * @return true if the bind was successful, false if an EGL error occurs
	 */
	public static native boolean eglBindAPI(int api);

	/**
	 * Returns the current rendering API.
	 *
	 * @return the rendering API bound to the current thread
	 */
	public static native int eglQueryAPI();

	/**
	 * Returns EGL to its state at thread initialization. This includes the following:<br>
	 * <p>
	 * For each client API supported by EGL, if there is a currently bound context,
	 * that context is released. This is equivalent to calling eglMakeCurrent
	 * with ctx set to EGL_NO_CONTEXT and both draw and read set to EGL_NO_SURFACE
	 * </p><br>
	 * <p>The current rendering API is reset to its value at thread initialization</p><br>
	 * <p>Any additional implementation-dependent per-thread state maintained by EGL
	 * is marked for deletion as soon as possible.</p>
	 *
	 * @return true if thread state was released successfully, false is an EGL error occurs
	 */
	static native boolean eglReleaseThread();

	/*
	EGLAPI EGLSurface EGLAPIENTRY eglCreatePbufferFromClientBuffer(EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list);
	EGLAPI EGLBoolean EGLAPIENTRY eglBindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer);
	EGLAPI EGLBoolean EGLAPIENTRY eglReleaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer);
	*/

	/**
	 * Specifies the minimum number of video frame periods per buffer swap for
	 * the window associated with the current context.
	 *
	 * @param dpy      the EGL display
	 * @param interval the frame interval
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	static void eglSwapInterval(EGLDisplay dpy, int interval) throws LWJGLException {
		//LWJGLUtil.log("eglSwapInterval");
		if ( !neglSwapInterval(dpy.getPointer(), interval) )
			throwEGLError("Failed to set swap interval.");
	}

	private static native boolean neglSwapInterval(long dpy_ptr, int interval);

	/**
	 * Creates a new EGL context for the current rendering API.
	 *
	 * @param dpy           the EGL display
	 * @param config        the EGL config
	 * @param share_context the EGL context to share data with
	 * @param attrib_list   the attribute list (may be null)
	 *
	 * @return the created EGL context
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	static EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, IntBuffer attrib_list) throws LWJGLException {
		//LWJGLUtil.log("eglCreateContext");
		checkAttribList(attrib_list);
		final long pointer = neglCreateContext(dpy.getPointer(), config.getPointer(),
		                                       share_context == null ? EGL_NO_CONTEXT : share_context.getPointer(),
		                                       MemoryUtil.getAddressSafe(attrib_list));

		if ( pointer == EGL_NO_CONTEXT )
			throwEGLError("Failed to create EGL context.");

		return new EGLContext(dpy, config, pointer);
	}

	private static native long neglCreateContext(long dpy_ptr, long config_ptr, long share_context_ptr, long attrib_list);

	/**
	 * Destroys a rendering context.
	 *
	 * @param dpy the EGL display
	 * @param ctx the EGL context
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	static void eglDestroyContext(EGLDisplay dpy, EGLContext ctx) throws LWJGLException {
		//LWJGLUtil.log("eglDestroyContext");
		if ( !neglDestroyContext(dpy.getPointer(), ctx.getPointer()) )
			throwEGLError("Failed to destroy context.");
	}

	private static native boolean neglDestroyContext(long dpy_ptr, long ctx_ptr);

	/**
	 * Binds the specified context to the current thread and to the draw and read surfaces.
	 *
	 * @param dpy  the EGL display
	 * @param draw the draw EGL surface
	 * @param read the read EGL surface
	 * @param ctx  the EGL context to make current
	 *
	 * @throws org.lwjgl.LWJGLException      if an EGL error occurs
	 * @throws PowerManagementEventException if an EGL power management event occurs
	 */
	static void eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) throws LWJGLException, PowerManagementEventException {
		//LWJGLUtil.log("eglMakeCurrent");
		if ( !neglMakeCurrent(dpy.getPointer(),
		                      draw == null ? EGL_NO_SURFACE : draw.getPointer(),
		                      read == null ? EGL_NO_SURFACE : read.getPointer(),
		                      ctx == null ? EGL_NO_CONTEXT : ctx.getPointer()) ) {
			final int error = eglGetError();
			if ( error == EGL_CONTEXT_LOST )
				throw new PowerManagementEventException();
			else
				throwEGLError("Failed to change the current context.", error);
		}
	}

	/**
	 * Releases the current context without assigning a new one.
	 *
	 * @param dpy the EGL display
	 *
	 * @throws org.lwjgl.LWJGLException      if an EGL error occurs
	 * @throws PowerManagementEventException if an EGL power management event occurs
	 * @see #eglMakeCurrent(EGLDisplay, EGLSurface, EGLSurface, EGLContext)
	 */
	public static void eglReleaseCurrent(EGLDisplay dpy) throws LWJGLException, PowerManagementEventException {
		//LWJGLUtil.log("eglReleaseCurrent");
		eglMakeCurrent(dpy, null, null, null);
	}

	private static native boolean neglMakeCurrent(long dpy_ptr, long draw_ptr, long read_ptr, long ctx_ptr);

	/**
	 * Returns the current EGL context for the current rendering API.
	 * If there is no context current, null is returned.
	 *
	 * @return the current context
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	public static EGLContext eglGetCurrentContext() throws LWJGLException {
		//LWJGLUtil.log("eglGetCurrentContext");
		// Get current context
		final long ctx = neglGetCurrentContext();
		if ( ctx == EGL_NO_CONTEXT )
			return null;

		// Get current display
		final EGLDisplay display = eglGetCurrentDisplay();

		// Query context's CONFIG_ID
		final IntBuffer attrib_list = APIUtil.getBufferInt();
		neglQueryContext(display.getPointer(), ctx, EGL_CONFIG_ID, MemoryUtil.getAddress0(attrib_list));

		final EGLConfig config = getEGLConfig(display, attrib_list);

		// Create the context handle
		return new EGLContext(display, config, ctx);
	}

	/**
	 * Returns true if the specified EGL context is the current context.
	 * This method is faster than using {@code #eglGetCurrentContext}
	 * and comparing the two EGLContext objects.
	 *
	 * @param context the EGL context
	 *
	 * @return true if the EGL context is current
	 *
	 * @see #eglGetCurrentContext()
	 */
	public static boolean eglIsCurrentContext(EGLContext context) {
		//LWJGLUtil.log("eglIsCurrentContext");
		return neglGetCurrentContext() == context.getPointer();
	}

	private static native long neglGetCurrentContext();

	/**
	 * Returns the EGL surfaces used for rendering by the current context.
	 * If there is no context current, null is returned.
	 *
	 * @param readdraw the read or draw surface
	 *
	 * @return the current surface
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	public static EGLSurface eglGetCurrentSurface(int readdraw) throws LWJGLException {
		//LWJGLUtil.log("eglGetCurrentSurface");
		final long surface = neglGetCurrentSurface(readdraw);
		if ( surface == EGL_NO_SURFACE )
			return null;

		// Get current display
		EGLDisplay display = eglGetCurrentDisplay();

		// Query context's CONFIG_ID
		final IntBuffer attrib_list = APIUtil.getBufferInt();
		if ( !neglQuerySurface(display.getPointer(), surface, EGL_CONFIG_ID, MemoryUtil.getAddress0(attrib_list)) )
			throwEGLError("Failed to query surface EGL config ID.");

		final EGLConfig config = getEGLConfig(display, attrib_list);

		// Create the surface handle
		return new EGLSurface(display, config, surface);
	}

	private static native long neglGetCurrentSurface(int readdraw);

	/**
	 * Returns the EGL display associated with the current context.
	 *
	 * @return the current display
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	public static EGLDisplay eglGetCurrentDisplay() throws LWJGLException {
		//LWJGLUtil.log("eglGetCurrentDisplay");
		return new EGLDisplay(neglGetCurrentDisplay());
	}

	private static native long neglGetCurrentDisplay();

	/**
	 * Returns the value of the specified EGL context attribute in the value parameter.
	 *
	 * @param dpy       the EGL display
	 * @param ctx       the EGL context
	 * @param attribute the context attribute
	 * @param value     the attribute value will be returned here
	 *
	 * @throws org.lwjgl.LWJGLException if an EGL error occurs
	 */
	public static void eglQueryContext(EGLDisplay dpy, EGLContext ctx, int attribute, IntBuffer value) throws LWJGLException {
		//LWJGLUtil.log("eglQueryContext");
		BufferChecks.checkBuffer(value, 1);
		if ( !neglQueryContext(dpy.getPointer(), ctx.getPointer(), attribute, MemoryUtil.getAddress(value)) )
			throwEGLError("Failed to query context attribute.");
	}

	private static native boolean neglQueryContext(long dpy_ptr, long ctx_ptr, int attribute, long value);

	/**
	 * Prevents native rendering API functions from executing until any
	 * outstanding client API rendering affecting the same surface is complete.
	 *
	 * @return true if the wait was successful, false is an EGL error occurs
	 */
	public static native boolean eglWaitClient();

	/**
	 * This method does the equivalent of:<br>
	 * <code>
	 * EGLenum api = eglQueryAPI();
	 * eglBindAPI(EGL_OPENGL_ES_API);
	 * eglWaitClient();
	 * eglBindAPI(api);
	 * </code>
	 *
	 * @return true if the wait was successful, false if an EGL error occurs
	 */
	public static native boolean eglWaitGL();

	/**
	 * Prevents a client API command sequence from executing until any outstanding
	 * native rendering affecting the same surface is complete.
	 *
	 * @param engine the native rendering engine
	 *
	 * @return true if the wait was successful, false if an EGL error occurs
	 */
	public static native boolean eglWaitNative(int engine);

	/**
	 * Posts the color buffer to the window.
	 *
	 * @param dpy     the EGL display
	 * @param surface the EGL back-buffered window surface
	 *
	 * @throws org.lwjgl.LWJGLException      if an EGL occurs
	 * @throws PowerManagementEventException if an EGL power management event occurs
	 */
	static void eglSwapBuffers(EGLDisplay dpy, EGLSurface surface) throws LWJGLException, PowerManagementEventException {
		//LWJGLUtil.log("eglSwapBuffers");
		if ( !neglSwapBuffers(dpy.getPointer(), surface.getPointer()) ) {
			final int error = eglGetError();
			if ( error == EGL_CONTEXT_LOST )
				throw new PowerManagementEventException();
			else
				throwEGLError("Failed to swap buffers.", error);
		}
	}

	private static native boolean neglSwapBuffers(long dpy_ptr, long surface_ptr);

	//EGLAPI EGLBoolean EGLAPIENTRY eglCopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target);

	/* --------------------------------
			HELPER METHODS
	-------------------------------- */

	static void checkAttribList(IntBuffer attrib_list) {
		if ( attrib_list == null )
			return;

		//BufferChecks.checkDirect(attrib_list);
		if ( attrib_list.remaining() % 2 != 1 )
			throw new IllegalArgumentException("Invalid number of values in attribute list.");
		if ( attrib_list.get(attrib_list.limit() - 1) != EGL_NONE )
			throw new IllegalArgumentException("The attribute list is not terminated with EGL_NONE.");
	}

	private static EGLConfig getEGLConfig(final EGLDisplay dpy, final IntBuffer attrib_list) throws LWJGLException {
		final int configID = attrib_list.get(0);

		// -- This fails on the emulator
		// Get EGL config used by the context
		attrib_list.put(0, EGL_CONFIG_ID).put(1, configID).put(2, EGL_NONE);

		final PointerBuffer configs_buffer = APIUtil.getBufferPointer(1);
		if ( !neglChooseConfig(dpy.getPointer(), MemoryUtil.getAddress(attrib_list), MemoryUtil.getAddress0(configs_buffer), 1, MemoryUtil.getAddress(attrib_list, 3)) )
			throwEGLError("Failed to choose EGL config.");

		return new EGLConfig(dpy, configs_buffer.get(0));

		// -- Emulator workaround
		/*
		EGLConfig config = null;

		final EGLConfig[] configs = eglGetConfigs(dpy, null, attrib_list);
		final int config_size = attrib_list.get(0);
		for ( int i = 0; i < config_size; i++ ) {
			if ( configs[i].getConfigID() == configID ) {
				config = configs[i];
				break;
			}
		}

		if ( config == null )
			throwEGLError("Failed to retrieve EGL config for current context.");

		return config;
		//*/
	}

	static void throwEGLError(final String msg) throws LWJGLException {
		throwEGLError(msg, eglGetError());
	}

	static void throwEGLError(String msg, final int error) throws LWJGLException {
		if ( error != EGL_SUCCESS )
			msg += " EGL error: " + Util.translateEGLErrorString(error);

		throw new LWJGLException(msg);
	}

}