package net.i2p.router.transport;
/*
 * free (adj.): unencumbered; not under the control of others
 * Written by jrandom in 2003 and released into the public domain
 * with no warranty of any kind, either expressed or implied.
 * It probably won't make your computer catch on fire, or eat
 * your children, but it might.  Use at your own risk.
 *
 */

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import net.i2p.data.router.RouterIdentity;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;

public class BandwidthLimitedOutputStream extends FilterOutputStream {
    private RouterIdentity _peer;
    private String _peerTarget;
    private RouterContext _context;
    private Log _log;
    private FIFOBandwidthLimiter.Request _currentRequest;
    
    public BandwidthLimitedOutputStream(RouterContext context, OutputStream source, RouterIdentity peer) {
        super(source);
        _context = context;
        _peer = peer;
        if (peer != null)
            _peerTarget = peer.getHash().toBase64();
        else
            _peerTarget = "unknown";
        _log = context.logManager().getLog(BandwidthLimitedOutputStream.class);
        _currentRequest = null;
    }
    
    public FIFOBandwidthLimiter.Request getCurrentRequest() { return _currentRequest; }
    
    @Override
    public void write(int val) throws IOException {
        if (_log.shouldLog(Log.DEBUG))
            _log.debug("Writing a single byte!", new Exception("Single byte from..."));
        long before = _context.clock().now();
        FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestOutbound(1, 0, _peerTarget);
        // only a single byte, no need to loop
        req.waitForNextAllocation();
        long waited = _context.clock().now() - before;
        if ( (waited > 1000) && (_log.shouldLog(Log.WARN)) )
            _log.warn("Waiting to write a byte took too long [" + waited + "ms");
        out.write(val);
    }
    @Override
    public void write(byte src[]) throws IOException {
        write(src, 0, src.length);
    }
    @Override
    public void write(byte src[], int off, int len) throws IOException {
        if (_log.shouldLog(Log.DEBUG))
            _log.debug("Writing " + len + " bytes");
        if (src == null) return;
        if (len <= 0) return;
        if (len + off > src.length)
            throw new IllegalArgumentException("what are you thinking?  len=" + len 
                                               + ", off=" + off + ", data=" + src.length);
        _currentRequest = _context.bandwidthLimiter().requestOutbound(len, 0, _peerTarget);
        
        int written = 0;
        while (written < len) {
            int allocated = len - _currentRequest.getPendingRequested();
            int toWrite = allocated - written;
            if (toWrite > 0) {
                try {
                    out.write(src, off + written, toWrite);
                } catch (IOException ioe) {
                    _currentRequest.abort();
                    _currentRequest = null;
                    throw ioe;
                }
                written += toWrite;
            }
            _currentRequest.waitForNextAllocation();
        }
        synchronized (this) {
            _currentRequest = null;
        }
    }
    
    @Override
    public void close() throws IOException {
        synchronized (this) {
            if (_currentRequest != null)
                _currentRequest.abort();
        }
        super.close();
    }
}
