package org.jgroups.tests;

import org.jgroups.Global;
import org.jgroups.util.*;
import org.testng.annotations.Test;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Bela Ban
 */
@Test(groups=Global.FUNCTIONAL, sequential=false)
public class SeqnoTest {

    public static void testConstructor() {
        SeqnoRange range=new SeqnoRange(10, 10);
        System.out.println(print(range));
        assert range.size() == 1;
        assert range.getLow() == 10;
        assert range.getHigh() == 10;
        assert range.contains(10);
        assert !range.contains(11);

        range=new SeqnoRange(10, 15);
        System.out.println(print(range));
        assert range.size() == 6;
        assert range.getLow() == 10;
        assert range.getHigh() == 15;
        assert range.contains(10);
        assert range.contains(14);
    }

    public static void testSetAndGetWith1Seqno() {
        Seqno range=new SeqnoRange(10, 10);
        assert range.getNumberOfMissingMessages() == 1;
        assert range.getNumberOfReceivedMessages() == 0;

        range.set(10);
        assert range.getNumberOfMissingMessages() == 0;
        assert range.getNumberOfReceivedMessages() == 1;

        assert range.get(10);

        range.clear(10);
        assert !range.get(10);
        assert range.getNumberOfMissingMessages() == 1;
        assert range.getNumberOfReceivedMessages() == 0;
    }

    public static void testSetAndGetWith5Seqnos() {
        SeqnoRange range=new SeqnoRange(10, 15);
        System.out.println("range=" + print(range));

        assert range.size() == 6;
        assert range.getNumberOfMissingMessages() == 6;
        assert range.getNumberOfReceivedMessages() == 0;

        range.set(10);
        assert range.getNumberOfMissingMessages() == 5;
        assert range.getNumberOfReceivedMessages() == 1;

        assert range.get(10);

        range.set(13);
        assert range.size() == 6;
        assert range.getNumberOfMissingMessages() == 4;
        assert range.getNumberOfReceivedMessages() == 2;

        range.set(13);
        assert range.size() == 6;
        assert range.getNumberOfMissingMessages() == 4;
        assert range.getNumberOfReceivedMessages() == 2;

        System.out.println("range=" + print(range));

        Collection<Range> xmits=range.getMessagesToRetransmit();
        Collection<Range> cleared_bits=range.getBits(false);

        System.out.println("xmits = " + xmits);
        System.out.println("cleared_bits = " + cleared_bits);

        assert xmits.equals(cleared_bits);
    }

    public static void testGetBits() {
        SeqnoRange range=new SeqnoRange(1, 100);
        System.out.println("range = " + range);
        assert range.size() == 100;

        Collection<Range> bits=range.getBits(false);
        assert bits.size() == 1;
        Range tmp=bits.iterator().next();
        assert tmp.low == 1 && tmp.high == 100;

        range.set(1,2);
        assert range.size() == 100;

        bits=range.getBits(true);
        assert bits != null && bits.size() == 1; // 1 range: [1-2]
        tmp=bits.iterator().next();
        assert tmp.low == 1 && tmp.high == 2;

        for(int i=1; i < 100; i++)
            range.set(i);

        bits=range.getBits(false);
        assert bits.size() == 1;
        tmp=bits.iterator().next();
        assert tmp.low == 100 && tmp.high == 100;

        for(int i=1; i <= range.size(); i++)
            range.clear(i);

        for(int i=2; i <= 99; i++)
            range.set(i);

        bits=range.getBits(true);
        assert bits.size() == 1;
        tmp=bits.iterator().next();
        assert tmp.low == 2 && tmp.high == 99;

        bits=range.getBits(false);
        assert bits.size() == 2;

        tmp=bits.iterator().next();
        assert tmp.low == 1 && tmp.high == 1;

        Iterator<Range> it=bits.iterator();
        it.next();
        tmp=it.next();
        assert tmp.low == 100 && tmp.high == 100;
    }


