
package org.jgroups.tests;


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

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;


/**
 * Tests various methods in JChannel
 * @author Bela Ban
 */
@Test(groups=Global.STACK_DEPENDENT)
public class ChannelTest extends ChannelTestBase {       

    public void testBasicOperations() throws Exception {
        JChannel a = createChannel(true,1).name("A");
        JChannel b=null;

        try {
            a.connect("testBasicOperations");
            assert a.isOpen();
            assert a.isConnected();
       
            assert a.getAddress() != null;
            assert a.getView() != null;
            assert a.getView().getMembers().contains(a.getAddress());
       
            a.connect("testBasicOperations");
            a.disconnect();
            assert a.isConnected() == false;
            assert a.isOpen();
            assert a.getAddress() == null;
            assert a.getView() == null;
            assert a.getClusterName() == null;
       
            a.connect("testBasicOperations");

            a.close();
       
            try {
                a.connect("testBasicOperations");
                assert false : "Should have generated exception, and it has not";
            }
            catch (Exception e) {
                assert e instanceof IllegalStateException;
            }
       
            assert a.isConnected() == false;
            assert a.isOpen() == false;
            assert a.getAddress() == null;
            assert a.getView() == null;
       
            assert a.getClusterName() == null;
       
            a = createChannel(true,2);
            a.connect("testBasicOperations");
            b = createChannel(a);
            b.connect("testBasicOperations");
       
            Util.sleep(1000);
       
            assert a.isOpen();
            assert a.isConnected();
       
            assert a.getAddress() != null;
            assert a.getView() != null;
            assert a.getView().getMembers().contains(a.getAddress());
            assert a.getView().getMembers().contains(b.getAddress());
       
            assert b.isOpen();
            assert b.isConnected();
       
            assert b.getAddress() != null;
            assert b.getView() != null;
            assert b.getView().getMembers().contains(b.getAddress());
            assert b.getView().getMembers().contains(a.getAddress());
       
            b.close();
            Util.sleep(1000);
       
            assert b.isOpen() == false;
            assert b.isConnected() == false;
       
            assert b.getAddress() == null;
            assert b.getView() == null;
       
            assert a.isOpen();
            assert a.isConnected();
       
            assert a.getAddress() != null;
            assert a.getView() != null;
            assert a.getView().getMembers().contains(a.getAddress());
            assert a.getView().getMembers().contains(b.getAddress()) == false;
        }
        finally {
            Util.close(a,b);
        }
    }


    public void testSendOnDisconnectedChannel() throws Exception {
        JChannel ch=createChannel();
        try {
            ch.send(null, "hello world");
            assert false : "sending on a disconnected channel should have failed";
        }
        catch(IllegalStateException ex) {
            System.out.println("received \"" + ex + "\" as expected: sending on a disconnected channel is not allowed");
        }
    }

    public void testSendOnClosedChannel() throws Exception {
        JChannel ch=createChannel();
        try {
            Util.close(ch);
            ch.send(null, "hello world");
            assert false : "sending on a closed channel should have failed";
        }
        catch(IllegalStateException ex) {
            System.out.println("received \"" + ex + "\" as expected: sending on a closed channel is not allowed");
        }
    }


    public void testViewChange() throws Exception {
        JChannel ch1 = createChannel(true,2);
        ViewChecker checker=new ViewChecker(ch1);
        ch1.setReceiver(checker);
        ch1.connect("testViewChange");

        JChannel ch2=createChannel(ch1);
        try {
            ch2.connect("testViewChange");
            assertTrue(checker.getReason(), checker.isSuccess());
            ch2.close();
            assertTrue(checker.getReason(), checker.isSuccess());
        }
        finally {
            Util.close(ch1,ch2);
        }
    }

    public void testViewChange2() throws Exception {
        JChannel a=createChannel(true, 2).name("A");
        JChannel b=createChannel(a).name("B");
        a.connect("testViewChange2");
        b.connect("testViewChange2");
        Util.waitUntilAllChannelsHaveSameView(10000, 1000, a,b);
        List<Address> expectedMembers = Arrays.asList(a.getAddress(), b.getAddress());
        List<Address> mbrs=a.getView().getMembers();
        assert expectedMembers.equals(mbrs);
        assert mbrs.equals(expectedMembers);
    }


    public void testIsConnectedOnFirstViewChange() throws Exception {
        JChannel ch1 = createChannel(true,2);        
        JChannel ch2=createChannel(ch1);
        ConnectedChecker tmp=new ConnectedChecker(ch2);
        ch2.setReceiver(tmp);
        try {
            ch1.connect("testIsConnectedOnFirstViewChange");
            ch2.connect("testIsConnectedOnFirstViewChange");
            assert tmp.isConnected();
        }
        finally {
            Util.close(ch1,ch2);
        }
    }



