package eu.gity.jmeter.ddos.common;

import eu.gity.jmeter.ddos.dnsamplification.DnsAmpGui;
import eu.gity.jmeter.ddos.dnsflood.DnsFloodGui;
import eu.gity.jmeter.ddos.icmpflood.IcmpFloodGui;
import cz.vut.jmeter.ddos.httpsflood.HttpsFloodGui;
import eu.gity.jmeter.ddos.ntpflood.NtpFloodGui;
import eu.gity.jmeter.ddos.simpleddosthreadgrp.SimpleDDoSThreadGroupGui;
import eu.gity.jmeter.ddos.slowloris.SlowLorisGui;
import eu.gity.jmeter.ddos.synflood.SynFloodGui;
import eu.gity.jmeter.ddos.trafgencustom.TrafgenCustomGui;
import eu.gity.jmeter.ddos.udpflood.UdpFloodGui;
import org.apache.jmeter.gui.action.ActionNames;
import org.apache.jmeter.gui.action.ActionRouter;
import org.apache.jmeter.gui.util.MenuFactory;
import org.apache.jmeter.threads.gui.AbstractThreadGroupGui;
import org.apache.jmeter.util.JMeterUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.io.File;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

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

/**
 * This class provides common utilities for DDoS components
 *
 * @author Gity a.s.
 */
public class DDoSUtils {
    private static final Logger log = LoggerFactory.getLogger(DDoSUtils.class);
    private static final DDoSUtils INSTANCE = new DDoSUtils();

    /**
     * Exists to disallow external instantiation but allows instantiation from eventual subclasses or the same package as
     * it would not make any big harm except some more resources consumption
     */
    protected DDoSUtils() {
    }

    /**
     * @return the singleton instance of this class
     */
    public static DDoSUtils getInstance() {
        return INSTANCE;
    }

    /**
     * Finds out where JMeter resides
     *
     * @return path to JMeter main directory
     */
    public String getJmeterHomeDir() {
        if (System.getProperty("jmeter.home") == null) {
            File userDir = new File(System.getProperty("user.dir"));
            return userDir.getAbsoluteFile().getParent();
        } else {
            return System.getProperty("jmeter.home");
        }
    }

    /**
     * Uses java.net.NetworkInterface to get a list of active interfaces. It excludes a loopback
     * interface.
     *
     * @return a list of valid interfaces
     */
    public List<String> getValidDevNames() {
        List<String> validDevNames = new ArrayList<String>();
        Enumeration<NetworkInterface> nets;
        try {
            nets = NetworkInterface.getNetworkInterfaces();
            for (NetworkInterface netInt : Collections.list(nets)) {
                if (netInt.isUp()) {
                    String netIntName = netInt.getName();
                    if (!netIntName.equals("lo")) {
                        validDevNames.add(netIntName);
                    }
                }
            }
        } catch (SocketException e) {
            log.error("Error while getting a list of network interfaces:\n" + e.getStackTrace().toString());
        }
        if (validDevNames.isEmpty()) {
            log.warn("No usable netowrk interfaces were found. Packet generation will not be possible until there is at least one.");
        }
        return validDevNames;
    }


    /**
     * Converts minimum and maximum border of the MAC addresses range to a format understandable to trafgen and insets these values
     * into trafgen dynammic function
     *
     * @param minMAC min MAC address value
     * @param maxMAC max MAC address value
     * @return a String containing converted values inserted into trafgen dynamic function syntax
     */
    public String macIncremental(String minMAC, String maxMAC) {
        if (minMAC.length() == 0 || maxMAC.length() == 0) {
            return "";
        }

        String hexMin = minMAC;
        String hexMax = maxMAC;

        hexMin = hexMin.replaceAll(":", ""); // nahrazeni ":" mezi oktety za mezerey
        hexMax = hexMax.replaceAll(":", "");

        long out1 = Long.parseLong(hexMin, 16); // prevedeni z hexa-decimalni na decimalni cislo
        long out2 = Long.parseLong(hexMax, 16);

        String out = "dinc(" + out1 + ", " + out2 + ")"; // vlozeni do syntaxe

        return out;
    }

