
package org.jgroups.tests;


import org.jgroups.Address;
import org.jgroups.Global;
import org.jgroups.Message;
import org.jgroups.stack.NakReceiverWindow;
import org.jgroups.stack.Retransmitter;
import org.jgroups.util.TimeScheduler;
import org.jgroups.util.Util;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.util.List;


@Test(groups=Global.FUNCTIONAL)
public class NakReceiverWindowTest {
    private static final Address sender=Util.createRandomAddress();
    private static final MyRetransmitCommand cmd=new MyRetransmitCommand();


    @DataProvider(name="createTimer")
    Object[][] createTimer() {
        return Util.createTimer();
    }


    @Test(dataProvider="createTimer")
    public void test1(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 1, timer);
            check(win, 0, 1, 1);
            assert win.get(23) == null;
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void test2(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer);
            check(win, 0, 100, 100);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void test3(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, new Message());
            assert win.get(1) != null;
            check(win, 0, 1, 0);
            win.add(2, new Message());
            check(win, 0, 2, 0);
            assert win.get(2) != null;
            win.remove();
            check(win, 0, 2, 1);
            win.remove();
            check(win, 0, 2, 2);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void test4(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 1, timer);
            win.add(2, new Message());
            check(win, 0, 2, 1);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void test5(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer);
            win.add(101, new Message());
            win.add(100, new Message());
            check(win, 0, 101, 100);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void test6(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer);
            win.add(101, new Message());
            System.out.println("win: " + win);
            win.add(100, new Message());
            System.out.println("win: " + win);
            check(win, 0, 101, 100);
            win.remove();
            System.out.println("win: " + win);
            check(win, 0, 101, 101);
            while((win.remove()) != null);
            check(win, 0, 101, 101);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testLowerBounds(TimeScheduler timer) {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, 50, timer);
            win.add(101, new Message());
            System.out.println("win: " + win);
            win.add(100, new Message());
            System.out.println("win: " + win);
            check(win, 50, 101, 100);
            win.remove();
            System.out.println("win: " + win);
            check(win, 50, 101, 101);
            while((win.remove()) != null);
            check(win, 50, 101, 101);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void test7(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, new Message());
            win.add(2, new Message());
            win.add(3, new Message());
            win.add(4, new Message());
            check(win, 0, 4, 0);
            System.out.println("Note that the subsequent warning is expected:");
            win.stable(4); // no-op because we haven't even removed 4 messages
            check(win, 0, 4, 0);
            while(win.remove() != null);
            check(win, 0, 4, 4);
            win.stable(4);
            check(win, 4, 4, 4);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testLowerBounds2(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, 50, timer);
            win.add(100, new Message());
            win.add(101, new Message());
            win.add(102, new Message());
            win.add(103, new Message());
            System.out.println("win: " + win);
            check(win, 50, 103, 100);
            System.out.println("Note that the subsequent warning is expected:");
            win.stable(103); // no-op because we haven't even removed 4 messages
            check(win, 50, 103, 100);
            while(win.remove() != null);
            check(win, 50, 103, 103);
            win.stable(103);
            System.out.println("win: " + win);
            check(win, 103, 103, 103);
        }
        finally {
            timer.stop();
        }
    }



    @Test(dataProvider="createTimer")
    public void test8(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, new Message());
            win.add(2, new Message());
            win.add(3, new Message());
            win.add(4, new Message());
            win.add(6, new Message());
            check(win, 0, 6, 0); // haven't delivered a message yet
            while(win.remove() != null);
            check(win, 0, 6, 4);
            win.add(5, new Message());
            check(win, 0, 6, 4);
            win.remove();
            check(win, 0, 6, 5);
            win.remove();
            check(win, 0, 6, 6);
            win.stable(4);
            check(win, 4, 6, 6);
            win.stable(6);
            check(win, 6, 6, 6);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testAdd(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            check(win, 0, 0, 0);
            win.add(0, new Message()); // discarded, next expected is 1
            check(win, 0, 0, 0);
            win.add(1, new Message());
            check(win, 0, 1, 0);
            win.add(2, new Message());
            win.add(3, new Message());
            win.add(4, new Message());
            check(win, 0, 4, 0);
            win.add(6, new Message());
            check(win, 0, 6, 0);
            win.add(5, new Message());
            check(win, 0, 6, 0);
            while(win.remove() != null) ;
            check(win, 0, 6, 6);
            win.stable(4);
            check(win, 4, 6, 6);
            win.stable(6);
            check(win, 6, 6, 6);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void test9(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, new Message());
            win.add(2, new Message());
            win.add(3, new Message());
            win.add(4, new Message());
            win.add(6, new Message());
            System.out.println("win: " + win);
            while((win.remove()) != null) ;
            win.stable(6); // 6 is ignore as it is >= highest delivered message
            System.out.println("win: " + win);
            assert win.get(2) != null;
            check(win, 0, 6, 4);
            win.add(5, new Message());
            check(win, 0, 6, 4);
            while((win.remove()) != null) ;
            check(win, 0, 6, 6);
            win.stable(6);
            check(win, 6, 6, 6);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testHighestDelivered(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, new Message());
            win.add(2, new Message());
            win.add(3, new Message());
            win.add(4, new Message());
            check(win, 0, 4, 0);
            win.add(10, new Message());
            check(win, 0, 10, 0);
            System.out.println("win: " + win);
            win.add(9, new Message());
            win.add(7, new Message());
            win.add(8, new Message());
            win.add(6, new Message());
            win.add(5, new Message());
            System.out.println("win: " + win);
            check(win, 0, 10, 0);
            while((win.remove()) != null) ;
            check(win, 0, 10, 10);
            win.stable(5);
            System.out.println("win: " + win);
            check(win, 5, 10, 10);
            win.stable(10);
            System.out.println("win: " + win);
            check(win, 10, 10, 10);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testMissingMessages(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, new Message());
            win.add(5, new Message());
            check(win, 0, 5, 0);
            win.add(6, new Message());
            check(win, 0, 6, 0);
            System.out.println("win: " + win);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testMissingMessages2(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, new Message());
            win.add(5, new Message());
            check(win, 0, 5, 0);
            win.add(8, new Message());
            check(win, 0, 8, 0);
            win.add(9, new Message());
            check(win, 0, 9, 0);
            System.out.println("win: " + win);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testMissingMessages3(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, new Message());
            win.add(5, new Message());
            check(win, 0, 5, 0);
            win.add(8, new Message());
            check(win, 0, 8, 0);
            win.add(9, new Message());
            check(win, 0, 9, 0);
            System.out.println("win: " + win);
            win.add(2, new Message());
            check(win, 0, 9, 0);
            win.add(3, new Message());
            win.add(4, new Message());
            check(win, 0, 9, 0);
            win.add(7, new Message());
            check(win, 0, 9, 0);
            win.add(6, new Message());
            check(win, 0, 9, 0);
            win.add(10, new Message());
            check(win, 0, 10, 0);
            win.add(11, new Message());
            check(win, 0, 11, 0);
            System.out.println("win: " + win);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testMissingMessages4(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer);
            win.add(101, new Message());
            win.add(105, new Message());
            check(win, 0, 105, 100);
            win.add(108, new Message());
            check(win, 0, 108, 100);
            win.add(109, new Message());
            check(win, 0, 109, 100);
            System.out.println("win: " + win);
            win.add(102, new Message());
            check(win, 0, 109, 100);
            win.add(103, new Message());
            win.add(104, new Message());
            check(win, 0, 109, 100);
            win.add(107, new Message());
            check(win, 0, 109, 100);
            win.add(106, new Message());
            check(win, 0, 109, 100);
            win.add(110, new Message());
            check(win, 0, 110, 100);
            win.add(110, new Message());
            check(win, 0, 110, 100);
            System.out.println("win: " + win);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testMissingMessages5(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer);
            win.add(101, new Message());
            check(win, 0, 101, 100);
            win.add(108, new Message());
            check(win, 0, 108, 100);
            win.remove();
            win.add(109, new Message());
            check(win, 0, 109, 101);
            System.out.println("win: " + win);
            win.add(102, new Message());
            check(win, 0, 109, 101);
            win.add(103, new Message());
            win.add(104, new Message());
            check(win, 0, 109, 101);
            win.add(107, new Message());
            check(win, 0, 109, 101);
            win.add(106, new Message());
            win.add(105, new Message());
            check(win, 0, 109, 101);
            win.add(110, new Message());
            check(win, 0, 110, 101);
            win.add(110, new Message());
            check(win, 0, 110, 101);
            System.out.println("win: " + win);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void test10(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, new Message());
            win.add(2, new Message());
            win.add(3, new Message());
            win.add(4, new Message());
            while((win.remove()) != null) ;
            check(win, 0, 4, 4);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void test10a(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, new Message());
            win.add(2, new Message());
            win.add(3, new Message());
            win.add(4, new Message());
            while((win.remove()) != null) ;
            win.stable(4);
            check(win, 4, 4, 4);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void test11(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, new Message());
            win.add(2, new Message());
            win.add(3, new Message());
            win.add(4, new Message());
            while((win.remove()) != null) ;
            win.destroy();
            check(win, 0, 0, 0);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void test12(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);

            win.add(1, new Message(null, null, new Integer(1)));
            win.add(2, new Message(null, null, new Integer(2)));
            win.add(3, new Message(null, null, new Integer(3)));

            Assert.assertEquals(1, ((Integer)win.remove().getObject()).intValue());
            Assert.assertEquals(2, ((Integer)win.remove().getObject()).intValue());
            Assert.assertEquals(3, ((Integer)win.remove().getObject()).intValue());
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void test13(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, new Message());
            win.add(2, new Message());
            win.add(3, new Message());
            win.add(4, new Message());
            check(win, 0, 4, 0);
            win.remove();
            win.remove();
            win.add(5, new Message());
            win.add(6, new Message());
            check(win, 0, 6, 2);
            win.stable(2);
            check(win, 2, 6, 2);
        }
        finally {
            timer.stop();
        }
    }



    @Test(dataProvider="createTimer")
    public void testAddOOBAtHead(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            boolean rc;
            rc=win.add(0, oob());
            assert !(rc);
            rc=win.add(1, oob());
            assert rc;
            rc=win.add(1, oob());
            assert !(rc);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testAddOOBAtTail(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            boolean rc;
            rc=win.add(1, oob());
            assert rc;
            rc=win.add(2, oob());
            assert rc;
            rc=win.add(2, oob());
            assert !(rc);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testAddOOBInTheMiddle(TimeScheduler timer) throws Exception {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            boolean rc;
            rc=win.add(3, oob());
            assert rc;
            rc=win.add(3, oob());
            assert !(rc);
            rc=win.add(1, oob());
            assert rc;
            rc=win.add(1, oob());
            assert !(rc);
            rc=win.add(2, oob());
            assert rc;
            rc=win.add(2, oob());
            assert !(rc);
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testUpdateHighestSeen(TimeScheduler timer) {
        add(1000, timer);
        add(2000, timer);
        add(3000, timer);
        add(4000, timer);
        add(5000, timer);
        add(10000, timer);
        add(15000, timer);
        add(20000, timer);
        add(30000, timer);
    }


    @Test(dataProvider="createTimer")
    public void test1000(TimeScheduler timer) {
        add(1000, timer);
    }


    @Test(dataProvider="createTimer")
    public void test10000(TimeScheduler timer) {
        add(10000, timer);
    }





    @Test(dataProvider="createTimer")
    public void testRemoveRegularAndOOBMessages(TimeScheduler timer) {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, msg());
            System.out.println("win = " + win);
            win.remove();
            System.out.println("win = " + win);
            assert win.getHighestDelivered() == 1;

            win.add(3, msg());
            win.remove();
            System.out.println("win = " + win);
            assert win.getHighestDelivered() == 1;

            win.add(2, oob());
            System.out.println("win = " + win);

            assert win.getHighestDelivered() == 1 : "highest_delivered should be 2, but is " + win.getHighestDelivered();
        }
        finally {
            timer.stop();
        }
    }


    @Test(dataProvider="createTimer")
    public void testRemoveMany(TimeScheduler timer) {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, msg());
            win.add(2, msg());
            win.add(3, msg());
            win.add(5, msg());
            win.add(6, msg());
            System.out.println("win = " + win);
            List<Message> msgs=win.removeMany(null);
            System.out.println("msgs = " + msgs);
            assert msgs.size() == 3;

            win.add(4, msg());
            msgs=win.removeMany(null);
            System.out.println("msgs = " + msgs);
            assert msgs.size() == 3;
        }
        finally {
            timer.stop();
        }
    }

    @Test(dataProvider="createTimer")
    public void testRemoveMany2(TimeScheduler timer) {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 4, timer);
            win.add(5, msg());
            win.add(6, msg());
            win.add(7, msg());
            win.add(9, msg());
            win.add(10, msg());
            System.out.println("win = " + win);
            List<Message> msgs=win.removeMany(null);
            System.out.println("msgs = " + msgs);
            assert msgs.size() == 3;

            win.add(8, msg());
            msgs=win.removeMany(null);
            System.out.println("msgs = " + msgs);
            assert msgs.size() == 3;
        }
        finally {
            timer.stop();
        }
    }




    @Test(dataProvider="createTimer")
    public void testRetransmitter(TimeScheduler timer) {
        try {
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer);
            win.add(1, msg());
            win.add(2, msg());
            win.add(3, msg());
            win.add(5, msg());
            win.add(6, msg());
            System.out.println("win = " + win);
            int num_pending_xmits=win.getPendingXmits();
            assert num_pending_xmits == 1;

            win.add(4, msg());
            num_pending_xmits=win.getPendingXmits();
            assert num_pending_xmits == 0;
        }
        finally {
            timer.stop();
        }
    }



    private static void add(int num_msgs, TimeScheduler timer) {
        try {
            long start, stop;
            double time_per_msg;
            NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 1, timer);
            start=System.currentTimeMillis();
            for(int i=1; i < 1 + num_msgs; i++) {
                win.add(i, new Message());
            }
            stop=System.currentTimeMillis();
            time_per_msg=(stop-start) / (double)num_msgs;
            System.out.println("-- time for " + num_msgs + " msgs: " + (stop-start) + ", " + time_per_msg + " ms/msg");
        }
        finally {
            timer.stop();
        }
    }


    private static Message oob() {
        Message retval=new Message();
        retval.setFlag(Message.OOB);
        return retval;
    }

    private static Message msg() {
        return new Message();
    }


    private static void check(NakReceiverWindow win, long lowest, long highest_received, long highest_delivered) {
        Assert.assertEquals(win.getLowestSeen(), lowest, "lowest=" + lowest + ", win.lowest=" + win.getLowestSeen());
        Assert.assertEquals(win.getHighestReceived(), highest_received, "highest_received=" + highest_received + ", win.highest_received=" + win.getHighestReceived());
        Assert.assertEquals(win.getHighestDelivered(), highest_delivered, "highest_delivered=" + highest_delivered + ", win.highest_delivered=" + win.getHighestDelivered());
    }


    private static class MyRetransmitCommand implements Retransmitter.RetransmitCommand {

        public void retransmit(long first_seqno, long last_seqno, Address sender) {
        }
    }


}
