package org.jgroups.tests;


import org.jgroups.*;
import org.jgroups.protocols.DISCARD;
import org.jgroups.protocols.MERGE3;
import org.jgroups.protocols.TCP_NIO2;
import org.jgroups.protocols.TP;
import org.jgroups.stack.ProtocolStack;
import org.jgroups.util.Promise;
import org.jgroups.util.Util;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.Arrays;
import java.util.Properties;

/**
 * Tests the NAKACK (retransmission) and STABLE (garbage collection) protocols
 * by discarding 10% of all network-bound messages
 * 
 * @author Bela Ban
 */
@Test(groups=Global.STACK_DEPENDENT,singleThreaded=true)
public class DiscardTest extends ChannelTestBase {
    JChannel a, b;
    static final long NUM_MSGS=10000;
    static final int MSG_SIZE=1000;
    private static final String GROUP="DiscardTest";
    final Promise<Long> ch1_all_received=new Promise<>();
    final Promise<Long> ch2_all_received=new Promise<>();



    @BeforeMethod
    protected void setUp() throws Exception {
        ch1_all_received.reset();
        ch2_all_received.reset();
    }
    
    @AfterMethod
    protected void tearDown() throws Exception {
        TP tp_a=a.getProtocolStack().getTransport(), tp_b=b.getProtocolStack().getTransport();
        if(tp_a instanceof TCP_NIO2) {
            System.out.printf("partial writes in A: %d, partial writes in B: %d\n",
                              ((TCP_NIO2)tp_a).numPartialWrites(), ((TCP_NIO2)tp_b).numPartialWrites());
        }
        Util.close(b, a);
    }

    public void testDiscardProperties() throws Exception {
        _testLosslessReception(true);
    }

    public void testFastProperties() throws Exception {
        _testLosslessReception(false);
    }

    private void _testLosslessReception(boolean discard) throws Exception {
        long start, stop;

        a=createChannel().name("A");
        a.setReceiver(new MyReceiver(ch1_all_received, NUM_MSGS, "A"));
        b=createChannel().name("B");
        b.setReceiver(new MyReceiver(ch2_all_received, NUM_MSGS, "B"));
        makeUnique(a,b);

        a.connect(GROUP);
        b.connect(GROUP);
        Util.waitUntilAllChannelsHaveSameView(10000, 500, a, b);

        if(discard) {
            DISCARD discard_prot=new DISCARD();
            Properties properties=new Properties();
            properties.setProperty("down", "0.1");

            a.getProtocolStack().insertProtocol(discard_prot, ProtocolStack.Position.BELOW, MERGE3.class);
            discard_prot=new DISCARD();
            properties=new Properties();
            properties.setProperty("down", "0.1");
            b.getProtocolStack().insertProtocol(discard_prot, ProtocolStack.Position.BELOW, MERGE3.class);
        }

        System.out.printf("sending %d %d-byte messages to all members (including myself)\n", NUM_MSGS, MSG_SIZE);
        start=System.currentTimeMillis();
        for(int i=0;i < NUM_MSGS;i++) {
            Message msg=createMessage(MSG_SIZE);
            a.send(msg);
            if(i % 1000 == 0)
                System.out.println("-- sent " + i + " messages");
        }

        System.out.println("-- waiting for ch1 and ch2 to receive " + NUM_MSGS + " messages");
        Long num_msgs;
        num_msgs=ch1_all_received.getResultWithTimeout(10000);
        System.out.println("-- received " + num_msgs + " messages on ch1");

        num_msgs=ch2_all_received.getResultWithTimeout(10000);
        stop=System.currentTimeMillis();
        System.out.println("-- received " + num_msgs + " messages on ch2");

        long diff=stop - start;
        double msgs_sec=NUM_MSGS / (diff / 1000.0);
        System.out.printf("== Sent and received %d in %d ms, %.2f msgs/sec\n", NUM_MSGS, diff, msgs_sec);
    }

    static class MyReceiver implements Receiver {
        final Promise<Long> p;
        final long num_msgs_expected;
        long num_msgs=0;
        String channel_name;
        boolean operational=true;

        public MyReceiver(final Promise<Long> p,final long num_msgs_expected,String channel_name) {
            this.p=p;
            this.num_msgs_expected=num_msgs_expected;
            this.channel_name=channel_name;
        }

        public void receive(Message msg) {
            if(!operational)
                return;
            num_msgs++;

            if(num_msgs > 0 && num_msgs % 1000 == 0)
                System.out.printf("-- received %d on %s\n", num_msgs, channel_name);

            if(num_msgs >= num_msgs_expected) {
                System.out.printf("SUCCESS: received all %d messages on %s\n", num_msgs_expected, channel_name);
                operational=false;
                p.setResult(num_msgs);
            }
        }

        public void viewAccepted(View new_view) {
            System.out.printf("-- view (%s): %s\n", channel_name, new_view);
        }
    }

    private static Message createMessage(int size) {
        byte[] buf=new byte[size];
        Arrays.fill(buf, (byte)'x');
        return new BytesMessage(null, buf);
    }


}
