/*
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ice4j.util;

/**
 * This originally comes from webrtc.org but has been moved here so that it can
 * be reused more easily.
 *
 * webrtc/modules/remote_bitrate_estimator/rate_statistics.cc
 * webrtc/modules/remote_bitrate_estimator/rate_statistics.h
 *
 * @author Lyubomir Marinov
 * @author Eng Chong Meng
 */
public class RateStatistics
{
    /**
     * Total count recorded in buckets.
     */
    private long accumulatedCount;

    /**
     * Counters are kept in buckets (circular buffer), with one bucket per millisecond.
     */
    private final long[] buckets;

    /**
     * Bucket index of oldest counter recorded in buckets.
     */
    private int oldestIndex;

    /**
     * Oldest time recorded in buckets.
     */
    private long oldestTime;

    /**
     * To convert counts/ms to desired units.
     */
    private final float scale;

    /**
     * Initializes a new {@link RateStatistics} instance with a default scale
     * of 8000 (i.e. if the input is in bytes, the result will be in bits per second).
     * @param windowSizeMs window size in ms for the rate estimation
     */
    public RateStatistics(int windowSizeMs)
    {
        this(windowSizeMs, 8000F);
    }

    /**
     * @param windowSizeMs window size in ms for the rate estimation
     * @param scale coefficient to convert counts/ms to desired units. For
     * example, if counts represents bytes, use <tt>8*1000</tt> to go to bits/s.
     */
    public RateStatistics(int windowSizeMs, float scale)
    { 
        buckets = new long[windowSizeMs + 1]; // N ms in (N+1) buckets.
        this.scale = scale / (buckets.length - 1);
    }

    private synchronized void eraseOld(long nowMs)
    {
        long newOldestTime = nowMs - buckets.length + 1;

        if (newOldestTime <= oldestTime)
            return;

        while (oldestTime < newOldestTime)
        {
            long countInOldestBucket = buckets[oldestIndex];

            accumulatedCount -= countInOldestBucket;
            buckets[oldestIndex] = 0L;
            if (++oldestIndex >= buckets.length)
            {
                oldestIndex = 0;
            }
            ++oldestTime;
            if (accumulatedCount == 0L)
            {
                // This guarantees we go through all the buckets at most once,
                // even if newOldestTime is far greater than oldestTime.
                break;
            }
        }
        oldestTime = newOldestTime;
    }

    public long getRate()
    {
        return getRate(System.currentTimeMillis());
    }

    public long getRate(long nowMs)
    {
        eraseOld(nowMs);
        return (long) (accumulatedCount * scale + 0.5F);
    }

    public long getAccumulatedCount()
    {
        return getAccumulatedCount(System.currentTimeMillis());
    }

    public long getAccumulatedCount(long nowMs)
    {
        eraseOld(nowMs);
        return accumulatedCount;
    }


    public void update(int count, long nowMs)
    {
        if (nowMs < oldestTime) // Too old data is ignored.
            return;

        synchronized (this)
        {
            eraseOld(nowMs);

            int nowOffset = (int) (nowMs - oldestTime);
            int index = oldestIndex + nowOffset;

            if (index >= buckets.length)
                index -= buckets.length;
            buckets[index] += count;
            accumulatedCount += count;
        }
    }
}
