// Copyright (C) 2004-2009 Peter Luschny, MIT License applies.
// See http://en.wikipedia.org/wiki/MIT_License
// Visit http://www.luschny.de/math/factorial/FastFactorialFunctions.htm
// Comments mail to: peter(at)luschny.de
package de.luschny.apps.factorial;

import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import de.luschny.apps.LoggedTextBox;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;

public class BenchmarkForm extends JFrame implements PropertyChangeListener {

    private static final long serialVersionUID = 1L;
    private FactorialTest test;

    public BenchmarkForm() {
        test = new FactorialTest();

        initComponents();

        // set defaults
        checkBoxArray[Candidate.IndexOfReference].setSelected(true);
        logToFileCheckBox.setSelected(true);
        verboseCheckBox.setSelected(true);

        initHandler();
    }

    private void initComponents() {
        checkBoxArray = new JCheckBox[21];
        menuBar = new JMenuBar();
        benchMenu = new JMenu();
        recommededMenu = new JMenuItem();
        parallelMenu = new JMenuItem();
        primeMenu = new JMenuItem();
        simpleMenu = new JMenuItem();
        lameMenu = new JMenuItem();
        ectMenu = new JMenu();
        sanityMenu = new JMenuItem();
        aboutMenu = new JMenuItem();
        algoLabel = new JLabel();
        logToFileCheckBox = new JCheckBox();
        showValueCheckBox = new JCheckBox();
        verboseCheckBox = new JCheckBox();
        startLabel = new JLabel();
        startField = new JTextField();
        stepLabel = new JLabel();
        stepBox = new JComboBox();
        cancelButton = new JButton();
        lenghtLabel = new JLabel();
        lenghtSpinner = new JSpinner();
        benchmarkButton = new JButton();
        algoSelection = new JPanel();
        scrollPane = new JScrollPane();
        textArea = new JTextArea();
        infoLabel = new JLabel();
        progressBar = new JProgressBar();
        CellConstraints cc = new CellConstraints();

        // ======== benchForm ========

        setTitle("Factorial Algorithm Benchmark");
        setFont(new Font("Verdana", Font.BOLD, 16));
        Container benchFormContentPane = getContentPane();
        benchFormContentPane.setLayout(new FormLayout(
                "6dlu, $lcgap, 4dlu, $lcgap, 82dlu, 10dlu, 6dlu, center:46dlu, $lcgap, 42dlu, $lcgap, 18dlu, $lcgap, center:58dlu, 6dlu, left:12dlu",
                "2dlu, $lgap, default, $lgap, top:176dlu, $lgap, default, $lgap, 16dlu, $lgap, 18dlu, $lgap, 16dlu, $lgap, 18dlu, $lgap, 4dlu"));
        ((FormLayout) benchFormContentPane.getLayout()).setRowGroups(new int[][]{
                    {9, 11, 13}});

        JCheckBox checkBox = new JCheckBox();

        Font font = benchMenu.getFont().deriveFont(benchMenu.getFont().getStyle() | Font.BOLD,
                benchMenu.getFont().getSize() - 1f);
        Font labelFont = algoLabel.getFont().deriveFont(algoLabel.getFont().getStyle() | Font.BOLD,
                algoLabel.getFont().getSize() - 1f);
        Font checkFont = checkBox.getFont().deriveFont(checkBox.getFont().getStyle() | Font.BOLD,
                checkBox.getFont().getSize() - 1f);
        Font buttonFont = cancelButton.getFont().deriveFont(cancelButton.getFont().getStyle() | Font.BOLD);

        // ======== benchMenu ========

        benchMenu.setText("Benchmark");
        benchMenu.setFont(font);

        // ---- recommendedMenu ----
        recommededMenu.setText("Recommended");
        recommededMenu.setFont(font);
        recommededMenu.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                toptenMenuActionPerformed(e);
            }
        });

        benchMenu.add(recommededMenu);

        // ---- primeMenu ----
        primeMenu.setText("Prime");
        primeMenu.setFont(font);
        primeMenu.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                primeMenuActionPerformed(e);
            }
        });

        benchMenu.add(primeMenu);

        // ---- parallelMenu ----
        parallelMenu.setText("Parallel");
        parallelMenu.setFont(font);
        parallelMenu.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                parallelMenuActionPerformed(e);
            }
        });

        benchMenu.add(parallelMenu);

        // ---- simpleMenu ----
        simpleMenu.setText("Simple");
        simpleMenu.setFont(font);
        simpleMenu.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                simpleMenuActionPerformed(e);
            }
        });

        benchMenu.add(simpleMenu);

        // ---- lameMenu ----
        lameMenu.setText("Lame");
        lameMenu.setFont(font);
        lameMenu.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                lameMenuActionPerformed(e);
            }
        });

        benchMenu.add(lameMenu);

        menuBar.add(benchMenu);

        // ======== ectMenu ========
        ectMenu.setText("Ecetera");
        ectMenu.setFont(font);

        // ---- sanityMenu ----
        sanityMenu.setText("Sanity Check");
        sanityMenu.setFont(font);
        sanityMenu.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                sanityMenuActionPerformed(e);
            }
        });

        ectMenu.add(sanityMenu);
        ectMenu.addSeparator();

        // ---- aboutMenu ----
        aboutMenu.setText("About");
        aboutMenu.setFont(font);
        aboutMenu.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                aboutMenuActionPerformed(e);
            }
        });

        ectMenu.add(aboutMenu);
        menuBar.add(ectMenu);

        setJMenuBar(menuBar);

        // ---- algoLabel ----
        algoLabel.setText("Algorithm");
        algoLabel.setFont(labelFont);
        benchFormContentPane.add(algoLabel, new CellConstraints(5, 3, 1, 1, CellConstraints.DEFAULT,
                CellConstraints.DEFAULT, new Insets(0, 12, 0, 0)));

        // ---- logToFileCheckBox ----
        logToFileCheckBox.setText("Log To File");
        logToFileCheckBox.setFont(checkFont);
        benchFormContentPane.add(logToFileCheckBox, new CellConstraints(8, 3, 3, 1, CellConstraints.LEFT,
                CellConstraints.DEFAULT, new Insets(0, 8, 0, 0)));

        // ---- showValueCheckBox ----
        showValueCheckBox.setText("Show Value");
        showValueCheckBox.setFont(checkFont);
        benchFormContentPane.add(showValueCheckBox, cc.xywh(10, 3, 3, 1, CellConstraints.RIGHT, CellConstraints.DEFAULT));

        // ---- verboseCheckBox ----
        verboseCheckBox.setText("Verbose");
        verboseCheckBox.setFont(checkFont);
        benchFormContentPane.add(verboseCheckBox, new CellConstraints(14, 3, 1, 1, CellConstraints.CENTER,
                CellConstraints.DEFAULT, new Insets(0, 4, 0, 0)));

        // ---- startLabel ----
        startLabel.setText("Start");
        startLabel.setFont(labelFont);
        benchFormContentPane.add(startLabel, cc.xywh(8, 9, 1, 1, CellConstraints.RIGHT, CellConstraints.DEFAULT));

        // ---- startField ----
        startField.setToolTipText("Enter start value of n");
        startField.setText("8000");
        benchFormContentPane.add(startField, cc.xy(10, 9));

        // ---- stepLabel ----
        stepLabel.setText("Stepfactor");
        stepLabel.setFont(labelFont);
        benchFormContentPane.add(stepLabel, cc.xywh(8, 11, 1, 1, CellConstraints.RIGHT, CellConstraints.DEFAULT));

        // ---- stepBox ----
        stepBox.setEditable(true);
        stepBox.setModel(new DefaultComboBoxModel(new String[]{"1.5", "2.0", "2.5", "3.0"}));
        stepBox.setSelectedIndex(1);
        benchFormContentPane.add(stepBox, cc.xy(10, 11));

        // ---- cancelButton ----
        cancelButton.setText("Cancel");
        cancelButton.setEnabled(false);
        cancelButton.setBackground(SystemColor.activeCaption);
        cancelButton.setForeground(SystemColor.window);
        cancelButton.setFont(buttonFont);
        cancelButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                cancelButtonActionPerformed(e);
            }
        });

        benchFormContentPane.add(cancelButton, cc.xywh(14, 11, 1, 1, CellConstraints.FILL, CellConstraints.DEFAULT));

        // ---- lenghtLabel ----
        lenghtLabel.setText("Length");
        lenghtLabel.setFont(labelFont);
        benchFormContentPane.add(lenghtLabel, cc.xywh(8, 13, 1, 1, CellConstraints.RIGHT, CellConstraints.DEFAULT));

        // ---- lenghtSpinner ----
        lenghtSpinner.setModel(new SpinnerListModel(new String[]{"1", "2", "3", "4", "5", "6", "7", "8", "9"}));
        benchFormContentPane.add(lenghtSpinner, cc.xy(10, 13));

        // ---- benchmarkButton ----
        benchmarkButton.setText("Benchmark!");
        benchmarkButton.setFont(buttonFont);
        benchmarkButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                benchmarkButtonActionPerformed(e);
            }
        });
        benchFormContentPane.add(benchmarkButton, cc.xywh(14, 13, 1, 1, CellConstraints.FILL, CellConstraints.DEFAULT));

        // ======== AlgoSelection ========

        algoSelection.setPreferredSize(new Dimension(47, 550));
        algoSelection.setBackground(SystemColor.window);
        algoSelection.setBorder(Borders.createEmptyBorder("1dlu, 1dlu, 1dlu, 1dlu"));
        algoSelection.setLayout(new FormLayout("left:default:grow", "21*(fill:default)"));

        for (int i = 0; i < checkBoxArray.length; i++) {
            checkBox = new JCheckBox();
            checkBox.setText(FactorialTest.getNames()[i]);
            checkBox.setBackground(SystemColor.window);
            checkBox.setMargin(new Insets(3, 4, 1, 0));
            algoSelection.add(checkBox, cc.xy(1, i + 1));
            checkBox.setFont(checkFont);
            checkBoxArray[i] = checkBox;
        }

        benchFormContentPane.add(algoSelection, cc.xywh(3, 5, 3, 9));

        // ---- textArea ----
        textArea.setLineWrap(true);
        textArea.setEditable(false);
        textArea.setDisabledTextColor(SystemColor.windowText);
        textArea.setBackground(SystemColor.window);
        textArea.setFont(new Font("Courier New", Font.PLAIN, 12));

        // ======== scrollPane ========
        scrollPane.setViewportView(textArea);
        benchFormContentPane.add(scrollPane, cc.xywh(7, 5, 9, 3));

        // ---- infoLabel ----
        infoLabel.setText("(c) 2008 Peter Luschny");
        infoLabel.setFont(labelFont);
        benchFormContentPane.add(infoLabel, cc.xywh(3, 15, 3, 1));
        benchFormContentPane.add(progressBar, cc.xywh(7, 15, 9, 1));
        pack();
        setLocationRelativeTo(getOwner());
    }
    private JCheckBox[] checkBoxArray;
    private JMenuBar menuBar;
    private JMenu benchMenu;
    private JMenuItem recommededMenu;
    private JMenuItem primeMenu;
    private JMenuItem parallelMenu;
    private JMenuItem simpleMenu;
    private JMenuItem lameMenu;
    private JMenu ectMenu;
    private JMenuItem sanityMenu;
    private JMenuItem aboutMenu;
    private JLabel algoLabel;
    private JCheckBox logToFileCheckBox;
    private JCheckBox showValueCheckBox;
    private JCheckBox verboseCheckBox;
    private JLabel startLabel;
    private JTextField startField;
    private JLabel stepLabel;
    private JComboBox stepBox;
    private JButton cancelButton;
    private JLabel lenghtLabel;
    private JSpinner lenghtSpinner;
    private JButton benchmarkButton;
    private JPanel algoSelection;
    private JTextArea textArea;
    private JScrollPane scrollPane;
    private JLabel infoLabel;
    private JProgressBar progressBar;
    private LoggedTextBox Winsole;
    private BenchmarkWorker worker;

    void initHandler() {

        test.selectedAlgo = new boolean[checkBoxArray.length];

        try {
            Winsole = new LoggedTextBox(textArea, "FactorialBench");
            Winsole.setLogToFile(true);
            BenchmarkApplication.printAppAndSysProps(Winsole.getPrintStream());
        } catch (IOException ioe) {
            JOptionPane.showMessageDialog(this, ioe.getMessage() + "\n", "Logging to file not possible.",
                    JOptionPane.ERROR_MESSAGE);
            return;
        }

        worker = new BenchmarkWorker(Winsole, this);

    }

    // Eigelesen werden muss die Liste der ausgewaehlten Algos
    // und die Werte von Stepfactor und BenchLength.
    // Keine Validierung notwendig: Startwert und Stepfactor sind valid.
    private boolean getParams() {
        String start = startField.getText();
        try {
            test.benchStart = Integer.parseInt(start);
        } catch (NumberFormatException nfe) {
            JOptionPane.showMessageDialog(this, "Start is not a valid integer.\n" + start, "Invalid Argument Error",
                    JOptionPane.ERROR_MESSAGE);
            return false;
        }
        if (test.benchStart < 0) {
            JOptionPane.showMessageDialog(this, "Start must be a positive integer.\n" + start,
                    "Invalid Argument Error", JOptionPane.ERROR_MESSAGE);
            return false;
        }
        if (test.benchStart > FactorialTest.BenchMax) {
            JOptionPane.showMessageDialog(this, "Start must be <= 9000000 because n! is huge.\n",
                    "Invalid Argument Error", JOptionPane.ERROR_MESSAGE);
            return false;
        }

        String length = lenghtSpinner.getValue().toString();
        try {
            test.benchLength = Integer.parseInt(length);
        } catch (NumberFormatException nfe) {
            JOptionPane.showMessageDialog(this, "Length l is not a valid integer: " + length + "\n",
                    "Invalid Argument Error", JOptionPane.ERROR_MESSAGE);
            return false;
        }

        String step = stepBox.getSelectedItem().toString();
        if (step.equals("1.5")) {
            test.stepFactor = 1.5;
        } else if (step.equals("2.0")) {
            test.stepFactor = 2.0;
        } else if (step.equals("2.5")) {
            test.stepFactor = 2.5;
        } else {
            test.stepFactor = 3.0;
        }

        Winsole.logToFile = logToFileCheckBox.isSelected();
        test.showFullValue = showValueCheckBox.isSelected();
        test.verbose = verboseCheckBox.isSelected();

        // Die Referenz im Benchmark ist immer gewaehlt (per Muffti)
        checkBoxArray[Candidate.IndexOfReference].setSelected(true);

        int c = 0, i = 0;
        for (JCheckBox algo : checkBoxArray) {
            boolean check = algo.isSelected();
            if (check) {
                c++;
            }
            test.selectedAlgo[i++] = check;
        }

        test.cardSelected = c;
        return true;
    }

    // Disable the input controls until the asynchronous benchmark is done.
    void EnableControls(boolean en) {
        lenghtSpinner.setEnabled(en);
        startField.setEnabled(en);
        stepBox.setEnabled(en);

        verboseCheckBox.setEnabled(en);
        logToFileCheckBox.setEnabled(en);
        showValueCheckBox.setEnabled(en);

        recommededMenu.setEnabled(en);
        primeMenu.setEnabled(en);
        simpleMenu.setEnabled(en);
        sanityMenu.setEnabled(en);

        for (JCheckBox algo : checkBoxArray) {
            algo.setEnabled(en);
        }
    }

    // Die AlgorithmCheckedListBox entsprechend einstellen
    void simpleMenuActionPerformed(ActionEvent e) {
        boolean[] SimpleAlgos = FactorialTest.getSimpleAlgos();
        int i = 0;
        for (JCheckBox algo : checkBoxArray) {
            algo.setSelected(SimpleAlgos[i++]);
        }
    }

    // Die AlgorithmCheckedListBox entsprechend einstellen
    void primeMenuActionPerformed(ActionEvent e) {
        boolean[] PrimeAlgos = FactorialTest.getPrimeAlgos();
        int i = 0;
        for (JCheckBox algo : checkBoxArray) {
            algo.setSelected(PrimeAlgos[i++]);
        }
    }

    // Die AlgorithmCheckedListBox entsprechend einstellen
    void parallelMenuActionPerformed(ActionEvent e) {
        boolean[] ParallelAlgos = FactorialTest.getParallelAlgos();
        int i = 0;
        for (JCheckBox algo : checkBoxArray) {
            algo.setSelected(ParallelAlgos[i++]);
        }
    }

    // Die AlgorithmCheckedListBox entsprechend einstellen
    void toptenMenuActionPerformed(ActionEvent e) {
        boolean[] TopTenAlgos = FactorialTest.getRecommendedAlgos();
        int i = 0;
        for (JCheckBox algo : checkBoxArray) {
            algo.setSelected(TopTenAlgos[i++]);
        }
    }

    void lameMenuActionPerformed(ActionEvent e) {
        boolean[] LameAlgos = FactorialTest.getLameAlgos();
        int i = 0;
        for (JCheckBox algo : checkBoxArray) {
            algo.setSelected(LameAlgos[i++]);
        }
    }

    void sanityMenuActionPerformed(ActionEvent e) {
        doTheBenchmark(e);
    }

    // Kleine Infobox anzeigen
    void aboutMenuActionPerformed(ActionEvent e) {
        AboutDialog dlg = new AboutDialog(this);
        dlg.setVisible(true);
    }

    void cancelButtonActionPerformed(ActionEvent e) {
        infoLabel.setText("Cancelation is in progress!");

        // Cancel the asynchronous operation.
        worker.cancelAsync();

        // Disable the Cancel button.
        cancelButton.setEnabled(false);
    }

    // Ignorieren?
    void exitMenuActionPerformed(ActionEvent e) {
    }

    void infoMenuActionPerformed(ActionEvent e) {
    }

    void benchmarkButtonActionPerformed(ActionEvent e) {
        doTheBenchmark(e);
    }

    // /////////////////////////////////////////////////////
    // Zentraler Flaschenhals: Benchmark-Schleife geht los
    // Ueber BenchLength von StartWert by times Stepfactor
    // Ueber alle ausgewaelhten Algorithmen
    private void doTheBenchmark(ActionEvent e) {
        EnableControls(false);

        test.sanityTest = e.getActionCommand().equalsIgnoreCase("Sanity Check");

        // Get the values from the this. If they are valid ...
        if (test.sanityTest || getParams()) {
            // Reset the text in the result label.
            infoLabel.setText("Benchmark is running!");

            // Disable the Start button until the benchmark is done.
            benchmarkButton.setEnabled(false);

            // Enable the Cancel button while
            // the benchmark runs.
            cancelButton.setEnabled(true);

            // Start the asynchronous background task.
            worker.execute(test);
        } else {
            EnableControls(true);
        }
    }

    // Hier kommen die Nachrichten waehrend des
    // Verlaufs des Hintergrundausfuehrung herein.
    @Override
    public void propertyChange(PropertyChangeEvent event) {
        if ("progress".equals(event.getPropertyName())) {
            progressBar.setValue((Integer) event.getNewValue());
        }
    }

    // This event handler deals with the results of the benchmark.
    void benchmarkCompleted(WorkerCompletedEvent event) {
        String msg;
        if (event.cancelled) {
            // The user canceled the benchmark.
            msg = "Benchmark was canceled.";
        } else if (event.error != null) {
            // There was an error during the benchmark.
            msg = "An error occurred.";

            JOptionPane.showMessageDialog(this, event.error.toString(), "Error in worker thread",
                    JOptionPane.ERROR_MESSAGE);

            Winsole.WriteLine();
            Winsole.WriteLine("Error in worker thread.");
            Winsole.WriteLine(event.error.toString());
            Winsole.WriteLine();
        } else {
            // The benchmark completed normally.
            msg = "Benchmark completed";
        }

        infoLabel.setText(msg);

        EnableControls(true);

        // Enable the Start button.
        benchmarkButton.setEnabled(true);

        // Disable the Cancel button.
        cancelButton.setEnabled(false);

        test.sanityTest = false;

        progressBar.setValue(0);
    }
}
