package com.limegroup.gnutella;

import com.limegroup.gnutella.settings.ApplicationSettings;
import com.limegroup.gnutella.settings.ConnectionSettings;
import com.limegroup.gnutella.settings.DownloadSettings;
import com.limegroup.gnutella.settings.UltrapeerSettings;
import com.limegroup.gnutella.settings.UploadSettings;
import com.limegroup.gnutella.statistics.BandwidthStat;
import com.limegroup.gnutella.util.CommonUtils;
import com.limegroup.gnutella.util.ManagedThread;
import com.limegroup.gnutella.util.NetworkUtils;
import com.limegroup.gnutella.util.ThreadFactory;

/**
 * This class determines whether or not this node has all of the necessary
 * characteristics for it to become a ultrapeer if necessary.  The criteria
 * uses include the node's upload and download bandwidth, the operating
 * system, the node's firewalled status, the average uptime, the current 
 * uptime, etc. <p>
 *
 * One of this class's primary functions is to run the timer that continually
 * checks the amount of bandwidth passed through upstream and downstream 
 * HTTP file transfers.  It records the maximum of the sum of these streams
 * to determine the node's bandwidth.
 */
//2345678|012345678|012345678|012345678|012345678|012345678|012345678|012345678|
public final class SupernodeAssigner {

	/**
	 * Constant value for whether or not the operating system qualifies
	 * this node for Ultrapeer status.
	 */
	private static final boolean ULTRAPEER_OS = CommonUtils.isUltrapeerOS();
	
	/**
	 * Constant for the number of milliseconds between the timer's calls
	 * to its <tt>Runnable</tt>s.
	 */
	public static final int TIMER_DELAY = 1000;

	/**
	 * Constant for the number of seconds between the timer's calls
	 * to its <tt>Runnable</tt>s.
	 */
	private static final int TIMER_DELAY_IN_SECONDS = TIMER_DELAY/1000;

    /**
	 * A <tt>BandwidthTracker</tt> instance for keeping track of the 
	 * upload bandwidth used for file uploads.
	 */
	private static BandwidthTracker _uploadTracker;

    /**
	 * A <tt>BandwidthTracker</tt> instance for keeping track of the 
	 * download bandwidth used for file downloads.
	 */
	private static BandwidthTracker _downloadTracker;
    
    /**
     * A reference to the Connection Manager
     */
    private static ConnectionManager _manager;

	/**
	 * Variable for the current uptime of this node.
	 */
	private static long _currentUptime = 0;

	/**
	 * Variable for the maximum number of bytes per second transferred 
	 * downstream over the history of the application.
	 */
	private static int _maxUpstreamBytesPerSec =
        UploadSettings.MAX_UPLOAD_BYTES_PER_SEC.getValue();

	/**
	 * Variable for the maximum number of bytes per second transferred 
	 * upstream over the history of the application.
	 */
	private static int _maxDownstreamBytesPerSec = 
        DownloadSettings.MAX_DOWNLOAD_BYTES_PER_SEC.getValue();
    

	/**
	 * Variable for whether or not this node has such good values that it is too
	 * good to pass up for becoming an Ultrapeer.
	 */
	private static volatile boolean _isTooGoodToPassUp = false;

	/**
	 * Variable for the last time we attempted to become an Ultrapeer.
	 */
	private static long _lastAttempt = 0L;

	/**
	 * Number of times we've tried to become an Ultrapeer.
	 */
	private static int _ultrapeerTries = 0;

    /** 
	 * Creates a new <tt>UltrapeerAssigner</tt>. 
	 *
	 * @param uploadTracker the <tt>BandwidthTracker</tt> instance for 
	 *                      tracking bandwidth used for uploads
	 * @param downloadTracker the <tt>BandwidthTracker</tt> instance for
	 *                        tracking bandwidth used for downloads
     * @param manager Reference to the ConnectionManager for this node
	 */
    public SupernodeAssigner(final BandwidthTracker uploadTracker, 
							 final BandwidthTracker downloadTracker,
                             ConnectionManager manager) {
		_uploadTracker = uploadTracker;
		_downloadTracker = downloadTracker;  
        _manager = manager;
    }
    
	/**
	 * Schedules a timer event to continually updates the upload and download
	 * bandwidth used.  Non-blocking.
     * @param router provides the schedule(..) method for the timing
     */
    public void start() {
        Runnable task=new Runnable() {
            public void run() {
                try {
                    collectBandwidthData();
                } catch(Throwable t) {
                    ErrorService.error(t);
                }
            }
        };            
        RouterService.schedule(task, 0, TIMER_DELAY);
    }

