/*
 * Created on Dec 1, 2008
 * Created by Paul Gardner
 * 
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */


package com.aelitis.azureus.core.networkmanager.impl;

import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.SystemTime;

import com.aelitis.azureus.core.networkmanager.NetworkManager;

public class 
ByteBucketST 
	implements ByteBucket
{
	  private int rate;
	  private int burst_rate;
	  private long avail_bytes;
	  private long prev_update_time;
	  
	  private boolean frozen;
	  
	  /**
	   * Create a new byte-bucket with the given byte fill (guaranteed) rate.
	   * Burst rate is set to default 1.2X of given fill rate.
	   * @param rate_bytes_per_sec fill rate
	   */
	  public ByteBucketST( int rate_bytes_per_sec ) {
	    this( rate_bytes_per_sec, rate_bytes_per_sec + (rate_bytes_per_sec/5) );
	  }
	  
	  /**
	   * Create a new byte-bucket with the given byte fill (guaranteed) rate
	   * and the given burst rate.
	   * @param rate_bytes_per_sec fill rate
	   * @param burst_rate max rate
	   */
	  private ByteBucketST( int rate_bytes_per_sec, int burst_rate ) {
	    this.rate = rate_bytes_per_sec;
	    this.burst_rate = burst_rate;
	    avail_bytes = 0; //start bucket empty
	    prev_update_time = SystemTime.getSteppedMonotonousTime();
	    ensureByteBucketMinBurstRate();
	  }
	  
	  
	  /**
	   * Get the number of bytes currently available for use.
	   * @return number of free bytes
	   */
	  public int getAvailableByteCount() {
		if ( avail_bytes < NetworkManager.UNLIMITED_RATE ){
			update_avail_byte_count();
		}
		
	    return (int)avail_bytes;
	  }
	  
	  
	  /**
	   * Update the bucket with the number of bytes just used.
	   * @param bytes_used
	   */
	  public void setBytesUsed( int bytes_used ) {
		if ( avail_bytes >= NetworkManager.UNLIMITED_RATE ){
		  return;
		}
		
	    avail_bytes -= bytes_used;
	    if( avail_bytes < 0 ){
	    	avail_bytes = 0;
	    	// this isn't synchronized so we can expect to see these every now and then (and we do...)
	    	//Debug.out( "avail_bytes < 0: " + avail_bytes);
	  	}
	  }
	  
	  
	  /**
	   * Get the configured fill rate.
	   * @return guaranteed rate in bytes per sec
	   */
	  public int getRate() {  return rate;  }
	  
	  
	  /**
	   * Get the configured burst rate.
	   * @return burst rate in bytes per sec
	   */
	  public int getBurstRate() {  return burst_rate;  }
	  
	  
	  /**
	   * Set the current fill/guaranteed rate, with a burst rate of 1.2X the given rate.
	   * @param rate_bytes_per_sec
	   */
	  public void setRate( int rate_bytes_per_sec ) {
	    setRate( rate_bytes_per_sec, rate_bytes_per_sec + (rate_bytes_per_sec/5));
	  }
	  
	  
	  /**
	   * Set the current fill/guaranteed rate, along with the burst rate.
	   * @param rate_bytes_per_sec
	   * @param burst_rate
	   */
	  public void setRate( int rate_bytes_per_sec, int burst_rate ) {
	    if( rate_bytes_per_sec < 0 ) {
	      Debug.out("rate_bytes_per_sec [" +rate_bytes_per_sec+ "] < 0");
	      rate_bytes_per_sec = 0;
	    }
	    if( burst_rate < rate_bytes_per_sec ) {
	      Debug.out("burst_rate [" +burst_rate+ "] < rate_bytes_per_sec [" +rate_bytes_per_sec+ "]");
	      burst_rate = rate_bytes_per_sec;
	    }
	    this.rate = rate_bytes_per_sec;
	    this.burst_rate = burst_rate;
	    if ( avail_bytes > burst_rate ){
	    	avail_bytes = burst_rate;
	    }
	    ensureByteBucketMinBurstRate();
	  }
	  
	  public void
	  setFrozen(
			boolean	f )
	  {
		  if ( f && frozen ){
			  Debug.out( "Already frozen!" );
		  }
		  frozen = f;
	  }
	  
	  private void update_avail_byte_count() {
		  if ( frozen ){
			  return;
		  }
	      final long now =SystemTime.getSteppedMonotonousTime();
	      if (prev_update_time <now) {
	          avail_bytes +=((now -prev_update_time) * rate) / 1000;
	          prev_update_time =now;
	          if( avail_bytes > burst_rate ) avail_bytes = burst_rate;
	          else if( avail_bytes < 0 )  Debug.out("ERROR: avail_bytes < 0: " + avail_bytes);
	      }
	  }

	  
	  /**
	   * Make sure the bucket's burst rate is at least MSS-sized,
	   * otherwise it will never allow a full packet's worth of data.
	   */
	  private void ensureByteBucketMinBurstRate() {
	    int mss = NetworkManager.getMinMssSize();
	    if( burst_rate < mss ) {  //oops, this won't ever allow a full packet
	      burst_rate = mss;  //so increase the max byte size
	    }
	  }
	  
}
