package org.jgroups.tests;

import org.jgroups.*;
import org.jgroups.protocols.*;
import org.jgroups.protocols.pbcast.GMS;
import org.jgroups.protocols.pbcast.NAKACK2;
import org.jgroups.protocols.pbcast.STABLE;
import org.jgroups.stack.Protocol;
import org.jgroups.util.Util;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;

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

/**
 * Tests NAKACK2 functionality, especially flag {@link Message.TransientFlag#DONT_LOOPBACK}.
 * @author Bela Ban
 * @since  3.5
 */
@Test(groups=Global.FUNCTIONAL)
public class NakackUnitTest {
    protected JChannel   a, b;
    protected MyReceiver ra, rb;


    @AfterMethod  protected void tearDown() throws Exception {Util.close(b, a);}

    // @Test(invocationCount=10)
    public void testMessagesToAllWithDontLoopback() throws Exception {
        a=create("A", false); b=create("B", false);
        createReceivers();
        _testMessagesToAllWithDontLoopback();
    }

    // @Test(invocationCount=10)
    public void testMessagesToOtherBatching() throws Exception {
        a=create("A", true); b=create("B", true);
        createReceivers();
        _testMessagesToAllWithDontLoopback();
    }

    protected void _testMessagesToAllWithDontLoopback() throws Exception {
        connect();
        Message[] msgs={
          msg(),
          msg().setFlag(Message.Flag.OOB),
          msg().setFlag(Message.Flag.OOB),
          msg().setFlag(Message.Flag.OOB).setFlag(Message.TransientFlag.DONT_LOOPBACK),
          msg().setFlag(Message.TransientFlag.DONT_LOOPBACK),
          msg().setFlag(Message.Flag.OOB),
          msg().setFlag(Message.Flag.OOB),
          msg().setFlag(Message.Flag.OOB).setFlag(Message.TransientFlag.DONT_LOOPBACK),
          msg().setFlag(Message.Flag.OOB).setFlag(Message.TransientFlag.DONT_LOOPBACK),
          msg().setFlag(Message.Flag.OOB).setFlag(Message.TransientFlag.DONT_LOOPBACK),
          msg().setFlag(Message.Flag.OOB).setFlag(Message.TransientFlag.DONT_LOOPBACK),
          msg().setFlag(Message.Flag.OOB)
        };

        send(a, msgs);
        checkReception(ra, 1,2,3,6,7,12);
        checkReception(rb, 1,2,3,4,5,6,7,8,9,10,11,12);
    }


    protected static void send(JChannel ch, Message... msgs) throws Exception {
        int cnt=1;
        for(Message msg: msgs) {
            msg.setObject(cnt++);
            ch.send(msg);
        }
    }

    protected static void checkReception(MyReceiver r, int... num) {
        List<Integer> received=r.list();
        for(int i=0; i < 10; i++) {
            if(received.size() == num.length)
                break;
            Util.sleep(500);
        }
        List<Integer> expected=new ArrayList<>(num.length);
        for(int n: num) expected.add(n);
        System.out.println("received=" + received + ", expected=" + expected);
        assert received.size() == expected.size() : "list=" + received + ", expected=" + expected;
        assert received.containsAll(expected) : "list=" + received + ", expected=" + expected;
    }

    protected void createReceivers() {
        a.setReceiver(ra=new MyReceiver());
        b.setReceiver(rb=new MyReceiver());
    }

    protected static Message msg() {return new BytesMessage(null);}

    protected static JChannel create(String name, boolean use_batching) throws Exception {
        Protocol[] protocols={
          new SHARED_LOOPBACK(),
          new SHARED_LOOPBACK_PING(),
          new MAKE_BATCH().sleepTime(100).multicasts(use_batching),
          new NAKACK2(),
          new UNICAST3(),
          new STABLE(),
          new GMS(),
          new FRAG2().setFragSize(8000),
        };
        return new JChannel(protocols).name(name);
    }

    protected void connect() throws Exception {
        a.connect("UnicastUnitTest");
        b.connect("UnicastUnitTest");
        Util.waitUntilAllChannelsHaveSameView(10000, 1000, a, b);
    }


    protected static class MyReceiver implements Receiver {
        protected final List<Integer> list=new ArrayList<>();

        public List<Integer> list()       {return list;}

        public void receive(Message msg) {
            Integer num=msg.getObject();
            synchronized(list) {
                list.add(num);
            }
        }
    }
}