
package org.jgroups.blocks;

import org.jgroups.Address;
import org.jgroups.Global;
import org.jgroups.Message;
import org.jgroups.View;
import org.jgroups.protocols.relay.SiteUUID;
import org.jgroups.util.RspList;
import org.jgroups.util.UUID;
import org.jgroups.util.Util;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * @author Bela Ban
 */
@Test(groups=Global.FUNCTIONAL,sequential=true)
public class GroupRequestTest {
    Address a, b, c;
    List<Address> dests=null;

    @BeforeClass
    void init() throws UnknownHostException {
        a=Util.createRandomAddress("A");
        b=Util.createRandomAddress("B");
        c=Util.createRandomAddress("C");
    }

    @BeforeMethod
    protected void setUp() throws Exception {
        dests=new ArrayList<Address>(Arrays.asList(a,b));
    }

    @AfterMethod
    protected void tearDown() throws Exception {
        dests.clear();
    }


    

    public void testMessageTimeout() throws Exception {
        _testMessageTimeout(true);
    }

    public void testMessageReception() throws Exception {
        _testMessageReception(true);
        _testMessageReception(false);
    }


    public void testMessageReceptionWithViewChange() throws Exception {
        _testMessageReceptionWithViewChange(true);
        _testMessageReceptionWithViewChange(false);
    }

    public void testMessageReceptionWithViewChangeMemberLeft() throws Exception {
        _testMessageReceptionWithViewChangeMemberLeft(true);
        _testMessageReceptionWithViewChangeMemberLeft(false);
    }


    public void testGetFirstWithResponseFilter() throws Exception {
        Object[] responses={new Message(null,a, new Long(1)),
          new Message(null,b, new Long(2)),
          new Message(null,c, new Long(3))};
        MyCorrelator corr=new MyCorrelator(true, responses, 500);
        dests.add(c);
        GroupRequest<Long> req=new GroupRequest<Long>(new Message(), corr, dests, new RequestOptions(ResponseMode.GET_FIRST, 0));
        req.setResponseFilter(new RspFilter() {
            int num_rsps=0;

            public boolean isAcceptable(Object response, Address sender) {
                boolean retval=response instanceof Long && ((Long)response).longValue() == 2L;
                System.out.println("-- received " + response + " from " + sender + ": " + (retval? "OK" : "NOTOK"));
                if(retval)
                    num_rsps++;
                return retval;
            }

            public boolean needMoreResponses() {
                return num_rsps < 1;
            }
        });
        corr.setGroupRequest(req);
        boolean rc=req.execute();
        System.out.println("group request is " + req);
        assert rc;
        assert req.isDone();
        RspList<Long> results=req.getResults();
        Assert.assertEquals(3, results.size());
        Assert.assertEquals(1, results.numReceived());
    }


    public void testGetAllWithResponseFilter() throws Exception {
        Object[] responses={new Message(null,a, new Long(1)),
          new Message(null,b, new Long(2)),
          new Message(null,c, new Long(3))};
        MyCorrelator corr=new MyCorrelator(true, responses, 500);
        dests.add(c);
        GroupRequest<Long> req=new GroupRequest<Long>(new Message(), corr, dests, new RequestOptions(ResponseMode.GET_ALL, 0));
        req.setResponseFilter(new RspFilter() {
            int num_rsps=0;

            public boolean isAcceptable(Object response, Address sender) {
                boolean retval=response instanceof Long &&
                        (((Long)response).longValue() == 1L || ((Long)response).longValue() == 2L);
                System.out.println("-- received " + response + " from " + sender + ": " + (retval? "OK" : "NOTOK"));
                if(retval)
                    num_rsps++;
                return retval;
            }

            public boolean needMoreResponses() {
                return num_rsps < 2;
            }
        });
        corr.setGroupRequest(req);
        boolean rc=req.execute();
        System.out.println("group request is " + req);
        assert rc;
        assert req.isDone();
        RspList<Long> results=req.getResults();
        Assert.assertEquals(3, results.size());
        Assert.assertEquals(2, results.numReceived());
    }

    /**
     * Tests reception of 3 null values, which are all rejected by the NonNullFilter. However, isDone() returns true
     * because we received responses for all 3 requests, even though all of them were rejected. If we continued here,
     * we'd block until we run into the timeout. See https://issues.jboss.org/browse/JGRP-1330 for details.
     */
    public void testAllNullResponsesWithFilter() {
        dests.add(c);
        GroupRequest<Boolean> req=new GroupRequest<Boolean>(new Message(), null, dests,
                                                            new RequestOptions(ResponseMode.GET_ALL, 10000));
        assert !req.isDone();

        req.setResponseFilter(new NonNullFilter());

        for(Address sender: dests)
            req.receiveResponse(null, sender, false);

        assert req.isDone();
    }
    

