package junit.awtui;

/*-
 * #%L
 * org.eclipse.jdt.ui.junit.sampleproject
 * %%
 * Copyright (C) 2020 Eclipse Foundation
 * %%
 * This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 * #L%
 */

import java.awt.*;
import java.awt.event.*;
import java.awt.image.ImageProducer;
import java.util.Vector;

import junit.framework.*;
import junit.runner.*;

/**
 * An AWT based user interface to run tests. Enter the name of a class which
 * either provides a static suite method or is a subclass of TestCase.
 * 
 * <pre>
 * Synopsis: java junit.awtui.TestRunner [-noloading] [TestCase]
 * </pre>
 * 
 * TestRunner takes as an optional argument the name of the testcase class to be
 * run.
 */
public class TestRunner extends BaseTestRunner {
	protected Frame fFrame;
	protected Vector fExceptions;
	protected Vector fFailedTests;
	protected Thread fRunner;
	protected TestResult fTestResult;

	protected TextArea fTraceArea;
	protected TextField fSuiteField;
	protected Button fRun;
	protected ProgressBar fProgressIndicator;
	protected List fFailureList;
	protected Logo fLogo;
	protected Label fNumberOfErrors;
	protected Label fNumberOfFailures;
	protected Label fNumberOfRuns;
	protected Button fQuitButton;
	protected Button fRerunButton;
	protected TextField fStatusLine;
	protected Checkbox fUseLoadingRunner;

	protected static final Font PLAIN_FONT = new Font("dialog", Font.PLAIN, 12);
	private static final int GAP = 4;

	public TestRunner() {
	}

	private void about() {
		AboutDialog about = new AboutDialog(fFrame);
		about.setModal(true);
		about.setLocation(300, 300);
		about.setVisible(true);
	}

	public void testStarted(String testName) {
		showInfo("Running: " + testName);
	}

	public void testEnded(String testName) {
		setLabelValue(fNumberOfRuns, fTestResult.runCount());
		synchronized (this) {
			fProgressIndicator.step(fTestResult.wasSuccessful());
		}
	}

	public void testFailed(int status, Test test, Throwable t) {
		switch (status) {
		case TestRunListener.STATUS_ERROR:
			fNumberOfErrors.setText(Integer.toString(fTestResult.errorCount()));
			appendFailure("Error", test, t);
			break;
		case TestRunListener.STATUS_FAILURE:
			fNumberOfFailures.setText(Integer.toString(fTestResult.failureCount()));
			appendFailure("Failure", test, t);
			break;
		}
	}

	protected void addGrid(Panel p, Component co, int x, int y, int w, int fill, double wx, int anchor) {
		GridBagConstraints c = new GridBagConstraints();
		c.gridx = x;
		c.gridy = y;
		c.gridwidth = w;
		c.anchor = anchor;
		c.weightx = wx;
		c.fill = fill;
		if (fill == GridBagConstraints.BOTH || fill == GridBagConstraints.VERTICAL)
			c.weighty = 1.0;
		c.insets = new Insets(y == 0 ? GAP : 0, x == 0 ? GAP : 0, GAP, GAP);
		p.add(co, c);
	}

	private void appendFailure(String kind, Test test, Throwable t) {
		kind += ": " + test;
		String msg = t.getMessage();
		if (msg != null) {
			kind += ":" + truncate(msg);
		}
		fFailureList.add(kind);
		fExceptions.addElement(t);
		fFailedTests.addElement(test);
		if (fFailureList.getItemCount() == 1) {
			fFailureList.select(0);
			failureSelected();
		}
	}

