package org.jgroups.tests.byteman;

import org.jboss.byteman.contrib.bmunit.BMNGRunner;
import org.jboss.byteman.contrib.bmunit.BMScript;
import org.jgroups.Global;
import org.jgroups.JChannel;
import org.jgroups.Message;
import org.jgroups.ReceiverAdapter;
import org.jgroups.protocols.PING;
import org.jgroups.protocols.SHARED_LOOPBACK;
import org.jgroups.protocols.UNICAST2;
import org.jgroups.protocols.pbcast.GMS;
import org.jgroups.protocols.pbcast.NAKACK2;
import org.jgroups.util.Util;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;

/**
 * Tests the behavior of queueing messages (in NAKACK{2}) before becoming a server
 * (https://issues.jboss.org/browse/JGRP-1522)
 * @author Bela Ban
 * @since 3.2
 */
@Test(groups=Global.BYTEMAN,sequential=true)
public class BecomeServerTest extends BMNGRunner {
    JChannel          a, b;

    @AfterMethod
    protected void cleanup() {
        Util.close(b,a);
    }


    /**
     * When we flush the server queue and one or more of the delivered messages triggers a response (in the same thread),
     * we need to make sure the channel is connected, or else the JOIN will fail as the exception happens on the same
     * thread. Note that the suggested fix on JGRP-1522 will solve this. Issue: https://issues.jboss.org/browse/JGRP-1522
     */
    @BMScript(dir="scripts/BecomeServerTest", value="testSendingOfMsgsOnUnconnectedChannel")
    public void testSendingOfMsgsOnUnconnectedChannel() throws Exception {
        a=createChannel("A");
        a.setReceiver(new ReceiverAdapter() {
            public void receive(Message msg)  {
                System.out.println("A: received message from " + msg.getSrc() + ": " + msg.getObject());
            }
        });
        a.connect("BecomeServerTest");

        new Thread("MsgSender-A") {
            public void run() {
                sendMessage(a, "hello from A"); // will be blocked by byteman rendezvous
            }
        }.start();

        b=createChannel("B");
        b.setReceiver(new ReceiverAdapter() {
            public void receive(Message msg) {
                System.out.println("B: received message from " + msg.getSrc() + ": " + msg.getObject());
                if(msg.getSrc().equals(a.getAddress())) {
                    try {
                        b.send(null, "This message would trigger an exception if the channel was not yet connected");
                    }
                    catch(Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });

        b.connect("BecomeServerTest");

        Util.waitUntilAllChannelsHaveSameSize(20000, 1000, a,b);

        System.out.println("\nA: " + a.getView() + "\nB: " + b.getView());
    }


    protected void sendMessage(JChannel ch, String message) {
        try {
            Message msg=new Message(null, message);
            msg.setFlag(Message.Flag.OOB);
            ch.send(msg);
        }
        catch(Exception e) {
            e.printStackTrace(System.err);
        }
    }



    protected JChannel createChannel(String name) throws Exception {
        JChannel ch=Util.createChannel(new SHARED_LOOPBACK().setValue("enable_bundling", false)
                                         .setValue("enable_unicast_bundling", false),
                                       new PING().setValue("timeout", 500).setValue("num_initial_members", 2),
                                       new NAKACK2().setValue("become_server_queue_size", 10),
                                       new UNICAST2(),
                                       new GMS().setValue("print_local_addr", false));
        ch.setName(name);
        return ch;
    }
}