    public void testAllNullResponsesWithFilterGetFirst() {
        dests.add(c);
        GroupRequest<Boolean> req=new GroupRequest<Boolean>(new Message(), null, dests,
                                                            new RequestOptions(ResponseMode.GET_FIRST, 10000));
        assert !req.isDone();

        req.setResponseFilter(new NonNullFilter());

        req.receiveResponse(null, dests.get(0), false);
        assert !req.isDone();

        req.receiveResponse(true, dests.get(1), false);
        assert req.isDone();
    }

    /**
     * Verifies that a received *and* suspected flag on a Rsp counts only once, to prevent premature termination of
     * a blocking RPC. https://issues.jboss.org/browse/JGRP-1505
     */
    public void testResponsesComplete() {
        GroupRequest<Integer> req=new GroupRequest<Integer>(null, null, Arrays.asList(a,b,c), RequestOptions.SYNC());
        checkComplete(req, false);

        req.receiveResponse(1, a, false);
        req.receiveResponse(2, b, true);
        checkComplete(req, false);

        req.receiveResponse(3, b, false);
        checkComplete(req, false);

        req.receiveResponse(4, c, false);
        checkComplete(req, true);


        req=new GroupRequest<Integer>(null, null, Arrays.asList(a,b,c), RequestOptions.SYNC());
        req.receiveResponse(1, a, false);
        checkComplete(req, false);

        req.receiveResponse(2, b, false);
        checkComplete(req, false);

        req.suspect(b);
        checkComplete(req, false);

        req.receiveResponse(3, c, false);
        checkComplete(req, true);
    }

    /**
     * Verifies that a received *and* suspected flag on a Rsp counts only once, to prevent premature termination of
     * a blocking RPC. https://issues.jboss.org/browse/JGRP-1505
     */
    public void testResponsesComplete2() {
        GroupRequest<Integer> req=new GroupRequest<Integer>(null, null, Arrays.asList(a,b,c), RequestOptions.SYNC());
        req.suspect(a);
        checkComplete(req, false);
        
        req.receiveResponse(1, a, false);
        checkComplete(req, false);

        req.receiveResponse(2, b, false);
        checkComplete(req, false);

        req.suspect(b);
        checkComplete(req, false);

        req.receiveResponse(3, c, false);
        req.suspect(c);
        checkComplete(req, true);
    }

    public void testResponsesComplete3() {
        SiteUUID.addToCache((short)0, "lon");
        SiteUUID.addToCache((short)1, "sfo");
        SiteUUID.addToCache((short)2, "nyc");
        Address one=new SiteUUID((UUID)Util.createRandomAddress("lon1"), "lon1", (short)0);
        Address two=new SiteUUID((UUID)Util.createRandomAddress("sfo1"), "sfo1", (short)1);
        Address three=new SiteUUID((UUID)Util.createRandomAddress("nyc1"), "nyc1", (short)2);

        GroupRequest<Integer> req=new GroupRequest<Integer>(null, null, Arrays.asList(one, two, three), RequestOptions.SYNC());
        req.suspect(one);
        req.receiveResponse(1, one, false);
        req.siteUnreachable((short)0);
        checkComplete(req, false);

        req.siteUnreachable((short)1);
        req.receiveResponse(2, two, false);
        req.suspect(two);
        checkComplete(req, false);

        req.siteUnreachable((short)2);
        checkComplete(req, true);
        req.suspect(three);
        checkComplete(req, true);
        req.receiveResponse(3, three, false);
        checkComplete(req, true);
    }

    protected static void checkComplete(Request req, boolean expect) {
        System.out.println("req = " + req);
        assert req.getResponsesComplete() == expect;
    }


    protected static class NonNullFilter implements RspFilter {
        private volatile boolean validResponse;

        public boolean isAcceptable(Object response, Address sender) {
            if(response != null)
                validResponse=true;
            return response != null;
        }

        public boolean needMoreResponses() {
            return !validResponse;
        }
    }


    /**
     * test group timeout. demonstrates that the timeout mechanism times out too
     * quickly as multiple responses are received by the GroupRequest.
	 * Demonstrates by group request receiving multiple messages in a timeframe
	 * less than the total timeout. the request will fail, as after each
	 * received message, the request alters the total timeout.
	 * 
	 * @throws Exception
	 */
    private void _testMessageTimeout(boolean async) throws Exception {
        
        // need multiple destinations to replicate error
        int destCount = 10;
        
        // total timeout to hear from all members
        final long timeout = destCount * 1000;
        
        // how long each destination should delay
        final long delay = 75L;
        Object[] responses = new Message[destCount];
        
        dests = new ArrayList<Address>();
        for (int i = 0; i < destCount; i++) {
            Address addr = Util.createRandomAddress();
            dests.add(addr);
            // how long does this simulated destination take to execute? the sum is just less than the total timeout
            responses[i] = new Message(null, addr, new Long(i));
        }
        
        MyCorrelator corr = new MyCorrelator(async, responses, delay);
        
        // instantiate request with dummy correlator
        GroupRequest<Long> req=new GroupRequest<Long>(new Message(), corr, dests, new RequestOptions(ResponseMode.GET_ALL, timeout));
        corr.setGroupRequest(req);
        boolean rc = req.execute();
        System.out.println("group request is " + req);
        assert rc;
        assert req.isDone();
        RspList<Long> results = req.getResults();
        Assert.assertEquals(dests.size(), results.size());
    }



