content: /**
   * Copyright 2020 Alexander Herzog
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package ui.script;
  
  import java.awt.BorderLayout;
  import java.awt.Toolkit;
  import java.awt.datatransfer.Clipboard;
  import java.awt.datatransfer.StringSelection;
  import java.awt.event.ActionListener;
  import java.io.File;
  import java.io.IOException;
  import java.io.Serializable;
  import java.nio.file.Files;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  
  import javax.swing.BorderFactory;
  import javax.swing.Icon;
  import javax.swing.JButton;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  import javax.swing.JTabbedPane;
  import javax.swing.JTextArea;
  import javax.swing.JToolBar;
  import javax.swing.SwingConstants;
  
  import language.Language;
  import simulator.editmodel.EditModel;
  import simulator.statistics.Statistics;
  import systemtools.MsgBox;
  import ui.images.Images;
  import ui.script.ScriptEditorPanel.ScriptMode;
  import ui.scriptrunner.JSModelTemplates;
  
  /**
   * Dieses Panel bietet einen Skript-Editor und eine Ausgabeanzeige.
   * Das Panel kann daher in verschiedene Funktionen zur Skriptanzeige
   * eingebettet werden.
   * @author Alexander Herzog
   *
   */
  public abstract class ScriptPanel extends JPanel {
  	/**
  	 * Serialisierungs-ID der Klasse
  	 * @see Serializable
  	 */
  	private static final long serialVersionUID = -5776312809204193826L;
  
  	/** Wird hier ein Modell bergeben (statt <code>null</code>, was auch zulssig ist), so wird eine Symbolleisten-Schaltflche zur Auswahl von Beispiel-Skripten angezeigt */
  	private final EditModel model;
  	/** Optionale Beispielskripte fr die jeweiligen Sprachen */
  	private final Map<ScriptEditorPanel.ScriptMode,String> example;
  	/** Registerreiter fr die verschiedenen Seiten (Skripteditor und Skriptausgabe) */
  	private final JTabbedPane tabs;
  	/** Schaltflche zur Anzige des Vorlagen-Dropdown-Mens */
  	private JButton templatesButton;
  	/** Skripteditor */
  	private final ScriptEditorPanel editor;
  	/** Schaltflchen, die whrend der Skriptausfhrung deaktiviert werden sollen ({@link #setEnabledGUI(boolean)}) */
  	private final List<JButton> buttons;
  	/** Wird whrend der Ausfhrung des Skripts angezeigt */
  	private final JLabel waitIndicator;
  
  	/** Skriptausgabe */
  	private final JTextArea outputArea;
  
  	/**
  	 * Thread zur Verarbeitung des Skripts
  	 * @see #commandRun()
  	 */
  	private volatile Thread thread=null;
  
  	/**
  	 * Konstruktor der Klasse
  	 * @param model	Wird hier ein Modell bergeben (statt <code>null</code>, was auch zulssig ist), so wird eine Symbolleisten-Schaltflche zur Auswahl von Beispiel-Skripten angezeigt
  	 * @param showRun	Gibt an, ob die "Skript ausfhren"-Schaltflche angezeigt werden soll
  	 * @param scriptFeatures	Skriptfunktionen, die im Vorlagen-Popupmen angeboten werden sollen
  	 * @param example	Optionale Beispielskripte fr die jeweiligen Sprachen
  	 */
  	public ScriptPanel(final EditModel model, final boolean showRun, final ScriptPopup.ScriptFeature[] scriptFeatures, final Map<ScriptEditorPanel.ScriptMode,String> example) {
  		super();
  		this.model=model;
  		this.example=example;
  
  		buttons=new ArrayList<>();
  
  		setLayout(new BorderLayout());
  		add(tabs=new JTabbedPane());
  
  		JPanel tab, top;
  		JToolBar toolbar;
  
  		editor=new ScriptEditorPanel("",ScriptEditorPanel.ScriptMode.Javascript,false,null,getModel(),getMiniStatistics(),getHelpRunnable(),scriptFeatures){
  			/**
  			 * Serialisierungs-ID der Klasse
  			 * @see Serializable
  			 */
  			private static final long serialVersionUID = -1345017777248153806L;
  			@Override
  			protected void addCustomToolbarButtons(final JToolBar toolbar) {
  				if (model!=null) templatesButton=addButton(toolbar,Language.tr("ParameterCompare.ScriptRunner.Script.Template"),Language.tr("ParameterCompare.ScriptRunner.Script.Template.Hint"),Images.SCRIPT_TEMPLATE.getIcon(),e->commandTemplate(),false);
  				if (ScriptPanel.this.example!=null) addButton(toolbar,Language.tr("ParameterCompare.ScriptRunner.Script.Example"),Language.tr("ParameterCompare.ScriptRunner.Script.Example.Hint"),Images.SCRIPT_EXAMPLE.getIcon(),e->commandLoadExample(),false);
  				if (showRun) addButton(toolbar,Language.tr("ParameterCompare.ScriptRunner.Script.Run"),Language.tr("ParameterCompare.ScriptRunner.Script.Run.Hint"),Images.SCRIPT_RUN.getIcon(),e->commandRun(),false);
  			}
  		};
  		tabs.add(Language.tr("ParameterCompare.ScriptRunner.Tab.Script"),editor);
  
  		tabs.add(Language.tr("ParameterCompare.ScriptRunner.Tab.Output"),tab=new JPanel(new BorderLayout()));
  		tab.add(top=new JPanel(new BorderLayout()),BorderLayout.NORTH);
  		top.add(toolbar=new JToolBar(SwingConstants.HORIZONTAL),BorderLayout.CENTER);
  		toolbar.setFloatable(false);
  		addButton(toolbar,Language.tr("ParameterCompare.ScriptRunner.Results.Copy"),Language.tr("ParameterCompare.ScriptRunner.Results.Copy.Hint"),Images.EDIT_COPY.getIcon(),e->commandResultsCopy(),true);
  		addButton(toolbar,Language.tr("ParameterCompare.ScriptRunner.Results.Save"),Language.tr("ParameterCompare.ScriptRunner.Results.Save.Hint"),Images.GENERAL_SAVE.getIcon(),e->commandResultsSave(),true);
  		addButton(toolbar,Language.tr("JSRunner.Toolbar.Clear"),Language.tr("JSRunner.Toolbar.Clear.Hint"),Images.SCRIPT_CLEAR.getIcon(),e->clearOutput(),true);
  
  		waitIndicator=new JLabel();
  		waitIndicator.setIcon(Images.GENERAL_WAIT_INDICATOR.getIcon());
  		waitIndicator.setVisible(false);
  		top.add(waitIndicator,BorderLayout.EAST);
  		waitIndicator.setBorder(BorderFactory.createEmptyBorder(5,5,0,15));
  
  		tab.add(new JScrollPane(outputArea=new JTextArea()),BorderLayout.CENTER);
  		outputArea.setEditable(false);
  
  		tabs.setIconAt(0,Images.SCRIPT_PANEL_INPUT.getIcon());
  		tabs.setIconAt(1,Images.SCRIPT_PANEL_OUTPUT.getIcon());
  	}
  
  	/**
  	 * Erstellt eine neue Schaltflche und fgt sie zur Symbolleiste hinzu.
  	 * @param toolbar	Symbolleiste auf der die neue Schaltflche eingefgt werden soll
  	 * @param title	Beschriftung der Schaltflche
  	 * @param hint	Tooltip fr die Schaltflche (darf <code>null</code> sein)
  	 * @param icon	Optionales Icon fr die Schaltflche (darf <code>null</code> sein)
  	 * @param listener	Aktion die beim Anklicken der Schaltflche ausgefhrt werden soll
  	 * @param addToList	Soll die Schaltflche auch zu der {@link #buttons}-Liste hinzugefgt werden?
  	 * @return	Neue Schaltflche (ist bereits in die Symbolleiste eingefgt)
  	 * @see #buttons
  	 */
  	private JButton addButton(final JToolBar toolbar, final String title, final String hint, final Icon icon, final ActionListener listener, final boolean addToList) {
  		final JButton button=new JButton(title);
  		button.setToolTipText(hint);
  		if (icon!=null) button.setIcon(icon);
  		button.addActionListener(listener);
  		toolbar.add(button);
  		if (addToList) buttons.add(button);
  		return button;
  	}
  
  	/**
  	 * Stellt ein Skript im Editor ein
  	 * @param mode	Neuer Skriptmodus
  	 * @param script	Neues Skript
  	 */
  	public void setEditorScript(final ScriptMode mode, final String script) {
  		editor.setScript(mode,script);
  	}
  
  	/**
  	 * Stellt ein Skript im Editor ein und versucht dabei die Sprache selbst zu erkennen
  	 * @param script	Neues Skript
  	 */
  	public void setEditorScript(final String script) {
  		editor.setScript(getScriptType(script),script);
  	}
  
  	/**
  	 * Liefert die aktuell fr den Editor gewhlte Skriptsprache
  	 * @return	Aktuelle Skriptsprache
  	 */
  	public ScriptEditorPanel.ScriptMode getEditorMode() {
  		return editor.getMode();
  	}
  
  	/**
  	 * Liefert das aktuelle Skript des Editors
  	 * @return	Aktuelles Skript
  	 */
  	public String getEditorScript() {
  		return editor.getScript();
  	}
  
  	/**
  	 * Liefert ein Statistik-Objekt, aus dem Daten fr den XML-Tag-Auswahl-Dialog ausgelesen werden
  	 * @return	Statistik-Objekt fr XML-Tag-Auswahl-Dialog
  	 */
  	protected abstract Statistics getMiniStatistics();
  
  	/**
  	 * Liefert optional ein Editor-Modell-Objekt, aus dem Daten fr den XML-Tag-Auswahl-Dialog ausgelesen werden.
  	 * Wird hier <code>null</code> geliefert, so wird der entsprechende Menpunkt nicht angezeigt
  	 * @return	Editor-Modell-Objekt fr XML-Tag-Auswahl-Dialog (oder <code>null</code>)
  	 */
  	protected abstract EditModel getModel();
  
  	/**
  	 * Liefert das Help-Runnable, das im XML-Tag-Auswahl-Dialog verwendet werden soll
  	 * @return	Help-Runnable fr XML-Tag-Auswahl-Dialog
  	 */
  	protected abstract Runnable getHelpRunnable();
  
  	/**
  	 * Wird aufgerufen, wenn die Toolbar-Schaltflche zur Skriptausfhrung angeklickt wurde
  	 * @param mode	Skriptsprache
  	 * @param script	Auszufhrendes Skript
  	 * @return	Gibt <code>true</code> zurck, wenn das Skript erfolgreich ausgefhrt werden konnte
  	 */
  	protected boolean run(final ScriptEditorPanel.ScriptMode mode, final String script) {
  		return false;
  	}
  
  	/**
  	 * Aktiviert oder deaktiviert die GUI
  	 * @param enabled	Aktivierungsstatus fr GUI
  	 */
  	public final synchronized void setEnabledGUI(final boolean enabled) {
  		for (JButton button: buttons) button.setEnabled(enabled);
  		editor.setEditable(enabled);
  	}
  
  	/**
  	 * Fgt einen Text an die Ausgabe an
  	 * @param text	Auszugebender Text
  	 */
  	public final synchronized void addOutput(final String text) {
  		outputArea.setText(outputArea.getText()+text);
  	}
  
  	/**
  	 * Wechselt auf die Editor-Seite.
  	 */
  	public void showEditorTab() {
  		tabs.setSelectedIndex(0);
  	}
  
  	/**
  	 * Wechselt auf die Ausgabe-Seite.
  	 */
  	public void showResultsTab() {
  		tabs.setSelectedIndex(1);
  	}
  
  	/**
  	 * Befehl: Vorlage laden
  	 */
  	private void commandTemplate() {
  		final JSModelTemplates templates=new JSModelTemplates(editor.getMode(),model);
  		templates.showMenu(templatesButton,code->{
  			if (!allowDiscard()) return;
  			editor.setScript(editor.getMode(),code);
  		});
  	}
  
  	/**
  	 * Befehl: Beispielskript laden
  	 */
  	private void commandLoadExample() {
  		final ScriptEditorPanel.ScriptMode mode=editor.getMode();
  		final String newScript=example.get(mode);
  		if (newScript==null || newScript.isBlank()) return;
  		if (!editor.allowDiscard()) return;
  		editor.setScript(mode,newScript);
  	}
  
  	/**
  	 * Befehl: Skript ausfhren
  	 */
  	private void commandRun() {
  		outputArea.setText("");
  		showResultsTab();
  		setEnabledGUI(false);
  		waitIndicator.setVisible(true);
  		thread=new Thread(()->{
  			run(editor.getMode(),editor.getScript());
  			setEnabledGUI(true);
  			waitIndicator.setVisible(false);
  			thread=null;
  		});
  		thread.start();
  	}
  
  	/**
  	 * Befehl: Ausgaben in die Zwischenablage kopieren
  	 */
  	private void commandResultsCopy() {
  		final StringSelection stringSelection=new StringSelection(outputArea.getText());
  		final Clipboard clipboard=Toolkit.getDefaultToolkit().getSystemClipboard();
  		clipboard.setContents(stringSelection,null);
  	}
  
  	/**
  	 * Befehl: Ausgaben speichern
  	 */
  	private void commandResultsSave() {
  		final String fileName=ScriptTools.selectTextSaveFile(this,Language.tr("ParameterCompare.ScriptRunner.Results.Save.Title"),null);
  		if (fileName==null) return;
  		final File file=new File(fileName);
  
  		if (file.exists()) {
  			if (!MsgBox.confirmOverwrite(this,file)) return;
  		}
  
  		try {
  			Files.write(file.toPath(),outputArea.getText().getBytes());
  		} catch (IOException e) {
  			MsgBox.error(this,Language.tr("ParameterCompare.ScriptRunner.Results.Save.Error.Title"),String.format(Language.tr("ParameterCompare.ScriptRunner.Results.Save.Error.Info"),file.toString()));
  			return;
  		}
  	}
  
  	/**
  	 * Lscht die Ausgabe
  	 */
  	public final void clearOutput() {
  		outputArea.setText("");
  	}
  
  	/**
  	 * Gibt an, ob das Panel geschlossen werden darf
  	 * (also alles gespeichert ist oder der Nutzer dem Verwerfen zugestimmt hat).
  	 * @return	Gibt <code>true</code> zurck, wenn das Panel geschlossen werden darf
  	 */
  	public boolean allowDiscard() {
  		return editor.allowDiscard();
  	}
  
  	/**
  	 * Versucht auf Basis des Skripttextes zu erkennen, um welche Sprache es sich handelt
  	 * @param script	Zu prfendes Skript
  	 * @return	Sprache des Skriptes
  	 */
  	public static ScriptMode getScriptType(final String script) {
  		ScriptMode mode=ScriptMode.Javascript;
  		if (script!=null) {
  			String s=script.trim().toLowerCase();
  			if (s.startsWith("void")) mode=ScriptMode.Java;
  			if (s.startsWith("public")) mode=ScriptMode.Java;
  			if (s.startsWith("private")) mode=ScriptMode.Java;
  			if (s.startsWith("protected")) mode=ScriptMode.Java;
  		}
  		return mode;
  	}
  }