package com.blazemeter.jmeter.threads;

import kg.apc.charting.AbstractGraphRow;
import kg.apc.charting.DateTimeRenderer;
import kg.apc.charting.GraphPanelChart;
import kg.apc.charting.rows.GraphRowExactValues;
import kg.apc.jmeter.DummyEvaluator;
import kg.apc.jmeter.JMeterVariableEvaluator;
import kg.apc.jmeter.gui.GuiBuilderHelper;
import org.apache.jmeter.gui.util.VerticalPanel;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.threads.gui.AbstractThreadGroupGui;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

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

public abstract class AbstractDynamicThreadGroupGui extends AbstractThreadGroupGui
        implements DocumentListener, Runnable, ActionListener {
    private static final Logger log = LoggerFactory.getLogger(AbstractDynamicThreadGroupGui.class);
    protected GraphPanelChart previewChart;
    protected ConcurrentHashMap<String, AbstractGraphRow> chartModel;
    protected boolean uiCreated = false;
    private ParamsPanel loadFields = null;
    private AdditionalFieldsPanel additionalFields = null;
    private JMeterVariableEvaluator evaluator = new DummyEvaluator();

    @Override
    public void configure(TestElement element) {
        super.configure(element);
        if (!uiCreated) {
            initUI();
        }
        if (element instanceof AbstractDynamicThreadGroup) {
            AbstractDynamicThreadGroup tg = (AbstractDynamicThreadGroup) element;
            loadFields.modelToUI(tg);
            additionalFields.modelToUI(tg);
            updateUI();
        }
    }

    @Override
    public void modifyTestElement(TestElement element) {
        super.configureTestElement(element);
        if (!uiCreated) {
            initUI();
        }

        if (element instanceof AbstractDynamicThreadGroup) {
            AbstractDynamicThreadGroup tg = (AbstractDynamicThreadGroup) element;
            loadFields.UItoModel(tg, evaluator);
            additionalFields.UItoModel(tg, evaluator);
        }
    }

    protected void initUI() {
        JPanel container = new VerticalPanel();
        loadFields = createLoadPanel();
        container.add((Component) loadFields, BorderLayout.NORTH);
        container.add(GuiBuilderHelper.getComponentWithMargin(getPreviewChart(), 2, 2, 0, 2), BorderLayout.CENTER);
        additionalFields = getAdditionalFieldsPanel();
        additionalFields.addActionListener(this);
        container.add(additionalFields, BorderLayout.SOUTH);
        add(container, BorderLayout.CENTER);
        uiCreated = true;
    }

    protected abstract AdditionalFieldsPanel getAdditionalFieldsPanel();

    @Override
    public void clearGui() {
        super.clearGui();
        if (uiCreated) {
            loadFields.clearUI();
            additionalFields.clearUI();
        }
    }

    protected abstract ParamsPanel createLoadPanel();


    protected abstract AbstractDynamicThreadGroup createThreadGroupObject();

    protected abstract void setChartPropertiesFromTG(AbstractDynamicThreadGroup atg);

    protected abstract String getRowLabel(double totalArrivals);

    protected abstract Color getRowColor();

    @Override
    public void insertUpdate(DocumentEvent documentEvent) {
        SwingUtilities.invokeLater(this);
    }

    @Override
    public void removeUpdate(DocumentEvent documentEvent) {
        SwingUtilities.invokeLater(this);
    }

    @Override
    public void changedUpdate(DocumentEvent documentEvent) {
        SwingUtilities.invokeLater(this);
    }

    @Override
    public void run() {
        updateUI();
    }

    @Override
    public void actionPerformed(ActionEvent actionEvent) {
        SwingUtilities.invokeLater(this);
    }

    @Override
    public TestElement createTestElement() {
        AbstractDynamicThreadGroup te = createThreadGroupObject();
        modifyTestElement(te);
        return te;
    }

    public void updateUI() {
        super.updateUI();
        if (!uiCreated) {
            log.debug("Won't update UI");
            return;
        }
        log.debug("Updating UI");

        AbstractDynamicThreadGroup atg = createThreadGroupObject();

        JMeterVariableEvaluator evaluator = new JMeterVariableEvaluator();
        loadFields.UItoModel(atg, evaluator);
        additionalFields.UItoModel(atg, evaluator);

        try {
            updateChart(atg);
        } catch (NumberFormatException e) {
            previewChart.setErrorMessage("The values entered cannot be rendered in preview...");
        } finally {
            setChartPropertiesFromTG(atg);
            previewChart.invalidateCache();
            previewChart.repaint();
        }

        if (loadFields instanceof LoadParamsFieldsPanel) {
            LoadParamsFieldsPanel panel = (LoadParamsFieldsPanel) loadFields;
            panel.changeUnitInLabels(atg.getUnit());
        }
    }

    protected void updateChart(AbstractDynamicThreadGroup atg) {
        double targetRate = atg.getTargetLevelAsDouble();
        long rampUp = atg.getRampUpSeconds();
        long holdFor = atg.getHoldSeconds();
        long stepsCount = atg.getStepsAsLong();
        double unitFactor = atg.getUnitFactor();

        chartModel.clear();
        previewChart.clearErrorMessage();
        AbstractGraphRow row = new GraphRowExactValues();
        row.setColor(getRowColor());
        row.setDrawLine(true);
        row.setMarkerSize(AbstractGraphRow.MARKER_SIZE_NONE);
        row.setDrawThickLines(true);

        row.add(0, 0); // initial value to force min Y

        double totalArrivals = 0;

        if (stepsCount > 0) {
            double stepSize = targetRate / (double) stepsCount;
            double stepLen = rampUp / (double) stepsCount;

            for (int n = 1; n <= stepsCount; n++) {
                double stepRate = stepSize * n;
                row.add(Math.round((n - 1) * stepLen * 1000), stepRate);
                row.add(Math.round(n * stepLen * 1000), stepRate);
                totalArrivals += stepLen * stepRate;
            }
        } else {
            row.add(rampUp * 1000, targetRate);
            totalArrivals += rampUp * targetRate / 2.0;
        }
        row.add((rampUp + holdFor) * 1000, targetRate);
        totalArrivals += holdFor * targetRate;
        totalArrivals /= unitFactor;

        previewChart.setxAxisLabelRenderer(new DateTimeRenderer(DateTimeRenderer.HHMMSS, 0));
        chartModel.put(getRowLabel(totalArrivals), row);
    }

    public Component getPreviewChart() {
        previewChart = new GraphPanelChart(false, true);
        chartModel = new ConcurrentHashMap<>();
        previewChart.setRows(chartModel);
        previewChart.setxAxisLabel("Elapsed Time");
        previewChart.setBorder(BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.LOWERED));
        return previewChart;
    }
}
