package org.jgroups.protocols;


import org.jgroups.Global;
import org.jgroups.JChannel;
import org.jgroups.protocols.pbcast.GMS;
import org.jgroups.protocols.pbcast.NAKACK2;
import org.jgroups.protocols.pbcast.STABLE;
import org.jgroups.protocols.rules.Rule;
import org.jgroups.protocols.rules.SUPERVISOR;
import org.jgroups.util.Util;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

@Test(groups=Global.FUNCTIONAL,sequential=true)
public class SUPERVISOR_Test {
    protected JChannel a, b;

    @BeforeMethod
    protected void setup() throws Exception {
        a=createChannel("A");
        a.connect("SUPERVISOR_Test");
        b=createChannel("B");
        b.connect("SUPERVISOR_Test");
        Util.waitUntilAllChannelsHaveSameSize(10000, 1000, a, b);
    }

    @AfterMethod
    protected void destroy() {
        Util.close(b,a);
    }

    public void testFailureDetectionRule() {
        SUPERVISOR sva=(SUPERVISOR)a.getProtocolStack().findProtocol(SUPERVISOR.class);
        sva.installRule(500, new RestartFailureDetector());

        SUPERVISOR svb=(SUPERVISOR)b.getProtocolStack().findProtocol(SUPERVISOR.class);
        svb.installRule(500, new RestartFailureDetector());

        assertFailureDetectorRunning(a, b);

        System.out.println("stopping failure detection, waiting for failure detection restart rule to restart failure detection");
        stopFailureDetection(a, b);

        for(int i=0; i < 10; i++) {
            if(isFailureDetectionRunning(a, b))
                break;
            Util.sleep(500);
        }

        assertFailureDetectorRunning(a, b);
    }

    protected boolean isFailureDetectionRunning(JChannel ... channels) {
        for(JChannel ch: channels) {
            FD fd=(FD)ch.getProtocolStack().findProtocol(FD.class);
            if(!fd.isMonitorRunning())
                return false;
        }
        return true;
    }

    protected JChannel createChannel(String name) throws Exception {
        JChannel ch=new JChannel(new SHARED_LOOPBACK(),
                                 new PING().setValue("timeout", 500),
                                 new FD(),
                                 new NAKACK2().setValue("use_mcast_xmit", false),
                                 new UNICAST3(),
                                 new STABLE().setValue("max_bytes", 50000),
                                 new GMS().setValue("print_local_addr", false),
                                 new SUPERVISOR());
        ch.setName(name);
        return ch;
    }


    protected void assertFailureDetectorRunning(JChannel ... channels) {
        for(JChannel ch: channels) {
            System.out.print("Checking " + ch.getName() + ": ");
            FD fd=(FD)ch.getProtocolStack().findProtocol(FD.class);
            assert fd.isMonitorRunning();
            System.out.println("running");
        }
    }

    protected void stopFailureDetection(JChannel ... channels) {
        for(JChannel ch: channels) {
            FD fd=(FD)ch.getProtocolStack().findProtocol(FD.class);
            fd.stopFailureDetection();
        }
    }


    protected static class RestartFailureDetector extends Rule {
        protected FD fd;

        public String name()        {return "MyRule";}
        public String description() {return "Checks if FD.Monitor is running and - if not - starts it";}

        public void init() {
            super.init();
            fd=(FD)sv.getProtocolStack().findProtocol(FD.class);
            if(fd == null)
                throw new IllegalStateException("FD not found in stack");
        }

        public boolean eval()      {return sv.getView().size() > 1 &&  !fd.isMonitorRunning();}
        public String  condition() {return "The view size is > 1 (" + sv.getView() + ") but the monitor is not running";}

        public void trigger() throws Throwable {
            System.out.println(getClass().getSimpleName() + ": restarting FD.Monitor: view=" + sv.getView());
            fd.startFailureDetection();
        }
    }


}
