package kg.apc.jmeter.threads;

import kg.apc.charting.AbstractGraphRow;
import kg.apc.charting.DateTimeRenderer;
import kg.apc.charting.GraphPanelChart;
import kg.apc.charting.rows.GraphRowSumValues;
import kg.apc.jmeter.JMeterPluginsUtils;
import kg.apc.jmeter.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.jorphan.collections.HashTree;

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

public class SteppingThreadGroupGui
        extends AbstractThreadGroupGui {

    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();
            }
        }
    }

    public static final String WIKIPAGE = "SteppingThreadGroup";

    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;
    private JTextField rampUp;

    public SteppingThreadGroupGui() {
        super();
        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("Elapsed time");
        chart.setYAxisLabel("Number of active threads");

        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");
        rampUp.setText("5");
    }

    private JPanel createParamsPanel() {
        JPanel panel = new JPanel(new GridLayout(0, 5, 5, 5));
        panel.setBorder(BorderFactory.createTitledBorder("Threads Scheduling Parameters"));

        panel.add(new JLabel("This group will start", JLabel.RIGHT));
        totalThreads = new JTextField(5);
        panel.add(totalThreads);
        panel.add(new JLabel("threads:", JLabel.LEFT));
        panel.add(new JLabel());
        panel.add(new JLabel());

        panel.add(new JLabel("First, wait for", JLabel.RIGHT));
        initialDelay = new JTextField(5);
        panel.add(initialDelay);
        panel.add(new JLabel("seconds;", JLabel.LEFT));
        panel.add(new JLabel());
        panel.add(new JLabel());

        panel.add(new JLabel("Then start", JLabel.RIGHT));
        incUserCountBurst = new JTextField(5);
        panel.add(incUserCountBurst);
        panel.add(new JLabel("threads; ", JLabel.LEFT));
        panel.add(new JLabel(""));
        panel.add(new JLabel());

        panel.add(new JLabel("Next, add", JLabel.RIGHT));
        incUserCount = new JTextField(5);
        panel.add(incUserCount);
        panel.add(new JLabel("threads every", JLabel.CENTER));
        incUserPeriod = new JTextField(5);
        panel.add(incUserPeriod);
        panel.add(new JLabel("seconds, ", JLabel.LEFT));

        panel.add(new JLabel());
        panel.add(new JLabel());
        panel.add(new JLabel("using ramp-up", JLabel.RIGHT));
        rampUp = new JTextField(5);
        panel.add(rampUp);
        panel.add(new JLabel("seconds.", JLabel.LEFT));

        panel.add(new JLabel("Then hold load for", JLabel.RIGHT));
        flightTime = new JTextField(5);
        panel.add(flightTime);
        panel.add(new JLabel("seconds.", JLabel.LEFT));
        panel.add(new JLabel());
        panel.add(new JLabel());

        panel.add(new JLabel("Finally, stop", JLabel.RIGHT));
        decUserCount = new JTextField(5);
        panel.add(decUserCount);
        panel.add(new JLabel("threads every", JLabel.CENTER));
        decUserPeriod = new JTextField(5);
        panel.add(decUserPeriod);
        panel.add(new JLabel("seconds.", JLabel.LEFT));

        registerJTextfieldForGraphRefresh(totalThreads);
        registerJTextfieldForGraphRefresh(initialDelay);
        registerJTextfieldForGraphRefresh(incUserCount);
        registerJTextfieldForGraphRefresh(incUserCountBurst);
        registerJTextfieldForGraphRefresh(incUserPeriod);
        registerJTextfieldForGraphRefresh(flightTime);
        registerJTextfieldForGraphRefresh(decUserCount);
        registerJTextfieldForGraphRefresh(decUserPeriod);
        registerJTextfieldForGraphRefresh(rampUp);

        return panel;
    }

    @Override
    public String getLabelResource() {
        return this.getClass().getSimpleName();
    }

    @Override
    public String getStaticLabel() {
        return JMeterPluginsUtils.prefixLabel("Stepping Thread Group");
    }

    @Override
    public TestElement createTestElement() {
        SteppingThreadGroup tg = new SteppingThreadGroup();
        modifyTestElement(tg);
        tg.setComment(JMeterPluginsUtils.getWikiLinkText(WIKIPAGE));
        return tg;
    }

    private void refreshPreview() {
        SteppingThreadGroup tgForPreview = new SteppingThreadGroup();
        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());
        tgForPreview.setRampUp(new CompoundVariable(rampUp.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 SteppingThreadGroup) {
            SteppingThreadGroup tg = (SteppingThreadGroup) te;
            tg.setProperty(SteppingThreadGroup.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.setRampUp(rampUp.getText());
            tg.setSamplerController((LoopController) loopPanel.createTestElement());

            refreshPreview();
        }
    }

    @Override
    public void configure(TestElement te) {
        super.configure(te);
        SteppingThreadGroup tg = (SteppingThreadGroup) 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());
        rampUp.setText(tg.getRampUp());

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

    private void updateChart(SteppingThreadGroup 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("Expected Active Users Count", 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));
    }
}
