package org.jgroups.tests;

import org.jgroups.Global;
import org.jgroups.JChannel;
import org.jgroups.Message;
import org.jgroups.ReceiverAdapter;
import org.jgroups.View;
import org.jgroups.protocols.TUNNEL;
import org.jgroups.stack.GossipRouter;
import org.jgroups.stack.ProtocolStack;
import org.jgroups.util.Promise;
import org.jgroups.util.Util;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 * Ensures that a disconnected channel reconnects correctly, for different stack
 * configurations.
 * 
 * 
 **/

@Test(groups = {Global.STACK_INDEPENDENT,"known-failures",Global.GOSSIP_ROUTER}, sequential = true)
public class TUNNEL_Test2 extends ChannelTestBase {
    private JChannel channel, coordinator;
    private GossipRouter gr1, gr2;
    private static final String props ="tunnel.xml";
    private static String bindAddress = "127.0.0.1";
    
    
    static {
        try {
            bindAddress = Util.getBindAddress(null).getHostAddress();
        } catch (Exception e) {
        }
    }

    @BeforeMethod
    void startRouter() throws Exception {
        gr1 = new GossipRouter(12003,bindAddress);
        gr1.start();

        gr2 = new GossipRouter(12004,bindAddress);
        gr2.start();
    }

    @AfterMethod
    void tearDown() throws Exception {
        Util.close(channel, coordinator);
        Util.sleep(1000);
        gr1.stop();
        gr2.stop();
    }
    
    private void modifyChannel(JChannel... channels) throws Exception {
        for (JChannel c : channels) {
            ProtocolStack stack = c.getProtocolStack();
            TUNNEL t = (TUNNEL) stack.getBottomProtocol();
            String s = bindAddress + "[" + gr1.getPort() + "],";
            s+=bindAddress+"[" + gr2.getPort() + "]";
            t.setGossipRouterHosts(s);
            t.init();
        }        
    }

    public void testSimpleConnect() throws Exception {
        channel = new JChannel(props);
        modifyChannel(channel);
        channel.connect("testSimpleConnect");
        assert channel.getLocalAddress() != null;
        assert channel.getView().size() == 1;
        channel.disconnect();
        assert channel.getLocalAddress() == null;
        assert channel.getView() == null;
    }

    /**
     * Tests connect with two members
     * 
     **/
    public void testConnectTwoChannels() throws Exception {
        coordinator = new JChannel(props);
        channel = new JChannel(props);
        modifyChannel(channel,coordinator);
        coordinator.connect("testConnectTwoChannels");
        channel.connect("testConnectTwoChannels");
        View view = channel.getView();
        assert view.size() == 2;
        assert view.containsMember(channel.getLocalAddress());
        assert view.containsMember(coordinator.getLocalAddress());

        channel.disconnect();
        Util.sleep(1000);
        view = coordinator.getView();
        assert view.size() == 1;
        assert view.containsMember(coordinator.getLocalAddress());
    }

    /**
     * Tests connect with two members but when both GR fail and restart
     * 
     **/
    public void testConnectTwoChannelsBothGRDownReconnect() throws Exception {
        coordinator = new JChannel(props);
        channel = new JChannel(props);
        modifyChannel(channel,coordinator);
        coordinator.connect("testConnectTwoChannelsBothGRDownReconnect");
        channel.connect("testConnectTwoChannelsBothGRDownReconnect");
        Util.sleep(1000);
        gr1.stop();
        gr2.stop();
        // give time to reconnect
        Util.sleep(3000);

        gr1.start();
        gr2.start();

        // give time to reconnect
        Util.sleep(3000);
        View view = coordinator.getView();
        assert view.size() == 2;
        assert view.containsMember(coordinator.getLocalAddress());
        assert view.containsMember(channel.getLocalAddress());

        view = channel.getView();
        assert view.size() == 2;
        assert view.containsMember(coordinator.getLocalAddress());
        assert view.containsMember(channel.getLocalAddress());
    }

    public void testConnectThreeChannelsWithGRDown() throws Exception {
        JChannel third = null;
        coordinator = new JChannel(props);
        channel = new JChannel(props);
        modifyChannel(channel,coordinator);
        coordinator.connect("testConnectThreeChannelsWithGRDown");
        channel.connect("testConnectThreeChannelsWithGRDown");

        third = new JChannel(props);
        modifyChannel(third);
        third.connect("testConnectThreeChannelsWithGRDown");
        Util.sleep(1000);
        View view = channel.getView();
        assert channel.getView().size() == 3;
        assert third.getView().size() == 3;
        assert view.containsMember(channel.getLocalAddress());
        assert view.containsMember(coordinator.getLocalAddress());

        // kill router and recheck views
        gr2.stop();
        Util.sleep(1000);

        view = channel.getView();
        assert channel.getView().size() == 3;
        assert third.getView().size() == 3;
        assert third.getView().containsMember(channel.getLocalAddress());
        assert third.getView().containsMember(coordinator.getLocalAddress());

    }

