/*
 * 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.scene.Mesh;
import com.jme3.shader.Shader;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import com.jme3.util.IntMap;

/**
 * The statistics class allows tracking of real-time rendering statistics.
 * <p>
 * The <code>Statistics</code> can be retrieved by using {@link Renderer#getStatistics() }.
 * 
 * @author Kirill Vainer
 */
public class Statistics {

    protected boolean enabled = false;

    protected int numObjects;
    protected int numTriangles;
    protected int numVertices;
    protected int numShaderSwitches;
    protected int numTextureBinds;
    protected int numFboSwitches;
    protected int numUniformsSet;

    protected int memoryShaders;
    protected int memoryFrameBuffers;
    protected int memoryTextures;

    protected IntMap<Void> shadersUsed = new IntMap<Void>();
    protected IntMap<Void> texturesUsed = new IntMap<Void>();
    protected IntMap<Void> fbosUsed = new IntMap<Void>();

    protected int lastShader = -1;
    
    /**
     * Returns a list of labels corresponding to each statistic.
     * 
     * @return a list of labels corresponding to each statistic.
     * 
     * @see #getData(int[]) 
     */
    public String[] getLabels(){
        return new String[]{ "Vertices",
                             "Triangles",
                             "Uniforms",

                             "Objects",

                             "Shaders (S)",
                             "Shaders (F)",
                             "Shaders (M)",

                             "Textures (S)",
                             "Textures (F)",
                             "Textures (M)",

                             "FrameBuffers (S)",
                             "FrameBuffers (F)",
                             "FrameBuffers (M)" };

    }

    /**
     * Retrieves the statistics data into the given array.
     * The array should be as large as the array given in 
     * {@link #getLabels() }.
     * 
     * @param data The data array to write to
     */
    public void getData(int[] data){
        data[0] = numVertices;
        data[1] = numTriangles;
        data[2] = numUniformsSet;
        data[3] = numObjects;

        data[4] = numShaderSwitches;
        data[5] = shadersUsed.size();
        data[6] = memoryShaders;

        data[7] = numTextureBinds;
        data[8] = texturesUsed.size();
        data[9] = memoryTextures;
        
        data[10] = numFboSwitches;
        data[11] = fbosUsed.size();
        data[12] = memoryFrameBuffers;
    }

    /**
     * Called by the Renderer when a mesh has been drawn.
     */
    public void onMeshDrawn(Mesh mesh, int lod, int count){
        if( !enabled )
            return;
            
        numObjects += 1;
        numTriangles += mesh.getTriangleCount(lod) * count;
        numVertices += mesh.getVertexCount() * count;
    }
    
    /**
     * Called by the Renderer when a mesh has been drawn.
     */
    public void onMeshDrawn(Mesh mesh, int lod){
        onMeshDrawn(mesh, lod, 1);
    }

    /**
     * Called by the Renderer when a shader has been utilized.
     * 
     * @param shader The shader that was used
     * @param wasSwitched If true, the shader has required a state switch
     */
    public void onShaderUse(Shader shader, boolean wasSwitched){
        assert shader.getId() >= 1;

        if( !enabled )
            return;
        
        // Reduces unneccessary hashmap lookups if
        // we already considered this shader.
        if (lastShader != shader.getId()) {
            lastShader = shader.getId();
            if (!shadersUsed.containsKey(shader.getId())) {
                shadersUsed.put(shader.getId(), null);
            }
        }

        if (wasSwitched)
            numShaderSwitches++;
    }

    /**
     * Called by the Renderer when a uniform was set.
     */
    public void onUniformSet(){
        if( !enabled )
            return;
        numUniformsSet ++;
    }

    /**
     * Called by the Renderer when a texture has been set.
     * 
     * @param image The image that was set
     * @param wasSwitched If true, the texture has required a state switch
     */
    public void onTextureUse(Image image, boolean wasSwitched){
        assert image.getId() >= 1;

        if( !enabled )
            return;
            
        if (!texturesUsed.containsKey(image.getId()))
            texturesUsed.put(image.getId(), null);

        if (wasSwitched)
            numTextureBinds ++;
    }

    /**
     * Called by the Renderer when a framebuffer has been set.
     * 
     * @param fb The framebuffer that was set
     * @param wasSwitched If true, the framebuffer required a state switch
     */
    public void onFrameBufferUse(FrameBuffer fb, boolean wasSwitched){
        if( !enabled )
            return;
            
        if (fb != null){
            assert fb.getId() >= 1;

            if (!fbosUsed.containsKey(fb.getId()))
                fbosUsed.put(fb.getId(), null);
        }

        if (wasSwitched)
            numFboSwitches ++;
    }
    
    /**
     * Clears all frame-specific statistics such as objects used per frame.
     */
    public void clearFrame(){
        shadersUsed.clear();
        texturesUsed.clear();
        fbosUsed.clear();

        numObjects = 0;
        numTriangles = 0;
        numVertices = 0;
        numShaderSwitches = 0;
        numTextureBinds = 0;
        numFboSwitches = 0;
        numUniformsSet = 0;
        
        lastShader = -1;
    }

    /**
     * Called by the Renderer when it creates a new shader
     */
    public void onNewShader(){
        if( !enabled )
            return;
        memoryShaders ++;
    }

    /**
     * Called by the Renderer when it creates a new texture
     */
    public void onNewTexture(){
        if( !enabled )
            return;
        memoryTextures ++;
    }

    /**
     * Called by the Renderer when it creates a new framebuffer
     */
    public void onNewFrameBuffer(){
        if( !enabled )
            return;
        memoryFrameBuffers ++;
    }

    /**
     * Called by the Renderer when it deletes a shader
     */
    public void onDeleteShader(){
        if( !enabled )
            return;
        memoryShaders --;
    }

    /**
     * Called by the Renderer when it deletes a texture
     */
    public void onDeleteTexture(){
        if( !enabled )
            return;
        memoryTextures --;
    }

    /**
     * Called by the Renderer when it deletes a framebuffer
     */
    public void onDeleteFrameBuffer(){
        if( !enabled )
            return;
        memoryFrameBuffers --;
    }

    /**
     * Called when video memory is cleared.
     */
    public void clearMemory(){
        memoryFrameBuffers = 0;
        memoryShaders = 0;
        memoryTextures = 0;
    }

    public void setEnabled( boolean f ) {
        this.enabled = f;
    }
    
    public boolean isEnabled() {
        return enabled;
    }
}
