package org.jgroups.tests;

import org.jgroups.*;
import org.jgroups.protocols.BARRIER;
import org.jgroups.protocols.EXAMPLE;
import org.jgroups.protocols.PING;
import org.jgroups.protocols.SHARED_LOOPBACK;
import org.jgroups.util.Util;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Tests the BARRIER protocol
 * @author Bela Ban
 */
@Test(groups=Global.FUNCTIONAL,sequential=true)
public class BARRIERTest {
    JChannel ch;
    PING     ping_prot;
    BARRIER  barrier_prot;
    EXAMPLE example_prot;


    @BeforeMethod
    void setUp() throws Exception {
        ping_prot=new PING();
        example_prot=new EXAMPLE();
        barrier_prot=new BARRIER();
        ch=Util.createChannel(new SHARED_LOOPBACK(), ping_prot, barrier_prot, example_prot);
        ch.connect("BARRIERTest");
    }

    @AfterMethod
    void destroy() {
        Util.close(ch);
    }

    public void testBlocking() {
        assert !barrier_prot.isClosed();
        ch.down(new Event(Event.CLOSE_BARRIER));
        assert barrier_prot.isClosed();
        ch.down(new Event(Event.OPEN_BARRIER));
        assert !barrier_prot.isClosed();
    }


    public void testThreadsBlockedOnBarrier() {
        MyReceiver receiver=new MyReceiver();
        ch.setReceiver(receiver);
        ch.down(new Event(Event.CLOSE_BARRIER));
        for(int i=0; i < 5; i++) {
            new Thread() {
                public void run() {
                    ping_prot.up(new Event(Event.MSG, new Message(null, null, null)));
                }
            }.start();
        }

        Util.sleep(2000);
        int num_in_flight_threads=barrier_prot.getNumberOfInFlightThreads();
        assert num_in_flight_threads == 0;

        ch.down(new Event(Event.OPEN_BARRIER));
        Util.sleep(2000);

        num_in_flight_threads=barrier_prot.getNumberOfInFlightThreads();
        assert num_in_flight_threads == 0;
        int received_msgs=receiver.getNumberOfReceivedMessages();
        assert received_msgs == 5 : "expected " + 5 + " messages but got " + received_msgs;
    }


    public void testThreadsBlockedOnMutex() throws InterruptedException {
        BlockingReceiver receiver=new BlockingReceiver();
        ch.setReceiver(receiver);

        Thread thread=new Thread() {
            public void run() {
                ping_prot.up(new Event(Event.MSG, new Message()));}
        };

        Thread thread2=new Thread() {
            public void run() {
                ping_prot.up(new Event(Event.MSG, new Message()));}
        };

        thread.start();
        thread2.start();

        thread.join();
        thread2.join();
    }




    static class MyReceiver extends ReceiverAdapter {
        AtomicInteger num_mgs_received=new AtomicInteger(0);

        public void receive(Message msg) {
            if(num_mgs_received.incrementAndGet() % 1000 == 0)
                System.out.println("<== " + num_mgs_received.get());
        }

        public int getNumberOfReceivedMessages() {
            return num_mgs_received.get();
        }
    }


    class BlockingReceiver extends ReceiverAdapter {

        public void receive(Message msg) {
            System.out.println("Thread " + Thread.currentThread().getId() + " receive() called - about to enter mutex");
            synchronized(this) {
                System.out.println("Thread " + Thread.currentThread().getId() + " entered mutex");
                Util.sleep(2000);
                System.out.println("Thread " + Thread.currentThread().getId() + " closing barrier");
                ch.down(new Event(Event.CLOSE_BARRIER));
                System.out.println("Thread " + Thread.currentThread().getId() + " closed barrier");
            }
        }
    }



}