    /**
     * Converts minimum and maximum border of the IP addresses range to a format understandable to trafgen and insets these values
     * into trafgen dynamic function
     *
     * @param minIP min IP address value
     * @param maxIP max IP address value
     * @return a String containing converted values inserted into trafgen dynamic function syntax
     */
    public String ipRandom(String minIP, String maxIP) {
        if (minIP.length() == 0 || maxIP.length() == 0) {
            return "";
        }

        String[] parsedMin = minIP.split("\\.");
        int min1 = Integer.parseInt(parsedMin[0]);
        int min2 = Integer.parseInt(parsedMin[1]);
        int min3 = Integer.parseInt(parsedMin[2]);
        int min4 = Integer.parseInt(parsedMin[3]);

        String[] parsedMax = maxIP.split("\\.");
        int max1 = Integer.parseInt(parsedMax[0]);
        int max2 = Integer.parseInt(parsedMax[1]);
        int max3 = Integer.parseInt(parsedMax[2]);
        int max4 = Integer.parseInt(parsedMax[3]);

        String binaryMin1 = "00000000" + Integer.toBinaryString(min1);

        int diff1 = 8 - Integer.toBinaryString(min1).length();
        binaryMin1 = binaryMin1.substring(8 - diff1);


        String binaryMin2 = "00000000" + Integer.toBinaryString(min2);
        int diff2 = 8 - Integer.toBinaryString(min2).length();
        binaryMin2 = binaryMin2.substring(8 - diff2);

        String binaryMin3 = "00000000" + Integer.toBinaryString(min3);
        int diff3 = 8 - Integer.toBinaryString(min3).length();
        binaryMin3 = binaryMin3.substring(8 - diff3);


        String binaryMin4 = "00000000" + Integer.toBinaryString(min4);
        int diff4 = 8 - Integer.toBinaryString(min4).length();
        binaryMin4 = binaryMin4.substring(8 - diff4);


        String binaryMax1 = "00000000" + Integer.toBinaryString(max1);
        int diff11 = 8 - Integer.toBinaryString(max1).length();
        binaryMax1 = binaryMax1.substring(8 - diff11);


        String binaryMax2 = "00000000" + Integer.toBinaryString(max2);
        int diff22 = 8 - Integer.toBinaryString(max2).length();
        binaryMax2 = binaryMax2.substring(8 - diff22);


        String binaryMax3 = "00000000" + Integer.toBinaryString(max3);
        int diff33 = 8 - Integer.toBinaryString(max3).length();
        binaryMax3 = binaryMax3.substring(8 - diff33);


        String binaryMax4 = "00000000" + Integer.toBinaryString(max4);
        int diff44 = 8 - Integer.toBinaryString(max4).length();
        binaryMax4 = binaryMax4.substring(8 - diff44);


        String binaryMin = binaryMin1 + binaryMin2 + binaryMin3 + binaryMin4;
        String binaryMax = binaryMax1 + binaryMax2 + binaryMax3 + binaryMax4;

        long outMin = Long.parseLong(binaryMin, 2);
        long outMax = Long.parseLong(binaryMax, 2);

        String out = "drnd(" + outMin + ", " + outMax + ")";

        return out;
    }

    /**
     * Converts decimal number to two hexadecimal numbers
     *
     * @param port decimal value to be converted
     * @return String containing the two comma separated hexadecimal numbers
     */
    public String toHexPair(String port) {
        int dec = Integer.parseInt(port);
        String hex2B = null;
        String hex1B = null;
        int decimal2;
        String binary = "00000000" + Integer.toBinaryString(dec);

        if (dec < 256) { // if the number is lower than 256 the first hexa number will be 0x00
            // cuts off zeroes to 8 bits total
            int diff = 8 - Integer.toBinaryString(dec).length();
            binary = binary.substring(8 - diff);

            //converts the second byte to decimal number
            decimal2 = Integer.parseInt(binary.substring(0, 8), 2);
            hex1B = "00";
        } else {
            // cuts off zeroes to 16 bits total
            int diff = 16 - Integer.toBinaryString(dec).length();
            binary = binary.substring(8 - diff);

            //converts both bytes to decimal numbers
            int decimal1 = Integer.parseInt(binary.substring(0, 8), 2);
            decimal2 = Integer.parseInt(binary.substring(8, 16), 2);

            //converts to hexa numbers
            hex1B = Integer.toString(decimal1, 16);
        }
        hex2B = Integer.toString(decimal2, 16);

        String out = " 0x" + hex1B + ",0x" + hex2B;

        return out;
    }

    /**
     * Converts IP address from dot-decimal notation to hexadecimal comma separated format
     *
     * @param ip IP address in dot-decimal notation
     * @return IP address in hexadecimal comma separated format
     */
    public String parseIPtoHex(String ip) {
        String[] parsedip = ip.split("\\.");
        List<Integer> dec = new ArrayList<Integer>();

        for (int i = 0; i < parsedip.length; i++) {
            dec.add(Integer.parseInt(parsedip[i]));
        }
        List<String> hex = new ArrayList<String>();
        for (int i = 0; i < dec.size(); i++) {
            if (dec.get(i) < 10) {
                hex.add(" 0x0" + Integer.toHexString(dec.get(i)) + ",");
            } else {
                hex.add(" 0x" + Integer.toHexString(dec.get(i)) + ",");
            }
        }

        String out = "";
        for (int i = 0; i < hex.size(); i++) {
            out = out + hex.get(i);
        }
        return out;
    }

