package eu.gity.jmeter.ddos.stairsddosthreadgrp;

import eu.gity.jmeter.ddos.common.DDoSTreeGuard;
import eu.gity.jmeter.ddos.common.DDoSUtils;
import eu.gity.jmeter.ddos.stairsddosthreadgrp.charting.AbstractGraphRow;
import eu.gity.jmeter.ddos.stairsddosthreadgrp.charting.DateTimeRenderer;
import eu.gity.jmeter.ddos.stairsddosthreadgrp.charting.GraphPanelChart;
import eu.gity.jmeter.ddos.stairsddosthreadgrp.charting.rows.GraphRowSumValues;
import eu.gity.jmeter.ddos.stairsddosthreadgrp.gui.GuiBuilderHelper;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.control.gui.LoopControlPanel;
import org.apache.jmeter.engine.util.CompoundVariable;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.threads.AbstractThreadGroup;
import org.apache.jmeter.threads.JMeterThread;
import org.apache.jmeter.threads.gui.AbstractThreadGroupGui;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.collections.HashTree;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.util.concurrent.ConcurrentHashMap;

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

public class StairsDDoSThreadGroupGui extends AbstractThreadGroupGui {

    private static final long serialVersionUID = 240L;
    protected ConcurrentHashMap<String, AbstractGraphRow> model;
    private GraphPanelChart chart;
    private JTextField initialDelay;
    private JTextField incUserCount;
    private JTextField incUserCountBurst;
    private JTextField incUserPeriod;
    private JTextField flightTime;
    private JTextField decUserCount;
    private JTextField decUserPeriod;
    private JTextField totalThreads;
    private LoopControlPanel loopPanel;
    public StairsDDoSThreadGroupGui() {
        super();
        DDoSTreeGuard.getInstance().registerListeners();
        init();
        initGui();
    }