	/**
	 * Sets EVER_ULTRAPEER_CAPABLE to true if this has the necessary
	 * requirements for becoming a ultrapeer if needed, based on 
	 * the node's bandwidth, operating system, firewalled status, 
	 * uptime, etc.  Does not modify the property if the capabilities
     * are not met.  If the user has disabled ultrapeer support, 
     * sets EVER_ULTRAPEER_CAPABLE to false.
	 */
	private static void setUltrapeerCapable() {
        if (UltrapeerSettings.DISABLE_ULTRAPEER_MODE.getValue()) {
			UltrapeerSettings.EVER_ULTRAPEER_CAPABLE.setValue(false);
            return;
        }

        // Ignore this check if we're already an Ultrapeer -- 
        // we already know we're Ultrapeer-capable in that
        // case
        if(RouterService.isSupernode())
            return;

        boolean isUltrapeerCapable = 
            //Is upstream OR downstream high enough?
            (_maxUpstreamBytesPerSec >= 
                    UltrapeerSettings.MIN_UPSTREAM_REQUIRED.getValue() ||
             _maxDownstreamBytesPerSec >= 
                    UltrapeerSettings.MIN_DOWNSTREAM_REQUIRED.getValue()) &&
            //AND I'm not a modem (in case estimate wrong)
            (ConnectionSettings.CONNECTION_SPEED.getValue() > SpeedConstants.MODEM_SPEED_INT) &&
            //AND is my average uptime OR current uptime high enough?
            (ApplicationSettings.AVERAGE_UPTIME.getValue() >= UltrapeerSettings.MIN_AVG_UPTIME.getValue() ||
             _currentUptime >= UltrapeerSettings.MIN_INITIAL_UPTIME.getValue()) &&
            //AND am I not firewalled?
			ConnectionSettings.EVER_ACCEPTED_INCOMING.getValue() &&
            //AND I have accepted incoming messages over UDP
            RouterService.isGUESSCapable() &&
            //AND am I a capable OS?
			ULTRAPEER_OS &&
			//AND I do not have a private address
			!NetworkUtils.isPrivate();

		long curTime = System.currentTimeMillis();

		// check if this node has such good values that we simply can't pass
		// it up as an Ultrapeer -- it will just get forced to be one
		_isTooGoodToPassUp = isUltrapeerCapable &&
            RouterService.acceptedIncomingConnection() &&
			(curTime - RouterService.getLastQueryTime() > 5*60*1000) &&
			(BandwidthStat.HTTP_UPSTREAM_BANDWIDTH.getAverage() < 1);

        // record new ultrapeer capable value.
        if(isUltrapeerCapable)
            UltrapeerSettings.EVER_ULTRAPEER_CAPABLE.setValue(true);

		if(_isTooGoodToPassUp && shouldTryToBecomeAnUltrapeer(curTime)) {
			_ultrapeerTries++;

			// try to become an Ultrapeer -- how persistent we are depends on
			// how many times we've tried, and so how long we've been
			// running for
			final int demotes = 4 * _ultrapeerTries;
			Runnable ultrapeerRunner = 
				new Runnable() {
					public void run() {
						RouterService.getConnectionManager().tryToBecomeAnUltrapeer(demotes);
					}
				};
            ThreadFactory.startThread(ultrapeerRunner, "UltrapeerAttemptThread");
		}
	}
	
	/**
	 * Checks whether or not we should try again to become an Ultrapeer.
	 *
	 * @param curTime the current time in milliseconds
	 * @return <tt>true</tt> if we should try again to become an Ultrapeer,
	 *  otherwise <tt>false</tt>
	 */
	private static boolean shouldTryToBecomeAnUltrapeer(long curTime) {
		if(curTime - _lastAttempt < UltrapeerSettings.UP_RETRY_TIME.getValue()) {
			return false;
		}
		_lastAttempt = curTime;
		return true;
	}

	/**
	 * Accessor for whether or not this machine has settings that are too good
	 * to pass up for Ultrapeer election.
	 *
	 * @return <tt>true</tt> if this node has extremely good Ultrapeer settings,
	 *  otherwise <tt>false</tt>
	 */
	public static boolean isTooGoodToPassUp() {
		return _isTooGoodToPassUp;
	}

	/**
	 * Collects data on the bandwidth that has been used for file uploads
	 * and downloads.
	 */
	private static void collectBandwidthData() {
		_currentUptime += TIMER_DELAY_IN_SECONDS;
        _uploadTracker.measureBandwidth();
        _downloadTracker.measureBandwidth();
        _manager.measureBandwidth();
        float bandwidth = 0;
        try {
            bandwidth = _uploadTracker.getMeasuredBandwidth();
        }catch(InsufficientDataException ide) {
            bandwidth = 0;
        }
		int newUpstreamBytesPerSec = 
            (int)bandwidth
           +(int)_manager.getMeasuredUpstreamBandwidth();
        bandwidth = 0;
        try {
            bandwidth = _downloadTracker.getMeasuredBandwidth();
        } catch (InsufficientDataException ide) {
            bandwidth = 0;
        }
		int newDownstreamBytesPerSec = 
            (int)bandwidth
           +(int)_manager.getMeasuredDownstreamBandwidth();
		if(newUpstreamBytesPerSec > _maxUpstreamBytesPerSec) {
			_maxUpstreamBytesPerSec = newUpstreamBytesPerSec;
			UploadSettings.MAX_UPLOAD_BYTES_PER_SEC.setValue(_maxUpstreamBytesPerSec);
		}
  		if(newDownstreamBytesPerSec > _maxDownstreamBytesPerSec) {
			_maxDownstreamBytesPerSec = newDownstreamBytesPerSec;
  			DownloadSettings.MAX_DOWNLOAD_BYTES_PER_SEC.setValue(_maxDownstreamBytesPerSec);
  		}
    
        //check if became ultrapeer capable
        setUltrapeerCapable();
        
	}

}