    /**
     * 
      **/
    public void testConnectSendMessage() throws Exception {
        final Promise<Message> msgPromise = new Promise<Message>();
        coordinator = new JChannel(props);
        modifyChannel(coordinator);
        coordinator.connect("testConnectSendMessage");
        coordinator.setReceiver(new PromisedMessageListener(msgPromise));

        channel = new JChannel(props);
        modifyChannel(channel);
        channel.connect("testConnectSendMessage");

        channel.send(new Message(null, null, "payload"));

        Message msg = msgPromise.getResult(20000);
        assert msg != null;
        assert "payload".equals(msg.getObject());
    }

    /**
      * 
       **/
    public void testConnectSendMessageSecondGRDown() throws Exception {
        final Promise<Message> msgPromise = new Promise<Message>();
        coordinator = new JChannel(props);
        modifyChannel(coordinator);
        coordinator.connect("testConnectSendMessageSecondGRDown");
        coordinator.setReceiver(new PromisedMessageListener(msgPromise));

        channel = new JChannel(props);
        modifyChannel(channel);
        channel.connect("testConnectSendMessageSecondGRDown");

        Util.sleep(1000);
        gr2.stop();

        channel.send(new Message(null, null, "payload"));

        View view = coordinator.getView();
        assert view.size() == 2;
        assert view.containsMember(coordinator.getLocalAddress());
        assert view.containsMember(channel.getLocalAddress());

        view = channel.getView();
        assert view.size() == 2;
        assert view.containsMember(coordinator.getLocalAddress());
        assert view.containsMember(channel.getLocalAddress());

        Message msg = msgPromise.getResult(20000);
        assert msg != null;
        assert "payload".equals(msg.getObject());

    }

    /**
     * 
      **/
    public void testConnectSendMessageBothGRDown() throws Exception {
        final Promise<Message> msgPromise = new Promise<Message>();
        coordinator = new JChannel(props);
        modifyChannel(coordinator);
        coordinator.connect("testConnectSendMessageBothGRDown");
        coordinator.setReceiver(new PromisedMessageListener(msgPromise));

        channel = new JChannel(props);
        modifyChannel(channel);
        channel.connect("testConnectSendMessageBothGRDown");
        Util.sleep(1000);
        gr1.stop();
        gr2.stop();
        
        // give time to reconnect
        Util.sleep(3000);

        gr1.start();
        gr2.start();
        
        // give time to reconnect
        Util.sleep(3000);

        

        channel.send(new Message(null, null, "payload"));

        View view = coordinator.getView();
        assert view.size() == 2;
        assert view.containsMember(coordinator.getLocalAddress());
        assert view.containsMember(channel.getLocalAddress());

        view = channel.getView();
        assert view.size() == 2;
        assert view.containsMember(coordinator.getLocalAddress());
        assert view.containsMember(channel.getLocalAddress());

        Message msg = msgPromise.getResult(20000);
        assert msg != null;
        assert "payload".equals(msg.getObject());
    }

    /**
     * 
      **/
    public void testConnectSendMessageBothGRDownOnlyOneUp() throws Exception {

        final Promise<Message> msgPromise = new Promise<Message>();
        coordinator = new JChannel(props);
        modifyChannel(coordinator);
        coordinator.connect("testConnectSendMessageBothGRDownOnlyOneUp");
        coordinator.setReceiver(new PromisedMessageListener(msgPromise));

        channel = new JChannel(props);
        modifyChannel(channel);
        channel.connect("testConnectSendMessageBothGRDownOnlyOneUp");
        
        Util.sleep(1000);
        gr1.stop();
        gr2.stop();

        gr1.start();
        // give time to reconnect
        Util.sleep(6000);
       

        channel.send(new Message(null, null, "payload"));

        View view = coordinator.getView();
        assert view.size() == 2;
        assert view.containsMember(coordinator.getLocalAddress());
        assert view.containsMember(channel.getLocalAddress());

        view = channel.getView();
        assert view.size() == 2;
        assert view.containsMember(coordinator.getLocalAddress());
        assert view.containsMember(channel.getLocalAddress());

        Message msg = msgPromise.getResult(20000);
        assert msg != null;
        assert "payload".equals(msg.getObject());
    }

    public void testConnectSendMessageFirstGRDown() throws Exception {

        final Promise<Message> msgPromise = new Promise<Message>();
        coordinator = new JChannel(props);
        modifyChannel(coordinator);
        coordinator.connect("testConnectSendMessageFirstGRDown");
        coordinator.setReceiver(new PromisedMessageListener(msgPromise));

        channel = new JChannel(props);
        modifyChannel(channel);
        channel.connect("testConnectSendMessageFirstGRDown");
        Util.sleep(1000);
        gr1.stop();

        channel.send(new Message(null, null, "payload"));
        View view = coordinator.getView();
        assert view.size() == 2;
        assert view.containsMember(coordinator.getLocalAddress());
        assert view.containsMember(channel.getLocalAddress());

        view = channel.getView();
        assert view.size() == 2;
        assert view.containsMember(coordinator.getLocalAddress());
        assert view.containsMember(channel.getLocalAddress());

        Message msg = msgPromise.getResult(20000);
        assert msg != null;
        assert "payload".equals(msg.getObject());

    }

    private static class PromisedMessageListener extends ReceiverAdapter {
        private final Promise<Message> promise;

        public PromisedMessageListener(Promise<Message> promise) {
            this.promise = promise;
        }

        public void receive(Message msg) {
            promise.setResult(msg);
        }
    }
}
