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.util.Util;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Tests message ordering
 * @author Bela Ban
 */
@Test(groups=Global.FUNCTIONAL,singleThreaded=true)
public class OrderingTest {
    protected static final int NUM_MSGS=200000;
    protected static final int NUM_SENDERS=2;
    protected static final int TOTAL_NUM_MSGS=NUM_MSGS * NUM_SENDERS;

    protected JChannel[] channels=new JChannel[NUM_SENDERS];
    protected MySender[] senders=new MySender[NUM_SENDERS];


    @BeforeMethod
    void init() throws Exception {
        System.out.println("creating " + NUM_SENDERS + " channels");
        for(int i=0; i < channels.length; i++) {
            channels[i]=createChannel();
            channels[i].setReceiver(new MyReceiver());
            senders[i]=new MySender(channels[i]);
            channels[i].connect("OrderingTest.testFIFOOrder");
        }
        System.out.println("done");

        System.out.println("\nwaiting for a cluster of " + NUM_SENDERS + " to form:");
        boolean done=true;
        for(int i=0; i < 20; i++) {
            for(JChannel ch: channels) {
                if(ch.getView().size() != NUM_SENDERS) {
                    done=false;
                    break;
                }
            }
            if(!done)
                Util.sleep(1000);
            else
                break;
        }
    }

    @AfterMethod
    void destroy() {
        for(int i=channels.length-1; i >= 0; i--) {
            Util.close(channels[i]);
        }
    }


    protected static JChannel createChannel() throws Exception {
        return new JChannel(new SHARED_LOOPBACK().setValue("oob_thread_pool_rejection_policy", "run")
                              .setValue("thread_pool_rejection_policy", "run")
                              .setValue("thread_pool_queue_max_size", 100000),
                            new SHARED_LOOPBACK_PING(),
                            new MERGE3(),
                            new FD_SOCK(),
                            new VERIFY_SUSPECT(),
                            new BARRIER(),
                            new NAKACK2().setValue("use_mcast_xmit", false).setValue("discard_delivered_msgs", true),
                            new UNICAST3(),
                            new STABLE().setValue("max_bytes", 50000),
                            new GMS().setValue("print_local_addr", false),
                            new UFC().setValue("max_credits", 2000000),
                            new MFC().setValue("max_credits", 2000000),
                            new FRAG2());
    }


    public void testFIFOOrdering() throws Exception {
        assert channels[0].getView().size() == NUM_SENDERS : "view[0] is " + channels[0].getView().size();
        System.out.println("done, view is " + channels[0].getView());

        System.out.println("\nstarting to send " + NUM_MSGS + " messages");
        for(int i=0; i < senders.length; i++)
            senders[i].start();
        for(int i=0; i < senders.length; i++) {
            MySender sender=senders[i];
            sender.join();
        }
        System.out.println("senders done");

        System.out.println("\nwaiting for message reception by all receivers:");
        boolean done;
        for(int i=0; i < 50; i++) {
            done=true;
            for(JChannel ch: channels) {
                MyReceiver receiver=(MyReceiver)ch.getReceiver();
                int received=receiver.getReceived();
                System.out.println(ch.getAddress() + ": " + received);
                STABLE stable=(STABLE)ch.getProtocolStack().findProtocol(STABLE.class);
                stable.gc();
                if(received != TOTAL_NUM_MSGS) {
                    done=false;
                    break;
                }
            }
            if(!done)
                Util.sleep(1000);
            else
                break;
        }
        for(JChannel ch: channels) {
            MyReceiver receiver=(MyReceiver)ch.getReceiver();
            System.out.println(ch.getAddress() + ": " + receiver.getReceived());
        }

        for(JChannel ch: channels) {
            MyReceiver receiver=(MyReceiver)ch.getReceiver();
            assert receiver.getReceived() == TOTAL_NUM_MSGS : "receiver had " + receiver.getReceived() +
              " messages (expected=" + TOTAL_NUM_MSGS + ")";
        }
        System.out.println("done");

        System.out.println("\nchecking message order");

        for(JChannel ch: channels) {
            MyReceiver receiver=(MyReceiver)ch.getReceiver();
            System.out.print(ch.getAddress() + ": ");
            boolean ok=receiver.getNumberOfErrors() == 0;
            System.out.println(ok? "OK" : "FAIL (" + receiver.getNumberOfErrors() + " errors)");
            assert ok : receiver.getNumberOfErrors() + " errors";
        }

        System.out.println("done");
    }


   /* private static boolean checkOrder(ConcurrentMap<Address,List<Integer>> map, boolean print_incorrect_elements) {
        boolean retval=true;
        for(Map.Entry<Address,List<Integer>> entry: map.entrySet()) {
            Address sender=entry.getKey();
            List<Integer> list=entry.getValue();
            int curr=1;
            for(Integer num: list) {
                if(!num.equals(curr)) {
                    retval=false;
                    if(!print_incorrect_elements)
                        return false;
                    System.err.println("element " + num + " != " + curr);
                }
                curr++;
            }
        }

        return retval;
    }*/


    protected static class MySender extends Thread {
        protected final JChannel ch;

        public MySender(JChannel ch) {
            this.ch=ch;
        }

        public void run() {
            for(int i=1; i <= NUM_MSGS; i++) {
                try {
                    Message msg=new Message(null, null, new Integer(i));
                    ch.send(msg);
                    if(i % 100000 == 0)
                        System.out.println(Thread.currentThread().getId() + ": " + i + " sent");
                }
                catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    protected static class MyReceiver extends ReceiverAdapter {
        protected final ConcurrentMap<Address,Integer> map=new ConcurrentHashMap<>();
        final AtomicInteger received=new AtomicInteger(0);
        protected int num_errors=0;

        public int getNumberOfErrors() {
            return num_errors;
        }

        public int getReceived() {
            return received.intValue();
        }

        public void receive(Message msg) {
            Integer num=(Integer)msg.getObject();
            Address sender=msg.getSrc();

            Integer current_seqno=map.get(sender);
            if(current_seqno == null) {
                current_seqno=new Integer(1);
                Integer tmp=map.putIfAbsent(sender, current_seqno);
                if(tmp != null)
                    current_seqno=tmp;
            }

            if(current_seqno.intValue() == num)
                map.put(sender, current_seqno + 1);
            else
                num_errors++;

            if(received.incrementAndGet() % 100000 == 0)
                System.out.println("received " + received);
        }
    }


    /*public static void main(String[] args) throws Exception {
        OrderingTest test=new OrderingTest();
        test.init();
        test.testFIFOOrdering();
        test.destroy();
    }*/
}
