package com.jme3.system.android;

import java.util.logging.Logger;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;

import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView.EGLConfigChooser;

/**
 * AndroidConfigChooser is used to determine the best suited EGL Config
 * @author larynx
 *
 */
public class AndroidConfigChooser implements EGLConfigChooser 
{
    private static final Logger logger = Logger.getLogger(AndroidConfigChooser.class.getName());
    
    protected int clientOpenGLESVersion = 0;
    protected EGLConfig bestConfig = null;
    protected EGLConfig fastestConfig = null;
    protected EGLConfig choosenConfig = null;
    protected ConfigType type;
    protected int pixelFormat;
    
    protected boolean verbose = false;
    
    private final static int EGL_OPENGL_ES2_BIT = 4;

    public enum ConfigType 
    {
        /**
         * RGB565, 0 alpha, 16 depth, 0 stencil
         */
        FASTEST,
        /**
         * RGB???, 0 alpha, >=16 depth, 0 stencil
         */
        BEST,
        /**
         * Turn off config chooser and use hardcoded
         * setEGLContextClientVersion(2);
         * setEGLConfigChooser(5, 6, 5, 0, 16, 0);
         */
        LEGACY
    }
    
    public AndroidConfigChooser(ConfigType type, boolean verbose)
    {
        this.type = type;
        this.verbose = verbose;
    }
        