    private void _testMessageReception(boolean async) throws Exception {
        Object[] responses={new Message(null,a, new Long(1)),new Message(null,b, new Long(2))};
        MyCorrelator corr=new MyCorrelator(async, responses, 0);
        GroupRequest<Object> req=new GroupRequest<Object>(new Message(), corr, dests, new RequestOptions(ResponseMode.GET_ALL, 0));
        corr.setGroupRequest(req);
        boolean rc=req.execute();
        System.out.println("group request is " + req);
        assert rc;
        assert req.isDone();
        RspList<Object> results=req.getResults();
        Assert.assertEquals(2, results.size());
    }



    private void _testMessageReceptionWithViewChange(boolean async) throws Exception {
        List<Address> new_dests=new ArrayList<Address>();
        new_dests.add(a);
        new_dests.add(b);
        new_dests.add(a);
        Object[] responses={new Message(null,a, new Long(1)),
          new View(Util.createRandomAddress(), 322649, new_dests),
          new Message(null,b, new Long(2))};
        MyCorrelator corr=new MyCorrelator(async, responses, 0);
        GroupRequest<Long> req=new GroupRequest<Long>(new Message(), corr, dests, new RequestOptions(ResponseMode.GET_ALL, 0));
        corr.setGroupRequest(req);
        boolean rc=req.execute();
        System.out.println("group request is " + req);
        assert rc;
        assert req.isDone();
        RspList<Long> results=req.getResults();
        Assert.assertEquals(2, results.size());
    }


    private void _testMessageReceptionWithViewChangeMemberLeft(boolean async) throws Exception {
        List<Address> new_dests=new ArrayList<Address>();
        new_dests.add(b);
        Object[] responses={new Message(null,b, new Long(1)),
          new View(Util.createRandomAddress(), 322649, new_dests)};
        MyCorrelator corr=new MyCorrelator(async, responses, 0);
        GroupRequest<Object> req=new GroupRequest<Object>(new Message(), corr, dests, new RequestOptions(ResponseMode.GET_ALL, 0));

        corr.setGroupRequest(req);
        System.out.println("group request before execution: " + req);
        boolean rc=req.execute();
        System.out.println("group request after execution: " + req);
        assert rc;
        assert req.isDone();
        RspList<Object> results=req.getResults();
        Assert.assertEquals(2, results.size());
    }




    protected static class MyCorrelator extends RequestCorrelator {
        protected GroupRequest request;
        protected boolean      async=true;
        protected Object[]     responses=null;
        protected long         delay=0;

        public MyCorrelator(boolean async, Object[] responses, long delay) {
            super(null, null, null);
            this.async=async;
            this.responses=responses;
            this.delay=delay;
        }

        public void setGroupRequest(GroupRequest r) {
            request=r;
        }


        public void sendRequest(long id, List<Address> dest_mbrs, Message msg, RspCollector coll) throws Exception {
            send();
        }

        public void sendRequest(long id, Collection<Address> dest_mbrs, Message msg, RspCollector coll, RequestOptions options) throws Exception {
            send();
        }

        protected void send() {
            if(async) {
                new Thread() {
                    public void run() {
                        sendResponses();
                    }
                }.start();
            }
            else {
                sendResponses();
            }
        }

        protected void sendResponses() {
            if(responses != null) {
                Object obj;
                for(int i=0; i < responses.length; i++) {
                    if(delay > 0)
                        Util.sleep(delay);
                    obj=responses[i];
                    if(obj == null) {
                        System.err.println("object was null");
                        continue;
                    }
                    if(obj instanceof Message) {
                        Message msg=(Message)obj;
                        Address sender=msg.getSrc();
                        Object retval=null;
                        try {
                            retval=Util.objectFromByteBuffer(msg.getBuffer());
                        }
                        catch(Exception e) {
                            e.printStackTrace();
                        }
                        request.receiveResponse(retval, sender, false);
                    }
                    else if(obj instanceof View)
                        request.viewChange((View)obj);
                }
            }
        }
    }

}
