package com.brackeen.javagamebook.util;

/**
    Wygładza skoki w odczycie czasu z słabego zegara.
    Jest to prosty algorytm, który jest nieco niedokładny 
    (czas wygładzony nieco śpieszy się w stosunku do bieżącego), ale
    daje lepiej wyglądające wyniki.
*/
public class TimeSmoothie {

    /**
        Jak często przeliczać ilość ramek na sekundę.
    */
    protected static final long FRAME_RATE_RECALC_PERIOD = 500;

    /**
        Nie pozwalamy na czas między ramkami dłuższy niż 100 ms.
    */
    protected static final long MAX_ELAPSED_TIME = 100;

    /**
        Średnia z kilku ostatnich próbek zebranych w czasie ostatnich 100 ms.
    */
    protected static final long AVERAGE_PERIOD = 100;

    protected static final int NUM_SAMPLES_BITS = 6; // 64 próbki
    protected static final int NUM_SAMPLES = 1 << NUM_SAMPLES_BITS;
    protected static final int NUM_SAMPLES_MASK = NUM_SAMPLES - 1;

    protected long[] samples;
    protected int numSamples = 0;
    protected int firstIndex = 0;

    // do obliczania ilości ramek na sekundę
    protected int numFrames = 0;
    protected long startTime;
    protected float frameRate;

    public TimeSmoothie() {
        samples = new long[NUM_SAMPLES];
    }

    /**
        Dodaje określoną próbkę czasu i zwraca średnią 
        z wszystkich zapisanych próbek.
    */
    public long getTime(long elapsedTime) {
        addSample(elapsedTime);
        return getAverage();
    }

    /**
        Dodaje próbkę czasu.
    */
    public void addSample(long elapsedTime) {
        numFrames++;

        // odczytanie czasu
        elapsedTime = Math.min(elapsedTime, MAX_ELAPSED_TIME);

        // dodanie próbki do listy
        samples[(firstIndex + numSamples) & NUM_SAMPLES_MASK] =
            elapsedTime;
        if (numSamples == samples.length) {
            firstIndex = (firstIndex + 1) & NUM_SAMPLES_MASK;
        }
        else {
            numSamples++;
        }
    }

    /**
        Pobieranie średniej z zapisanych próbek dźwięku.
    */
    public long getAverage() {
        long sum = 0;
        for (int i=numSamples-1; i>=0; i--) {
            sum+=samples[(firstIndex + i) & NUM_SAMPLES_MASK];

            // jeżeli osiągnięty został okres uśredniania, obliczamy 
            // i zwracamy średnią.
            if (sum >= AVERAGE_PERIOD) {
                Math.round((double)sum / (numSamples-i));
            }
        }
        return Math.round((double)sum / numSamples);
    }


    /**
        Zwraca ilość ramek na sekundę (ilość wywołań getTime() lub
        addSample() w czasie rzeczywistym). Ilość ramek na sekundę 
        jest przeliczana co 500ms.
    */
    public float getFrameRate() {
        long currTime = System.currentTimeMillis();

        // obliczenie ilości ramek na sekundę co 500 milisekund
        if (currTime > startTime + FRAME_RATE_RECALC_PERIOD) {
            frameRate = (float)numFrames * 1000 /
                (currTime - startTime);
            startTime = currTime;
            numFrames = 0;
        }

        return frameRate;
    }
}