    public static void testSet() {
        SeqnoRange range=new SeqnoRange(10, 15);
        range.set(11, 12, 13, 14);
        System.out.println("range=" + print(range));
        assert range.size() == 6;
        assert range.getNumberOfReceivedMessages() == 4;
        assert range.getNumberOfMissingMessages() == 2;
        Collection<Range> xmits=range.getMessagesToRetransmit();
        assert xmits.size() == 2;
        Iterator<Range> it=xmits.iterator();
        Range r=it.next();
        assert r.low == 10 && r.high == 10;
        r=it.next();
        assert r.low == 15 && r.high == 15;


        range=new SeqnoRange(10, 15);
        range.set(10,11,12,13,14);
        System.out.println("range=" + print(range));
        assert range.size() == 6;
        assert range.getNumberOfReceivedMessages() == 5;
        assert range.getNumberOfMissingMessages() == 1;
        xmits=range.getMessagesToRetransmit();
        assert xmits.size() == 1;
        it=xmits.iterator();
        r=it.next();
        assert r.low == 15 && r.high == 15;

        range=new SeqnoRange(10, 15);
        range.set(11,12,13,14,15);
        System.out.println("range=" + print(range));
        assert range.size() == 6;
        assert range.getNumberOfReceivedMessages() == 5;
        assert range.getNumberOfMissingMessages() == 1;
        xmits=range.getMessagesToRetransmit();
        assert xmits.size() == 1;
        it=xmits.iterator();
        r=it.next();
        assert r.low == 10 && r.high == 10;

        range=new SeqnoRange(10, 15);
        range.set(10,11,12,13,14,15);
        System.out.println("range=" + print(range));
        assert range.size() == 6;
        assert range.getNumberOfReceivedMessages() == 6;
        assert range.getNumberOfMissingMessages() == 0;
        xmits=range.getMessagesToRetransmit();
        assert xmits.isEmpty();

        range=new SeqnoRange(10, 15);
        range.set(11,12,14,15);
        System.out.println("range=" + print(range));
        assert range.size() == 6;
        assert range.getNumberOfReceivedMessages() == 4;
        assert range.getNumberOfMissingMessages() == 2;
        xmits=range.getMessagesToRetransmit();
        assert xmits.size() == 2;
        it=xmits.iterator();
        r=it.next();
        assert r.low == 10 && r.high == 10;
        r=it.next();
        assert r.low == 13 && r.high == 13;

        range.set(13);
        assert range.getNumberOfReceivedMessages() == 5;
        assert range.getNumberOfMissingMessages() == 1;
        xmits=range.getMessagesToRetransmit();
        it=xmits.iterator();
        r=it.next();
        assert r.low == 10 && r.high == 10;

        range.set(10);
        System.out.println("range=" + print(range));
        assert range.getNumberOfReceivedMessages() == 6;
        assert range.getNumberOfMissingMessages() == 0;
        xmits=range.getMessagesToRetransmit();
        assert xmits.isEmpty();
    }

    @Test(expectedExceptions=IllegalArgumentException.class)
    public static void testSetOfInvalidIndex() {
        SeqnoRange range=new SeqnoRange(10, 10);
        range.set(9);
    }
    
    
    public static void testCompareTo() {
        TreeMap<Seqno,Seqno> map=new TreeMap<Seqno,Seqno>(new SeqnoComparator());

        Seqno[] ranges=new Seqno[]{new SeqnoRange(900,905), new Seqno(222), new SeqnoRange(700,800), new SeqnoRange(23,200)};

        for(Seqno range: ranges)
            map.put(range, range);

        System.out.println("map = " + map.keySet());
        assert map.size() == ranges.length;

        for(long num: new long[]{0, 1, 201, 202, 223, 1000}) {
            checkNull(map, num);
        }

        checkInRange(map,  23,  23, 200);
        checkInRange(map, 100,  23, 200);
        checkInRange(map, 200,  23, 200);
        checkInRange(map, 222, 222, 222);
        checkInRange(map, 750, 700, 800);
        checkInRange(map, 905, 900, 905);
    }


