/**
 * G4blGui.java  -- run G4beamline with a Graphical User Interface.
 *
 * NOTE: this program expects to be run:
 *	java -cp $G4BL_DIR/share/g4beamline G4blGui [input.file]
 * It uses the user classpath to find the G4beamline install directory,
 * expecting the classpath to be the share/g4beamline directory under 
 * $G4BL_DIR.
 *
 * Copyright 2007-2012 by Tom Roberts, all rights reserved.
 * This program is released under the Gnu Public License.
 */

import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.net.URL;

public class G4blGui extends WindowAdapter implements ActionListener, 
							HyperlinkListener {
    // CopyThread will copy a sub-process' stderr/stdout to stderr/stdout.
    static class CopyThread extends Thread {
	BufferedReader in;
	PrintStream out;
	CopyThread(InputStream _in, PrintStream _out) {
		super();
		in = new BufferedReader(new InputStreamReader(_in));
		out = _out;
		start();
	}
	public void run() {
		try {
			String line;
			while( (line=in.readLine()) != null)
				out.println(line);
			in.close();
		} catch(Exception e) { }
	}
    };
    JFrame frame;
    JButton runButton;
    JTextField inputFileField;
    JTextField outputFileField;
    JTextField paramField;
    JTextField otherViewerField;
    JCheckBox historootEventsField;
    JSpinner nViewerEventsField;
    JSpinner nViewerRunsField;
    String viewer;
    JTextArea outputArea;
    boolean doScroll;
    BufferedWriter outputFile;
    JScrollPane helpScroll;
    JScrollPane outputScroll;
    JEditorPane helpPane;
    Process g4beamlineProcess;
    Process wiredProcess;
    JScrollBar scroll;
    int lastScroll;
    boolean aborted;
    String osName;
    String system=".";
    Map<String,String> env;
    File currentDirectory;
    File inputFile;
    String filesep;
    String pathsep;
    String g4bl_dir;
    boolean needToRunWired;
    String g4blProcessID;

    public G4blGui(String[] args) {
	aborted = false;
	filesep = System.getProperty("file.separator");
	pathsep = System.getProperty("path.separator");
	osName = System.getProperty("os.name");
	currentDirectory = new File(System.getProperty("user.dir"));
	String ext = "";
	if(osName.startsWith("Windows")){
		currentDirectory = new File(System.getProperty("user.home"));
		ext = ".exe";
	}
	if(!currentDirectory.exists() || !currentDirectory.isDirectory())
		currentDirectory = new File(System.getProperty("user.home"));
	inputFile = null;
	viewer = "none";
	outputArea = null;
	doScroll = true;
	outputFile = null;
	g4beamlineProcess = null;
	wiredProcess = null;
	lastScroll = 0;
	needToRunWired = false;

	// handle args[] -- change directory to directory containing input.file
	if(args.length == 1) {
		inputFile = new File(args[0]);
		File d = inputFile.getParentFile();
		if(inputFile == null || d == null || !inputFile.exists() ||
		   !d.exists() || !d.isDirectory())
			inputFile = null; // fail silently
		else
			currentDirectory = d;
	}

	// find the installation directory G4BL_DIR
	g4bl_dir = Util.findG4blDir();
	if(g4bl_dir == null) {
		JOptionPane.showMessageDialog(frame, 
				"Cannot determine install directory.\n");
		System.exit(1);
	}
	File dir = new File(g4bl_dir);

	// find SYSTEM
	if(osName.startsWith("Windows"))
		system = "WIN32";
	else if(osName.startsWith("Mac"))
		system = "Darwin";
	else if(osName.startsWith("Linux"))
		system = "Linux";
	System.out.printf("SYSTEM='%s'\n",system);

	// Get the current environmennt into a writable Map<>
	env = new HashMap<String,String>();
	Map<String,String> oldenv = System.getenv();
	Iterator<String> iter = oldenv.keySet().iterator();
	while(iter.hasNext()) {
		String key = iter.next();
		String val = oldenv.get(key);
		env.put(key,val);
	}

	env.put("G4BL_DIR",g4bl_dir);

	// check for G4BEAMLINE set
	if(!env.containsKey("G4BEAMLINE")) {
		env.put("G4BEAMLINE",g4bl_dir + "/bin/g4beamline");
	} else {
		System.out.printf("G4BEAMLINE='%s'\n",env.get("G4BEAMLINE"));
	}

	// set ROOTSYS 
	if(!env.containsKey("ROOTSYS"))
		env.put("ROOTSYS","");

	// Put physics datasets into the environment (not overwriting)
	String geant4_data = Util.findGeant4Data();
	while(geant4_data == null) try {
		int ok = JOptionPane.showConfirmDialog(null,
		   "The Geant4Data are not found; they are required to run G4beamline.\n"+
		   "If you already have them, you can manually copy or link them to\n"+
		   "   "+g4bl_dir+"/Geant4Data\n"+
		   "Otherwise, select Yes to run G4blData to download them,\n"+
		   "or No to exit G4beamline.","Error",JOptionPane.YES_NO_OPTION);
		if(ok == JOptionPane.NO_OPTION)
			System.exit(1);
		// Geant4Data does not exist -- run G4blData
		runG4blData();
		 geant4_data = Util.findGeant4Data();
	} catch(Exception e) { System.out.println(e.toString()); }
	File data = new File(geant4_data);
	String[] list = data.list();
	if(list != null) {
		for(int j=0; j<list.length; ++j) {
			File f = new File(data,list[j]);
			if(!f.exists() || !f.isDirectory()) continue;
			if(list[j].startsWith("G4ABLA")) {
				env.put("G4ABLADATA",f.getAbsolutePath());
			} else if(list[j].startsWith("G4EMLOW")) {
				env.put("G4LEDATA",f.getAbsolutePath());
			} else if(list[j].startsWith("G4NDL")) {
				env.put("G4NEUTRONHPDATA",f.getAbsolutePath());
			} else if(list[j].startsWith("G4NEUTRONXS")) {
				env.put("G4NEUTRONXSDATA",f.getAbsolutePath());
			} else if(list[j].startsWith("G4PII")) {
				env.put("G4PIIDATA",f.getAbsolutePath());
			} else if(list[j].startsWith("PhotonEvaporation")) {
				env.put("G4LEVELGAMMADATA",f.getAbsolutePath());
			} else if(list[j].startsWith("RadioactiveDecay")) {
				env.put("G4RADIOACTIVEDATA",f.getAbsolutePath());
			} else if(list[j].startsWith("RealSurface")) {
				env.put("G4REALSURFACEDATA",f.getAbsolutePath());
			}
		}
	}

        // Create the window.
        frame = new JFrame("G4beamline");
	frame.setIconImage(Toolkit.getDefaultToolkit().getImage(g4bl_dir
					+"/share/g4beamline/g4blicon.png"));
	frame.addWindowListener(this);
	Box header = Box.createVerticalBox();
	// header row 1
	header.add(Box.createVerticalStrut(5));
	Box row = Box.createHorizontalBox();
	row.add(Box.createHorizontalStrut(5));
	row.add(new JLabel("   Input file: "));
	inputFileField = new JTextField(15);
	inputFileField.setEditable(false);
	if(inputFile != null)
		inputFileField.setText(inputFile.getPath());
	row.add(inputFileField);
	row.add(Box.createHorizontalStrut(5));
	JButton browseButton = new JButton("Browse");
	browseButton.addActionListener(this);
	row.add(browseButton);
	row.add(new JLabel("     Output file: "));
	outputFileField = new JTextField(10);
	outputFileField.setText("g4bl.out");
	row.add(outputFileField);
	row.add(Box.createHorizontalStrut(15));
	JButton b = new JButton("Help");
	b.addActionListener(this);
	row.add(b);
	row.add(Box.createHorizontalStrut(15));
	header.add(row);
	// header row 2
	header.add(Box.createVerticalStrut(5));
	row = Box.createHorizontalBox();
	row.add(Box.createRigidArea(new Dimension(5,0)));
	row.add(new JLabel("Parameters: "));
	paramField = new JTextField(40);
	row.add(paramField);
	row.add(Box.createHorizontalStrut(5));
	historootEventsField = new JCheckBox("HistoRoot Events");
	//historootEventsField.addActionListener(this);
	row.add(historootEventsField);
	row.add(Box.createHorizontalStrut(5));
	header.add(row);
	// header row 3
	header.add(Box.createVerticalStrut(5));
	row = Box.createHorizontalBox();
	row.add(Box.createRigidArea(new Dimension(5,0)));
	JPanel viewerPanel = new JPanel();
	viewerPanel.setBackground(new Color(255,255,180)); // pale yellow
	viewerPanel.setOpaque(true);
	viewerPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
	viewerPanel.add(new JLabel(" Viewer: "));
	ButtonGroup group = new ButtonGroup();
	JRadioButton button = new JRadioButton("none",true);
	button.addActionListener(this);
	button.setOpaque(false);
	group.add(button);
	viewerPanel.add(button);
	button = new JRadioButton("best");
	button.addActionListener(this);
	button.setOpaque(false);
	group.add(button);
	viewerPanel.add(button);
	button = new JRadioButton("Other:");
	button.addActionListener(this);
	button.setOpaque(false);
	group.add(button);
	viewerPanel.add(button);
	otherViewerField = new JTextField(10);
	otherViewerField.setMaximumSize(otherViewerField.getPreferredSize());
	viewerPanel.add(otherViewerField);
	viewerPanel.add(new JLabel("  events/run: "));
	nViewerEventsField = new JSpinner(new SpinnerNumberModel(1,1,9999,1));
	nViewerEventsField.setValue(1);
	nViewerEventsField.setMaximumSize(nViewerEventsField.getPreferredSize());
	viewerPanel.add(nViewerEventsField);
	viewerPanel.add(new JLabel("  # runs: "));
	nViewerRunsField = new JSpinner(new SpinnerNumberModel(1,1,99,1));
	nViewerRunsField.setValue(10);
	nViewerRunsField.setMaximumSize(nViewerRunsField.getPreferredSize());
	viewerPanel.add(nViewerRunsField);
	viewerPanel.doLayout(); // fix the size of the viewer panel
	viewerPanel.setMinimumSize(viewerPanel.getPreferredSize());
	viewerPanel.setMaximumSize(viewerPanel.getPreferredSize());
	row.add(viewerPanel);
	row.add(Box.createGlue());
	row.add(new JLabel("    "));
	runButton = new JButton("  Run  "); // spaces so "Abort" will fit
	runButton.addActionListener(this);
        frame.getRootPane().setDefaultButton(runButton);
	row.add(runButton);
	row.add(Box.createHorizontalStrut(15));
	header.add(row);
	header.add(Box.createVerticalStrut(5));

	frame.getContentPane().add(header,BorderLayout.PAGE_START);

	displayHelp();

        // Display the window.
        frame.pack();
        frame.setVisible(true);

	// scroll back to the top of outputArea
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
		scroll.setValue(scroll.getMinimum());
            }
        });
    }

    // all listener actions come here (except Hyperlink)
    public void actionPerformed(ActionEvent event) {
        String eventName = event.getActionCommand();
//System.out.printf("event=%s\n",eventName);
	if(eventName.equals("  Run  ")) {
		aborted = false;
		outputSetup();
		try { 
			if(wiredProcess != null) wiredProcess.destroy();
			wiredProcess = null;
			deleteStaleFiles();
			g4beamlineProcess = runG4beamline();
			if(g4beamlineProcess != null)
				new RunMonitorThread().start();
		} catch(Exception e) {
			JOptionPane.showMessageDialog(frame,
				"Program Failed - Exception\n" + e.toString());
			System.out.printf("Program Failed - Exception: %s\n",
				e.toString());
		}
	} else if(eventName.equals("Abort")) {
		aborted = true;
		runButton.setText("  Run  ");
		doAbort();
	} else if(eventName.equals("Browse")) {
		JFileChooser chooser = new JFileChooser();
		chooser.setCurrentDirectory(currentDirectory);
		if(chooser.showOpenDialog(frame) == 
						JFileChooser.APPROVE_OPTION) {
			inputFile = chooser.getSelectedFile();
			currentDirectory = chooser.getCurrentDirectory();
			inputFileField.setText(inputFile.getPath());
		}
	} else if(eventName.equals("none")) {
		viewer = "none";
	} else if(eventName.equals("best")) {
		viewer = "best";
		if(osName.startsWith("Linux") || osName.startsWith("Mac")) {
			if(System.getenv("DISPLAY") == null)
			JOptionPane.showMessageDialog(frame,
				"X-windows is needed but DISPLAY is not set.");
		}
	} else if(eventName.equals("Other:")) {
		viewer = "Other";
	} else if(eventName.equals("Help")) {
		displayHelp();
	}
    }

    void displayHelp() {
	// HTML JEditorPane for help text (replaced by outputArea when
	// Run is pushed)
	if(helpScroll != null) return;
	try {
		if(outputScroll != null)
			frame.getContentPane().remove(outputScroll);
		outputScroll = null;
		outputArea = null;
		helpPane = new JEditorPane("file:" + g4bl_dir +
				filesep + "share/g4beamline/Help.html");
		helpPane.setEditable(false);
		helpPane.setPreferredSize(new JTextArea(32,60).getPreferredSize());
		helpPane.addHyperlinkListener(this);
		JScrollPane s = new JScrollPane(helpPane);
		helpScroll = s;
		scroll = s.getVerticalScrollBar();
		frame.getContentPane().add(s,BorderLayout.CENTER); 
		frame.validate();
	} catch(Exception e) { 
		System.out.printf("Error: %s\n",e.toString());
	}
    }

    void runG4blData() throws Exception {
	// Use a separate Java VM so we can wait on it.
	String cmd[] = new String[4];
	cmd[0] = "java";
	cmd[1] = "-classpath";
	cmd[2] = g4bl_dir+"/share/g4beamline";
	cmd[3] = "G4blData";
	System.out.println("Running '"+cmd[0]+" "+cmd[1]+" "+cmd[2]+" "+cmd[3]+"'");
	Process proc = Runtime.getRuntime().exec(cmd);
	new CopyThread(proc.getErrorStream(),System.err);
	new CopyThread(proc.getInputStream(),System.out);
	proc.waitFor();
    }

    public void hyperlinkUpdate(HyperlinkEvent e) {
	if(helpPane == null) return;
	URL url = e.getURL();
	if(url == null) return;
	if(e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
		String s = url.toString();
		if(url.getRef() != null) {
			helpPane.scrollToReference(url.getRef());
		} else if(s.endsWith("file:/G4blData")) {
			try { runG4blData(); } catch(Exception ee) { }
		}
    	}
    }

    /** doabort() will abort G4beamline.
     *  seems to work, even though it might be just killing the shell.
     **/
    void doAbort() {
    	if(g4beamlineProcess != null) {
		if(g4blProcessID.length() > 0) {
			try {
				if(Runtime.getRuntime().
				   exec("kill -2 "+g4blProcessID).waitFor()==0)
					Thread.sleep(1000);
			} catch(Exception e) { /* ignore */}
		}
		if(g4beamlineProcess != null) {
			g4beamlineProcess.destroy();
			g4beamlineProcess = null;
			try { Thread.sleep(1000); } catch(Exception e) { }
		}
		scroll.setValue(scroll.getMaximum());
	}
    }

    /** doabort() will abort G4beamline.
     *  Doesn't use Process.destroy() as that might refer to a shell.
     **/
    void doAbortOLD() {
	try { 
		if(osName.startsWith("Windows")) {
			// Some systems don't have taskkill, so try
			// them both, ignoring errors.
			try {
			    Runtime.getRuntime().exec("tskill g4beamline");
			} catch(Exception e1) { /* ignore error */ }
			Runtime.getRuntime().exec("taskkill /f /im g4beamline.exe");
		} else {
			Runtime.getRuntime().exec("killall g4beamline");
		}
	} catch(Exception e) { /* ignore error */ }
    }

    /** user close of the main window comes here
     **/
    public void windowClosing(WindowEvent event) {
    	doAbort();
	if(wiredProcess != null) wiredProcess.destroy();
	System.exit(0);
    }

    /**
     * runG4beamline() -- run the program with its environment set up
     * for portability, do everything of the g4bl script, here in Java.
     **/
    Process runG4beamline() throws java.io.IOException {
	// verify input.file has been selected, and its directory
    	if(currentDirectory == null || !currentDirectory.isDirectory() ||
	   inputFile == null || !inputFile.exists()) {
		JOptionPane.showMessageDialog(frame,
				"No input file selected.\n" +
				"Use the Browse button to select one.");
		return null;
	}

	// display data sets to the user, and verify their presence
	int nData=0;
	if(env.containsKey("G4ABLADATA")) {
		++nData;
		System.out.printf("G4ABLADATA='%s'\n",env.get("G4ABLADATA"));
	}
	if(env.containsKey("G4LEDATA")) {
		++nData;
		System.out.printf("G4LEDATA='%s'\n",env.get("G4LEDATA"));
	}
	if(env.containsKey("G4NEUTRONHPDATA")) {
		++nData;
		System.out.printf("G4NEUTRONHPDATA='%s'\n",env.get("G4NEUTRONHPDATA"));
	}
	if(env.containsKey("G4NEUTRONXSDATA")) {
		++nData;
		System.out.printf("G4NEUTRONXSDATA='%s'\n",env.get("G4NEUTRONXSDATA"));
	}
	if(env.containsKey("G4PIIDATA")) {
		++nData;
		System.out.printf("G4PIIDATA='%s'\n",env.get("G4PIIDATA"));
	}
	if(env.containsKey("G4LEVELGAMMADATA")) {
		++nData;
		System.out.printf("G4LEVELGAMMADATA='%s'\n",env.get("G4LEVELGAMMADATA"));
	}
	if(env.containsKey("G4RADIOACTIVEDATA")) {
		++nData;
		System.out.printf("G4RADIOACTIVEDATA='%s'\n",env.get("G4RADIOACTIVEDATA"));
	}
	if(env.containsKey("G4REALSURFACEDATA")) {
		++nData;
		System.out.printf("G4REALSURFACEDATA='%s'\n",env.get("G4REALSURFACEDATA"));
	}
	if(nData == 0) {
		JOptionPane.showMessageDialog(frame, "Geant4 data files not found");
		return null;
	}

	// get library search paths (different for each OS)
	if(system.equals("Linux")) {
		String ld_library_path = env.get("LD_LIBRARY_PATH");
		if(ld_library_path == null) ld_library_path = "";
		ld_library_path = g4bl_dir + "/lib" + pathsep + ld_library_path;
		env.put("LD_LIBRARY_PATH",ld_library_path);
		System.out.printf("LD_LIBRARY_PATH=%s\n",ld_library_path);
	} else if(system.equals("Darwin")) {
		String dyld_library_path = env.get("DYLD_LIBRARY_PATH");
		if(dyld_library_path == null) dyld_library_path = "";
		dyld_library_path = g4bl_dir + "/lib" + pathsep + dyld_library_path;
		env.put("DYLD_LIBRARY_PATH",dyld_library_path);
		System.out.printf("DYLD_LIBRARY_PATH=%s\n",dyld_library_path);
	} else if(system.equals("WIN32")) {
		String path = env.get("PATH");
		if(path == null) path = env.get("Path");
		if(path == null) path = "";
		path += pathsep + g4bl_dir + "/bin";
		env.put("PATH",path);
		System.out.printf("PATH=%s\n",path);
	}

	// get the updated environment into an array for Runtime.exec()
	String[] envarray = new String[env.size()];
	Iterator<String> iter = env.keySet().iterator();
	int i = 0;
	while(iter.hasNext()) {
		String key = iter.next();
		String val = env.get(key);
		envarray[i++] = key + "=" + val;
	}

	// get viewer, determine actual viewer
	String v = viewer;
	if(v.equals("Other")) v = otherViewerField.getText();
	if(v.startsWith("Wired")) v = "Wired";
	if(v.startsWith("wired")) v = "Wired";
	if(v.equals("best")) {
		if(osName.startsWith("Windows"))
			v = "OIWin32";
		else
			v = "OIX";
	}
	needToRunWired = v.equals("Wired");
//System.out.printf("v=%s viewer=%s\n",v,viewer);

	// execute command, permitting spaces in g4bl_dir and inputFile
	String cmd = "/path/to/g4beamline /path/to/input.file viewer=" + v +
		" " + paramField.getText() + " "; // (ends with ' ')
	Vector<String> cmdv = new Vector<String>();
	StringBuilder sb = new StringBuilder();
	for(int pos=0; pos<cmd.length(); ++pos) {
		char c = cmd.charAt(pos);
		if(c == ' ') {
			if(sb.length() > 0) {
				cmdv.add(sb.toString());
				sb.setLength(0);
			}
			continue;
		}
		if(c == '"' || c == '\'') { // quotes themselves are omitted
			++pos;
			while(pos < cmd.length() && cmd.charAt(pos) != c)
				sb.append(cmd.charAt(pos++));
			continue;
		}
		sb.append(c);
	}
	if(historootEventsField.isSelected()) {
		String d = inputFile.getParent();
		if(d == null) d = ".";
		cmdv.add("eventcuts file='" + d + "/histo_event.txt'");
	}
	cmdv.set(0,env.get("G4BEAMLINE"));
	cmdv.set(1,inputFile.getAbsolutePath());
	String[] cmdarray = (String [])cmdv.toArray(new String[0]);
	System.out.printf("exec prog:%s\n",cmdarray[0]);
	for(i=1; i<cmdarray.length; ++i)
		System.out.printf("     arg%d:%s\n",i,cmdarray[i]);
	Process process = Runtime.getRuntime().exec(cmdarray,envarray,
							currentDirectory);

	if(process == null)
		JOptionPane.showMessageDialog(frame,"Program failed.\n" +
			"exec returned null");
	return process;
    }

    /**
     * runWired() -- run the Wired program 
     **/
    Process runWired() throws java.io.IOException {
	Process process=null;
	String[] cmdarray=null;
	if(osName.startsWith("Windows")) {
		// Windows cmd is very finicky -- call just hangs, but
		// this cumbersome workaround using start seems to work.
		// NOTE: assumes Wired is installed into C:\Wired
		cmdarray = new String[8];
		cmdarray[0] = "cmd";
		cmdarray[1] = "/C";
		cmdarray[2] = "start";
		cmdarray[3] = "/B";
		cmdarray[4] = "/DC:\\Wired\\bin";
		cmdarray[5] = "wired.bat";
	} else {
		cmdarray = new String[3];
		cmdarray[0] = g4bl_dir + "/Wired/bin/wired";
	}
	cmdarray[cmdarray.length-2] = "-file";
	cmdarray[cmdarray.length-1] = inputFile.getParent() + "/G4Data0.heprep";
	try {
System.out.printf("runWired");
for(int i=0; i<cmdarray.length; ++i) System.out.printf(" %s",cmdarray[i]);
System.out.printf("\n");
		process = Runtime.getRuntime().exec(cmdarray);
		if(process == null) System.out.printf("runWired NULL\n");
	} catch(java.io.IOException e) {
		System.out.printf("runWired exception %s\n",e.toString());
		throw e;
	}
	return process;
    }


    /** deleteStaleFiles() will delete *.heprep in the directory
     **/
    void deleteStaleFiles() {
	if(inputFile == null || !viewer.equals("none")) return;
    	File dir = inputFile.getParentFile();
	File[] list = dir.listFiles();
	if(list != null) {
		for(int i=0; i<list.length; ++i) {
			if(list[i].getPath().endsWith(".heprep"))
				list[i].delete();
		}
	}
    }

    /** outputSetup() will setup outputFile and outputArea.
     **/
    void outputSetup() {
	if(outputArea == null) {
		if(helpScroll != null)
			frame.getContentPane().remove(helpScroll);
		helpScroll = null;
		helpPane = null;
		outputArea = new JTextArea();
		outputArea.setEditable(false);
		outputArea.setFont(new Font("Monospaced",Font.PLAIN,12));
		outputScroll = new JScrollPane(outputArea);
		scroll = outputScroll.getVerticalScrollBar();
		frame.getContentPane().add(outputScroll,
					BorderLayout.CENTER); 
		frame.validate();
	}
	outputArea.setText("");
	doScroll = true;
	if(outputFile != null)
		try { outputFile.close(); } catch(Exception e) { }
	try { outputFile = new BufferedWriter(new FileWriter(
		new File(currentDirectory,outputFileField.getText())));
	} catch(Exception e) {
		JOptionPane.showMessageDialog(frame,
			"Cannot write Output File!");
    	}
    }

    /** outputClose() will close outputFile 
     **/
    void outputClose() {
	try { outputFile.close(); } catch(Exception e) { }
	outputFile = null;
    }

    /** outputLine() will output 1 line to the output file and outputArea.
     **/
    synchronized void outputLine(String line) {
	doScroll = (scroll.getValue() >=
	     scroll.getMaximum()-scroll.getVisibleAmount()-64 ||
	     scroll.getValue() >= lastScroll ||
	     line.startsWith("g4beamline: simulation aborted"));
	try {
		outputFile.write(line + "\n");
	} catch(Exception e) { }
	int nLines = outputArea.getLineCount();
	if(nLines > 20000) try { // when huge, just keep last lines
		outputArea.replaceRange(
		   "-- DELETED (full output kept in output file) --\n",
		   outputArea.getLineStartOffset(0),
		   outputArea.getLineStartOffset(nLines-15000));
	} catch(Exception e) { }
	outputArea.append(line + '\n');
	if(doScroll) {
	    scroll.setValue(scroll.getMaximum());
	    lastScroll = scroll.getValue();
	}
    }

	/** internal class RunMonitorThread copies G4beamline stdout to 
	 *  outputLine() and handles the finalization of output stuff when
	 *  G4beamline exits. This is the place where we know that gbeamline
	 *  has exited, so some rather surprising stuff is here (e.g. 
	 *  runButton, other threads, ....).
	 *  (readline() will return null when G4beamline exits.)
	 **/
	class RunMonitorThread extends Thread {
	    public RunMonitorThread() { }
            public void run() {
		if(g4beamlineProcess == null) return;
		// outputSetup() already called.
		runButton.setText("Abort");
		try {
			StderrThread stderrThread = new StderrThread();
			stderrThread.start();
        		StdinThread stdinThread = new StdinThread();
			stdinThread.start();
			g4blProcessID = "";
			try {
				BufferedReader in = new BufferedReader(
					new InputStreamReader(
					g4beamlineProcess.getInputStream()));
				String line;
				while((line=in.readLine()) != null) {
				    if(g4blProcessID.length() == 0 &&
				       line.startsWith("G4beamline Process ID"))
					   g4blProcessID = line.split(" ")[3];
				  outputLine(line);
				}
				in.close();
			} catch(Exception e) {
				System.err.println(e);
				outputArea.append(e.toString() + 
		    		    "\nProgram output is probably lost.\n\n");
			}
			sleep(100); // 100 ms to give them a chance to finish
			stdinThread.interrupt();
			stderrThread.interrupt();
		} catch(Exception e) {
			System.err.println(e);
			outputArea.append(e.toString() + 
		    		"\nProgram output is probably lost.\n\n");
		} finally {
			try {
		    	    if(g4beamlineProcess.waitFor() != 0 || aborted) {
				needToRunWired = false;
				if(!aborted)
			  		JOptionPane.showMessageDialog(frame,
							"Program Failed");
		    	    }
		    	    if(aborted)
				outputArea.append("\n*** User Abort ***\n\n");
			} catch(Exception e) { /* ignore exception */ }
		}
		if(g4beamlineProcess != null) g4beamlineProcess.destroy();
		g4beamlineProcess = null;
		scroll.setValue(scroll.getMaximum());
		outputClose();
		if(needToRunWired)  {
			outputArea.append("\n... Running the Wired3 event viewer ...\n");
			// jump to bottom of outputArea for this message
			scroll.setValue(scroll.getMaximum());
			try { wiredProcess=runWired(); } catch(Exception e) { }
		}
		outputArea.append("\n\n\n");
		if(doScroll) {
			try { sleep(100); } catch(Exception e) { }
			scroll.setValue(scroll.getMaximum());
		}
		runButton.setText("  Run  ");
	     }
        }

	/** internal class StderrThread copies G4beamline stderr to outputLine()
	 *  (readline() will return null when G4beamline exits.)
	 **/
	class StderrThread extends Thread {
	    public StderrThread() { }
            public void run() {
		if(g4beamlineProcess == null) return;
		BufferedReader in=null;
		try {
		  in = new BufferedReader(
		    new InputStreamReader(g4beamlineProcess.getErrorStream()));
		  String line;
		  while((line=in.readLine()) != null) {
			outputLine(line);
		  }
		} catch(Exception e) {
		    System.err.println(e);
		    outputArea.append(e.toString() + 
		    		"\nProgram output is probably lost.\n\n");
		}
		try { if(in != null) in.close(); } catch(Exception e) { }
            }
        };

	/** internal class StdinThread writes "/run/beamOn N" to stdin.
	 **/
	class StdinThread extends Thread {
	    public StdinThread() { }
            public void run() {
		if(g4beamlineProcess == null) return;
		BufferedWriter stdin=null;
    		try {
		    int nViewerEvents = (Integer)nViewerEventsField.getValue();
		    int nRuns = (Integer)nViewerRunsField.getValue();
		    stdin = new BufferedWriter(
				new OutputStreamWriter(
					g4beamlineProcess.getOutputStream()));
		    for(int i=0; i<nRuns; ++i) {
			stdin.write("/run/beamOn "+nViewerEvents);
			stdin.newLine();
		    }
		    stdin.write("exit");
		    stdin.newLine();
		} catch(Exception e) { 
		    System.err.println("Error in StdinThread: " + e + "\n");
		}
		try { if(stdin != null) stdin.close(); } catch(Exception e) { }
            }
        }


    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     **/
    private static void createAndShowGUI(String[] args) {
        // Use window system decorations. Uncomment for LookAndFeel
        //JFrame.setDefaultLookAndFeelDecorated(true);

        new G4blGui(args);
    }

    /**
     * main program -- create and display GUI
     **/
    static String[] mainArgs;
    public static void main(String[] args) {
	mainArgs = args;

	System.out.printf("This java console window gets error messages and"
		+" status information.\nIt can usually be ignored.\n");

        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI(mainArgs);
            }
        });
    }
}