/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.support.math;

import java.io.DataOutputStream;

import freenet.support.Logger;
import freenet.support.Logger.LogLevel;

/**
 * Simple running average: linear mean of the last N reports.
 * @author amphibian
 */
public final class SimpleRunningAverage implements RunningAverage, Cloneable {
	private static final long serialVersionUID = -1;
    final double[] refs;
    int nextSlotPtr=0;
    int curLen=0;
    double total=0;
    int totalReports = 0;
    final double initValue;
    private boolean logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);

    @Override
	public final SimpleRunningAverage clone() {
    	// Deep copy needed. Implement Cloneable to shut up findbugs.
        return new SimpleRunningAverage(this);
    }
    
    /**
     * Clear the SRA
     */
    public synchronized void clear() {
        nextSlotPtr = 0;
        curLen = 0;
        totalReports = 0;
        total = 0;
        for(int i=0;i<refs.length;i++) refs[i] = 0.0;
    }
    
    /**
     *
     * @param length
     * @param initValue
     */
    public SimpleRunningAverage(int length, double initValue) {
        refs = new double[length];
        this.initValue = initValue;
        totalReports = 0;
    }
    
    /**
     *
     * @param a
     */
    public SimpleRunningAverage(SimpleRunningAverage a) {
        this.curLen = a.curLen;
        this.initValue = a.initValue;
        this.nextSlotPtr = a.nextSlotPtr;
        this.refs = a.refs.clone();
        this.total = a.total;
        this.totalReports = a.totalReports;
    }

    /**
     *
     * @return
     */
    @Override
    public synchronized double currentValue() {
        if(curLen == 0) return initValue;
        return total/curLen;
    }

    @Override
    public synchronized double valueIfReported(double r) {
        if(curLen < refs.length) {
            return (total+r)/(curLen+1);
        } else {
            // Don't increment curLen because it won't be incremented.
            return (total+r-refs[nextSlotPtr])/curLen;
        }
    }

    /**
     *
     * @param d
     */
    @Override
    public synchronized void report(double d) {
        totalReports++;
		if (logDEBUG)
			Logger.debug(this, "report(" + d + ") on " + this);
		if (curLen < refs.length)
			curLen++;
		else
			total -= popValue();
		pushValue(d);
		total += d;
	}

    /**
     *
     * @param value
     */
    protected synchronized void pushValue(double value){
		refs[nextSlotPtr] = value;
		nextSlotPtr++;
		if(nextSlotPtr >= refs.length) nextSlotPtr = 0;
    }

    /**
     *
     * @return
     */
    protected synchronized double popValue(){
		return refs[nextSlotPtr];
	}

    @Override
	public synchronized String toString() {
        return super.toString() + ": curLen="+curLen+", ptr="+nextSlotPtr+", total="+
        	total+", average="+total/curLen;
    }
    
    /**
     *
     * @param d
     */
    @Override
    public void report(long d) {
        report((double)d);
    }

    /**
     * 
     * @param out
     */
    public void writeDataTo(DataOutputStream out) {
        throw new UnsupportedOperationException();
    }

    @Override
    public synchronized long countReports() {
        return totalReports;
    }

    /**
     *
     * @param targetValue
     * @return
     */
    public synchronized double minReportForValue(double targetValue) {
        if(curLen < refs.length) {
            /** Don't need to remove any values before reporting,
             * so is slightly simpler.
             * (total + report) / (curLen + 1) >= targetValue =>
             * report / (curLen + 1) >= targetValue - total/(curLen+1)
             * => report >= (targetValue - total/(curLen + 1)) * (curLen+1)
             * => report >= targetValue * (curLen + 1) - total
             * EXAMPLE:
             * Mean (5, 5, 5, 5, 5, X) = 10 
             * X = 10 * 6 - 25 = 35
             * => Mean = (25 + 35) / 6 = 60/6 = 10
             */
            return targetValue * (curLen + 1) - total;
        } else {
            /** Essentially the same, but:
             * 1) Length will be curLen, not curLen+1, because is full.
             * 2) Take off the value that will be taken off first.
             */
            return targetValue * curLen - (total - refs[nextSlotPtr]);
        }
    }
}
