package org.jgroups.tests;

import org.jgroups.Global;
import org.jgroups.JChannel;
import org.jgroups.PhysicalAddress;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.protocols.BasicTCP;
import org.jgroups.protocols.TCPPING;
import org.jgroups.protocols.TP;
import org.jgroups.protocols.UDP;
import org.jgroups.stack.Protocol;
import org.jgroups.stack.ProtocolStack;
import org.jgroups.util.ResourceManager;
import org.jgroups.util.StackType;
import org.jgroups.util.Util;
import org.testng.annotations.*;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.InetAddress;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Bela Ban
 * @author Vladimir Blagojevic
 * @author <a href="mailto://brian.stansberry@jboss.com">Brian Stansberry</a>
 */
@Test(groups = "base", sequential = true)
public class ChannelTestBase {
    protected String  channel_conf = "udp.xml";
    protected String  bind_addr = null;
    protected Log     log;

    @BeforeClass
    @Parameters(value = { "channel.conf", "use_blocking" })
    protected void initializeBase(@Optional("udp.xml") String chconf, @Optional("false") String use_blocking) throws Exception {
        log=LogFactory.getLog(this.getClass());
        Test annotation = this.getClass().getAnnotation(Test.class);
        // this should never ever happen!
        if (annotation == null)
            throw new Exception("Test is not marked with @Test annotation");

        StackType type=Util.getIpStackType();
        bind_addr=type == StackType.IPv6 ? "::1" : "127.0.0.1";
        this.channel_conf = chconf;
        bind_addr = Util.getProperty(new String[]{Global.BIND_ADDR}, null, "bind_addr", bind_addr);
        System.setProperty(Global.BIND_ADDR, bind_addr);
    }


    @AfterClass(alwaysRun = true)
    protected void nullifyInstanceFields() {
        for (Class<?> current = this.getClass(); current.getSuperclass() != null; current = current.getSuperclass()) {
            Field[] fields = current.getDeclaredFields();
            for (Field f : fields) {
                try {
                    if (!Modifier.isStatic(f.getModifiers()) && !f.getDeclaringClass().isPrimitive()) {
                        f.setAccessible(true);
                        f.set(this, null);
                    }
                } catch (Exception e) {
                }
            }
        }
    }

    protected String getBindAddress() {
        return bind_addr;
    }


    protected final static void assertTrue(boolean condition) {
        Util.assertTrue(condition);
    }

    protected final static void assertTrue(String message, boolean condition) {
        Util.assertTrue(message,condition);
    }

    protected final static void assertFalse(boolean condition) {
        Util.assertFalse(condition);
    }

    protected final static void assertFalse(String message, boolean condition) {
        Util.assertFalse(message,condition);
    }

    protected final static void assertEquals(String message, Object val1, Object val2) {
        Util.assertEquals(message,val1,val2);
    }

    protected final static void assertEquals(Object val1, Object val2) {
        Util.assertEquals(null,val1,val2);
    }

    protected final static void assertNotNull(String message, Object val) {
        Util.assertNotNull(message,val);
    }

    protected final static void assertNotNull(Object val) {
        Util.assertNotNull(null,val);
    }

    protected final static void assertNull(String message, Object val) {
        Util.assertNull(message,val);
    }

    protected final static void assertNull(Object val) {
        Util.assertNull(null, val);
    }

    /**
     * Creates a channel and modifies the configuration such that no other channel will able to join
     * this one even if they have the same cluster name (if unique = true). This is done by
     * modifying mcast_addr and mcast_port with UDP, and by changing TCP.start_port, TCP.port_range
     * and TCPPING.initial_hosts with TCP. Mainly used to run TestNG tests concurrently.
     * 
     * @param num The number of channels we will create. Only important (for port_range) with TCP, ignored by UDP
     */
    protected JChannel createChannel(boolean unique, int num) throws Exception {
        return new DefaultChannelTestFactory().createChannel(unique, num);
    }

    protected JChannel createChannel(boolean unique, int num, String name) throws Exception {
        return new DefaultChannelTestFactory().createChannel(unique, num).name(name);
    }

    protected JChannel createChannel() throws Exception {
        return new DefaultChannelTestFactory().createChannel();
    }

    protected JChannel createChannel(boolean unique) throws Exception {
        return createChannel(unique,2);
    }

    protected JChannel createChannel(JChannel ch) throws Exception {
        return new DefaultChannelTestFactory().createChannel(ch);
    }

    protected JChannel createChannel(JChannel ch, String name) throws Exception {
        return new DefaultChannelTestFactory().createChannel(ch).name(name);
    }

    protected static String getUniqueClusterName() {
        return getUniqueClusterName(null);
    }

    protected static String getUniqueClusterName(String base_name) {
        return ResourceManager.getUniqueClusterName(base_name);
    }

    /**
     * Default channel factory used in junit tests
     */
    protected class DefaultChannelTestFactory {

        public JChannel createChannel() throws Exception {
            return createChannel(channel_conf);
        }

        public JChannel createChannel(boolean unique, int num) throws Exception {
            JChannel c = createChannel(channel_conf);
            if(unique)
                makeUnique(c, num);
            return c;
        }

        public JChannel createChannel(final JChannel ch) throws Exception {
            return new JChannel(ch);
        }

        private JChannel createChannel(String configFile) throws Exception {
            return new JChannel(configFile);
        }

        protected void makeUnique(JChannel channel, int num) throws Exception {
            String str = Util.getProperty(new String[]{ Global.UDP_MCAST_ADDR, "jboss.partition.udpGroup" },
                                          null, "mcast_addr", null);
            makeUnique(channel, num, str);
        }

        protected void makeUnique(JChannel channel, int num, String mcast_address) throws Exception {
            ProtocolStack stack = channel.getProtocolStack();
            Protocol transport = stack.getTransport();

            if (transport instanceof UDP) {
                short mcast_port = ResourceManager.getNextMulticastPort(InetAddress.getByName(bind_addr));
                ((UDP) transport).setMulticastPort(mcast_port);
                if (mcast_address != null) {
                    ((UDP) transport).setMulticastAddress(InetAddress.getByName(mcast_address));
                } else {
                    String mcast_addr = ResourceManager.getNextMulticastAddress();
                    ((UDP) transport).setMulticastAddress(InetAddress.getByName(mcast_addr));
                }
            } else if (transport instanceof BasicTCP) {
                List<Integer> ports = ResourceManager.getNextTcpPorts(InetAddress.getByName(bind_addr), num);
                ((TP) transport).setBindPort(ports.get(0));
                // ((TP) transport).setPortRange(num);

                Protocol ping = stack.findProtocol(TCPPING.class);
                if (ping == null)
                    throw new IllegalStateException("TCP stack must consist of TCP:TCPPING - other config are not supported");

                List<String> initial_hosts=ports.stream().map(port -> String.format("%s[%d]", bind_addr, port))
                  .collect(Collectors.toList());
                String tmp = Util.printListWithDelimiter(initial_hosts, ",", 2000, false);
                List<PhysicalAddress> init_hosts = Util.parseCommaDelimitedHosts(tmp, 0);
                ((TCPPING)ping).setInitialHosts(init_hosts);
            } else {
                throw new IllegalStateException("Only UDP and TCP are supported as transport protocols");
            }
        }
    }



}
