package net.i2p.router;

import net.i2p.data.Hash;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.peermanager.TunnelHistory;
import net.i2p.stat.Rate;
import net.i2p.stat.RateAverages;
import net.i2p.stat.RateStat;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
import net.i2p.util.SystemVersion;

/**
 * Simple throttle that basically stops accepting messages or nontrivial 
 * requests if the jobQueue lag is too large.  
 *
 */
public class RouterThrottleImpl implements RouterThrottle {
    protected final RouterContext _context;
    private final Log _log;
    private volatile String _tunnelStatus;
    
    /** 
     * arbitrary hard limit - if it's taking this long to get 
     * to a job, we're congested.
     *
     */
    private static final long JOB_LAG_LIMIT_NETWORK = 2*1000;
    private static final long JOB_LAG_LIMIT_NETDB = 2*1000;
    // TODO reduce
    private static final long JOB_LAG_LIMIT_TUNNEL = 500;
    public static final String PROP_MAX_TUNNELS = "router.maxParticipatingTunnels";
    public static final int DEFAULT_MAX_TUNNELS = 10*1000;
    private static final String PROP_MAX_PROCESSINGTIME = "router.defaultProcessingTimeThrottle";
    private static final long DEFAULT_REJECT_STARTUP_TIME = 10*60*1000;
    private static final String PROP_REJECT_STARTUP_TIME = "router.rejectStartupTime";
    private static final int DEFAULT_MIN_THROTTLE_TUNNELS = SystemVersion.isAndroid() ? 100 :
                                                            SystemVersion.isARM() ? 500 : 1000;

    /**
     *  TO BE FIXED - SEE COMMENTS BELOW
     */
    private static final int DEFAULT_MAX_PROCESSINGTIME = 2250;

    /** tunnel acceptance */
    public static final int TUNNEL_ACCEPT = 0;
    
