/*
 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.javafx.perf;

import javafx.scene.Scene;

import com.sun.javafx.tk.Toolkit;

public abstract class PerformanceTracker {
    /**
     * This class provides a way to track performance metrics such as first
     * paint, instant fps, average fps.<p>
     *
     * Typical use scenario is to obtain the tracker from a {@code Scene}, and use it
     * to get instant or average fps. It is also possible to execute a user
     * function every time the scene is repainted {@see #onPulse}.
     *
     */
    private static SceneAccessor sceneAccessor;

    /**
     * Use method instead of def to avoid explicit initialization which could
     * be circular (this class may be referenced before the toolkit is initialized).
     *
     * @treatAsPrivate implementation detail
     */
    public static boolean isLoggingEnabled() {
        return Toolkit.getToolkit().getPerformanceTracker().perfLoggingEnabled;
    }

    /**
     * @treatAsPrivate implementation detail
     */
    public static abstract class SceneAccessor {
        public abstract void setPerfTracker(Scene scene, PerformanceTracker tracker);
        public abstract PerformanceTracker getPerfTracker(Scene scene);
    }

    /**
     * Creates a {@code PerformanceTracker} for this scene. There could be only one
     * performance tracker per scene so once a tracker is created for a scene it
     * will be returned for each {@code getSceneTracker} call until the tracker
     * is released with {@link #releaseSceneTracker(Scene)}. <p>
     *
     * @return an instance of {@code PerformanceTracker} associated with the scene
     * or null if the tracker couldn't be created.
     */
    public static PerformanceTracker getSceneTracker(Scene scene) {
        PerformanceTracker tracker = null;
        if (sceneAccessor != null) {
            tracker = sceneAccessor.getPerfTracker(scene);
            if (tracker == null) {
                 tracker = Toolkit.getToolkit().createPerformanceTracker();
            }
            sceneAccessor.setPerfTracker(scene, tracker);
        }
        return tracker;
    }

    /**
     * Removes the tracker from the scene.
     */
    public static void releaseSceneTracker(Scene scene) {
        if (sceneAccessor != null) {
            sceneAccessor.setPerfTracker(scene, null);
        }
    }

    /**
     * @treatAsPrivate implementation detail
     */
    public static void setSceneAccessor(SceneAccessor accessor) {
        sceneAccessor = accessor;
    }

    // TODO: tdv implement media-specific tracker
    //public function getMediaTracker(player : MediaPlayer) : PerformanceTracker {
    //    null;
    //}

    /**
     * Log an event with given description.
     *
     * @treatAsPrivate implementation detail
     */
    public static void logEvent(String desc) {
        Toolkit.getToolkit().getPerformanceTracker().doLogEvent(desc);
    }

    /**
     * Output full log of events so far.
     *
     * @treatAsPrivate implementation detail
     */
    public static void outputLog() {
        Toolkit.getToolkit().getPerformanceTracker().doOutputLog();
    }

    /**
     * @treatAsPrivate implementation detail
     */
    private boolean perfLoggingEnabled;
    protected boolean isPerfLoggingEnabled() { return perfLoggingEnabled; }
    protected void setPerfLoggingEnabled(boolean value) { perfLoggingEnabled = value; }

    private boolean firstPulse = true;
    private float instantFPS;
    private int instantFPSFrames;
    private long instantFPSStartTime;
    private long avgStartTime;
    private int avgFramesTotal;
    private float instantPulses;
    private int instantPulsesFrames;
    private long instantPulsesStartTime;
    private long avgPulsesStartTime;
    private int avgPulsesTotal;

    protected abstract long nanoTime();
    /**
     * @treatAsPrivate implementation detail
     */
    public abstract void doOutputLog();
    /**
     * @treatAsPrivate implementation detail
     */
    public abstract void doLogEvent(String s);

    /**
     * Returns the number of frames rendered in the last second or so.
     */
    public synchronized float getInstantFPS() { return instantFPS; }

    /**
     * Returns the average FPS in the time period since the least call
     * to {@link #resetAverageFPS()}.
     */
    public synchronized float getAverageFPS() {
        long nsseconds = nanoTime() - avgStartTime;
        if (nsseconds > 0) {
            return ((avgFramesTotal * 1000000000f) / nsseconds);
        }
        return getInstantFPS();
    }

    public synchronized void resetAverageFPS() {
        avgStartTime = nanoTime();
        avgFramesTotal = 0;
    }

    /**
     * Returns the number of pulses received in the last second or so.
     */
    public float getInstantPulses() { return instantPulses; }

    /**
     * Returns the average pulses per second in the time period since the least call
     * to {@link #resetAveragePulses()}.
     */
    public float getAveragePulses() {
        long nsseconds = nanoTime() - avgPulsesStartTime;
        if (nsseconds > 0) {
            return ((avgPulsesTotal * 1000000000f) / nsseconds);
        }
        return getInstantPulses();
    }

    public void resetAveragePulses() {
        avgPulsesStartTime = nanoTime();
        avgPulsesTotal = 0;
    }

    /**
     * @treatAsPrivate implementation detail
     */
    public void pulse() {
        calcPulses();
        updateInstantFps();
        if (firstPulse) {
            doLogEvent("first repaint");
            firstPulse = false;
            resetAverageFPS();
            resetAveragePulses();
            if (onFirstPulse != null) {
                onFirstPulse.run();
            }
        }

        if (onPulse != null) onPulse.run();
    }

    public void frameRendered() {
        calcFPS();
        if (onRenderedFrameTask != null) {
            onRenderedFrameTask.run();
        }
    }

    private void calcPulses() {
        avgPulsesTotal++;
        instantPulsesFrames++;
        updateInstantPulses();
    }

    private synchronized void calcFPS() {
        avgFramesTotal++;
        instantFPSFrames++;
        updateInstantFps();
    }

    private synchronized void updateInstantFps() {
        long timeSince = nanoTime() - instantFPSStartTime;
        if (timeSince > 1000000000) {
            instantFPS = ((1000000000f * instantFPSFrames) / timeSince);
            instantFPSFrames = 0;
            instantFPSStartTime = nanoTime();
        }
    }

    private void updateInstantPulses() {
        long timeSince = nanoTime() - instantPulsesStartTime;
        if (timeSince > 1000000000) {
            instantPulses = ((1000000000f * instantPulsesFrames) / timeSince);
            instantPulsesFrames = 0;
            instantPulsesStartTime = nanoTime();
        }
    }

    /**
     * Called on every rendering pulse.
     */
    private Runnable onPulse;
    public void setOnPulse(Runnable value) { onPulse = value; }
    public Runnable getOnPulse() { return onPulse; }

    /**
     * Called on the first rendering pulse since this tracker has been created.
     */
    private Runnable onFirstPulse;
    public void setOnFirstPulse(Runnable value) { onFirstPulse = value; }
    public Runnable getOnFirstPulse() { return onFirstPulse; }

    private Runnable onRenderedFrameTask;
    public void setOnRenderedFrameTask(Runnable value) { onRenderedFrameTask = value; }
    public Runnable getOnRenderedFrameTask() { return onRenderedFrameTask; }
}