    public static void testCompareTo2() {
        TreeMap<Seqno,Seqno> map=new TreeMap<Seqno,Seqno>(new SeqnoComparator());

        Seqno[] ranges=new Seqno[]{new SeqnoRange(900,905), new Seqno(550), new Seqno(222),
                new SeqnoRange(700,800), new Seqno(650), new SeqnoRange(23,200)};

        for(Seqno range: ranges)
            map.put(range, range);

        System.out.println("map = " + map.keySet());
        assert map.size() == 6;

        for(long num: new long[]{0, 1, 201, 202, 223, 1000}) {
            checkNull(map, num);
        }

        checkInRange(map, 550, 550, 550);
        checkInRange(map, 650, 650, 650);

        checkInRange(map,  23,  23, 200);
        checkInRange(map, 100,  23, 200);
        checkInRange(map, 200,  23, 200);
        checkInRange(map, 222, 222, 222);
        checkInRange(map, 750, 700, 800);
        checkInRange(map, 905, 900, 905);
    }



    public static void testLargeRange() {
        SeqnoRange range=new SeqnoRange(0, 1500);

        Set<Integer> sorted_set=new TreeSet<Integer>();
        for(int i=0; i < 500; i++) {
            int num=(int)Util.random(1499);
            sorted_set.add(num);
        }

        for(int num: sorted_set)
            range.set(num);

        int num_set=sorted_set.size();
        System.out.println("set " + num_set + " bits");
        assert range.getNumberOfReceivedMessages() == num_set;
        Collection<Range> missing=range.getMessagesToRetransmit();
        System.out.println("missing = " + missing);
    }


    public static void testRemovalFromTreeMap() {
        Map<Seqno,Seqno> map=new TreeMap<Seqno,Seqno>(new SeqnoComparator());

        Seqno[] ranges=new Seqno[]{new SeqnoRange(900,905), new Seqno(222), new Seqno(500),
                new SeqnoRange(700,800), new Seqno(801), new SeqnoRange(23,200)};

        for(Seqno range: ranges)
            map.put(range, range);

        System.out.println("map = " + map.keySet());
        assert map.size() == ranges.length;

        for(Seqno r: ranges) {
            Seqno range=map.get(r);
            assert range != null;
            assert range == r; // must point to the same object in memory
        }

        for(Seqno r: ranges) {
            Seqno range=map.remove(r);
            assert range != null;
            assert range == r;
        }

        assert map.isEmpty();
    }


    public static void testRemovalFromHashMap() {
        Map<Seqno,Seqno> map=new ConcurrentHashMap<Seqno,Seqno>();

        Seqno[] ranges=new Seqno[]{new SeqnoRange(900,905), new Seqno(222), new SeqnoRange(700,800),
                new SeqnoRange(23,200), new Seqno(201), new Seqno(205)};

        for(Seqno range: ranges)
            map.put(range, range);

        System.out.println("map = " + map.keySet());
        assert map.size() == ranges.length;

        for(Seqno r: ranges) {
            Seqno range=map.get(r);
            assert range != null;
            assert range == r; // must point to the same object in memory
        }

        for(Seqno r: ranges) {
            Seqno range=map.remove(r);
            assert range != null;
            assert range == r;
        }

        assert map.isEmpty();
    }


    private static void checkInRange(Map<Seqno,Seqno> map, long seqno, long from, long to) {
        Seqno val=map.get(new Seqno(seqno, true));
        System.out.println("seqno=" + seqno + ", val = " + val);
        assert val != null;
        assert val.contains(seqno);
        assert val.getLow() == from;
        if(val instanceof SeqnoRange)
            assert ((SeqnoRange)val).getHigh() == to;
    }

    private static void checkNull(Map<Seqno,Seqno> map, long seqno) {
        Seqno val=map.get(new Seqno(seqno, true));
        assert val == null;
    }


    private static String print(Seqno seqno) {
        StringBuilder sb=new StringBuilder();
        sb.append(seqno.toString());
        sb.append(", size= " + seqno.size());
        if(seqno instanceof SeqnoRange) {
            sb.append(", received=" + ((SeqnoRange)seqno).printBits(true) + " (" + seqno.getNumberOfReceivedMessages() + ")");
            sb.append(", missing=" + ((SeqnoRange)seqno).printBits(false) + " (" + seqno.getNumberOfMissingMessages() + ")");
        }
        return sb.toString();
    }
}