    public void testNoViewIsReceivedAfterDisconnect() throws Exception {
        JChannel ch1 = createChannel(true,2);        
        JChannel ch2=createChannel(ch1);
        MyViewChecker ra = new MyViewChecker(ch2);
        ch2.setReceiver(ra);

        try {
            ch1.connect("testNoViewIsReceivedAfterDisconnect");
            ch2.connect("testNoViewIsReceivedAfterDisconnect");
            Util.sleep(500);
            ch2.disconnect();
            Util.sleep(1000);
            assert !ra.receivedViewWhenDisconnected : "Received view where not member";
        }
        finally {
            Util.close(ch1,ch2);
        }
    }


    public void testNoViewIsReceivedAfterClose() throws Exception {
        JChannel ch1 = createChannel(true,2);        
        JChannel ch2=createChannel(ch1);
        MyViewChecker ra = new MyViewChecker(ch2);
        ch2.setReceiver(ra);

        try {
            ch1.connect("testNoViewIsReceivedAfterClose");
            ch2.connect("testNoViewIsReceivedAfterClose");
            Util.sleep(200);
            ch2.close();
            Util.sleep(1000);
            assert !ra.receivedViewWhenDisconnected : "Received view where not member";
        }
        finally {
            Util.close(ch1,ch2);
        }
    }


    @Test(expectedExceptions={NullPointerException.class})
    public void testNullMessage() throws Exception {
        JChannel ch1 = createChannel(true,2);        
        try{
            ch1.connect("testNullMessage");
            ch1.send(null);
        }
        finally{
            Util.close(ch1);
        }             
    }


    public void testOrdering() throws Exception {
        final int NUM=100;
        JChannel ch=createChannel(true, 2);
        MyReceiver receiver=new MyReceiver(NUM);
        ch.setReceiver(receiver);
        try {
            ch.connect("testOrdering");
            for(int i=1;i <= NUM;i++) {
                ch.send(new Message(null, i));
            }
            receiver.waitForCompletion();
            List<Integer> nums=receiver.getNums();
            checkMonotonicallyIncreasingNumbers(nums);
        }
        finally {
            Util.close(ch);
        }
    }

    private static void checkMonotonicallyIncreasingNumbers(List<Integer> nums) {
        int current=-1;
        for(int num: nums) {
            if(current < 0) {
                current=num;
            }
            else {
                assert ++current == num : "list is " + nums;
            }
        }
    }


    private static class MyReceiver extends ReceiverAdapter {
        final List<Integer> nums=new LinkedList<>();
        final int expected;

        public MyReceiver(int expected) {
            this.expected=expected;
        }

        public List<Integer> getNums() {
            return nums;
        }

        public void waitForCompletion() throws InterruptedException {
            synchronized(nums) {
                while(nums.size() < expected) {
                    nums.wait();
                }
            }
        }

        public void receive(Message msg) {
            Integer num=(Integer)msg.getObject();
            synchronized(nums) {
                nums.add(num);
                if(nums.size() >= expected) {
                    nums.notifyAll();
                }
            }
        }
    }


    private static class ConnectedChecker extends ReceiverAdapter {
        boolean connected=false;

        public ConnectedChecker(JChannel channel) {
            this.channel=channel;
        }

        final JChannel channel;

        public boolean isConnected() {
            return connected;
        }

        public void viewAccepted(View new_view) {
            connected=channel.isConnected();
        }
    }

    private static class ViewChecker extends ReceiverAdapter {
        final JChannel channel;
        boolean success=true;
        String reason="";

        public ViewChecker(JChannel channel) {
            this.channel=channel;
        }

        public String getReason() {
            return reason;
        }

        public boolean isSuccess() {
            return success;
        }

        public void viewAccepted(View new_view) {
            View view=channel.getView();
            String str="viewAccepted(): channel's view=" + view + "\nreceived view=" + new_view;
            // System.out.println(str);           
            if(view!= null && !view.equals(new_view)) {
                success=false;
                reason+=str + "\n";
            }
        }
    }


    private static class MyViewChecker extends ReceiverAdapter {
        private boolean receivedViewWhenDisconnected;
        private final JChannel ch;

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

        public void viewAccepted(View new_view) {
            receivedViewWhenDisconnected = !new_view.containsMember(ch.getAddress());
        }

    }



}


