package eu.gity.jmeter.ddos.dnsflood;

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 java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

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

/**
 * This GUI class provides user interface to setup DNS flood DoS attack
 *
 * @author Gity a.s.
 */
public class DnsFloodGui 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 sourcePortSingle;
    private JTextField sourcePortMIN;
    private JTextField sourcePortMAX;
    private JCheckBox randomPort;
    private JTextField destPort;
    private JTextField dnsQuery;
    private JTextField number;
    private JTextField rate;
    private boolean dynamicRate;

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

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

    /**
     * {@inheritDoc}
     */
    @Override
    public String getLabelResource() {
        return DDOS_DNS_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_DNS_FLOOD_TITLE);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public TestElement createTestElement() {
        DnsFloodSampler sampler = new DnsFloodSampler();
        modifyTestElement(sampler);
        final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        service.schedule(new Runnable() {
            @Override
            public void run() {
                configure(sampler);
            }
        }, 10000, TimeUnit.MILLISECONDS);
        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(DnsFloodSampler.INTERF, selectedDev.toString());
        }

        te.setProperty(DnsFloodSampler.TARGETIP, targetIP.getText());
        te.setProperty(DnsFloodSampler.SOURCEIPSINGLE, sourceIPsingle.getText());
        te.setProperty(DnsFloodSampler.RANDOMIP, randomIP.isSelected());
        te.setProperty(DnsFloodSampler.SOURCEIPMIN, sourceIPMIN.getText());
        te.setProperty(DnsFloodSampler.SOURCEIPMAX, sourceIPMAX.getText());
        te.setProperty(DnsFloodSampler.SOURCEPORTSINGLE, sourcePortSingle.getText());
        te.setProperty(DnsFloodSampler.RANDOMPORT, randomPort.isSelected());
        te.setProperty(DnsFloodSampler.SOURCEPORTMIN, sourcePortMIN.getText());
        te.setProperty(DnsFloodSampler.SOURCEPORTMAX, sourcePortMAX.getText());
        te.setProperty(DnsFloodSampler.DPORT, destPort.getText());
        te.setProperty(DnsFloodSampler.QUERY, dnsQuery.getText());
        te.setProperty(DnsFloodSampler.NUMBER, number.getText());
        te.setProperty(DnsFloodSampler.RATE, rate.getText());

        //Following blocks assigns final values for trafgen config to separate properties  	 
        if (randomIP.isSelected()) {
            te.setProperty(DnsFloodSampler.SOURCEIPTG, DDoSUtils.getInstance().ipRandom(sourceIPMIN.getText(), sourceIPMAX.getText()));
        } else {
            te.setProperty(DnsFloodSampler.SOURCEIPTG, sourceIPsingle.getText());
        }
        if (randomPort.isSelected()) {
            te.setProperty(DnsFloodSampler.SOURCEPORTTG, "drnd(" + sourcePortMIN.getText() + "," + sourcePortMAX.getText() + ")");
        } else {
            te.setProperty(DnsFloodSampler.SOURCEPORTTG, sourcePortSingle.getText());
        }

        String[] parsed = dnsQuery.getText().split("\\.");
        StringBuilder out = new StringBuilder();
        for (int i = 0; i < parsed.length; i++) {
            int lenght = parsed[i].length();
            String hex = null;
            if (lenght < 10) {
                hex = "   0x0" + Integer.toHexString(lenght);
            } else {
                hex = "   0x" + Integer.toHexString(lenght);
            }
            out.append(hex + ",	" + "\"" + parsed[i] + "\"," + "\n");
        }
        te.setProperty(DnsFloodSampler.QUERYTG, out.toString());
        ((DnsFloodSampler) 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(DnsFloodSampler.INTERF));
        targetIP.setText(element.getPropertyAsString(DnsFloodSampler.TARGETIP));
        sourceIPsingle.setText(element.getPropertyAsString(DnsFloodSampler.SOURCEIPSINGLE));
        randomIP.setSelected(element.getPropertyAsBoolean(DnsFloodSampler.RANDOMIP, false));
        sourceIPMIN.setText(element.getPropertyAsString(DnsFloodSampler.SOURCEIPMIN));
        sourceIPMAX.setText(element.getPropertyAsString(DnsFloodSampler.SOURCEIPMAX));
        destPort.setText(element.getPropertyAsString(DnsFloodSampler.DPORT));
        dnsQuery.setText(element.getPropertyAsString(DnsFloodSampler.QUERY));
        sourcePortSingle.setText(element.getPropertyAsString(DnsFloodSampler.SOURCEPORTSINGLE));
        randomPort.setSelected(element.getPropertyAsBoolean(DnsFloodSampler.RANDOMPORT, false));
        sourcePortMIN.setText(element.getPropertyAsString(DnsFloodSampler.SOURCEPORTMIN));
        sourcePortMAX.setText(element.getPropertyAsString(DnsFloodSampler.SOURCEPORTMAX));

        ((DnsFloodSampler) element).checkInit();
        if (((DnsFloodSampler) element).isDynamicRate()) {
            dynamicRate = true;
            element.setProperty(DnsFloodSampler.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(DnsFloodSampler.DYNAMICRATE, false); //Must be here to prevent DYNAMICRATE property from being deleted by JMeter
            number.setText(element.getPropertyAsString(DnsFloodSampler.NUMBER));
            rate.setText(element.getPropertyAsString(DnsFloodSampler.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_IP_ADDRESS)); //$NON-NLS-1$
        targetIP = new JTextField("192.168.0.254");
        targetIP.setName("ip");
        targetIP.setInputVerifier(verifier);
        label.setLabelFor(targetIP);
        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);
        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$
        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_IP_ADDRESS)));

        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 IP setting
     *
     * @return panel containing GUI components for setting source IP
     */
    private JPanel sourcePortPanel() {
        sourcePortMIN = new JTextField("1025");
        sourcePortMIN.setName("port");
        sourcePortMIN.setInputVerifier(verifier);
        sourcePortMIN.setEnabled(false);

        sourcePortMAX = new JTextField("1035");
        sourcePortMAX.setName("port");
        sourcePortMAX.setInputVerifier(verifier);
        sourcePortMAX.setEnabled(false);

        sourcePortSingle = new JTextField("1025");
        sourcePortSingle.setName("port");
        sourcePortSingle.setInputVerifier(verifier);

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

            private void enableRandom(boolean b) {
                sourcePortMIN.setEnabled(b);
                sourcePortMAX.setEnabled(b);
                sourcePortSingle.setEnabled(!b);
            }
        });

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

        JLabel labelMin = new JLabel("Min: ");
        labelMin.setLabelFor(sourcePortMIN);
        JLabel labelMax = new JLabel("Max: ");
        labelMax.setLabelFor(sourcePortMAX);

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

        sourcePortPanel.add(labelS);
        sourcePortPanel.add(sourcePortSingle);
        sourcePortPanel.add(randomPort);
        sourcePortPanel.add(labelMin);
        sourcePortPanel.add(sourcePortMIN);
        sourcePortPanel.add(labelMax);
        sourcePortPanel.add(sourcePortMAX);

        return sourcePortPanel;
    }


    /**
     * Creates a panel for destination port setting
     *
     * @return panel containing GUI components for setting destination port
     */
    private JPanel destPortPanel() {
        JLabel label = new JLabel(JMeterUtils.getLocaleString(DDOS_UDP_PORT)); //$NON-NLS-1$
        destPort = new JTextField("53");
        destPort.setName("port");
        destPort.setInputVerifier(verifier);
        label.setLabelFor(destPort);
        JPanel destPortPanel = new JPanel(new BorderLayout(5, 0));
        destPortPanel.add(label, BorderLayout.WEST);
        destPortPanel.add(destPort, BorderLayout.CENTER);
        return destPortPanel;
    }

    /**
     * Creates a panel for DNS query setting
     *
     * @return panel containing GUI components for setting DNS query
     */
    private JPanel dnsQueryPanel() {
        JLabel label = new JLabel(JMeterUtils.getLocaleString(DDOS_QUERY)); //$NON-NLS-1$
        dnsQuery = new JTextField("www.gity.eu");
        dnsQuery.setName("domain");
        dnsQuery.setInputVerifier(verifier);
        label.setLabelFor(dnsQuery);
        JPanel dnsQueryPanel = new JPanel(new BorderLayout(5, 0));
        dnsQueryPanel.add(label, BorderLayout.WEST);
        dnsQueryPanel.add(dnsQuery, BorderLayout.CENTER);
        return dnsQueryPanel;
    }

    /**
     * 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("5");
        number.setName("naturalOrNothing");
        number.setInputVerifier(verifier);
        label.setLabelFor(number);
        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("100");
        rate.setName("naturalOrNothing");
        rate.setInputVerifier(verifier);
        label.setLabelFor(rate);
        JPanel ratePanel = new JPanel(new BorderLayout(5, 0));
        ratePanel.add(label, BorderLayout.WEST);
        ratePanel.add(rate, BorderLayout.CENTER);
        return ratePanel;
    }

    /**
     * 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;
    }

    /**
     * Performs GUI initialization
     */
    private void init() {
        setLayout(new BorderLayout(5, 5));
        setBorder(makeBorder());
        add(makeTitlePanel(), BorderLayout.NORTH); // Add the standard title
        VerticalPanel mainPanel = new VerticalPanel();
        JPanel source = new VerticalPanel();
        source.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(Color.LIGHT_GRAY, Color.BLACK), JMeterUtils.getLocaleString(DDOS_SOURCE)));
        JPanel target = new VerticalPanel();
        target.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(Color.LIGHT_GRAY, Color.BLACK), JMeterUtils.getLocaleString(DDOS_VICTIM_TARGET_DNS_SERVER)));
        JPanel attack = new VerticalPanel();
        attack.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(Color.LIGHT_GRAY, Color.BLACK), JMeterUtils.getLocaleString(DDOS_ATTACK_STRENGTH)));
        source.add(sourceIPPanel());
        source.add(sourcePortPanel());

        target.add(createServerPanel());
        target.add(destPortPanel());
        target.add(dnsQueryPanel());

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

        // Main Panel
        mainPanel.add(interfPanel());
        mainPanel.add(target);
        mainPanel.add(source);
        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);
        }
        randomPort.setEnabled(en);
        if (randomPort.isSelected()) {
            sourcePortMIN.setEnabled(en);
            sourcePortMAX.setEnabled(en);
        } else {
            sourcePortSingle.setEnabled(en);
        }
        destPort.setEnabled(en);
        dnsQuery.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);
    }
}