    /**
     * Gets called by the GLSurfaceView class to return the best config
     */    
    @Override
    public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display)
    {
        logger.info("GLSurfaceView asks for egl config, returning: ");
        logEGLConfig(choosenConfig, display, egl);
        return choosenConfig;
    }
    
    /**
     * findConfig is used to locate the best config and init the chooser with
     * @param egl
     * @param display
     * @return true if successfull, false if no config was found
     */
    public boolean findConfig(EGL10 egl, EGLDisplay display)
    {           
        
        if (type == ConfigType.BEST)
        {        	
        	ComponentSizeChooser compChooser = new ComponentSizeChooser(8, 8, 8, 8, 16, 0);
        	choosenConfig = compChooser.chooseConfig(egl, display);
            logger.info("JME3 using best EGL configuration available here: ");
        }
        else
        {
        	ComponentSizeChooser compChooser = new ComponentSizeChooser(5, 6, 5, 0, 16, 0);
        	choosenConfig = compChooser.chooseConfig(egl, display);
            logger.info("JME3 using fastest EGL configuration available here: ");
        }
        
        if (choosenConfig != null)
        {
            logger.info("JME3 using choosen config: "); 
            logEGLConfig(choosenConfig, display, egl);               
            pixelFormat = getPixelFormat(choosenConfig, display, egl);
            clientOpenGLESVersion = getOpenGLVersion(choosenConfig, display, egl);
            return true;
        }
        else
        {
            logger.severe("###ERROR### Unable to get a valid OpenGL ES 2.0 config, nether Fastest nor Best found! Bug. Please report this.");
            clientOpenGLESVersion = 1;
            pixelFormat = PixelFormat.UNKNOWN;
            return false;
        }        
    }
    
    private int getPixelFormat(EGLConfig conf, EGLDisplay display, EGL10 egl)
    {
        int[] value = new int[1];
        int result = PixelFormat.RGB_565;

        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_RED_SIZE, value);
        if (value[0] == 8)
        {
            result = PixelFormat.RGBA_8888;
            /*
            egl.eglGetConfigAttrib(display, conf, EGL10.EGL_ALPHA_SIZE, value);
            if (value[0] == 8)
            {
                result = PixelFormat.RGBA_8888;
            }
            else
            {
                result = PixelFormat.RGB_888;
            }*/
        }
        
        if (verbose)
        {
            logger.info("Using PixelFormat " + result);                            
        }
    
        return result;                    
    }
    
    private int getOpenGLVersion(EGLConfig conf, EGLDisplay display, EGL10 egl)
    {
        int[] value = new int[1];
        int result = 1;
                        
        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_RENDERABLE_TYPE, value);
        // Check if conf is OpenGL ES 2.0
        if ((value[0] & EGL_OPENGL_ES2_BIT) != 0)
        {
            result = 2;
        }

        return result;                    
    }
    
    /**
     * log output with egl config details
     * @param conf
     * @param display
     * @param egl
     */
    public void logEGLConfig(EGLConfig conf, EGLDisplay display, EGL10 egl)
    {
        int[] value = new int[1];

        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_RED_SIZE, value);
        logger.info(String.format("EGL_RED_SIZE  = %d", value[0] ) );
        
        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_GREEN_SIZE, value);
        logger.info(String.format("EGL_GREEN_SIZE  = %d", value[0] ) );
        
        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_BLUE_SIZE, value);
        logger.info(String.format("EGL_BLUE_SIZE  = %d", value[0] ) );
        
        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_ALPHA_SIZE, value);
        logger.info(String.format("EGL_ALPHA_SIZE  = %d", value[0] ) );
        
        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_DEPTH_SIZE, value);
        logger.info(String.format("EGL_DEPTH_SIZE  = %d", value[0] ) );
                
        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_STENCIL_SIZE, value);
        logger.info(String.format("EGL_STENCIL_SIZE  = %d", value[0] ) );

        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_RENDERABLE_TYPE, value);
        logger.info(String.format("EGL_RENDERABLE_TYPE  = %d", value[0] ) );
        
        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_SURFACE_TYPE, value);
        logger.info(String.format("EGL_SURFACE_TYPE  = %d", value[0] ) );               
    }
    

    
    
    // Getter - Setter
    
    public EGLConfig getBestConfig() 
    {
        return bestConfig;
    }

    public void setBestConfig(EGLConfig bestConfig) 
    {
        this.bestConfig = bestConfig;
    }
    public EGLConfig getFastestConfig() 
    {
        return fastestConfig;
    }

    public void setFastestConfig(EGLConfig fastestConfig) 
    {
        this.fastestConfig = fastestConfig;
    }
        
    public int getClientOpenGLESVersion() 
    {
        return clientOpenGLESVersion;
    }

    public void setClientOpenGLESVersion(int clientOpenGLESVersion) 
    {
        this.clientOpenGLESVersion = clientOpenGLESVersion;
    }
    
    public int getPixelFormat() 
    {
        return pixelFormat;
    }
    
    
    
    private abstract class BaseConfigChooser implements EGLConfigChooser 
    {
		public BaseConfigChooser(int[] configSpec) 
		{
		    mConfigSpec = filterConfigSpec(configSpec);
		}
		
		public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) 
		{
		    int[] num_config = new int[1];
		    if (!egl.eglChooseConfig(display, mConfigSpec, null, 0,
		            num_config)) {
		        throw new IllegalArgumentException("eglChooseConfig failed");
		    }
		
		    int numConfigs = num_config[0];
		
		    if (numConfigs <= 0) {
		        throw new IllegalArgumentException(
		                "No configs match configSpec");
		    }
		
		    EGLConfig[] configs = new EGLConfig[numConfigs];
		    if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
		            num_config)) {
		        throw new IllegalArgumentException("eglChooseConfig#2 failed");
		    }
		    EGLConfig config = chooseConfig(egl, display, configs);
		    if (config == null) {
		        throw new IllegalArgumentException("No config chosen");
		    }
		    return config;
		}
		
		abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
		        EGLConfig[] configs);
		
		protected int[] mConfigSpec;
		
		private int[] filterConfigSpec(int[] configSpec) 
		{
		    if (clientOpenGLESVersion != 2) {
		        return configSpec;
		    }
		    /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
		     * And we know the configSpec is well formed.
		     */
		    int len = configSpec.length;
		    int[] newConfigSpec = new int[len + 2];
		    System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1);
		    newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE;
		    newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
		    newConfigSpec[len+1] = EGL10.EGL_NONE;
		    return newConfigSpec;
		}
    }
    
    /**
     * Choose a configuration with exactly the specified r,g,b,a sizes,
     * and at least the specified depth and stencil sizes.
     */
    private class ComponentSizeChooser extends BaseConfigChooser 
    {
        public ComponentSizeChooser(int redSize, int greenSize, int blueSize,
                int alphaSize, int depthSize, int stencilSize) 
        {
            super(new int[] {
                    EGL10.EGL_RED_SIZE, redSize,
                    EGL10.EGL_GREEN_SIZE, greenSize,
                    EGL10.EGL_BLUE_SIZE, blueSize,
                    EGL10.EGL_ALPHA_SIZE, alphaSize,
                    EGL10.EGL_DEPTH_SIZE, depthSize,
                    EGL10.EGL_STENCIL_SIZE, stencilSize,
                    EGL10.EGL_NONE});
            mValue = new int[1];
            mRedSize = redSize;
            mGreenSize = greenSize;
            mBlueSize = blueSize;
            mAlphaSize = alphaSize;
            mDepthSize = depthSize;
            mStencilSize = stencilSize;
       }

        @Override
        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) 
        {
            for (EGLConfig config : configs) 
            {
                int d = findConfigAttrib(egl, display, config,
                        EGL10.EGL_DEPTH_SIZE, 0);
                int s = findConfigAttrib(egl, display, config,
                        EGL10.EGL_STENCIL_SIZE, 0);
                if ((d >= mDepthSize) && (s >= mStencilSize)) 
                {
                    int r = findConfigAttrib(egl, display, config,
                            EGL10.EGL_RED_SIZE, 0);
                    int g = findConfigAttrib(egl, display, config,
                             EGL10.EGL_GREEN_SIZE, 0);
                    int b = findConfigAttrib(egl, display, config,
                              EGL10.EGL_BLUE_SIZE, 0);
                    int a = findConfigAttrib(egl, display, config,
                            EGL10.EGL_ALPHA_SIZE, 0);
                    if ((r == mRedSize) && (g == mGreenSize)
                            && (b == mBlueSize) && (a == mAlphaSize)) {
                        return config;
                    }
                }
            }
            return null;
        }

        private int findConfigAttrib(EGL10 egl, EGLDisplay display,
                EGLConfig config, int attribute, int defaultValue) 
        {

            if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) 
            {
                return mValue[0];
            }
            return defaultValue;
        }

        private int[] mValue;
        // Subclasses can adjust these values:
        protected int mRedSize;
        protected int mGreenSize;
        protected int mBlueSize;
        protected int mAlphaSize;
        protected int mDepthSize;
        protected int mStencilSize;
    }
    
    
    
    
}