    /** = TrivialPreprocessor.PREPROCESSED_SIZE */
    private static final int PREPROCESSED_SIZE = 1024;

    
    public RouterThrottleImpl(RouterContext context) {
        _context = context;
        _log = context.logManager().getLog(RouterThrottleImpl.class);
        setTunnelStatus();
        _context.simpleTimer2().addEvent(new ResetStatus(), 5*1000 + _context.getProperty(PROP_REJECT_STARTUP_TIME, DEFAULT_REJECT_STARTUP_TIME));
        _context.statManager().createRateStat("router.throttleNetworkCause", "How lagged the jobQueue was when an I2NP was throttled", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
        //_context.statManager().createRateStat("router.throttleNetDbCause", "How lagged the jobQueue was when a networkDb request was throttled", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
        _context.statManager().createRateStat("router.throttleTunnelCause", "How lagged the jobQueue was when a tunnel request was throttled", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
        _context.statManager().createRateStat("tunnel.bytesAllocatedAtAccept", "How many bytes had been 'allocated' for participating tunnels when we accepted a request?", "Tunnels", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
        _context.statManager().createRateStat("router.throttleTunnelProcessingTime1m", "How long it takes to process a message (1 minute average) when we throttle a tunnel?", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
        _context.statManager().createRateStat("router.throttleTunnelProcessingTime10m", "How long it takes to process a message (10 minute average) when we throttle a tunnel?", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
        _context.statManager().createRateStat("router.throttleTunnelMaxExceeded", "How many tunnels we are participating in when we refuse one due to excees?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
        _context.statManager().createRateStat("router.throttleTunnelProbTooFast", "How many tunnels beyond the previous 1h average are we participating in when we throttle?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
        //_context.statManager().createRateStat("router.throttleTunnelProbTestSlow", "How slow are our tunnel tests when our average exceeds the old average and we throttle?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
        _context.statManager().createRateStat("router.throttleTunnelBandwidthExceeded", "How much bandwidth is allocated when we refuse due to bandwidth allocation?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
        _context.statManager().createRateStat("router.throttleTunnelBytesAllowed", "How many bytes are allowed to be sent when we get a tunnel request (period is how many are currently allocated)?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
        _context.statManager().createRateStat("router.throttleTunnelBytesUsed", "Used Bps at request (period = max KBps)?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
        _context.statManager().createRateStat("router.throttleTunnelFailCount1m", "How many messages failed to be sent in the last 2 minutes when we throttle based on a spike in failures (period = 10 minute average failure count)?", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000});
        //_context.statManager().createRateStat("router.throttleTunnelQueueOverload", "How many pending tunnel request messages have we received when we reject them due to overload (period = time to process each)?", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000});
    }
    
    /**
     *  Reset status from starting up to not-starting up,
     *  in case we don't get a tunnel request soon after the 20 minutes is up.
     *
     *  @since 0.8.12
     */
    private class ResetStatus implements SimpleTimer.TimedEvent {
        public void timeReached() {
            if (_tunnelStatus.equals(_x("Rejecting tunnels: Starting up")))
                cancelShutdownStatus();
        }
    }

    /** 
     * Should we accept any more data from the network for any sort of message, 
     * taking into account our current load, or should we simply slow down?  
     *
     * FIXME only called by SSU Receiver, not NTCP!
     * FIXME should put warning on the console
     * FIXME or should we do this at all? We have Codel queues all over now...
     */
    public boolean acceptNetworkMessage() {
        //if (true) return true;
        long lag = _context.jobQueue().getMaxLag();
        if ( (lag > JOB_LAG_LIMIT_NETWORK) && (_context.router().getUptime() > 60*1000) ) {
            if (_log.shouldLog(Log.WARN))
                _log.warn("Throttling network reader, as the job lag is " + lag);
            _context.statManager().addRateData("router.throttleNetworkCause", lag);
            return false;
        } else {
            return true;
        }
    }
    
    /** @deprecated unused, function moved to netdb */
    @Deprecated
    public boolean acceptNetDbLookupRequest(Hash key) { 
        long lag = _context.jobQueue().getMaxLag();
        if (lag > JOB_LAG_LIMIT_NETDB) {
            if (_log.shouldLog(Log.DEBUG))
                _log.debug("Refusing netDb request, as the job lag is " + lag);
            _context.statManager().addRateData("router.throttleNetDbCause", lag);
            return false;
        } else {
            return true;
        } 
    }
    
    /**
     *  If we should send a reject, return a nonzero reject code.
     *  Anything that causes us to drop a request instead of rejecting it
     *  must go in BuildHandler.handleInboundRequest(), not here.
     *
     *  @return 0 for accept or nonzero reject code
     */
    public int acceptTunnelRequest() { 
        if (_context.router().gracefulShutdownInProgress()) {
            if (_log.shouldLog(Log.WARN))
                _log.warn("Refusing tunnel request since we are shutting down ASAP");
            setShutdownStatus();
            // Don't use CRIT because this tells everybody we are shutting down
            return TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
        }
        
        // Don't use CRIT because we don't want peers to think we're failing
        if (_context.router().getUptime() < DEFAULT_REJECT_STARTUP_TIME) {
            setTunnelStatus(_x("Rejecting tunnels: Starting up"));
            return TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
        }

    /**** Moved to BuildHandler
        long lag = _context.jobQueue().getMaxLag();
        if (lag > JOB_LAG_LIMIT_TUNNEL) {
            if (_log.shouldLog(Log.WARN))
                _log.warn("Refusing tunnel request, as the job lag is " + lag);
            _context.statManager().addRateData("router.throttleTunnelCause", lag);
            setTunnelStatus(_x("Rejecting tunnels: High job lag"));
            return TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
        } 
     ****/
        
        RateAverages ra = RateAverages.getTemp();
        
        // TODO
        // This stat is highly dependent on transport mix.
        // For NTCP, it is queueing delay only, ~25ms
        // For SSU it is queueing + ack time, ~1000 ms.
        // (SSU acks may be delayed so it is much more than just RTT... and the delay may
        // counterintuitively be more when there is low traffic)
        // Change the stat or pick a better stat.
        RateStat rs = _context.statManager().getRate("transport.sendProcessingTime");
        Rate r = null;
        if (rs != null)
            r = rs.getRate(60*1000);

        //Reject tunnels if the time to process messages and send them is too large. Too much time implies congestion.
        if(r != null) {
            r.computeAverages(ra,false);
            
            int maxProcessingTime = _context.getProperty(PROP_MAX_PROCESSINGTIME, DEFAULT_MAX_PROCESSINGTIME);

            //Set throttling if necessary
            if((ra.getAverage() > maxProcessingTime*0.9 
                    || ra.getCurrent() > maxProcessingTime
                    || ra.getLast() > maxProcessingTime)) {
                if(_log.shouldLog(Log.WARN)) {
                    _log.warn("Refusing tunnel request due to sendProcessingTime " +
                              ((int)ra.getCurrent()) + " / " +
                              ((int)ra.getLast()) + " / " +
                              ((int)ra.getAverage()) + " / " +
                              maxProcessingTime +
                              " current/last/avg/max ms");
                }
                setTunnelStatus(_x("Rejecting tunnels: High message delay"));
                return TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
            }
        }

        
        int numTunnels = _context.tunnelManager().getParticipatingCount();
        int maxTunnels = _context.getProperty(PROP_MAX_TUNNELS, DEFAULT_MAX_TUNNELS);
        if (numTunnels >= maxTunnels) {
            if (_log.shouldLog(Log.WARN))
                _log.warn("Refusing tunnel request since we are already participating in " 
                          + numTunnels + " (our max is " + maxTunnels + ")");
            _context.statManager().addRateData("router.throttleTunnelMaxExceeded", numTunnels);
            setTunnelStatus(_x("Rejecting tunnels: Limit reached"));
            return TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
        }

        /*
         * Throttle if we go above a minimum level of tunnels AND the maximum participating
         * tunnels is default or lower.
         *
         * Lag based statistics use a moving average window (of for example 10 minutes), they are therefore
         * sensitive to sudden rapid growth of load, which are not instantly detected by these metrics.
         * Reduce tunnel growth if we are growing faster than the lag based metrics can detect reliably.
         */
        if ((numTunnels > getMinThrottleTunnels()) && (DEFAULT_MAX_TUNNELS >= maxTunnels)) {
            Rate avgTunnels = _context.statManager().getRate("tunnel.participatingTunnels").getRate(10*60*1000);
            if (avgTunnels != null) {
                double avg = avgTunnels.getAvgOrLifetimeAvg();
                double tunnelGrowthFactor = getTunnelGrowthFactor();
                int min = getMinThrottleTunnels();
                if (avg < min)
                    avg = min;
                // if the current tunnel count is higher than 1.3 * the average...
                if ( (avg > 0) && (avg*tunnelGrowthFactor < numTunnels) ) {
                    // we're accelerating, lets try not to take on too much too fast
                    double probAccept = (avg*tunnelGrowthFactor) / numTunnels;
                    probAccept *= probAccept; // square the decelerator for tunnel counts
                    int v = _context.random().nextInt(100);
                    if (v < probAccept*100) {
                        // ok
                        if (_log.shouldLog(Log.INFO))
                            _log.info("Probabalistically accept tunnel request (p=" + probAccept 
                                      + " v=" + v + " avg=" + avg + " current=" + numTunnels + ")");
                    } else {
                        if (_log.shouldLog(Log.WARN))
                            _log.warn("Probabalistically refusing tunnel request (avg=" + avg
                                      + " current=" + numTunnels + ")");
                        _context.statManager().addRateData("router.throttleTunnelProbTooFast", (long)(numTunnels-avg));
                        // hard to do {0} from here
                        //setTunnelStatus("Rejecting " + (100 - (int) probAccept*100) + "% of tunnels: High number of requests");
                        if (probAccept <= 0.5)
                            setTunnelStatus(_x("Rejecting most tunnels: High number of requests"));
                        else if (probAccept <= 0.9)
                            setTunnelStatus(_x("Accepting most tunnels"));
                        else
                            setTunnelStatus(_x("Accepting tunnels"));
                        return TunnelHistory.TUNNEL_REJECT_PROBABALISTIC_REJECT;
                    }
                } else {
                    if (_log.shouldLog(Log.INFO))
                        _log.info("Accepting tunnel request, since the tunnel count average is " + avg
                                      + " and we only have " + numTunnels + ")");
                }
            }
        }
        
        double tunnelTestTimeGrowthFactor = getTunnelTestTimeGrowthFactor();
        Rate tunnelTestTime1m = _context.statManager().getRate("tunnel.testSuccessTime").getRate(1*60*1000);
        Rate tunnelTestTime10m = _context.statManager().getRate("tunnel.testSuccessTime").getRate(10*60*1000);
        if ( (tunnelTestTime1m != null) && (tunnelTestTime10m != null) && (tunnelTestTime1m.getLastEventCount() > 0) ) {
            double avg1m = tunnelTestTime1m.getAverageValue();
            double avg10m = tunnelTestTime10m.getAvgOrLifetimeAvg();

            if (avg10m < 5000)
                avg10m = 5000; // minimum before complaining

            if ( (avg10m > 0) && (avg1m > avg10m * tunnelTestTimeGrowthFactor) ) {
                double probAccept = (avg10m*tunnelTestTimeGrowthFactor)/avg1m;
                probAccept = probAccept * probAccept; // square the decelerator for test times
                int v = _context.random().nextInt(100);
                if (v < probAccept*100) {
                    // ok
                    if (_log.shouldLog(Log.INFO))
                        _log.info("Probabalistically accept tunnel request (p=" + probAccept 
                                  + " v=" + v + " test time avg 1m=" + avg1m + " 10m=" + avg10m + ")");
                //} else if (false) {
                //    if (_log.shouldLog(Log.WARN))
                //        _log.warn("Probabalistically refusing tunnel request (test time avg 1m=" + avg1m
                //                  + " 10m=" + avg10m + ")");
                //    _context.statManager().addRateData("router.throttleTunnelProbTestSlow", (long)(avg1m-avg10m), 0);
                //    setTunnelStatus("Rejecting " + ((int) probAccept*100) + "% of tunnels: High test time");
                //    return TunnelHistory.TUNNEL_REJECT_PROBABALISTIC_REJECT;
                }
            } else {
                // not yet...
                //if (_log.shouldLog(Log.INFO))
                //    _log.info("Accepting tunnel request, since 60m test time average is " + avg10m
                //              + " and past 1m only has " + avg1m + ")");
            }
        }
        
        // ok, we're not hosed, but can we handle the bandwidth requirements 
        // of another tunnel?
        rs = _context.statManager().getRate("tunnel.participatingMessageCountAvgPerTunnel");
        r = null;
        double messagesPerTunnel = DEFAULT_MESSAGES_PER_TUNNEL_ESTIMATE;
        if (rs != null) {
            r = rs.getRate(60*1000);
            if (r != null) 
                messagesPerTunnel = r.computeAverages(ra, true).getAverage();
        }
        if (messagesPerTunnel < DEFAULT_MESSAGES_PER_TUNNEL_ESTIMATE)
            messagesPerTunnel = DEFAULT_MESSAGES_PER_TUNNEL_ESTIMATE;

        double bytesAllocated = messagesPerTunnel * numTunnels * PREPROCESSED_SIZE;
        
        if (!allowTunnel(bytesAllocated, numTunnels)) {
            _context.statManager().addRateData("router.throttleTunnelBandwidthExceeded", (long)bytesAllocated);
            return TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
        }
        
/***
        int queuedRequests = _context.tunnelManager().getInboundBuildQueueSize();
        int timePerRequest = 1000;
        rs = _context.statManager().getRate("tunnel.decryptRequestTime");
        if (rs != null) {
            r = rs.getRate(60*1000);
            if (r.getLastEventCount() > 0)
                timePerRequest = (int)r.getAverageValue();
            else
                timePerRequest = (int)rs.getLifetimeAverageValue();
        }
        float pctFull = (queuedRequests * timePerRequest) / (4*1000f);
        double pReject = Math.pow(pctFull, 16); //1 - ((1-pctFull) * (1-pctFull));
***/
        // let it in because we drop overload- rejecting may be overkill,
        // especially since we've done the cpu-heavy lifting to figure out
        // whats up
        /*
        if ( (pctFull >= 1) || (pReject >= _context.random().nextFloat()) ) {
            if (_log.shouldLog(Log.WARN))
                _log.warn("Rejecting a new tunnel request because we have too many pending requests (" + queuedRequests 
                          + " at " + timePerRequest + "ms each, %full = " + pctFull);
            _context.statManager().addRateData("router.throttleTunnelQueueOverload", queuedRequests, timePerRequest);
            return TunnelHistory.TUNNEL_REJECT_TRANSIENT_OVERLOAD;
        }
        */

        // ok, all is well, let 'er in
        _context.statManager().addRateData("tunnel.bytesAllocatedAtAccept", (long)bytesAllocated, 60*10*1000);
        
        //if (_log.shouldLog(Log.DEBUG))
        //    _log.debug("Accepting a new tunnel request (now allocating " + bytesAllocated + " bytes across " + numTunnels 
        //               + " tunnels with lag of " + lag + ")");
        return TUNNEL_ACCEPT;
    }

    private static final int DEFAULT_MESSAGES_PER_TUNNEL_ESTIMATE = 40; // .067KBps
    /** also limited to 90% - see below */
    private static final int MIN_AVAILABLE_BPS = 4*1024; // always leave at least 4KBps free when allowing
    private static final String LIMIT_STR = _x("Rejecting tunnels: Bandwidth limit");
    
    /**
     * with bytesAllocated already accounted for across the numTunnels existing
     * tunnels we have agreed to, can we handle another tunnel with our existing
     * bandwidth?
     *
     */
    private boolean allowTunnel(double bytesAllocated, int numTunnels) {
        int maxKBpsIn = _context.bandwidthLimiter().getInboundKBytesPerSecond();
        int maxKBpsOut = _context.bandwidthLimiter().getOutboundKBytesPerSecond();
        int maxKBps = Math.min(maxKBpsIn, maxKBpsOut);
        int usedIn = Math.min(_context.router().get1sRateIn(), _context.router().get15sRateIn());
        int usedOut = Math.min(_context.router().get1sRate(true), _context.router().get15sRate(true));
        int used = Math.max(usedIn, usedOut);
        int used1mIn = _context.router().get1mRateIn();
        int used1mOut = _context.router().get1mRate(true);

        // Check the inbound and outbound total bw available (separately)
        // We block all tunnels when share bw is over (max * 0.9) - 4KB
        // This gives reasonable growth room for existing tunnels on both low and high
        // bandwidth routers. We want to be rejecting tunnels more aggressively than
        // dropping packets with WRED
        int availBps = Math.min((maxKBpsIn*1024*9/10) - usedIn, (maxKBpsOut*1024*9/10) - usedOut);
        if (availBps < MIN_AVAILABLE_BPS) {
            if (_log.shouldLog(Log.WARN)) _log.warn("Reject, avail (" + availBps + ") less than min");
            setTunnelStatus(LIMIT_STR);
            return false;
        }

        // Now compute the share bw available, using
        // the bytes-allocated estimate for the participating tunnels
        // (if lower than the total bw, which it should be),
        // since some of the total used bandwidth may be for local clients
        double share = _context.router().getSharePercentage();
        used = Math.min(used, (int) (bytesAllocated / (10*60)));
        availBps = Math.min(availBps, (int)(((maxKBps*1024)*share) - used));

        // Write stats before making decisions
        _context.statManager().addRateData("router.throttleTunnelBytesUsed", used, maxKBps);
        _context.statManager().addRateData("router.throttleTunnelBytesAllowed", availBps, (long)bytesAllocated);

        // Now see if 1m rates are too high
        long overage = Math.max(used1mIn - (maxKBpsIn*1024), used1mOut - (maxKBpsOut*1024));
        if ( (overage > 0) && 
             ((overage/(maxKBps*1024f)) > _context.random().nextFloat()) ) {
            if (_log.shouldLog(Log.WARN)) _log.warn("Reject tunnel, 1m rate (" + overage + " over) indicates overload.");
            setTunnelStatus(LIMIT_STR);
            return false;
        }

            // limit at 90% - 4KBps (see above)
            float maxBps = (maxKBps * 1024f * 0.9f) - MIN_AVAILABLE_BPS;
            float pctFull = (maxBps - availBps) / (maxBps);
            double probReject = Math.pow(pctFull, 16); // steep curve 
            double rand = _context.random().nextFloat();
            boolean reject = rand <= probReject;
            if (reject && _log.shouldLog(Log.WARN))
                _log.warn("Reject avail/maxK/used " + availBps + "/" + maxKBps + "/" 
                          + used + " pReject = " + probReject + " pFull = " + pctFull + " numTunnels = " + numTunnels 
                          + " rand = " + rand + " est = " + bytesAllocated);
            else if (_log.shouldLog(Log.DEBUG))
                _log.debug("Accept avail/maxK/used " + availBps + "/" + maxKBps + "/" 
                           + used + " pReject = " + probReject + " pFull = " + pctFull + " numTunnels = " + numTunnels 
                           + " rand = " + rand + " est = " + bytesAllocated);
            if (probReject >= 0.9)
                setTunnelStatus(LIMIT_STR);
            else if (probReject >= 0.5)
                // hard to do {0} from here
                //setTunnelStatus("Rejecting " + ((int)(100.0*probReject)) + "% of tunnels: Bandwidth limit");
                setTunnelStatus(_x("Rejecting most tunnels: Bandwidth limit"));
            else if(probReject >= 0.1)
                // hard to do {0} from here
                //setTunnelStatus("Accepting " + (100-(int)(100.0*probReject)) + "% of tunnels");
                setTunnelStatus(_x("Accepting most tunnels"));
            else
                setTunnelStatus(_x("Accepting tunnels"));
            return !reject;
        
        
        /*
        if (availBps <= 8*1024) {
            // lets be more conservative for people near their limit and assume 1KBps per tunnel
            boolean rv = ( (numTunnels + 1)*1024 < availBps);
            if (_log.shouldLog(Log.DEBUG))
                _log.debug("Nearly full router (" + availBps + ") with " + numTunnels + " tunnels, allow a new request? " + rv);
            return rv;
        }
        */

/***
        double growthFactor = ((double)(numTunnels+1))/(double)numTunnels;
        double toAllocate = (numTunnels > 0 ? bytesAllocated * growthFactor : 0);
        
        double allocatedBps = toAllocate / (10 * 60);
        double pctFull = allocatedBps / availBps;
        
        if ( (pctFull < 1.0) && (pctFull >= 0.0) ) { // (_context.random().nextInt(100) > 100 * pctFull) {
            if (_log.shouldLog(Log.DEBUG))
                _log.debug("Allowing the tunnel w/ " + pctFull + " of our " + availBps
                           + "Bps/" + allocatedBps + "KBps allocated through " + numTunnels + " tunnels");
            return true;
        } else {
            double probAllow = availBps / (allocatedBps + availBps);
            boolean allow = (availBps > MIN_AVAILABLE_BPS) && (_context.random().nextFloat() <= probAllow);
            if (allow) {
                if (_log.shouldLog(Log.INFO))
                    _log.info("Probabalistically allowing the tunnel w/ " + (pctFull*100d) + "% of our " + availBps 
                               + "Bps allowed (" + toAllocate + "bytes / " + allocatedBps 
                               + "Bps) through " + numTunnels + " tunnels");
                return true;
            } else {
                if (_log.shouldLog(Log.WARN))
                    _log.warn("Rejecting the tunnel w/ " + (pctFull*100d) + "% of our " + availBps 
                               + "Bps allowed (" + toAllocate + "bytes / " + allocatedBps 
                               + "Bps) through " + numTunnels + " tunnels");
                return false;
            }
        }
***/
    }
    
    /** dont ever probabalistically throttle tunnels if we have less than this many */
    private int getMinThrottleTunnels() { 
        return _context.getProperty("router.minThrottleTunnels", DEFAULT_MIN_THROTTLE_TUNNELS);
    }
    
    private double getTunnelGrowthFactor() {
        try {
            return Double.parseDouble(_context.getProperty("router.tunnelGrowthFactor", "1.3"));
        } catch (NumberFormatException nfe) {
            return 1.3;
        }
    }

    private double getTunnelTestTimeGrowthFactor() {
        try {
            return Double.parseDouble(_context.getProperty("router.tunnelTestTimeGrowthFactor", "1.3"));
        } catch (NumberFormatException nfe) {
            return 1.3;
        }
    }
    
    public long getMessageDelay() {
        RateStat rs = _context.statManager().getRate("transport.sendProcessingTime");
        if (rs == null)
            return 0;
        Rate delayRate = rs.getRate(60*1000);
        return (long)delayRate.getAverageValue();
    }
    
    public long getTunnelLag() {
        Rate lagRate = _context.statManager().getRate("tunnel.testSuccessTime").getRate(10*60*1000);
        return (long)lagRate.getAverageValue();
    }
    
    public double getInboundRateDelta() {
        RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize");
        if (receiveRate == null)
            return 0;
        double nowBps = getBps(receiveRate.getRate(60*1000));
        double fiveMinBps = getBps(receiveRate.getRate(5*60*1000));
        double hourBps = getBps(receiveRate.getRate(60*60*1000));
        double dailyBps = getBps(receiveRate.getRate(24*60*60*1000));
        
        if (nowBps < 0) return 0;
        if (dailyBps > 0) return nowBps - dailyBps;
        if (hourBps > 0) return nowBps - hourBps;
        if (fiveMinBps > 0) return nowBps - fiveMinBps;
        return 0;
    }

    private static double getBps(Rate rate) {
        if (rate == null) return -1;
        double bytes = rate.getLastTotalValue();
        return (bytes*1000.0d)/rate.getPeriod(); 
    }
    
    public String getTunnelStatus() {
        return _tunnelStatus;
    }

    private void setTunnelStatus() {
// NPE, too early
//        if (_context.router().getRouterInfo().getBandwidthTier().equals("K"))
//            setTunnelStatus("Not expecting tunnel requests: Advertised bandwidth too low");
//        else
            setTunnelStatus(_x("Rejecting tunnels: Starting up"));
    }

    /** @since 0.8.12 */
    public void setShutdownStatus() {
        setTunnelStatus(_x("Rejecting tunnels: Shutting down"));
    }

    /** @since 0.8.12 */
    public void cancelShutdownStatus() {
        // try hard to guess the state, before we actually get a request
        int maxTunnels = _context.getProperty(PROP_MAX_TUNNELS, DEFAULT_MAX_TUNNELS);
        RouterInfo ri = _context.router().getRouterInfo();
        if (maxTunnels > 0 &&
            !_context.router().isHidden() &&
            ri != null && !ri.getBandwidthTier().equals("K")) {
            setTunnelStatus(_x("Accepting tunnels"));
        } else {
            setTunnelStatus(_x("Rejecting tunnels"));
        }
    }

    public void setTunnelStatus(String msg) {
        _tunnelStatus = msg;
    }

    /**
     *  Mark a string for extraction by xgettext and translation.
     *  Use this only in static initializers.
     *  It does not translate!
     *  @return s
     */
    private static final String _x(String s) {
        return s;
    }
}
