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 net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;

/**
 * Handle messages from router to router.  This class is NOT threadsafe
 *
 */
public class I2NPMessageHandler {
    private final Log _log;
    private final I2PAppContext _context;
    private long _lastReadBegin;
    private long _lastReadEnd;
    private int _lastSize;
    private byte _messageBuffer[];
    private I2NPMessage _lastRead;
    
    public I2NPMessageHandler(I2PAppContext context) {
        _context = context;
        _log = context.logManager().getLog(I2NPMessageHandler.class);
        _lastSize = -1;
    }
    
    /**
     * Read an I2NPMessage from the stream and return the fully populated object.
     *
     * This is only called by I2NPMessageReader which is unused.
     * All transports provide encapsulation and so we have byte arrays available.
     *
     * @deprecated use the byte array method to avoid an extra copy if you have it
     *
     * @throws I2NPMessageException if there is a problem handling the particular
     *          message - if it is an unknown type or has improper formatting, etc.
     */
    @Deprecated
    public I2NPMessage readMessage(InputStream in) throws IOException, I2NPMessageException {
        if (_messageBuffer == null) _messageBuffer = new byte[38*1024]; // more than necessary
        try {
            int type = (int)DataHelper.readLong(in, 1);
            _lastReadBegin = System.currentTimeMillis();
            I2NPMessage msg = I2NPMessageImpl.createMessage(_context, type);
            // can't be null
            //if (msg == null)
            //    throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message");
            try {
                _lastSize = msg.readBytes(in, type, _messageBuffer);
            } catch (I2NPMessageException ime) {
                throw ime;
            } catch (RuntimeException e) {
                if (_log.shouldLog(Log.WARN))
                    _log.warn("Error reading the stream", e);
                throw new I2NPMessageException("Unknown error reading the " + msg.getClass().getSimpleName(), e); 
            }
            _lastReadEnd = System.currentTimeMillis();
            return msg;
        } catch (DataFormatException dfe) {
            throw new I2NPMessageException("Error reading the message", dfe);
        }
    }
    
    /** clear the last message read from a byte array with an offset */
    public I2NPMessage lastRead() { 
        I2NPMessage rv = _lastRead;
        _lastRead = null;
        return rv;
    }
    
    /**
     * Read an I2NPMessage from the byte array and return the fully populated object.
     *
     * @throws I2NPMessageException if there is a problem handling the particular
     *          message - if it is an unknown type or has improper formatting, etc.
     */
    public I2NPMessage readMessage(byte data[]) throws I2NPMessageException {
        readMessage(data, 0, data.length);
        return lastRead();
    }

    /**
     *  Result is retreived with lastRead()
     */
    public int readMessage(byte data[], int offset) throws I2NPMessageException {
        return readMessage(data, offset, data.length - offset);
    }

    /**
     *  Set a limit on the max to read from the data buffer, so that
     *  we can use a large buffer but prevent the reader from reading off the end.
     *
     *  Result is retreived with lastRead()
     *
     *  @param maxLen read no more than this many bytes from data starting at offset, even if it is longer
     *                must be at least 16
     *  @since 0.8.12
     */
    public int readMessage(byte data[], int offset, int maxLen) throws I2NPMessageException {
        int cur = offset;
        // we will assume that maxLen is >= 1 here. It's checked to be >= 16 in readBytes()
        int type = data[cur] & 0xff;
        cur++;
        _lastReadBegin = System.currentTimeMillis();
        I2NPMessage msg = I2NPMessageImpl.createMessage(_context, type);
        // can't be null
        //if (msg == null) {
        //    int sz = data.length-offset;
        //   boolean allZero = false;
        //    for (int i = offset; i < data.length; i++) {
        //        if (data[i] != 0) {
        //            allZero = false;
        //            break;
        //        }
        //    }
        //    throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message (remaining sz=" 
        //                                   + sz + " all zeros? " + allZero + ")");
        //}
        try {
            _lastSize = msg.readBytes(data, type, cur, maxLen - 1);
            cur += _lastSize;
        } catch (I2NPMessageException ime) {
            throw ime;
        } catch (RuntimeException e) {
            if (_log.shouldLog(Log.WARN))
                _log.warn("Error reading the stream", e);
            throw new I2NPMessageException("Unknown error reading the " + msg.getClass().getSimpleName(), e); 
        }
        _lastReadEnd = System.currentTimeMillis();
        _lastRead = msg;
        return cur - offset;
    }
    
    public long getLastReadTime() { return _lastReadEnd - _lastReadBegin; }
    public int getLastSize() { return _lastSize; }
    
/****
    public static void main(String args[]) {
        try {
            I2NPMessage msg = new I2NPMessageHandler(I2PAppContext.getGlobalContext()).readMessage(new FileInputStream(args[0]));
            System.out.println(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
****/
}
