/*
 * Copyright (c) 2009-2012 jMonkeyEngine
 * 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 'jMonkeyEngine' 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 com.jme3.renderer;

import com.jme3.shader.Shader;
import com.jme3.shader.Shader.ShaderSource;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.FrameBuffer.RenderBuffer;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import java.util.Collection;

/**
 * <code>Caps</code> is an enum specifying a capability that the {@link Renderer}
 * supports.
 * 
 * @author Kirill Vainer
 */
public enum Caps {

    /**
     * Supports {@link FrameBuffer FrameBuffers}.
     * <p>
     * OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.<br>
     * OpenGL ES: Renderer supports OpenGL ES 2.0.
     *//**
     * Supports {@link FrameBuffer FrameBuffers}.
     * <p>
     * OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.<br>
     * OpenGL ES: Renderer supports OpenGL ES 2.0.
     *//**
     * Supports {@link FrameBuffer FrameBuffers}.
     * <p>
     * OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.<br>
     * OpenGL ES: Renderer supports OpenGL ES 2.0.
     *//**
     * Supports {@link FrameBuffer FrameBuffers}.
     * <p>
     * OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.<br>
     * OpenGL ES: Renderer supports OpenGL ES 2.0.
     */
    FrameBuffer,

    /**
     * Supports framebuffer Multiple Render Targets (MRT)
     * <p>
     * OpenGL: Renderer exposes the GL_ARB_draw_buffers extension
     */
    FrameBufferMRT,

    /**
     * Supports framebuffer multi-sampling
     * <p>
     * OpenGL: Renderer exposes the GL EXT framebuffer multisample extension<br>
     * OpenGL ES: Renderer exposes GL_APPLE_framebuffer_multisample or
     * GL_ANGLE_framebuffer_multisample.
     */
    FrameBufferMultisample,

    /**
     * Supports texture multi-sampling
     * <p>
     * OpenGL: Renderer exposes the GL_ARB_texture_multisample extension<br>
     * OpenGL ES: Renderer exposes the GL_IMG_multisampled_render_to_texture
     * extension.
     */
    TextureMultisample,

    /**
     * Supports OpenGL 2.0 or OpenGL ES 2.0.
     */
    OpenGL20,
    
    /**
     * Supports OpenGL 2.1
     */
    OpenGL21,
    
    /**
     * Supports OpenGL 3.0
     */
    OpenGL30,
    
    /**
     * Supports OpenGL 3.1
     */
    OpenGL31,
    
    /**
     * Supports OpenGL 3.2
     */
    OpenGL32,
    /**
     * Supports OpenGL 3.3
     */
    OpenGL33,
    /**
     * Supports OpenGL 4.0
     */
    OpenGL40,
    /**
     * Do not use.
     * 
     * @deprecated do not use.
     */
    @Deprecated
    Reserved0,
    
    /**
     * Supports GLSL 1.0
     */
    GLSL100,
    
    /**
     * Supports GLSL 1.1
     */
    GLSL110,
    
    /**
     * Supports GLSL 1.2
     */
    GLSL120,
    
    /**
     * Supports GLSL 1.3
     */
    GLSL130,
    
    /**
     * Supports GLSL 1.4
     */
    GLSL140,
    
    /**
     * Supports GLSL 1.5
     */
    GLSL150,
    
    /**
     * Supports GLSL 3.3
     */
    GLSL330,
    /**
     * Supports GLSL 4.0
     */
    GLSL400,
    /**
     * Supports reading from textures inside the vertex shader.
     */
    VertexTextureFetch,

    /**
     * Supports geometry shader.
     */
    GeometryShader,
    /**
     * Supports Tesselation shader
     */
    TesselationShader,
    /**
     * Supports texture arrays
     */
    TextureArray,

    /**
     * Supports texture buffers
     */
    TextureBuffer,

    /**
     * Supports floating point & half textures (Format.RGB16F)
     */
    FloatTexture,

    /**
     * Supports floating point FBO color buffers (Format.RGB16F)
     */
    FloatColorBuffer,

    /**
     * Supports floating point depth buffer
     */
    FloatDepthBuffer,

    /**
     * Supports Format.RGB111110F for textures
     */
    PackedFloatTexture,

    /**
     * Supports Format.RGB9E5 for textures
     */
    SharedExponentTexture,

    /**
     * Supports Format.RGB111110F for FBO color buffers
     */
    PackedFloatColorBuffer,

    /**
     * Supports Format.RGB9E5 for FBO color buffers
     */
    SharedExponentColorBuffer,
    
    /**
     * Do not use.
     * 
     * @deprecated do not use.
     */
    @Deprecated
    Reserved1,

    /**
     * Supports Non-Power-Of-Two (NPOT) textures and framebuffers
     */
    NonPowerOfTwoTextures,

    /**
     * Supports geometry instancing.
     */
    MeshInstancing,

    /**
     * Supports VAO, or vertex buffer arrays
     */
    VertexBufferArray,

    /**
     * Supports multisampling on the screen
     */
    Multisample,
    
    /**
     * Supports FBO with Depth24Stencil8 image format
     */
    PackedDepthStencilBuffer,
    
    /**
     * Supports sRGB framebuffers and sRGB texture format
     */
    Srgb,
    
    /**
     * Supports blitting framebuffers.
     */
    FrameBufferBlit,
    
    /**
     * Supports {@link Format#DXT1} and sister formats.
     */
    TextureCompressionS3TC,
    
    /**
     * Supports anisotropic texture filtering.
     */
    TextureFilterAnisotropic,
    
    /**
     * Supports {@link Format#ETC1} texture compression.
     */
    TextureCompressionETC1,
    
