package eu.gity.jmeter.ddos.icmpflood;

import eu.gity.jmeter.ddos.common.DDoSInputVerifier;
import eu.gity.jmeter.ddos.common.DDoSUtils;
import org.apache.jmeter.engine.StandardJMeterEngine;
import org.apache.jmeter.gui.util.HorizontalPanel;
import org.apache.jmeter.gui.util.MenuFactory;
import org.apache.jmeter.gui.util.VerticalPanel;
import org.apache.jmeter.samplers.gui.AbstractSamplerGui;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.util.JMeterUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static eu.gity.jmeter.ddos.properties.strings.DdosPropertiesStrings.*;

/**
 * This GUI class provides user interface to setup ICMP flood DoS attack
 *
 * @author Gity a.s.
 */
public class IcmpFloodGui extends AbstractSamplerGui {
    private static final long serialVersionUID = 240L;

    private JTextField targetIP;
    private JTextField sourceIPsingle;
    private JTextField sourceIPMIN;
    private JTextField sourceIPMAX;
    private JCheckBox randomIP;
    private JTextField number;
    private JTextField rate;
    private JTextField sMACsingle;
    private JTextField sMACmin;
    private JTextField sMACmax;
    private JCheckBox incrementalMAC;
    private JTextField dMAC;
    private JTextField ttl;
    private boolean dynamicRate;

    private final JComboBox<String> selectInt = new JComboBox<String>();
    private final DDoSInputVerifier verifier = new DDoSInputVerifier();

    /**
     * An array of ICMP message types and codes for filling combo box
     */
    private final String[] typesAndCodes = {"0 -0 Echo Reply", "3 -0 Destination Unreachable - Net Unreachable", "3 -1 Destination Unreachable - Host Unreachable",
            "3 -2 Destination Unreachable - Protocol Unreachable", "3 -3 Destination Unreachable - Port Unreachable",
            "3 -4 Destination Unreachable - Fragmentation Needed", "3 -5 Destination Unreachable - Source Root Failed",
            "3 -6 Destination Unreachable - Destination Network Unknown", "3 -7 Destination Unreachable - Destination Host Unknown",
            "3 -8 Destination Unreachable - Source Host Isolated", "3 -9 Destination Unreachable - Communication with Destination Network is Administratively Prohibited",
            "3 -10 Destination Unreachable - Communication with Destination Host is Administratively Prohibited", "3 -11 Destination Unreachable - Destination Network Unreachable for Type of Service",
            "3 -12 Destination Unreachable - Destination Host Unreachable for Type of Service", "3 -13 Destination Unreachable - Communication Administratively Prohibited",
            "3 -14 Destination Unreachable - Host Precedence Violation", "3 -15 Destination Unreachable - Precedence cutoff in effect",
            "4 -0 Source Quench",
            "5 -0 Redirect Datagram for the Network (or subnet)", "5 -1 Redirect Datagram for the Host", "5 -2 Redirect Datagram for the Type of Service and Network",
            "5 -3 Redirect Datagram for the Type of Service and Host",
            "6 -0 Alternate Host Address", "8 -0 Echo Request",
            "11-0 Time to Live exceeded in Transit", "11-1 Fragment Reassembly Time Exceeeded",
            "13-0 Timestamp", "14-0 Timestamp Reply", "15-0 Information Request",
            "16-0 Information Reply", "17-0 Address Mask Request", "18-0 Address Mask Reply", "30-0 Traceroute", "31-0 Datagram Conversion Error",
    };

    private final JComboBox<String> selectTypeCode = new JComboBox<>(typesAndCodes);

