package org.jgroups.blocks;

import java.util.Map;

import org.jgroups.Address;
import org.jgroups.Channel;
import org.jgroups.Global;
import org.jgroups.JChannel;
import org.jgroups.Message;
import org.jgroups.blocks.mux.MuxMessageDispatcher;
import org.jgroups.blocks.mux.MuxUpHandler;
import org.jgroups.tests.ChannelTestBase;
import org.jgroups.util.Rsp;
import org.jgroups.util.Util;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

/**
 * @author Paul Ferraro
 */
@Test(groups=Global.STACK_DEPENDENT)
public class MuxMessageDispatcherTest extends ChannelTestBase {

    private JChannel[] channels = new JChannel[2];

    private MessageDispatcher[] dispatchers  = new MessageDispatcher[2];
    private MessageDispatcher[][] muxDispatchers = new MessageDispatcher[2][2];
    private MethodCall method = new MethodCall("getName", new Object[0], new Class[0]);
    
    @BeforeClass
    void setUp() throws Exception {

        channels[0] = createChannel(true);
        channels[1] = createChannel(channels[0]);
        
        for (int i = 0; i < dispatchers.length; i++) {

            dispatchers[i] = new MessageDispatcher(channels[i], null, null, new MuxRequestListener("dispatcher[" + i + "]"));

            channels[i].setUpHandler(new MuxUpHandler(dispatchers[i].getProtocolAdapter()));

            for (int j = 0; j < muxDispatchers[i].length; j++) {
                muxDispatchers[i][j] = new MuxMessageDispatcher((short) j, channels[i], null, null, new MuxRequestListener("muxDispatcher[" + i + "][" + j + "]"));
            }
 
            channels[i].connect("MuxMessageDispatcherTest");

            Util.sleep(1000);
        }
    }

    @AfterClass
    void tearDown() throws Exception {
        for (int i = 0; i < dispatchers.length; ++i) {
            channels[i].disconnect();
            channels[i].close();
            dispatchers[i].stop();
            
            for (int j = 0; j < muxDispatchers[i].length; ++j) {
                muxDispatchers[i][j].stop();
            }
        }
    }

    public void testCastMessage() throws Exception {

        Message message = new Message();
        
        // Validate normal dispatchers
        Map<Address, Rsp> responses = dispatchers[0].castMessage(null, message, RequestOptions.SYNC());

        Assert.assertEquals(responses.size(), 2);
        
        for (int i = 0; i < dispatchers.length; ++i) {
            
            verifyResponse(responses, channels[i], "dispatcher[" + i + "]");
        }

        // Validate muxed dispatchers
        for (int j = 0; j < muxDispatchers[0].length; ++j) {
            
            responses = muxDispatchers[0][j].castMessage(null, message, RequestOptions.SYNC());

            Assert.assertEquals(responses.size(), 2);
            
            for (int i = 0; i < dispatchers.length; ++i) {
                
                verifyResponse(responses, channels[i], "muxDispatcher[" + i + "][" + j + "]");
            }
        }
        
        final Address address = channels[0].getAddress();
        
        RspFilter filter = new RspFilter() {

            public boolean isAcceptable(Object response, Address sender) {
                return !sender.equals(address);
            }

            public boolean needMoreResponses() {
                return true;
            }
        };
        
        // Validate muxed rpc dispatchers w/filter
        responses = muxDispatchers[0][0].castMessage(null, message, RequestOptions.SYNC().setRspFilter(filter));

        Assert.assertEquals(responses.size(), 2);
        verifyResponse(responses, channels[0], null);
        verifyResponse(responses, channels[1], "muxDispatcher[1][0]");
        
        muxDispatchers[1][0].stop();
        
        // Validate stopped mux dispatcher response is auto-filtered
        responses = muxDispatchers[0][0].castMessage(null, message, RequestOptions.SYNC().setRspFilter(null));

        Assert.assertEquals(responses.size(), 2);
        verifyResponse(responses, channels[0], "muxDispatcher[0][0]");
        verifyResponse(responses, channels[1], null);
        
        // Validate stopped mux dispatcher response is auto-filtered and custom filter is applied
        responses = muxDispatchers[0][0].castMessage(null, message, RequestOptions.SYNC().setRspFilter(filter));

        Assert.assertEquals(responses.size(), 2);
        verifyResponse(responses, channels[0], null);
        verifyResponse(responses, channels[1], null);
        
        muxDispatchers[1][0].start();
        
        // Validate restarted mux dispatcher functions normally
        responses = muxDispatchers[0][0].castMessage(null, message, RequestOptions.SYNC().setRspFilter(null));

        Assert.assertEquals(responses.size(), 2);
        verifyResponse(responses, channels[0], "muxDispatcher[0][0]");
        verifyResponse(responses, channels[1], "muxDispatcher[1][0]");
    }

    public void testSendMessage() throws Throwable {

        final Address address = channels[1].getAddress();
        Message message = new Message(address);
        
        // Validate normal dispatchers
        Object response = dispatchers[0].sendMessage(message, RequestOptions.SYNC());

        Assert.assertEquals(response, "dispatcher[1]");

        // Validate muxed dispatchers
        for (int j = 0; j < muxDispatchers[0].length; ++j) {
            
            response = muxDispatchers[0][j].sendMessage(message, RequestOptions.SYNC());

            Assert.assertEquals(response, "muxDispatcher[1][" + j + "]");
        }
        
        // Filter testing is disabled for now pending filter improvements in JGroups 3
        
//        // Validate muxed rpc dispatchers w/filter
//        
//        RspFilter filter = new RspFilter() {
//
//            @Override
//            public boolean isAcceptable(Object response, Address sender) {
//                return !sender.equals(address);
//            }
//
//            @Override
//            public boolean needMoreResponses() {
//                return true;
//            }
//        };
//        
//        response = muxDispatchers[0][0].callRemoteMethod(address, method, RequestOptions.SYNC.setRspFilter(filter));
//
//        Assert.assertNull(address);
//        
//        // Validate stopped mux dispatcher response is auto-filtered
//        muxDispatchers[1][0].stop();
//        
//        response = muxDispatchers[0][0].callRemoteMethod(address, method, RequestOptions.SYNC.setRspFilter(null));
//
//        Assert.assertNull(address);
//        
//        // Validate restarted mux dispatcher functions normally
//        muxDispatchers[1][0].start();
//        
//        response = muxDispatchers[0][0].callRemoteMethod(address, method, RequestOptions.SYNC.setRspFilter(null));
//
//        Assert.assertEquals(response, "muxDispatcher[1][0]");
    }

    private static void verifyResponse(Map<Address, Rsp> responses, Channel channel, Object expected) {
        Rsp<?> response = responses.get(channel.getAddress());
        String address = channel.getAddress().toString();
        Assert.assertNotNull(response, address);
        Assert.assertFalse(response.wasSuspected(), address);
        if (expected != null) {
            Assert.assertTrue(response.wasReceived(), address);
            Assert.assertEquals(response.getValue(), expected, address);
        } else {
            Assert.assertFalse(response.wasReceived(), address);
        }
    }

    public static class MuxRequestListener implements RequestHandler {
        private final String name;

        public MuxRequestListener(String name) {
            this.name = name;
        }

        public Object handle(Message msg) {
            return this.name;
        }
    }
}