    /**
     * Supports {@link Format#ETC1} texture compression by uploading
     * the texture as ETC2 (they are backwards compatible).
     */
    TextureCompressionETC2,
    
    /**
     * Supports OpenGL ES 2
     */
    OpenGLES20,
    
    /**
     * Supports RGB8 / RGBA8 textures
     */
    Rgba8,
    
    /**
     * Supports depth textures.
     */
    DepthTexture,
    
    /**
     * Supports 32-bit index buffers.
     */
    IntegerIndexBuffer,
    
    /**
     * Partial support for non-power-of-2 textures, typically found
     * on OpenGL ES 2 devices.
     * <p>
     * Use of NPOT textures is allowed iff:
     * <ul>
     * <li>The {@link Texture.WrapMode} is set to 
     * {@link Texture.WrapMode#EdgeClamp}.</li>
     * <li>Mip-mapping is not used, meaning {@link Texture.MinFilter} is set to
     * {@link Texture.MinFilter#BilinearNoMipMaps} or 
     * {@link Texture.MinFilter#NearestNoMipMaps}</li>
     * </ul>
     */
    PartialNonPowerOfTwoTextures,
    
    /**
     * When sampling cubemap edges, interpolate between the adjecent faces
     * instead of just sampling one face.
     * <p>
     * Improves the quality of environment mapping.
     */
    SeamlessCubemap,
    
    /**
     * Running with OpenGL 3.2+ core profile.
     * 
     * Compatibility features will not be available.
     */
    CoreProfile,
    
    /**
     * GPU can provide and accept binary shaders.
     */
    BinaryShader;

    /**
     * Returns true if given the renderer capabilities, the texture
     * can be supported by the renderer.
     * <p>
     * This only checks the format of the texture, non-power-of-2
     * textures are scaled automatically inside the renderer 
     * if are not supported natively.
     * 
     * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }.
     * @param tex The texture to check
     * @return True if it is supported, false otherwise.
     */
    public static boolean supports(Collection<Caps> caps, Texture tex){
        if (tex.getType() == Texture.Type.TwoDimensionalArray
         && !caps.contains(Caps.TextureArray))
            return false;

        Image img = tex.getImage();
        if (img == null)
            return true;

        Format fmt = img.getFormat();
        switch (fmt){
            case Depth24Stencil8:
                return caps.contains(Caps.PackedDepthStencilBuffer);
            case Depth32F:
                return caps.contains(Caps.FloatDepthBuffer);
            case RGB16F_to_RGB111110F:
            case RGB111110F:
                return caps.contains(Caps.PackedFloatTexture);
            case RGB16F_to_RGB9E5:
            case RGB9E5:
                return caps.contains(Caps.SharedExponentTexture);
            default:
                if (fmt.isFloatingPont())
                    return caps.contains(Caps.FloatTexture);
                        
                return true;
        }
    }
    
    private static boolean supportsColorBuffer(Collection<Caps> caps, RenderBuffer colorBuf){
        Format colorFmt = colorBuf.getFormat();
        if (colorFmt.isDepthFormat())
            return false;

        if (colorFmt.isCompressed())
            return false;

        switch (colorFmt){
            case RGB111110F:
                return caps.contains(Caps.PackedFloatColorBuffer);
            case RGB16F_to_RGB111110F:
            case RGB16F_to_RGB9E5:
            case RGB9E5:
                return false;
            default:
                if (colorFmt.isFloatingPont())
                    return caps.contains(Caps.FloatColorBuffer);

                return true;
        }
    }

    /**
     * Returns true if given the renderer capabilities, the framebuffer
     * can be supported by the renderer.
     * 
     * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }.
     * @param fb The framebuffer to check
     * @return True if it is supported, false otherwise.
     */
    public static boolean supports(Collection<Caps> caps, FrameBuffer fb){
        if (!caps.contains(Caps.FrameBuffer))
            return false;

        if (fb.getSamples() > 1
         && !caps.contains(Caps.FrameBufferMultisample))
            return false;

        RenderBuffer depthBuf = fb.getDepthBuffer();
        if (depthBuf != null){
            Format depthFmt = depthBuf.getFormat();
            if (!depthFmt.isDepthFormat()){
                return false;
            }else{
                if (depthFmt == Format.Depth32F
                 && !caps.contains(Caps.FloatDepthBuffer))
                    return false;
                
                if (depthFmt == Format.Depth24Stencil8
                 && !caps.contains(Caps.PackedDepthStencilBuffer))
                    return false;
            }
        }
        for (int i = 0; i < fb.getNumColorBuffers(); i++){
            if (!supportsColorBuffer(caps, fb.getColorBuffer(i))){
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if given the renderer capabilities, the shader
     * can be supported by the renderer.
     * 
     * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }.
     * @param shader The shader to check
     * @return True if it is supported, false otherwise.
     */
    public static boolean supports(Collection<Caps> caps, Shader shader){
        for (ShaderSource source : shader.getSources()) {
            if (source.getLanguage().startsWith("GLSL")) {
                int ver = Integer.parseInt(source.getLanguage().substring(4));
                switch (ver) {
                    case 100:
                        if (!caps.contains(Caps.GLSL100)) return false;
                    case 110:
                        if (!caps.contains(Caps.GLSL110)) return false;
                    case 120:
                        if (!caps.contains(Caps.GLSL120)) return false;
                    case 130:
                        if (!caps.contains(Caps.GLSL130)) return false;
                    case 140:
                        if (!caps.contains(Caps.GLSL140)) return false;
                    case 150:
                        if (!caps.contains(Caps.GLSL150)) return false;
                    case 330:
                        if (!caps.contains(Caps.GLSL330)) return false;
                    default:
                        return false;
                }
            }
        }
        return true;
    }

}