    /**
     * Ctor which starts GUI initialization
     */
    public IcmpFloodGui() {
        init();
        registerTestStateListener();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getLabelResource() {
        return DDOS_ICMP_FLOOD_TITLE; // $NON-NLS-1$
    }

    /**
     * @return The name of component used in title, tree and name of the test
     */
    @Override
    public String getStaticLabel() {
        return JMeterUtils.getLocaleString(DDOS_ICMP_FLOOD_TITLE);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public TestElement createTestElement() {
        IcmpFloodSampler sampler = new IcmpFloodSampler();
        modifyTestElement(sampler);
        return sampler;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void modifyTestElement(TestElement te) {
        te.clear();
        configureTestElement(te);
        Object selectedDev = selectInt.getSelectedItem();
        if (selectedDev != null) { //Maybe no network devices were found       
            te.setProperty(IcmpFloodSampler.INTERF, selectedDev.toString());
        }

        te.setProperty(IcmpFloodSampler.DMAC, dMAC.getText());
        te.setProperty(IcmpFloodSampler.SMACSINGLE, sMACsingle.getText());
        te.setProperty(IcmpFloodSampler.SMACMIN, sMACmin.getText());
        te.setProperty(IcmpFloodSampler.SMACMAX, sMACmax.getText());
        te.setProperty(IcmpFloodSampler.INCREMENTALMAC, incrementalMAC.isSelected());

        te.setProperty(IcmpFloodSampler.TARGETIP, targetIP.getText());
        te.setProperty(IcmpFloodSampler.SOURCEIPSINGLE, sourceIPsingle.getText());
        te.setProperty(IcmpFloodSampler.RANDOMIP, randomIP.isSelected());
        te.setProperty(IcmpFloodSampler.SOURCEIPMIN, sourceIPMIN.getText());
        te.setProperty(IcmpFloodSampler.SOURCEIPMAX, sourceIPMAX.getText());

        te.setProperty(IcmpFloodSampler.TTL, ttl.getText());
        te.setProperty(IcmpFloodSampler.TYPE, selectTypeCode.getSelectedItem().toString());

        te.setProperty(IcmpFloodSampler.NUMBER, number.getText());
        te.setProperty(IcmpFloodSampler.RATE, rate.getText());

        //Following blocks assigns final values for trafgen config to separate properties  	 
        if (randomIP.isSelected()) {
            te.setProperty(IcmpFloodSampler.SOURCEIPTG, DDoSUtils.getInstance().ipRandom(sourceIPMIN.getText(), sourceIPMAX.getText()));
        } else {
            te.setProperty(IcmpFloodSampler.SOURCEIPTG, sourceIPsingle.getText());
        }
        if (incrementalMAC.isSelected()) {
            te.setProperty(IcmpFloodSampler.SMACTG, DDoSUtils.getInstance().macIncremental(sMACmin.getText(), sMACmax.getText()));
        } else {
            te.setProperty(IcmpFloodSampler.SMACTG, sMACsingle.getText());
        }

        te.setProperty(IcmpFloodSampler.TYPETG, selectTypeCode.getSelectedItem().toString().substring(0, 2).replaceAll("\\s+", ""));
        te.setProperty(IcmpFloodSampler.CODETG, selectTypeCode.getSelectedItem().toString().substring(3, 5).replaceAll("\\s+", ""));
        ((IcmpFloodSampler) te).checkInit();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void configure(TestElement element) {
        super.configure(element);

        List<String> devs = DDoSUtils.getInstance().getValidDevNames();
        if (devs.size() > selectInt.getItemCount()) {
            selectInt.setModel(new DefaultComboBoxModel<String>(devs.toArray(new String[devs.size()])));
        }
        selectInt.setSelectedItem(element.getPropertyAsString(IcmpFloodSampler.INTERF));
        dMAC.setText(element.getPropertyAsString(IcmpFloodSampler.DMAC));
        sMACsingle.setText(element.getPropertyAsString(IcmpFloodSampler.SMACSINGLE));
        incrementalMAC.setSelected(element.getPropertyAsBoolean(IcmpFloodSampler.INCREMENTALMAC, false));
        sMACmin.setText(element.getPropertyAsString(IcmpFloodSampler.SMACMIN));
        sMACmax.setText(element.getPropertyAsString(IcmpFloodSampler.SMACMAX));
        targetIP.setText(element.getPropertyAsString(IcmpFloodSampler.TARGETIP));
        sourceIPsingle.setText(element.getPropertyAsString(IcmpFloodSampler.SOURCEIPSINGLE));
        randomIP.setSelected(element.getPropertyAsBoolean(IcmpFloodSampler.RANDOMIP, false));
        sourceIPMIN.setText(element.getPropertyAsString(IcmpFloodSampler.SOURCEIPMIN));
        sourceIPMAX.setText(element.getPropertyAsString(IcmpFloodSampler.SOURCEIPMAX));
        ttl.setText(element.getPropertyAsString(IcmpFloodSampler.TTL));
        selectTypeCode.setSelectedItem(element.getPropertyAsString(IcmpFloodSampler.TYPE));

        ((IcmpFloodSampler) element).checkInit();
        if (((IcmpFloodSampler) element).isDynamicRate()) {
            dynamicRate = true;
            element.setProperty(IcmpFloodSampler.DYNAMICRATE, true); //Must be here to prevent DYNAMICRATE property from being deleted by JMeter
            number.setEnabled(false);
            rate.setEnabled(false);
        } else {
            dynamicRate = false;
            element.setProperty(IcmpFloodSampler.DYNAMICRATE, false); //Must be here to prevent DYNAMICRATE property from being deleted by JMeter
            number.setText(element.getPropertyAsString(IcmpFloodSampler.NUMBER));
            rate.setText(element.getPropertyAsString(IcmpFloodSampler.RATE));
            if (selectInt.isEnabled()) { //Means the test is not running
                number.setEnabled(true);
                rate.setEnabled(true);
            }
        }
    }

    /**
     * Creates a panel for target IP setting
     *
     * @return panel containing GUI components for setting target IP
     */
    private JPanel createServerPanel() {
        JLabel label = new JLabel(JMeterUtils.getLocaleString(DDOS_TARGET_IP)); //$NON-NLS-1$

        targetIP = new JTextField("1.1.1.1");
        label.setLabelFor(targetIP);
        targetIP.setName("ip");
        targetIP.setInputVerifier(verifier);

        JPanel serverPanel = new JPanel(new BorderLayout(5, 0));

        serverPanel.add(label, BorderLayout.WEST);
        serverPanel.add(targetIP, BorderLayout.CENTER);

        return serverPanel;
    }

    /**
     * Creates a panel for source IP setting
     *
     * @return panel containing GUI components for setting source IP
     */
    private JPanel sourceIPPanel() {
        sourceIPsingle = new JTextField("192.168.0.1");
        sourceIPsingle.setName("ip");
        sourceIPsingle.setInputVerifier(verifier);
        //$NON-NLS-1$
        sourceIPMIN = new JTextField("192.168.0.1");
        sourceIPMIN.setName("ip");
        sourceIPMIN.setInputVerifier(verifier);
        sourceIPMIN.setEnabled(false);

        sourceIPMAX = new JTextField("192.168.0.10");
        sourceIPMAX.setName("ip");
        sourceIPMAX.setInputVerifier(verifier);
        sourceIPMAX.setEnabled(false);

        randomIP = new JCheckBox(JMeterUtils.getLocaleString(DDOS_RANDOM)); // $NON-NLS-1$
        // add a listener to activate or not randomIP selection
        randomIP.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(final ItemEvent e) {
                enableRandom(e.getStateChange() == ItemEvent.SELECTED);
            }

            private void enableRandom(boolean b) {
                sourceIPMIN.setEnabled(b);
                sourceIPMAX.setEnabled(b);
                sourceIPsingle.setEnabled(!b);
            }
        });

        final JPanel sourcePanel = new HorizontalPanel();
        sourcePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils.getLocaleString(DDOS_SOURCE_IP)));

        JLabel labelMin = new JLabel("Min: ");
        labelMin.setLabelFor(sourceIPMIN);
        JLabel labelMax = new JLabel("Max: ");
        labelMax.setLabelFor(sourceIPMAX);

        JLabel labelS = new JLabel(JMeterUtils.getLocaleString(DDOS_SINGLE_VALUE));
        labelS.setLabelFor(sourceIPsingle);

        sourcePanel.add(labelS);
        sourcePanel.add(sourceIPsingle);
        sourcePanel.add(randomIP);
        sourcePanel.add(labelMin);
        sourcePanel.add(sourceIPMIN);
        sourcePanel.add(labelMax);
        sourcePanel.add(sourceIPMAX);

        return sourcePanel;
    }