    /**
     * Creeates popup menu allowing to add DDoS samplers
     *
     * @param tgGui reference to DDoS Thread Group GUI object
     * @return popup menu
     */
    public JPopupMenu getDDoSThreadGroupPopupMenu(AbstractThreadGroupGui tgGui) {
        JPopupMenu pop = new JPopupMenu();

        //Adding only DDoS samplers is allowed   
        JMenuItem addDnsAmp = new JMenuItem("Add " + JMeterUtils.getLocaleString(DDOS_DNS_AMPLIFICATION_TITLE));
        addDnsAmp.addActionListener(ActionRouter.getInstance());
        addDnsAmp.setName((DnsAmpGui.class).getCanonicalName());
        addDnsAmp.setActionCommand(ActionNames.ADD);
        pop.add(addDnsAmp);

        JMenuItem addDnsServer = new JMenuItem("Add " + JMeterUtils.getLocaleString(DDOS_DNS_FLOOD_TITLE));
        addDnsServer.addActionListener(ActionRouter.getInstance());
        addDnsServer.setName((DnsFloodGui.class).getCanonicalName());
        addDnsServer.setActionCommand(ActionNames.ADD);
        pop.add(addDnsServer);

        JMenuItem addIcmpFlood = new JMenuItem("Add " + JMeterUtils.getLocaleString(DDOS_ICMP_FLOOD_TITLE));
        addIcmpFlood.addActionListener(ActionRouter.getInstance());
        addIcmpFlood.setName((IcmpFloodGui.class).getCanonicalName());
        addIcmpFlood.setActionCommand(ActionNames.ADD);
        pop.add(addIcmpFlood);

        JMenuItem addNtpFlood = new JMenuItem("Add " + JMeterUtils.getLocaleString(DDOS_NTP_FLOOD_TITLE));
        addNtpFlood.addActionListener(ActionRouter.getInstance());
        addNtpFlood.setName((NtpFloodGui.class).getCanonicalName());
        addNtpFlood.setActionCommand(ActionNames.ADD);
        pop.add(addNtpFlood);

        if (tgGui instanceof SimpleDDoSThreadGroupGui) { //SlowLoris does not use variable pps
            JMenuItem addSlowLoris = new JMenuItem("Add " + JMeterUtils.getLocaleString(DDOS_SLOWLORIS_TITLE));
            addSlowLoris.addActionListener(ActionRouter.getInstance());
            addSlowLoris.setName((SlowLorisGui.class).getCanonicalName());
            addSlowLoris.setActionCommand(ActionNames.ADD);
            pop.add(addSlowLoris);
        }

        JMenuItem addSynFlood = new JMenuItem("Add " + JMeterUtils.getLocaleString(DDOS_SYN_FLOOD_TITLE));
        addSynFlood.addActionListener(ActionRouter.getInstance());
        addSynFlood.setName((SynFloodGui.class).getCanonicalName());
        addSynFlood.setActionCommand(ActionNames.ADD);
        pop.add(addSynFlood);

        JMenuItem addTrafgenCustom = new JMenuItem("Add " + JMeterUtils.getLocaleString(DDOS_TRAFGEN_CUSTOM_TITLE));
        addTrafgenCustom.addActionListener(ActionRouter.getInstance());
        addTrafgenCustom.setName((TrafgenCustomGui.class).getCanonicalName());
        addTrafgenCustom.setActionCommand(ActionNames.ADD);
        pop.add(addTrafgenCustom);

        JMenuItem addUdpFlood = new JMenuItem("Add " + JMeterUtils.getLocaleString(DDOS_UDP_FLOOD_TITLE));
        addUdpFlood.addActionListener(ActionRouter.getInstance());
        addUdpFlood.setName((UdpFloodGui.class).getCanonicalName());
        addUdpFlood.setActionCommand(ActionNames.ADD);
        pop.add(addUdpFlood);

        //Rest of the menu remains unchanged
        if (tgGui.isEnabled() &&
                // Check test is not started already
                !JMeterUtils.isTestRunning()) {
            pop.addSeparator();

            JMenuItem runTg = new JMenuItem(JMeterUtils.getResString("run_threadgroup"));
            runTg.setName("run_threadgroup");
            runTg.addActionListener(ActionRouter.getInstance());
            runTg.setActionCommand(ActionNames.RUN_TG);
            pop.add(runTg);

            JMenuItem runTgNotimers = new JMenuItem(JMeterUtils.getResString("run_threadgroup_no_timers"));
            runTgNotimers.setName("run_threadgroup_no_timers");
            runTgNotimers.addActionListener(ActionRouter.getInstance());
            runTgNotimers.setActionCommand(ActionNames.RUN_TG_NO_TIMERS);
            pop.add(runTgNotimers);

            JMenuItem validateTg = new JMenuItem(JMeterUtils.getResString("validate_threadgroup"));
            validateTg.setName("validate_threadgroup");
            validateTg.addActionListener(ActionRouter.getInstance());
            validateTg.setActionCommand(ActionNames.VALIDATE_TG);
            pop.add(validateTg);
        }
        MenuFactory.addEditMenu(pop, true);
        MenuFactory.addFileMenu(pop, false);

        return pop;
    }
}
