package org.jgroups.tests;

import org.jgroups.*;
import org.jgroups.protocols.TP;
import org.jgroups.util.Promise;
import org.jgroups.util.Util;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 * Tests unicasts to self (loopback of transport protocol)
 * @author Richard Achmatowicz 12 May 2008
 * @author Bela Ban Dec 31 2003
 */
@Test(groups=Global.STACK_DEPENDENT,sequential=true)
public class UnicastLoopbackTest extends ChannelTestBase {
    JChannel channel=null;


    @BeforeMethod
    protected void setUp() throws Exception {
        channel=createChannel(true, 1);
    }

    @AfterMethod
    protected void tearDown() throws Exception {
        Util.close(channel);
    }


    /**
     * Tests that when UNICAST messages are sent with TP.loopback == true, the following conditions hold:
     * (i) no messages touch the network
     * (ii) all messages are correctly received
     * 
     * @throws TimeoutException
     */
    public void testUnicastMsgsWithLoopback() throws Exception {
    	final long TIMEOUT = 2 * 10000 ;
    	final int NUM=1000;
    	long num_msgs_sent_before = 0 ;
    	long num_msgs_sent_after = 0 ;

    	Promise<Boolean> p = new Promise<Boolean>() ;
    	MyReceiver receiver = new MyReceiver(NUM, p) ;
    	channel.setReceiver(receiver) ;
    	channel.connect("demo-group") ;

    	Address local_addr=channel.getAddress();

    	// set the loopback property on transport
    	setLoopbackProperty(channel, true) ;

    	num_msgs_sent_before = getNumMessagesSentViaNetwork(channel) ;

    	// send NUM UNICAST messages to ourself 
    	for(int i=1; i <= NUM; i++) {
    		channel.send(new Message(local_addr, null, new Integer(i)));
    		if(i % 100 == 0)
    			System.out.println("-- sent " + i);
    	}

    	num_msgs_sent_after = getNumMessagesSentViaNetwork(channel) ;

    	// when loopback == true, messages should not touch the network
        System.out.println("num msgs before: " + num_msgs_sent_before + ", num msgs after: " + num_msgs_sent_after);
        assert num_msgs_sent_before <= num_msgs_sent_after;
        assert num_msgs_sent_after < NUM/10;

        try {
    		// wait for all messages to be received
    		p.getResultWithTimeout(TIMEOUT) ;
    	}
    	catch(TimeoutException te) {
    		// timeout exception occurred 
    		Assert.fail("Test timed out before all messages were received") ;
    	}

    }


    /**
     * Returns the number of messages sent across the network.
     * 
     * @param ch 
     * @return the number of messages sent across the network
     * @throws Exception
     */
    private static long getNumMessagesSentViaNetwork(JChannel ch) throws Exception {

    	TP transport = ch.getProtocolStack().getTransport();
    	if (transport == null) {
    		throw new Exception("transport layer is not present - check default stack configuration") ;
    	}

    	return transport.getNumMessagesSent();
    }


    /**
     * Set the value of the loopback property on the transport layer.
     * 
     * @param ch
     * @param loopback
     * @throws Exception
     */
    private static void setLoopbackProperty(JChannel ch, boolean loopback) throws Exception {

    	TP transport =ch.getProtocolStack().getTransport();
    	if (transport == null) {
    		throw new Exception("transport layer is not present - check default stack configuration") ;
    	}

    	// check if already set correctly
    	if ((loopback && transport.isLoopback()) || (!loopback && !transport.isLoopback()))
    		return ;

    	// otherwise, set it
    	transport.setLoopback(loopback);
    }

    /**
     * A receiver which waits for all messages to be received and 
     * then sets a promise.
     */
    private static class MyReceiver extends ReceiverAdapter {

    	private final int numExpected ;
    	private int numReceived;
    	private final Promise<Boolean> p ;

    	public MyReceiver(int numExpected, Promise<Boolean> p) {
    		this.numExpected = numExpected ;
    		this.numReceived = 0 ;
    		this.p = p ;
    	}

    	// when we receive a Message, we update the count of messages received
    	public void receive(Message msg) {

    		Integer num=(Integer)msg.getObject();
    		numReceived++;
    		if(num != null && num.intValue() % 100 == 0)
    			System.out.println("-- received " + num);

    		// if we have received NUM messages, set the result
    		if (numReceived >= numExpected)
    			p.setResult(Boolean.TRUE) ;
    	}

    	public int getNumMsgsReceived() {
    		return numReceived ;
    	}
    }


}
