package net.i2p.router.tunnel;

import static junit.framework.Assert.assertTrue;

import java.util.ArrayList;
import java.util.List;

import net.i2p.data.Hash;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.DataMessage;
import net.i2p.data.i2np.I2NPMessage;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public abstract class GatewayTestBase extends RouterTestBase {

    
    private static TunnelGatewayPumper _pumper;
    
    private TunnelGateway.QueuePreprocessor _preprocessor;
    protected TunnelGateway.Sender _sender;
    protected TestReceiver _receiver;
    private TunnelGateway _gw;
    
    @BeforeClass
    public static void gatewayClassSetup() {
        _pumper = new TunnelGatewayPumper(_context);
    }
    
    @Before
    public void baseSetUp() {
        _preprocessor = new BatchedPreprocessor(_context,"test pre-processor");
        setupSenderAndReceiver();
        _gw = new PumpedTunnelGateway(_context, _preprocessor, _sender, _receiver, _pumper);
    }
    
    /** sets up the sender and receiver.  Subclasses must override */
    protected abstract void setupSenderAndReceiver();
    
    /**
     * @return at which hop to start the decryption process
     */
    protected abstract int getLastHop();
    
    @Test
    public void testSmall() throws Exception {
        int runCount = 1;
        
        List<DataMessage> messages = new ArrayList<DataMessage>(runCount);
        long start = _context.clock().now();
    
        for (int i = 0; i < runCount; i++) {
            DataMessage m = getTestMessage(64);
            messages.add(m);
            _gw.add(m, null, null);
        }
        
        Thread.sleep(1000);
        
        List<I2NPMessage> received = _receiver.clearReceived();
        for (int i = 0; i < messages.size(); i++) {
            assertTrue(received.contains(((I2NPMessage)messages.get(i))));
        }
    }
    
    @Test
    public void testRouter() throws Exception {
        int runCount = 1;
        
        List<DataMessage> messages = new ArrayList<DataMessage>(runCount);
        long start = _context.clock().now();
    
        for (int i = 0; i < runCount; i++) {
            DataMessage m = getTestMessage(64);
            Hash to = new Hash(new byte[Hash.HASH_LENGTH]);
            java.util.Arrays.fill(to.getData(), (byte)0xFF);
            messages.add(m);
            _gw.add(m, to, null);
        }
        
        Thread.sleep(1000);
        
        List<I2NPMessage> received = _receiver.clearReceived();
        for (int i = 0; i < messages.size(); i++) {
            assertTrue(received.contains(((I2NPMessage)messages.get(i))));
        }
    }
    
    @Test
    public void testTunnel() throws Exception {
        int runCount = 1;
        
        List<DataMessage> messages = new ArrayList<DataMessage>(runCount);
        long start = _context.clock().now();
    
        for (int i = 0; i < runCount; i++) {
            DataMessage m = getTestMessage(64);
            Hash to = new Hash(new byte[Hash.HASH_LENGTH]);
            java.util.Arrays.fill(to.getData(), (byte)0xFF);
            TunnelId tunnel = new TunnelId(42);
            byte data[] = m.toByteArray();
            messages.add(m);
            _gw.add(m, to, tunnel);
        }
        
        Thread.sleep(1000);
        
        List<I2NPMessage> received = _receiver.clearReceived();
        for (int i = 0; i < messages.size(); i++) {
            assertTrue(received.contains(((I2NPMessage)messages.get(i))));
        }
    }
    
    @Test
    public void testLarge() throws Exception {
        int runCount = 1;
        
        List<DataMessage> messages = new ArrayList<DataMessage>(runCount);
        long start = _context.clock().now();
    
        for (int i = 0; i < runCount; i++) {
            DataMessage m = getTestMessage(1024);
            messages.add(m);
            _gw.add(m, null, null);
        }
        
        Thread.sleep(1000);
        
        List<I2NPMessage> received = _receiver.clearReceived();
        for (int i = 0; i < messages.size(); i++) {
            assertTrue(received.contains(((I2NPMessage)messages.get(i))));
        }
    }
    
    private static DataMessage getTestMessage(int size) {
        DataMessage m = new DataMessage(_context);
        m.setData(new byte[size]);
        java.util.Arrays.fill(m.getData(), (byte)0xFF);
        m.setMessageExpiration(_context.clock().now() + 60*1000);
        m.setUniqueId(_context.random().nextLong(I2NPMessage.MAX_ID_VALUE));
        
        byte [] data = m.toByteArray(); // not sure why, maybe side-effect? --zab
        return m;
    }
    
    protected class TestReceiver implements TunnelGateway.Receiver, FragmentHandler.DefragmentedReceiver {
        private TunnelCreatorConfig _config;
        private FragmentHandler _handler;
        private volatile List<I2NPMessage> _received;
        public TestReceiver(TunnelCreatorConfig config) {
            _config = config;
            _handler = new FragmentHandler(_context, TestReceiver.this);
            _received = new ArrayList<I2NPMessage>(1000);
        }
        public long receiveEncrypted(byte[] encrypted) {
            // fake all the hops...
            
            for (int i = 1; i <= _config.getLength() - getLastHop(); i++) {
                HopProcessor hop = new HopProcessor(_context, _config.getConfig(i));
                assertTrue(hop.process(encrypted, 0, encrypted.length, _config.getConfig(i).getReceiveFrom()));
            }
            
            handleAtEndpoint(encrypted);
            
            _handler.receiveTunnelMessage(encrypted, 0, encrypted.length);
            return -1; // or do we need to return the real message ID?
        }        
        
        protected void handleAtEndpoint(byte [] encrypted) {
        }
        
        public void receiveComplete(I2NPMessage msg, Hash toRouter, TunnelId toTunnel) {
            _received.add(msg);
        }
        public List<I2NPMessage> clearReceived() { 
            List<I2NPMessage> rv = _received; 
            _received = new ArrayList<I2NPMessage>();
            return rv;
        }
        @Override
        public Hash getSendTo() {
            // TODO Auto-generated method stub
            return null;
        }
    }
}