    protected final void init() {
        // JMeterPluginsUtils.addHelpLinkToPanel(this, WIKIPAGE);
        JPanel containerPanel = new JPanel(new BorderLayout());
        containerPanel.add(createParamsPanel(), BorderLayout.NORTH);
        chart = new GraphPanelChart(false, true);
        model = new ConcurrentHashMap<>();
        chart.setRows(model);
        chart.getChartSettings().setDrawFinalZeroingLines(true);

        chart.setxAxisLabel(JMeterUtils.getLocaleString(DDOS_ELAPSED_TIME));
        chart.setYAxisLabel(JMeterUtils.getLocaleString(DDOS_PACKET_RATE));

        chart.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.LOWERED));

        containerPanel.add(GuiBuilderHelper.getComponentWithMargin(chart, 2, 2, 0, 2), BorderLayout.CENTER);

        add(containerPanel, BorderLayout.CENTER);

        // this magic LoopPanel provides functionality for thread loops
        createControllerPanel();
    }

    @Override
    public void clearGui() {
        super.clearGui();
        initGui();
    }

    // Initialise the gui field values
    private void initGui() {
        totalThreads.setText("100");
        initialDelay.setText("0");
        incUserCount.setText("10");
        incUserCountBurst.setText("0");
        incUserPeriod.setText("30");
        flightTime.setText("60");
        decUserCount.setText("5");
        decUserPeriod.setText("1");
    }

    private JPanel createParamsPanel() {
        JPanel panel = new JPanel(new GridLayout(0, 5, 5, 5));
        panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getLocaleString(DDOS_LOAD_SCHEDULING_PARAMETERS)));

        panel.add(new JLabel(JMeterUtils.getLocaleString(DDOS_MAX_LOAD), JLabel.RIGHT));
        totalThreads = new JTextField(5);
        panel.add(totalThreads);
        panel.add(new JLabel("pps;", JLabel.LEFT));
        panel.add(new JLabel());
        panel.add(new JLabel());

        panel.add(new JLabel(JMeterUtils.getLocaleString(DDOS_INITIAL_DELAY), JLabel.RIGHT));
        initialDelay = new JTextField(5);
        panel.add(initialDelay);
        panel.add(new JLabel(JMeterUtils.getLocaleString(DDOS_SECONDS) + ";", JLabel.LEFT));
        panel.add(new JLabel());
        panel.add(new JLabel());

        panel.add(new JLabel(JMeterUtils.getLocaleString(DDOS_START_AT), JLabel.RIGHT));
        incUserCountBurst = new JTextField(5);
        panel.add(incUserCountBurst);
        panel.add(new JLabel("pps; ", JLabel.LEFT));
        panel.add(new JLabel(""));
        panel.add(new JLabel());

        panel.add(new JLabel(JMeterUtils.getLocaleString(DDOS_INCREASE_BY), JLabel.RIGHT));
        incUserCount = new JTextField(5);
        panel.add(incUserCount);
        panel.add(new JLabel("pps " + JMeterUtils.getLocaleString(DDOS_EVERY), JLabel.CENTER));
        incUserPeriod = new JTextField(5);
        panel.add(incUserPeriod);
        panel.add(new JLabel(JMeterUtils.getLocaleString(DDOS_SECONDS) + ",", JLabel.LEFT));

        panel.add(new JLabel(JMeterUtils.getLocaleString(DDOS_HOLD_LOAD), JLabel.RIGHT));
        flightTime = new JTextField(5);
        panel.add(flightTime);
        panel.add(new JLabel(JMeterUtils.getLocaleString(DDOS_SECONDS) + ".", JLabel.LEFT));
        panel.add(new JLabel());
        panel.add(new JLabel());

        panel.add(new JLabel(JMeterUtils.getLocaleString(DDOS_FINALLY_DECREASE_LOAD_BY), JLabel.RIGHT));
        decUserCount = new JTextField(5);
        panel.add(decUserCount);
        panel.add(new JLabel("pps " + JMeterUtils.getLocaleString(DDOS_EVERY), JLabel.CENTER));
        decUserPeriod = new JTextField(5);
        panel.add(decUserPeriod);
        panel.add(new JLabel(JMeterUtils.getLocaleString(DDOS_SECONDS) + ".", JLabel.LEFT));

        registerJTextfieldForGraphRefresh(totalThreads);
        registerJTextfieldForGraphRefresh(initialDelay);
        registerJTextfieldForGraphRefresh(incUserCount);
        registerJTextfieldForGraphRefresh(incUserCountBurst);
        registerJTextfieldForGraphRefresh(incUserPeriod);
        registerJTextfieldForGraphRefresh(flightTime);
        registerJTextfieldForGraphRefresh(decUserCount);
        registerJTextfieldForGraphRefresh(decUserPeriod);
        return panel;
    }

    @Override
    public String getLabelResource() {
        return DDOS_STAIRS_THREAD_GROUP_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_STAIRS_THREAD_GROUP_TITLE);
    }

    @Override
    public TestElement createTestElement() {
        StairsDDoSThreadGroup tg = new StairsDDoSThreadGroup();
        modifyTestElement(tg);
        return tg;
    }

    private void refreshPreview() {
        StairsDDoSThreadGroup tgForPreview = new StairsDDoSThreadGroup();
        tgForPreview.setNumThreads(new CompoundVariable(totalThreads.getText()).execute());
        tgForPreview.setThreadGroupDelay(new CompoundVariable(initialDelay.getText()).execute());
        tgForPreview.setInUserCount(new CompoundVariable(incUserCount.getText()).execute());
        tgForPreview.setInUserCountBurst(new CompoundVariable(incUserCountBurst.getText()).execute());
        tgForPreview.setInUserPeriod(new CompoundVariable(incUserPeriod.getText()).execute());
        tgForPreview.setOutUserCount(new CompoundVariable(decUserCount.getText()).execute());
        tgForPreview.setOutUserPeriod(new CompoundVariable(decUserPeriod.getText()).execute());
        tgForPreview.setFlightTime(new CompoundVariable(flightTime.getText()).execute());

        if (tgForPreview.getInUserCountAsInt() == 0) {
            tgForPreview.setInUserCount(new CompoundVariable(totalThreads.getText()).execute());
        }
        if (tgForPreview.getOutUserCountAsInt() == 0) {
            tgForPreview.setOutUserCount(new CompoundVariable(totalThreads.getText()).execute());
        }
        updateChart(tgForPreview);
    }

    @Override
    public void modifyTestElement(TestElement te) {
        super.configureTestElement(te);

        if (te instanceof StairsDDoSThreadGroup) {
            StairsDDoSThreadGroup tg = (StairsDDoSThreadGroup) te;
            tg.setProperty(StairsDDoSThreadGroup.NUM_THREADS, totalThreads.getText());
            tg.setThreadGroupDelay(initialDelay.getText());
            tg.setInUserCount(incUserCount.getText());
            tg.setInUserCountBurst(incUserCountBurst.getText());
            tg.setInUserPeriod(incUserPeriod.getText());
            tg.setOutUserCount(decUserCount.getText());
            tg.setOutUserPeriod(decUserPeriod.getText());
            tg.setFlightTime(flightTime.getText());
            tg.setSamplerController((LoopController) loopPanel.createTestElement());
            tg.broadcastSamplerRateStrategy();
            refreshPreview();
        }
    }

    @Override
    public void configure(TestElement te) {
        super.configure(te);
        StairsDDoSThreadGroup tg = (StairsDDoSThreadGroup) te;
        totalThreads.setText(tg.getNumThreadsAsString());
        initialDelay.setText(tg.getThreadGroupDelay());
        incUserCount.setText(tg.getInUserCount());
        incUserCountBurst.setText(tg.getInUserCountBurst());
        incUserPeriod.setText(tg.getInUserPeriod());
        decUserCount.setText(tg.getOutUserCount());
        decUserPeriod.setText(tg.getOutUserPeriod());
        flightTime.setText(tg.getFlightTime());

        TestElement controller = (TestElement) tg.getProperty(AbstractThreadGroup.MAIN_CONTROLLER).getObjectValue();
        if (controller != null) {
            loopPanel.configure(controller);
        }
    }

    private void updateChart(StairsDDoSThreadGroup tg) {
        model.clear();

        GraphRowSumValues row = new GraphRowSumValues();
        row.setColor(Color.RED);
        row.setDrawLine(true);
        row.setMarkerSize(AbstractGraphRow.MARKER_SIZE_NONE);
        row.setDrawThickLines(true);

        final HashTree hashTree = new HashTree();
        hashTree.add(new LoopController());
        JMeterThread thread = new JMeterThread(hashTree, null, null);
        long now = System.currentTimeMillis();

        // test start
        chart.setxAxisLabelRenderer(new DateTimeRenderer(DateTimeRenderer.HHMMSS, now - 1)); // -1
        // because
        // row.add(thread.getStartTime()
        // -
        // 1,
        // 0)
        row.add(now, 0);
        row.add(now + tg.getThreadGroupDelayAsInt(), 0);

        int numThreads = tg.getNumThreads();

        // users in
        for (int n = 0; n < numThreads; n++) {
            thread.setThreadNum(n);
            tg.scheduleThread(thread, now);
            row.add(thread.getStartTime() - 1, 0);
            row.add(thread.getStartTime(), 1);
        }

        // users out
        for (int n = 0; n < numThreads; n++) {
            thread.setThreadNum(n);
            tg.scheduleThread(thread, now);
            row.add(thread.getEndTime() - 1, 0);
            row.add(thread.getEndTime(), -1);
        }

        model.put(JMeterUtils.getLocaleString(DDOS_EXPECTED_LOAD), row);
        chart.invalidateCache();
        chart.repaint();
    }

    private JPanel createControllerPanel() {
        loopPanel = new LoopControlPanel(false);
        LoopController looper = (LoopController) loopPanel.createTestElement();
        looper.setLoops(-1);
        looper.setContinueForever(true);
        loopPanel.configure(looper);
        return loopPanel;
    }

    private void registerJTextfieldForGraphRefresh(final JTextField tf) {
        tf.addActionListener(loopPanel);
        tf.getDocument().addDocumentListener(new FieldChangesListener(tf));
    }

    @Override
    public JPopupMenu createPopupMenu() {
        return DDoSUtils.getInstance().getDDoSThreadGroupPopupMenu(this);
    }

    private class FieldChangesListener implements DocumentListener {

        private final JTextField tf;

        public FieldChangesListener(JTextField field) {
            tf = field;
        }

        private void update() {
            refreshPreview();
        }

        @Override
        public void insertUpdate(DocumentEvent e) {
            if (tf.hasFocus()) {
                update();
            }
        }

        @Override
        public void removeUpdate(DocumentEvent e) {
            if (tf.hasFocus()) {
                update();
            }
        }

        @Override
        public void changedUpdate(DocumentEvent e) {
            if (tf.hasFocus()) {
                update();
            }
        }
    }
}