	/**
	 * Creates the JUnit menu. Clients override this method to add additional menu
	 * items.
	 */
	protected Menu createJUnitMenu() {
		Menu menu = new Menu("JUnit");
		MenuItem mi = new MenuItem("About...");
		mi.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				about();
			}
		});
		menu.add(mi);

		menu.addSeparator();
		mi = new MenuItem("Exit");
		mi.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				System.exit(0);
			}
		});
		menu.add(mi);
		return menu;
	}

	protected void createMenus(MenuBar mb) {
		mb.add(createJUnitMenu());
	}

	protected TestResult createTestResult() {
		return new TestResult();
	}

	protected Frame createUI(String suiteName) {
		Frame frame = new Frame("JUnit");
		Image icon = loadFrameIcon();
		if (icon != null)
			frame.setIconImage(icon);

		frame.setLayout(new BorderLayout(0, 0));
		frame.setBackground(SystemColor.control);
		final Frame finalFrame = frame;

		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				finalFrame.dispose();
				System.exit(0);
			}
		});

		MenuBar mb = new MenuBar();
		createMenus(mb);
		frame.setMenuBar(mb);

		// ---- first section
		Label suiteLabel = new Label("Test class name:");

		fSuiteField = new TextField(suiteName != null ? suiteName : "");
		fSuiteField.selectAll();
		fSuiteField.requestFocus();
		fSuiteField.setFont(PLAIN_FONT);
		fSuiteField.setColumns(40);
		fSuiteField.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				runSuite();
			}
		});
		fSuiteField.addTextListener(new TextListener() {
			public void textValueChanged(TextEvent e) {
				fRun.setEnabled(fSuiteField.getText().length() > 0);
				fStatusLine.setText("");
			}
		});
		fRun = new Button("Run");
		fRun.setEnabled(false);
		fRun.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				runSuite();
			}
		});
		boolean useLoader = useReloadingTestSuiteLoader();
		fUseLoadingRunner = new Checkbox("Reload classes every run", useLoader);
		if (inVAJava())
			fUseLoadingRunner.setVisible(false);

		// ---- second section
		fProgressIndicator = new ProgressBar();

		// ---- third section
		fNumberOfErrors = new Label("0000", Label.RIGHT);
		fNumberOfErrors.setText("0");
		fNumberOfErrors.setFont(PLAIN_FONT);

		fNumberOfFailures = new Label("0000", Label.RIGHT);
		fNumberOfFailures.setText("0");
		fNumberOfFailures.setFont(PLAIN_FONT);

		fNumberOfRuns = new Label("0000", Label.RIGHT);
		fNumberOfRuns.setText("0");
		fNumberOfRuns.setFont(PLAIN_FONT);

		Panel numbersPanel = createCounterPanel();

		// ---- fourth section
		Label failureLabel = new Label("Errors and Failures:");

		fFailureList = new List(5);
		fFailureList.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				failureSelected();
			}
		});
		fRerunButton = new Button("Run");
		fRerunButton.setEnabled(false);
		fRerunButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				rerun();
			}
		});

		Panel failedPanel = new Panel(new GridLayout(0, 1, 0, 2));
		failedPanel.add(fRerunButton);

		fTraceArea = new TextArea();
		fTraceArea.setRows(5);
		fTraceArea.setColumns(60);

		// ---- fifth section
		fStatusLine = new TextField();
		fStatusLine.setFont(PLAIN_FONT);
		fStatusLine.setEditable(false);
		fStatusLine.setForeground(Color.red);

		fQuitButton = new Button("Exit");
		fQuitButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		});

		// ---------
		fLogo = new Logo();

		// ---- overall layout
		Panel panel = new Panel(new GridBagLayout());

		addGrid(panel, suiteLabel, 0, 0, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST);

		addGrid(panel, fSuiteField, 0, 1, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST);
		addGrid(panel, fRun, 2, 1, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER);
		addGrid(panel, fUseLoadingRunner, 0, 2, 2, GridBagConstraints.NONE, 1.0, GridBagConstraints.WEST);
		addGrid(panel, fProgressIndicator, 0, 3, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST);
		addGrid(panel, fLogo, 2, 3, 1, GridBagConstraints.NONE, 0.0, GridBagConstraints.NORTH);

		addGrid(panel, numbersPanel, 0, 4, 2, GridBagConstraints.NONE, 0.0, GridBagConstraints.WEST);

		addGrid(panel, failureLabel, 0, 5, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST);
		addGrid(panel, fFailureList, 0, 6, 2, GridBagConstraints.BOTH, 1.0, GridBagConstraints.WEST);
		addGrid(panel, failedPanel, 2, 6, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER);
		addGrid(panel, fTraceArea, 0, 7, 2, GridBagConstraints.BOTH, 1.0, GridBagConstraints.WEST);

		addGrid(panel, fStatusLine, 0, 8, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.CENTER);
		addGrid(panel, fQuitButton, 2, 8, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER);

		frame.add(panel, BorderLayout.CENTER);
		frame.pack();
		return frame;
	}

	protected Panel createCounterPanel() {
		Panel numbersPanel = new Panel(new GridBagLayout());
		addToCounterPanel(numbersPanel, new Label("Runs:"), 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
				GridBagConstraints.NONE, new Insets(0, 0, 0, 0));
		addToCounterPanel(numbersPanel, fNumberOfRuns, 1, 0, 1, 1, 0.33, 0.0, GridBagConstraints.CENTER,
				GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 40));
		addToCounterPanel(numbersPanel, new Label("Errors:"), 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
				GridBagConstraints.NONE, new Insets(0, 8, 0, 0));
		addToCounterPanel(numbersPanel, fNumberOfErrors, 3, 0, 1, 1, 0.33, 0.0, GridBagConstraints.CENTER,
				GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 40));
		addToCounterPanel(numbersPanel, new Label("Failures:"), 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
				GridBagConstraints.NONE, new Insets(0, 8, 0, 0));
		addToCounterPanel(numbersPanel, fNumberOfFailures, 5, 0, 1, 1, 0.33, 0.0, GridBagConstraints.CENTER,
				GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0));
		return numbersPanel;
	}

	private void addToCounterPanel(Panel counter, Component comp, int gridx, int gridy, int gridwidth, int gridheight,
			double weightx, double weighty, int anchor, int fill, Insets insets) {

		GridBagConstraints constraints = new GridBagConstraints();
		constraints.gridx = gridx;
		constraints.gridy = gridy;
		constraints.gridwidth = gridwidth;
		constraints.gridheight = gridheight;
		constraints.weightx = weightx;
		constraints.weighty = weighty;
		constraints.anchor = anchor;
		constraints.fill = fill;
		constraints.insets = insets;
		counter.add(comp, constraints);
	}

	public void failureSelected() {
		fRerunButton.setEnabled(isErrorSelected());
		showErrorTrace();
	}

	private boolean isErrorSelected() {
		return fFailureList.getSelectedIndex() != -1;
	}

	private Image loadFrameIcon() {
		Toolkit toolkit = Toolkit.getDefaultToolkit();
		try {
			java.net.URL url = BaseTestRunner.class.getResource("smalllogo.gif");
			return toolkit.createImage((ImageProducer) url.getContent());
		} catch (Exception ex) {
		}
		return null;
	}

	public Thread getRunner() {
		return fRunner;
	}

	public static void main(String[] args) {
		new TestRunner().start(args);
	}

	public static void run(Class test) {
		String args[] = { test.getName() };
		main(args);
	}

	public void rerun() {
		int index = fFailureList.getSelectedIndex();
		if (index == -1)
			return;

		Test test = (Test) fFailedTests.elementAt(index);
		rerunTest(test);
	}

	private void rerunTest(Test test) {
		if (!(test instanceof TestCase)) {
			showInfo("Could not reload " + test.toString());
			return;
		}
		Test reloadedTest = null;
		TestCase rerunTest = (TestCase) test;
		try {
			Class reloadedTestClass = getLoader().reload(test.getClass());
			reloadedTest = TestSuite.createTest(reloadedTestClass, rerunTest.getName());
		} catch (Exception e) {
			showInfo("Could not reload " + test.toString());
			return;
		}
		TestResult result = new TestResult();
		reloadedTest.run(result);

		String message = reloadedTest.toString();
		if (result.wasSuccessful())
			showInfo(message + " was successful");
		else if (result.errorCount() == 1)
			showStatus(message + " had an error");
		else
			showStatus(message + " had a failure");
	}

	protected void reset() {
		setLabelValue(fNumberOfErrors, 0);
		setLabelValue(fNumberOfFailures, 0);
		setLabelValue(fNumberOfRuns, 0);
		fProgressIndicator.reset();
		fRerunButton.setEnabled(false);
		fFailureList.removeAll();
		fExceptions = new Vector(10);
		fFailedTests = new Vector(10);
		fTraceArea.setText("");

	}

	protected void runFailed(String message) {
		showStatus(message);
		fRun.setLabel("Run");
		fRunner = null;
	}

	synchronized public void runSuite() {
		if (fRunner != null && fTestResult != null) {
			fTestResult.stop();
		} else {
			setLoading(shouldReload());
			fRun.setLabel("Stop");
			showInfo("Initializing...");
			reset();

			showInfo("Load Test Case...");

			final Test testSuite = getTest(fSuiteField.getText());
			if (testSuite != null) {
				fRunner = new Thread() {
					public void run() {
						fTestResult = createTestResult();
						fTestResult.addListener(TestRunner.this);
						fProgressIndicator.start(testSuite.countTestCases());
						showInfo("Running...");

						long startTime = System.currentTimeMillis();
						testSuite.run(fTestResult);

						if (fTestResult.shouldStop()) {
							showStatus("Stopped");
						} else {
							long endTime = System.currentTimeMillis();
							long runTime = endTime - startTime;
							showInfo("Finished: " + elapsedTimeAsString(runTime) + " seconds");
						}
						fTestResult = null;
						fRun.setLabel("Run");
						fRunner = null;
						System.gc();
					}
				};
				fRunner.start();
			}
		}
	}

	private boolean shouldReload() {
		return !inVAJava() && fUseLoadingRunner.getState();
	}

	private void setLabelValue(Label label, int value) {
		label.setText(Integer.toString(value));
		label.invalidate();
		label.getParent().validate();

	}

	public void setSuiteName(String suite) {
		fSuiteField.setText(suite);
	}

	private void showErrorTrace() {
		int index = fFailureList.getSelectedIndex();
		if (index == -1)
			return;

		Throwable t = (Throwable) fExceptions.elementAt(index);
		fTraceArea.setText(getFilteredTrace(t));
	}

	private void showInfo(String message) {
		fStatusLine.setFont(PLAIN_FONT);
		fStatusLine.setForeground(Color.black);
		fStatusLine.setText(message);
	}

	protected void clearStatus() {
		showStatus("");
	}

	private void showStatus(String status) {
		fStatusLine.setFont(PLAIN_FONT);
		fStatusLine.setForeground(Color.red);
		fStatusLine.setText(status);
	}

	/**
	 * Starts the TestRunner
	 */
	public void start(String[] args) {
		String suiteName = processArguments(args);
		fFrame = createUI(suiteName);
		fFrame.setLocation(200, 200);
		fFrame.setVisible(true);

		if (suiteName != null) {
			setSuiteName(suiteName);
			runSuite();
		}
	}
}