    /**
     * Creates a panel for source MAC setting
     *
     * @return panel containing GUI components for setting source MAC
     */
    private JPanel sMACPanel() {
        JLabel label = new JLabel(JMeterUtils.getLocaleString(DDOS_SINGLE_VALUE)); //$NON-NLS-1$

        sMACsingle = new JTextField("aa:bb:cc:dd:ee:ff");
        label.setLabelFor(sMACsingle);
        sMACsingle.setName("mac");
        sMACsingle.setInputVerifier(verifier);

        sMACmin = new JTextField("aa:bb:cc:dd:ee:ff");
        sMACmin.setName("mac");
        sMACmin.setInputVerifier(verifier);
        sMACmin.setEnabled(false);

        sMACmax = new JTextField("aa:bb:cc:dd:ef:ff");
        sMACmax.setName("mac");
        sMACmax.setInputVerifier(verifier);
        sMACmax.setEnabled(false);

        incrementalMAC = new JCheckBox(JMeterUtils.getLocaleString(DDOS_INCREMENT)); // $NON-NLS-1$
        incrementalMAC.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(final ItemEvent e) {
                enableRandom(e.getStateChange() == ItemEvent.SELECTED);
            }

            private void enableRandom(boolean b) {
                sMACmin.setEnabled(b);
                sMACmax.setEnabled(b);
                sMACsingle.setEnabled(!b);
            }
        });

        JLabel labelMin = new JLabel("Min: ");
        labelMin.setLabelFor(sMACmin);
        JLabel labelMax = new JLabel("Max: ");
        labelMax.setLabelFor(sMACmax);

        JPanel sMACPanel = new HorizontalPanel();
        sMACPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), JMeterUtils.getLocaleString(DDOS_SOURCE_MAC)));

        sMACPanel.add(label);
        sMACPanel.add(sMACsingle);
        sMACPanel.add(incrementalMAC);
        sMACPanel.add(labelMin);
        sMACPanel.add(sMACmin);
        sMACPanel.add(labelMax);
        sMACPanel.add(sMACmax);

        return sMACPanel;
    }

    /**
     * Creates a panel for destination MAC setting
     *
     * @return panel containing GUI components for setting destination MAC
     */
    private JPanel dMACPanel() {
        JLabel label = new JLabel(JMeterUtils.getLocaleString(DDOS_DEST_MAC)); //$NON-NLS-1$

        dMAC = new JTextField("ff:ff:ff:ff:ff:ff");
        label.setLabelFor(dMAC);
        dMAC.setName("mac");
        dMAC.setInputVerifier(verifier);

        JPanel dMACPanel = new JPanel(new BorderLayout(5, 0));

        dMACPanel.add(label, BorderLayout.WEST);
        dMACPanel.add(dMAC, BorderLayout.CENTER);

        return dMACPanel;
    }

    /**
     * Creates a panel for TTL setting
     *
     * @return panel containing GUI components for setting TTL
     */
    private JPanel ttlPanel() {
        JLabel label = new JLabel("TTL:"); //$NON-NLS-1$

        ttl = new JTextField("64");
        label.setLabelFor(ttl);
        ttl.setName("natural");
        ttl.setInputVerifier(verifier);

        JPanel ttlPanel = new JPanel(new BorderLayout(5, 0));

        ttlPanel.add(label, BorderLayout.WEST);
        ttlPanel.add(ttl, BorderLayout.CENTER);

        return ttlPanel;
    }


    /**
     * Creates a panel for choosing output interface
     *
     * @return panel containing GUI components for choosing output interface
     */
    private JPanel typesPanel() {
        JLabel label = new JLabel(JMeterUtils.getLocaleString(DDOS_ICMP_TYPES_AND_CODES)); //$NON-NLS-1$
        label.setLabelFor(selectTypeCode);

        JPanel typesPanel = new JPanel(new BorderLayout(5, 0));

        selectTypeCode.setSelectedIndex(23);

        typesPanel.add(label, BorderLayout.WEST);
        typesPanel.add(selectTypeCode, BorderLayout.CENTER);

        return typesPanel;
    }

    /**
     * Creates a panel for choosing output interface
     *
     * @return panel containing GUI components for choosing output interface
     */
    private JPanel interfPanel() {
        JLabel label = new JLabel(JMeterUtils.getLocaleString(DDOS_NET_INTERFACE)); //$NON-NLS-1$
        label.setLabelFor(selectInt);

        JPanel interfPanel = new JPanel(new BorderLayout(5, 0));

        interfPanel.add(label, BorderLayout.WEST);
        interfPanel.add(selectInt, BorderLayout.CENTER);

        return interfPanel;
    }


    /**
     * Creates a panel for setting a number of packets to be generated
     *
     * @return panel containing GUI components for setting number of packets
     */
    private JPanel numberPanel() {
        JLabel label = new JLabel(JMeterUtils.getLocaleString(DDOS_PACKET_COUNT)); //$NON-NLS-1$

        number = new JTextField("100");
        label.setLabelFor(number);
        number.setName("naturalOrNothing");
        number.setInputVerifier(verifier);

        JPanel numberPanel = new JPanel(new BorderLayout(5, 0));

        numberPanel.add(label, BorderLayout.WEST);
        numberPanel.add(number, BorderLayout.CENTER);

        return numberPanel;
    }

    /**
     * Creates a panel for setting generation rate
     *
     * @return panel containing GUI components for setting generation rate
     */
    private JPanel ratePanel() {
        JLabel label = new JLabel(JMeterUtils.getLocaleString(DDOS_PACKET_RATE)); //$NON-NLS-1$

        rate = new JTextField("10");
        label.setLabelFor(rate);
        rate.setName("naturalOrNothing");
        rate.setInputVerifier(verifier);

        JPanel ratePanel = new JPanel(new BorderLayout(5, 0));

        ratePanel.add(label, BorderLayout.WEST);
        ratePanel.add(rate, BorderLayout.CENTER);

        return ratePanel;
    }


    /**
     * Performs GUI initialization
     */
    private void init() {
        setLayout(new BorderLayout(5, 5));
        setBorder(makeBorder());
        add(makeTitlePanel(), BorderLayout.NORTH); // Add the standard title panel   

        VerticalPanel mainPanel = new VerticalPanel();

        JPanel linkLayer = new VerticalPanel();
        linkLayer.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(Color.LIGHT_GRAY, Color.BLACK), JMeterUtils.getLocaleString(DDOS_LINK_LAYER))); // ohraniceni

        JPanel ipLayer = new VerticalPanel();
        ipLayer.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(Color.LIGHT_GRAY, Color.BLACK), JMeterUtils.getLocaleString(DDOS_IP_LAYER)));

        JPanel icmp = new VerticalPanel();
        icmp.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(Color.LIGHT_GRAY, Color.BLACK), JMeterUtils.getLocaleString(DDOS_ICMP)));

        JPanel attack = new VerticalPanel();
        attack.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(Color.LIGHT_GRAY, Color.BLACK), JMeterUtils.getLocaleString(DDOS_ATTACK_STRENGTH)));


        linkLayer.add(sMACPanel());
        linkLayer.add(dMACPanel());

        ipLayer.add(createServerPanel());
        ipLayer.add(sourceIPPanel());
        ipLayer.add(ttlPanel());

        icmp.add(typesPanel());

        attack.add(numberPanel());
        attack.add(ratePanel());

        mainPanel.add(interfPanel());
        mainPanel.add(linkLayer);
        mainPanel.add(ipLayer);
        mainPanel.add(icmp);
        mainPanel.add(attack);

        add(mainPanel, BorderLayout.CENTER);
    }


    /**
     * Enables/disables GUI components (used when test starts/ends)
     *
     * @param en true if the components should be enabled, false if disabled
     */
    private void enableGUIComponents(boolean en) {
        targetIP.setEnabled(en);
        if (randomIP.isSelected()) {
            sourceIPMIN.setEnabled(en);
            sourceIPMAX.setEnabled(en);
        } else {
            sourceIPsingle.setEnabled(en);
        }
        randomIP.setEnabled(en);
        if (!dynamicRate) {
            number.setEnabled(en);
            rate.setEnabled(en);
        }
        incrementalMAC.setEnabled(en);
        if (incrementalMAC.isSelected()) {
            sMACmin.setEnabled(en);
            sMACmax.setEnabled(en);
        } else {
            sMACsingle.setEnabled(en);
        }
        dMAC.setEnabled(en);
        ttl.setEnabled(en);
        selectInt.setEnabled(en);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clearGui() {
        super.clearGui();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<String> getMenuCategories() {
        return new ArrayList<String>();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public JPopupMenu createPopupMenu() {
        JPopupMenu pop = new JPopupMenu();
        MenuFactory.addEditMenu(pop, true);
        MenuFactory.addFileMenu(pop);
        return pop;
    }

    /**
     * Registers a listener which reacts to start/end of the test events
     */
    private void registerTestStateListener() {
        TestStateListener testStateListener = new TestStateListener() {

            @Override
            public void testStarted() {
                enableGUIComponents(false);
            }

            @Override
            public void testStarted(String host) {
                testStarted();
            }

            @Override
            public void testEnded() {
                //Register again for a next run
                registerTestStateListener();
                enableGUIComponents(true);
            }

            @Override
            public void testEnded(String host) {
                testEnded();
            }
        };
        StandardJMeterEngine.register(testStateListener);
    }
}
