package net.i2p.data.i2np;
/*
 * 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.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;

import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;

/**
 * The I2NPMessageReader reads an InputStream (using
 * {@link I2NPMessageHandler I2NPMessageHandler}) and passes out events to a registered
 * listener, where events are either messages being received, exceptions being
 * thrown, or the connection being closed.  Routers should use this rather
 * than read from the stream themselves.
 *
 * Deprecated - unused.
 * This was used by the old TCP transport.
 * Both the NTCP and SSU transports provide encapsulation
 * of I2NP messages, so they use I2NPMessageHandlers directly.
 * If we ever add a transport that does not provide encapsulation,
 * this will be useful again.
 *
 * @deprecated unused
 *
 * @author jrandom
 */
@Deprecated
public class I2NPMessageReader {
    private Log _log;
    private RouterContext _context;
    private InputStream _stream;
    private I2NPMessageEventListener _listener;
    private I2NPMessageReaderRunner _reader;
    private Thread _readerThread;
    
    public I2NPMessageReader(RouterContext context, InputStream stream, I2NPMessageEventListener lsnr) {
        this(context, stream, lsnr, "I2NP Reader");
    }
    
    public I2NPMessageReader(RouterContext context, InputStream stream, I2NPMessageEventListener lsnr, String name) {
        _context = context;
        _log = context.logManager().getLog(I2NPMessageReader.class);
        _stream = stream;
        setListener(lsnr);
        _reader = new I2NPMessageReaderRunner();
        _readerThread = new I2PThread(_reader);
        _readerThread.setName(name);
        _readerThread.setDaemon(true);
    }
    
    public void setListener(I2NPMessageEventListener lsnr) { _listener = lsnr; }
    public I2NPMessageEventListener getListener() { return _listener; }
    
    /**
     * Instruct the reader to begin reading messages off the stream
     *
     */
    public void startReading() { _readerThread.start(); }

    /**
     * Have the already started reader pause its reading indefinitely
     * @deprecated unused
     */
    @Deprecated
    public void pauseReading() { _reader.pauseRunner(); }

    /**
     * Resume reading after a pause
     * @deprecated unused
     */
    @Deprecated
    public void resumeReading() { _reader.resumeRunner(); }

    /**
     * Cancel reading.
     *
     */
    public void stopReading() { _reader.cancelRunner(); }
    
    /**
     * Defines the different events the reader produces while reading the stream
     *
     */
    public static interface I2NPMessageEventListener {
        /**
         * Notify the listener that a message has been received from the given
         * reader
         *
         */
        public void messageReceived(I2NPMessageReader reader, I2NPMessage message, long msToRead, int bytesRead);
        /**
         * Notify the listener that an exception was thrown while reading from the given
         * reader
         *
         */
        public void readError(I2NPMessageReader reader, Exception error);
        /**
         * Notify the listener that the stream the given reader was running off
         * closed
         *
         */
        public void disconnected(I2NPMessageReader reader);
    }
    
    private class I2NPMessageReaderRunner implements Runnable {
        private boolean _doRun;
        private boolean _stayAlive;
        private I2NPMessageHandler _handler;
        public I2NPMessageReaderRunner() {
            _doRun = true;
            _stayAlive = true;
            _handler = new I2NPMessageHandler(_context);
        }

        /** deprecated unused */
        public void pauseRunner() { _doRun = false; }

        /** deprecated unused */
        public void resumeRunner() { _doRun = true; }

        public void cancelRunner() {
            _doRun = false;
            _stayAlive = false;
        }
        public void run() {
            while (_stayAlive) {
                while (_doRun) {
                    while (!_context.throttle().acceptNetworkMessage()) {
                        try { Thread.sleep(500 + _context.random().nextInt(512)); } catch (InterruptedException ie) {}
                    }
                    
                    // do read
                    try {
                        I2NPMessage msg = _handler.readMessage(_stream);
                        if (msg != null) {
                            long msToRead = _handler.getLastReadTime();
                            int bytesRead = _handler.getLastSize();
                            //msToRead += injectLag(bytesRead);
                            _listener.messageReceived(I2NPMessageReader.this, msg, msToRead, bytesRead);
                        }
                    } catch (I2NPMessageException ime) {
                        if (_log.shouldLog(Log.WARN))
                            _log.warn("Error handling message", ime);
                        _listener.readError(I2NPMessageReader.this, ime);
                        _listener.disconnected(I2NPMessageReader.this);
                        cancelRunner();
                    } catch (InterruptedIOException iioe) { 
                        // not all I2NPMessageReaders support this, but some run off sockets which throw
                        // SocketTimeoutExceptions or InterruptedIOExceptions
                        if (_log.shouldLog(Log.INFO))
                            _log.info("Disconnecting due to inactivity", iioe);
                        _listener.disconnected(I2NPMessageReader.this);
                        cancelRunner();
                    } catch (IOException ioe) {
                        if (_log.shouldLog(Log.WARN))
                            _log.warn("IO Error handling message", ioe);
                        _listener.disconnected(I2NPMessageReader.this);
                        cancelRunner();
                    } catch (RuntimeException e) {
                        _log.log(Log.CRIT, "error reading msg!", e);
                        _listener.readError(I2NPMessageReader.this, e);
                        _listener.disconnected(I2NPMessageReader.this);
                        cancelRunner();
                    }
                }
                // ??? unused
                if (_stayAlive && !_doRun) {
                    // pause .5 secs when we're paused
                    try { Thread.sleep(500); } catch (InterruptedException ie) {}
                }
            }
            // boom bye bye bad bwoy
        }
        
     /****
        private final long injectLag(int size) {
            if (true) { 
                return 0;
            } else {
                boolean shouldLag = _context.random().nextInt(1000) > size;
                if (!shouldLag) return 0;
                
                long readLag = getReadLag();
                if (readLag > 0) {
                    long lag = _context.random().nextLong(readLag);
                    if (lag > 0) {
                        try { Thread.sleep(lag); } catch (InterruptedException ie) {}
                        return lag;
                    } else {
                        return 0;
                    }
                } else {
                    return 0;
                }
            }
        }
        
        private final long getReadLag() {
            return _context.getProperty("router.injectLagMs", 0L); 
        }
     ****/
    }
}
