/*
 * Copyright 2007 Sun Microsystems, Inc.
 *
 * This file is part of jVoiceBridge.
 *
 * jVoiceBridge is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License version 2 as 
 * published by the Free Software Foundation and distributed hereunder 
 * to you.
 *
 * jVoiceBridge is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Sun designates this particular file as subject to the "Classpath"
 * exception as provided by Sun in the License file that accompanied this 
 * code. 
 */

package com.sun.voip;

public class TickerSleep implements Ticker {

    private String id;
    private int timePeriod;

    private int sleepTime;

    private long startTime;
    private int count;
    private int overSlept;
    private int underSlept;
    private int minDrift;
    private int maxDrift;
    private int totalDrift;

    private long lastTime;

    private int[] sleepDistribution = new int[200];

    private boolean armed;

    public TickerSleep(String id) {
	this.id = id;
    }

    public void arm(long delay, long timePeriod) {
	this.timePeriod = (int) timePeriod;

        String s = System.getProperty("os.name");

        if (s.startsWith("Mac OS X") == true) {
	    /*
	     * Mac OS has millisecond sleep granuarity
	     * and we can take advantage of that.
	     */
	    sleepTime = (int) timePeriod;
	} else {
	    sleepTime = (int) timePeriod / 2;
	}

	if (sleepTime == 0) {
	    sleepTime = 1;
	}

	startTime = System.currentTimeMillis();
	lastTime = startTime;

        minDrift = 0;
        maxDrift = 0;
        totalDrift = 0;
        count = 0;

	armed = true;
    }

    public void disarm() {
	armed = false;
    }

    public void tick() throws TickerException {
	if (!armed) {
	    throw new TickerException(id + ":  ticker not armed");
	}

	long start = System.currentTimeMillis();

	int drift = getDrift();

	totalDrift += drift;

	if (drift > maxDrift) {
	    maxDrift = drift;
	}

	if (drift < minDrift) {
	    minDrift = drift;
	}

	count++;

	if (Logger.logLevel == -99) {
	    Logger.println("drift " + drift);
	    Logger.logLevel = 3;
	}

	if (drift > sleepTime) { 
	    overSlept++;
	    return;
	} else if (drift < -sleepTime) {
	    underSlept++;
	}

	do {
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
	    }
	    drift = getDrift();
	} while (drift < -sleepTime);

	updateSleepDistribution(start);
    }

    private int getDrift() {
	int actualElapsed = (int) (System.currentTimeMillis() - startTime);

	int expectedElapsed = count * timePeriod;

	return actualElapsed - expectedElapsed;
    }

    public int getMinDrift() {
	return minDrift;
    }

    public int getMaxDrift() {
	return maxDrift;
    }

    private void updateSleepDistribution(long start) {
	int elapsed = (int) (System.currentTimeMillis() - start);	

	if (elapsed < 0) {
	    elapsed = 0;
	} else if (elapsed >= sleepDistribution.length) {
	    elapsed = sleepDistribution.length - 1;
	}

	sleepDistribution[elapsed]++;
    }

    public double getAvg() {
	return ((double)(System.currentTimeMillis() - startTime)) / count;
    }
	
    public void printStatistics() {
	if (count > 0) {
	    Logger.println(id
	        + " average time between ticks " 
	        + ((float)(System.currentTimeMillis() - startTime) / 
		(float)count) + " ms");

	    if (Logger.logLevel >= Logger.LOG_INFO) {
	        Logger.println("Total calls TickerSleep " + count);
	        Logger.println("OverSlept count " + overSlept);
	        Logger.println("UnderSlept count " + underSlept);
    	        Logger.println("Minimum drift " + getMinDrift());
	        Logger.println("Maximum drift " + getMaxDrift());
	        Logger.println("Average drift " + (totalDrift / count));

	        Logger.println("");

	        Logger.println(id + " Sleep time distribution");

	        Logger.println(id + " ms\tCount");
           
	        for (int i = 0; i < sleepDistribution.length; i++) {
                    if (sleepDistribution[i] != 0) {
		        Logger.println(id + " " + i + "\t\t" 
			    + sleepDistribution[i]);
                    }
                }

                Logger.println("");
	    }
	}
    }

    public static void main(String args[]) {
	TickerSleep tickerSleep = new TickerSleep("Test");

	tickerSleep.arm(RtpPacket.PACKET_PERIOD, RtpPacket.PACKET_PERIOD);

	while (true) {
	    try {
	        tickerSleep.tick();
	    } catch (TickerException e) {
		Logger.println("tick() failed! " + e.getMessage());
	    }

	    Logger.println(" avg " + tickerSleep.getAvg());
	}
    }

}
