/*
 * Copyright (c) 2005, Stanford University. All rights reserved. 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions
 * are met: 
 *  - Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer. 
 *  - Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the distribution. 
 *  - Neither the name of the Stanford University nor the names of its 
 *    contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission. 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE. 
 */

/*
 * Created on Sep 13, 2005
 * Original author: Christopher Bruns
 */
package org.simtk.isimsu;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

import java.util.regex.*;
import java.text.DecimalFormat;

import javax.jnlp.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;

import org.jdom.*;

import org.simtk.pdb.*;
import org.simtk.molecularstructure.*;
import org.simtk.mvc.SimpleObservable;
import org.simtk.gui.*;
import edu.stanford.ejalbert.BrowserLauncher;

public class ISIMWrapper 
extends JFrame 
implements SaltSelectionListener
{
    protected IonSelectionDialog ionDialog;
    protected IsimParameterDialog isimParameterDialog;
    
    public String programName = "SimTK ISIM";
    public String interfaceName = programName + " Interface";
    public String isimInterfaceVersion = "0.9.9";
    
    public static final long serialVersionUID = 01L;
    LoadMoleculeAction loadMoleculeAction;
    // String pqrFileName;
    
    APBSParameters apbsParameters = null;
    ISIMParameters isimParameters = null;
    
    File parentWorkingDirectory; // parent directory of the next two
    File programDirectory;
    File workingDirectory;
    
    // if true, then results from previous run copied to working directory and
    // flag set; idea is to allow testing of program w/o waiting for 
    // apbs/isim calculations to run
    
    int isTestRun;
    
    // File pqrStructureFile;
    SaltRangeSet availableIonConditionRanges = new SaltRangeSet();
    // java.util.List availableIonConditions = new SaltRangeSet();
    
    LogDialog logDialog;
    
    public Image isimIcon = new ImageIcon(getClass().getClassLoader().getResource("resources/images/isim_logo1_small.png")).getImage();
    
    ISIMTask 
        loadMacromoleculeTask, 
        loadIonTask, 
        adjustAPBSParametersTask, 
        // computeMacromoleculePotentialTask,
        computeCombinedPotentialTask,
        setIsimParameterTask,
        apbsAndIsimTask, 
        // inspectParametersTask,
        // computeGCMCPotentialTask,
        viewIonCountsTask
        ;
    
    ISIMStepRow chooseMoleculeRow;
    ISIMStepRow apbsSimulateRow = null;
    ISIMStepRow isimCombinedRow,
                viewIonCountsRow;
     ISIMStepRowWithProgressFile  isimSimulateRow;
    
    ISIMWrapper() { // Constructor
        super();

        setTitle(interfaceName);
        
        // Center of screen is better than the upper left corner
        setLocationRelativeTo(null);
        
        // Use a custom icon
        this.setIconImage(isimIcon);

        // Create a window for log messages
        logDialog = new LogDialog(this);
        logDialog.append("Progress log (" + programName + ")\n");
        logDialog.append("Started " + new Date() + "\n");
        logDialog.append("\n");

        // loadIonConditions("resources/ion_conditions/test_ion_conditions2.xml");
        // loadIonConditions("resources/ion_conditions/NaMgCl.xml");
        // loadIonConditions("resources/ion_conditions/marke_NaMgCl.xml");
        loadIonConditions("resources/ion_conditions/MgNaCl_basic_marke.xml");
        // loadIonConditions("resources/ion_conditions/CaCl.xml");

        // Create ionDialog after ion ranges are loaded, but before
        // ChooseIonsAction is created (in createStepRows())
        ionDialog = new IonSelectionDialog(this, availableIonConditionRanges);
        ionDialog.addSaltSelectionListener(this);

        
        // Establish task dependencies
        createTasks();
        
        // Use first ion condition to start with
        // SaltCondition initialSaltCondition = (SaltCondition) availableIonConditions.get(0);

        // apbsParameters = new APBSParameters(2.00, null, null, loadIonTask, initialSaltCondition);
        apbsParameters = new APBSParameters();
        apbsParameters.setDependentTasks(loadIonTask, adjustAPBSParametersTask, loadMacromoleculeTask);
        apbsParameters.setMoleculePermittivity(2.0);
        apbsParameters.setIons(null);

        // dime options are 65, 97, 129, and 161 (for nlev = 4)
        apbsParameters.setDimension(97, 97, 97);
        
        // Isim Parameters
        
        isimParameters      = new ISIMParameters();
        isimParameterDialog = new IsimParameterDialog( apbsParameters, isimParameters, this, logDialog );

        // Create one structure loading action object, for both button and menu activation
        loadMoleculeAction = new LoadMoleculeAction(this);

        isTestRun = 0;
        
        // Make close button well... close.
        // Revert to Frame behavior, w/ actionListener etc.
        // Disable JFrame handling of close event
        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        // Add old way of handling close event
        addWindowListener
        (new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                cleanUpAndExit();
               }
            }
        );
        
        createMenuBar();
        
        createStepRows();
        
        createDirectories();
        
        apbsParameters.setMacroionPotentialFile(new File(workingDirectory, "macroionPotential.dx"));

//        SaltCondition noIonsCondition = new SaltCondition();
//        noIonsCondition.setName("(no ions)");
//        availableIonConditions.add(noIonsCondition);
//        apbsParameters.setIons(noIonsCondition);        
                
        pack();

    }
    
    /**
     * @param args
     */
    public static void main(String[] args) {
        // Try to get Windows look and feel on Windows platform
        // Unless a non-Java look and feel is already selected
        if (UIManager.getLookAndFeel().getClass().getName().equals(UIManager.getCrossPlatformLookAndFeelClassName()))
            try {
                UIManager.setLookAndFeel(
                    UIManager.getSystemLookAndFeelClassName() );
            } catch (Exception e) {}

        // Poorly documented new way to make sure that the GUI is created in the Event thread
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                //
                // Build and start the GUI here.
                //
                ISIMWrapper isimFrame = new ISIMWrapper();
                isimFrame.setVisible(true);
                // isimFrame.showLicenseDialog();
            }
        }); 

    }

    public void saltSelected(SaltSelectionEvent event) {
        SaltCondition condition = event.getSaltCondition();

        // Use this as the simulation condition and declare step complete
        apbsParameters.setIons(condition);
        
        System.out.println("Salt selected");
    }
    
    // set flag indicating test run is active and copy results from
    // results directory to working directory
    
    public void setIsTestRun( int inputIsTestRun, String resultsDirectory, String osName ) {
    	
    	isTestRun = inputIsTestRun;
        if( (isTestRun > 0) && (resultsDirectory != null) ){
        	
           // issue command to copy files from directory containing earliers results
           // to working directory
        	
           String[] cpCommand = new String[4];
           if( osName.compareTo( "Windows" ) == 0 ){
              cpCommand[0]       = "C:\\apps\\cygwin\\bin\\cp";
           } else {
        	  cpCommand[0]       = "/bin/cp";
           }
           cpCommand[1]       = "-R";
           cpCommand[2]       = resultsDirectory;
           cpCommand[3]       = workingDirectory.toString();
           
           logDialog.append( "Test run -- copying results from \n" + resultsDirectory + 
        		             " to working directory \n" + workingDirectory + 
        		             " using command=<" +
        		             cpCommand[0] + " " + cpCommand[1] + ">\n\n" );
           
           
           ExternalProcessManager cpProcess = new ExternalProcessManager( cpCommand, workingDirectory, null );
           cpProcess.start();
           try { // wait for it to finish
        	    cpProcess.join();
        	    
                if (cpProcess.exception == null) {
                	logDialog.append("\n\nCopied results from " + cpCommand[2] + " to " + cpCommand[3] + "\n\n");
                	return;
               } else {
                   // FAILURE - directory not copied
            	   logDialog.append("Results directory not copied: " + cpProcess.exception.toString()  + "\n\n" );
               }    
           } catch (InterruptedException exc) {
        	   logDialog.append("InterruptedException -- Results directory not copied: " + exc.toString()  + "\n\n" );
           }

           // if this point is reached, the copy failed; if testRun set to 0, then 
           // APBS and ISIM will be run -- on windows, just copy 'by hand' so want testRun flag
           // to be set to nonzero value (copy done before the view results button is activated)
           
          // isTestRun = 0;
           
           return;

        }
    }

    public int isTestRun( ) {    	
    	return isTestRun;
    }

    // Load precomputed excess chemical potential data for ion conditions
    private void loadIonConditions(String resourceString) {
        logDialog.append("Loading ion conditions...\n");

        ClassLoader classLoader = getClass().getClassLoader();
        URL ionXMLURL = classLoader.getResource(resourceString);
        if (ionXMLURL == null) {
            JOptionPane.showMessageDialog(this, "Unable to load ion conditions resource XML file " + resourceString);
            return;
        }

        try {
            availableIonConditionRanges.addRanges(ionXMLURL);
        } 
        catch (JDOMException exc) {
            exc.printStackTrace();
        }
        catch (IOException exc) {
            exc.printStackTrace();
            JOptionPane.showMessageDialog(this, "Problem loading ion conditions resource XML file " + resourceString);
        }
        
        logDialog.append("Ion conditions loaded.\n");
    }
    
    // get logDialog
    
    LogDialog getLogDialog( ){
    	return logDialog;
    }
    
    /**
     * This method encodes the task dependencies of the kind that might
     * be specified in a makefile.
     *
     */
    void createTasks() {
        loadMacromoleculeTask          = new ISIMTask();
        loadIonTask                    = new ISIMTask();
        adjustAPBSParametersTask       = new ISIMTask();
        apbsAndIsimTask                = new ISIMTask();
        setIsimParameterTask           = new ISIMTask();
        computeCombinedPotentialTask   = new ISIMTask();
        viewIonCountsTask              = new ISIMTask();
        
        // We need a macromolecule before we compute the macromolecule potential
        apbsAndIsimTask.addPrerequisite(loadMacromoleculeTask);
        // computeMacromoleculePotentialTask.addPrerequisite(inspectParametersTask);
        // Since temperature and permittivity are now part of the ion dialog, APBS depends upon ion step
        apbsAndIsimTask.addPrerequisite(adjustAPBSParametersTask);
        // and upon the load Ion task
        apbsAndIsimTask.addPrerequisite(loadIonTask);

        // We need a macromolecule before we compute the macromolecule potential
        // computeMacromoleculePotentialTask.addPrerequisite(loadMacromoleculeTask);
        // computeMacromoleculePotentialTask.addPrerequisite(inspectParametersTask);
        // Since temperature and permittivity are now part of the ion dialog, APBS depends upon ion step
        // computeMacromoleculePotentialTask.addPrerequisite(adjustAPBSParametersTask);
        // and upon the load Ion task
        // computeMacromoleculePotentialTask.addPrerequisite(loadIonTask);

        // The second potential calculation requires both macromolecule and ions
        computeCombinedPotentialTask.addPrerequisite(loadMacromoleculeTask);
        computeCombinedPotentialTask.addPrerequisite(adjustAPBSParametersTask);
        computeCombinedPotentialTask.addPrerequisite(loadIonTask);
        
        // ISIM requires the ions, macromolecule, and the first macromolecule potential
        // computeGCMCPotentialTask.addPrerequisite(loadMacromoleculeTask);
        // computeGCMCPotentialTask.addPrerequisite(adjustAPBSParametersTask);
        // computeGCMCPotentialTask.addPrerequisite(loadIonTask);
        // computeGCMCPotentialTask.addPrerequisite(computeMacromoleculePotentialTask);
        
        // viewIonCountsTask.addPrerequisite(computeGCMCPotentialTask);
        viewIonCountsTask.addPrerequisite(apbsAndIsimTask);
        
        // Some tasks are good enough at the start, others need to be performed...
        loadIonTask.setStatus(DependentTask.READY_FOR_UPDATE, "(no ions)"); // Lack of ions is a reasonable state...
        adjustAPBSParametersTask.setStatus(DependentTask.CURRENT_AND_COMPLETE, "");
        setIsimParameterTask.setStatus(DependentTask.CURRENT_AND_COMPLETE, "Optional");
        loadMacromoleculeTask.setStatus(DependentTask.READY_FOR_UPDATE, "(no molecule loaded)");
        // computeMacromoleculePotentialTask.setStatus(DependentTask.WAITING_FOR_PREREQUISITES, "");
        computeCombinedPotentialTask.setStatus(DependentTask.WAITING_FOR_PREREQUISITES, "");
                
        // computeGCMCPotentialTask.setStatus(DependentTask.WAITING_FOR_PREREQUISITES, "");
        apbsAndIsimTask.setStatus(DependentTask.WAITING_FOR_PREREQUISITES, "");
        viewIonCountsTask.setStatus(DependentTask.WAITING_FOR_PREREQUISITES, "");
        
        
    }

    void createDirectories() {
        // Create a top level directory
        String topDirectoryName = "ISIM";
        try {
            File tempDir = File.createTempFile(topDirectoryName, "");
            
            // Turn the normal temp file into a directory
            if (!tempDir.delete())
                throw new IOException("Unable to replace temporary file " + tempDir);
            if (!tempDir.mkdir())
                throw new IOException("Unable to create temporary directory " + tempDir);
            
            parentWorkingDirectory = tempDir;
            workingDirectory = new File(tempDir, "working");
            programDirectory = workingDirectory;
            
            if (! programDirectory.exists())
                if (! programDirectory.mkdir()) throw new IOException("Unable to create program directory " + programDirectory);
            if (! workingDirectory.exists())
                if (! workingDirectory.mkdir()) throw new IOException("Unable to create working directory " + workingDirectory);
        }
        catch (IOException exc) {
            exc.printStackTrace();
            System.exit(1);
        }

    }
    
    void createMenuBar() {
        // Menus
        JMenuBar menuBar = new JMenuBar();
        
        JMenu menu = new JMenu("File");
        menuBar.add(menu);
        
        JMenuItem menuItem;

        // menuItem = new JMenuItem("Load PQR Structure File...");
        // menuItem.addActionListener(loadMoleculeAction);
        // menu.add(menuItem);
        
        menuItem = new JMenuItem("About " + interfaceName + "...");
        menuItem.addActionListener(new AboutAction(this));
        menu.add(menuItem);
        
        menu.add(new JSeparator());

        menuItem = new JMenuItem("Save Results");
        // menuItem.addActionListener(new SaveAction(this));
        menu.add(menuItem);
       
        menuItem = new JMenuItem("Quit");
        menuItem.addActionListener(new QuitAction(this));
        menu.add(menuItem);
        
        menu = new JMenu("View");
        menuBar.add(menu);
        
        menuItem = new JMenuItem("View Log");
        menuItem.addActionListener(new ViewLogAction(this));
        menu.add(menuItem);
        
        menu = new JMenu("Help");
        try {
            menuBar.setHelpMenu(menu); // Not yet implemented error!?!?
        } catch (java.lang.Error error) {
            // setHelpMenu seems to be "not yet implemented in java 1.4.2_06
            menuBar.add(menu);
        }
        
        menuItem = new JMenuItem("About " + interfaceName + "...");
        menuItem.addActionListener(new AboutAction(this));
        menu.add(menuItem);
        
        menu.add(new JSeparator());
        menuItem = new JMenuItem("Web Links:");
        menuItem.setEnabled(false);
        menu.add(menuItem);
        
        menuItem = new JMenuItem("  Report a program problem (bug)...");
        menuItem.addActionListener(new BrowserLaunchAction
                ("https://simtk.org/tracker/?func=add&atid=310&group_id=85"));
        menu.add(menuItem);

        menuItem = new JMenuItem("  Request a new program feature...");
        menuItem.addActionListener(new BrowserLaunchAction
                ("https://simtk.org/tracker/?func=add&atid=311&group_id=85"));
        menu.add(menuItem);


        setJMenuBar(menuBar);        
    }

    void createStepRows() {
        // One white container for all step rows
        JPanel stepsPanel = new JPanel();
        stepsPanel.setBackground(Color.WHITE);

        GridBagLayout layout = new GridBagLayout();
        stepsPanel.setLayout(layout);
        // Small border around outer edge for better white space feel
        stepsPanel.setBorder(new EmptyBorder(10,10,10,10));
        
        // Put top row of header information
        ISIMStepRow headerRow = new ISIMStepRow(null, "");
        headerRow.setLabels("", "", "Press buttons to perform tasks", "Status");
        headerRow.insertButtonRow(stepsPanel, layout, ISIMStepRow.HEADER_ROW);
        
        // Create "choose molecule" row
        chooseMoleculeRow = new ISIMStepRow(loadMacromoleculeTask, "Choose Molecule...");
        // chooseMoleculeRow.setLabels("", "(no molecule loaded)", "Choose Molecule...", "No");
        chooseMoleculeRow.button.addActionListener(loadMoleculeAction);
        chooseMoleculeRow.button.setToolTipText("Click this button to begin choosing a macromolecule structure.");
        chooseMoleculeRow.insertButtonRow(stepsPanel, layout, ISIMStepRow.BUTTON_ROW);
        chooseMoleculeRow.updateRow();
                
//        // Create "inspect parameters" row
//        ISIMStepRow paramsRow = new ISIMStepRow(inspectParametersTask, "Inspect parameters...");
//        // paramsRow.setLabels("Temperature, Permittivity, etc. ", "", "Inspect parameters...", "Yes");
//        // paramsRow.insertWithButton(stepsPanel, layout);
//        paramsRow.button.addActionListener(new InspectParametersAction(apbsParameters, this));
//        paramsRow.updateRow();
//
        ClassLoader classLoader = getClass().getClassLoader();
        Icon computerIcon = new ImageIcon(classLoader.getResource("resources/images/ComputerIcon.png"));

        // Create "choose ions" row
        ISIMStepRow ionsRow = new ISIMStepRow(loadIonTask, "Choose ion conditions...");
        // ionsRow.setLabels("", "(no ions selected)", "Choose ion conditions...", "Yes");
        ionsRow.insertButtonRow(stepsPanel, layout, ISIMStepRow.BUTTON_ROW);
        ionsRow.button.addActionListener(new ChooseIonsAction(ionDialog));
        ionsRow.button.setToolTipText("Click this button to begin choosing dissolved ions.");
        ionsRow.updateRow();

//      Create "Set simulation parameters" row
        ISIMStepRow simulationParameters = new ISIMStepRow( setIsimParameterTask, "Set Simulation Parameters");
        // ionsRow.setLabels("", "(no ions selected)", "Choose ion conditions...", "Yes");
        simulationParameters.insertButtonRow( stepsPanel, layout, ISIMStepRow.BUTTON_ROW );
        simulationParameters.button.addActionListener(new SetParameterAction( isimParameterDialog) );
        simulationParameters.button.setToolTipText("Set Simulation parameters.");
        simulationParameters.updateRow();

        // Create "compute Poisson-Boltzman potential" row
        // apbsSimulateRow = new ISIMStepRow(apbsAndIsimTask, "Simulate Ions Apbs" );
        // apbsSimulateRow.insertButtonRow(stepsPanel, layout, ISIMStepRow.BUTTON_ROW);
        // apbsSimulateRow.button.addActionListener(new SimulateIons(this));
        // apbsSimulateRow.button.setIcon(computerIcon);
        // apbsSimulateRow.button.setToolTipText("Solve Poisson's Equation (APBS).\n WARNING: This might take several minutes.");
        // apbsSimulateRow.updateRow();

        // Create "compute Poisson-Boltzman potential" row
        isimSimulateRow = new ISIMStepRowWithProgressFile(apbsAndIsimTask, "Simulate Ions Isim", this );
        isimSimulateRow.insertButtonRow(stepsPanel, layout, ISIMStepRow.BUTTON_ROW);
        isimSimulateRow.button.addActionListener(new SimulateIons(this));
        isimSimulateRow.button.setIcon(computerIcon);
        isimSimulateRow.button.setToolTipText("Perform GCMC simulation (ISIM).\n WARNING: This might take several minutes.");
        isimSimulateRow.updateRow();

        // Create "compute Poisson-Boltzman potential" row
        // apbsRow = new ISIMStepRow(computeMacromoleculePotentialTask, "Compute macromolecule field (without ions)");
        // apbsRow.insertButtonRow(stepsPanel, layout, ISIMStepRow.BUTTON_ROW);
        // apbsRow.button.addActionListener(new ComputeMoleculeAPBS(this));
        // apbsRow.button.setIcon(computerIcon);
        // apbsRow.button.setToolTipText("Compute nonlinear Poisson-Boltzmann potential (APBS).\nWARNING: This might take a few minutes.");
        // apbsRow.updateRow();

        // Create "compute Poisson-Boltzman potential" row
        //ISIMStepRow apbsCombinedRow = new ISIMStepRow(computeCombinedPotentialTask, "Compute combined field");
        // apbsCombinedRow.setLabels("Poisson-Boltzmann Potential (APBS)", "", "Compute combined field", "No");
        // apbsCombinedRow.insertWithButton(stepsPanel, layout);
        // apbsCombinedRow.button.addActionListener(new ComputeMoleculeAPBS(this));
        // apbsCombinedRow.button.setIcon(computerIcon);
        // apbsCombinedRow.updateRow();

        // Create "compute GCMC potential" row
        // isimCombinedRow = new ISIMStepRow(computeGCMCPotentialTask, "Compute GCMC combined field");
        // isimCombinedRow.setLabels("", "", "Compute GCMC combined field", "No");
        // isimCombinedRow.insertButtonRow(stepsPanel, layout, ISIMStepRow.BUTTON_ROW);
        // isimCombinedRow.button.addActionListener(new ComputeISIM(this));
        // isimCombinedRow.button.setIcon(computerIcon);
        // isimCombinedRow.button.setToolTipText("Compute Grand Canonical Monte Carlo ion distribution (ISIM).\nWARNING: This might take a few minutes.");
        // isimCombinedRow.updateRow();

        // Create "view ion counts" row
        viewIonCountsRow = new ISIMStepRow(viewIonCountsTask, "View results");
        viewIonCountsRow.insertButtonRow(stepsPanel, layout, ISIMStepRow.BUTTON_ROW);
        viewIonCountsRow.button.addActionListener(new ViewIonCountsAction(this));
        viewIonCountsRow.button.setToolTipText("Click to see summary of ion simulation results.");
        viewIonCountsRow.updateRow();

        getContentPane().add(stepsPanel);
        
        // Create help for all step rows
        chooseMoleculeRow.setHelpText(
                "" + programName + " Help: Load Molecule step"
                ,
                "You must choose a molecule about which to compute the ionic\n" +
                "environment.  Usually this molecule is RNA, DNA, or protein\n" +
                "\n"+
                "Press the \"Choose Molecule\" Button to load a macromolecular\n" +
                "structure file.  The file can be either in Protein Data Bank\n" +
                "(PDB) format, or in PQR format.  PQR format contains partial\n" +
                "charge information for each atom, while PDB format does not.\n"
                ,
                this
                );

        ionsRow.setHelpText(
                "" + programName + " Help: Selected dissolved ions step"
                ,
                "Select the set of dissolved ions in the solution.  Concentrations\n" +
                "are specified in units of millimoles per liter (mM).  Other parameters\n" +
                "include temperature, permittivity of the solvent, and permittivity of\n" +
                "the macroion.  Excess chemical potentials for each ionic condition\n" +
                "are expensive to calculate, so they are precomputed.\n"                
                ,
                this
                );
        
        if( apbsSimulateRow != null ){
        
           apbsSimulateRow.setHelpText(
                   "" + programName + " Help: Compute macromolecule field step"
                   , 
                   "First, computes the macromolecule electrostatic field using\n" +
                   "APBS; then simulates ion positions using ISIM.\n"
                   ,
                   this
                   );
        }

        
        isimSimulateRow.setHelpText(
                "" + programName + " Help: Compute macromolecule field step"
                , 
                "First, computes the macromolecule electrostatic field using\n" +
                "APBS; then simulates ion positions using ISIM.\n"
                ,
                this
                );

//        apbsRow.setHelpText(
//                "" + programName + " Help: Compute macromolecule field step"
//                , 
//                "Computes the electrostatic field around the macromolecule using\n" +
//                "the Poisson Boltzmann equation.  This step\n" +
//                "does not include the dissolved ions in the field calculation, but is\n" +
//                "required for the later GCMC calculation step.  The field caculation is\n" +
//                "performed using the program APBS.\n"
//                ,
//                this
//                );

//        isimCombinedRow.setHelpText(
//                "" + programName + " Help: Compute complete field step"
//                , 
//                "Computes the electrostatic field around the macromolecule using\n" +
//                "a Grand Canonical Monte Carlo (GCMC) method.  This step\n" +
//                "includes both the macromolecule and the dissolved ions in the field\n"+
//                "calculation.  The calculation is performed using the program ISIM.\n"
//                ,
//                this
//                );

        viewIonCountsRow.setHelpText(
                "" + programName + " Help: View ion counts"
                , 
                "'View ion counts' displays a table summarizing the average number\n" +
                "of ions in the simulation, compared to the number expected had the\n" +
                "macroion been absent.  The difference counts can be thought of as\n" +
                "'salt ions associated with the macromolecule'\n"
                ,
                this
                );
        
    }

    String errorMessage;
    
    MoleculeAcquisitionMethodDialog moleculeAcquisitionMethodDialog = null;
    void askUserToChooseAMolecule() {        
        if (moleculeAcquisitionMethodDialog == null) 
            moleculeAcquisitionMethodDialog = new ISIMLoadMoleculeDialog(this, null);
        moleculeAcquisitionMethodDialog.setVisible(true);
    }
    
    void convertPDBToPQR(File pdbFile) {
        
        String convertMessage = "Converting PDB format structure file "+pdbFile+" to PQR format...\n";
        appendToLog(convertMessage);
        
        // Run this outside of the event thread
        PDBToPQRThread thread = 
            new PDBToPQRThread(
                    new ISIMStepParameters(programDirectory, 
                            workingDirectory,
                            loadMacromoleculeTask
                            ),
                    apbsParameters,
                    pdbFile,
                    this
                    );
        thread.start();
        
        // Attach progress monitor to conversion thread
        ProgressDialog progressDialog = chooseMoleculeRow;
        loadMacromoleculeTask.setStatus(DependentTask.ACTIVELY_UPDATING, "Converting to PQR format");
        
        ProgressManager progressManager;
        
        assert(progressDialog != null);
        
        if (progressDialog == null)
            progressManager = 
                new ProgressManager(
                        thread, 
                        this, 
                        convertMessage);
        else 
            progressManager = new ProgressManager(thread, progressDialog);        
        progressManager.start();        
    }
    
    void measureStructureSize (File pqrFile) {
        
        String convertMessage = "Measuring size of molecule in file "+pqrFile+"...\n";
        appendToLog(convertMessage);
        
        // Run this outside of the event thread
        RunPsizeThread thread = 
            new RunPsizeThread(
                    new ISIMStepParameters(programDirectory, 
                            workingDirectory,
                            loadMacromoleculeTask
                            ),
                    apbsParameters,
                    pqrFile,
                    this
                    );
        thread.start();
        
        // Attach progress monitor to conversion thread
        ProgressDialog progressDialog = chooseMoleculeRow;
        loadMacromoleculeTask.setStatus(DependentTask.ACTIVELY_UPDATING, "Measuring molecule size");
        
        ProgressManager progressManager;
        
        assert(progressDialog != null);
        
        if (progressDialog == null)
            progressManager = 
                new ProgressManager(
                        thread, 
                        this, 
                        convertMessage);
        else 
            progressManager = new ProgressManager(thread, progressDialog);        
        progressManager.start();        
    }

    
    public void setPqrFile(File pqrFile) {
        
        // ATOM   7948  H21 G     414      38.623  26.597  22.182  0.3200 0.2245
        //                                   X       Y       Z     CHARGE RADIUS
        // 0000000001111111111222222222233333333334444444444555555555566666666667
        // 1234567890123456789012345678901234567890123456789012345678901234567890
        Pattern pqrAtomPattern = Pattern.compile("^(ATOM .{57})([0-9 ][0-9]\\.[0-9]{4})\\s*$");
        DecimalFormat radiusFormat = new DecimalFormat(" 0.0000");
        
        // Expand radii by 1.4 Angstroms, to match method in Mark E.'s thesis
        File expandedDir = new File(workingDirectory, "expanded");
        expandedDir.mkdir();
        File expandedPqr = new File(expandedDir, pqrFile.getName());
        double radiusIncrement = 1.40;
        try {
            // Copies src file to dst file.
            // If the dst file does not exist, it is created
            BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(pqrFile)));
            PrintStream out = new PrintStream(expandedPqr);
            
            String pqrLine;
            while ((pqrLine = in.readLine()) != null) {
                Matcher matcher = pqrAtomPattern.matcher(pqrLine);
                if (matcher.matches()) {
                    
                    out.print(matcher.group(1));

                    // TODO modify radius
                    double radius = new Double(matcher.group(2));
                    radius += radiusIncrement;
                    out.println(radiusFormat.format(radius));
                }
                else {
                    // Not an atom line?  -- send through unmolested
                    out.println(pqrLine);
                }
            }

            in.close();
            out.close();
        } 
        catch (IOException exc) {
            appendToLog("!!!ERROR creating PQR file with solvent expanded radii");
            appendToLog("    Using unexpanded file instead.");
            
            expandedPqr = pqrFile;
        }

        // Run psize.py on the molecule to get the molecule dimensions
        measureStructureSize(expandedPqr);

        // moleculeFile should already be in PQR format
        apbsParameters.setPqrInputFile(expandedPqr);        
        // loadMacromoleculeTask.dataDescription = pqrFile.getName();
        // loadMacromoleculeTask.setStatus(DependentTask.CURRENT_AND_COMPLETE, pqrFile.getName());
        
        appendToLog("Using molecule structure file " + expandedPqr + "\n");
    }
    
    /**
     * Routine to recursively delete a directory and its contents.
     * Adapted from http://joust.kano.net/weblog/archives/000071.html
     * Note that this routine could be very dangerous.
     * 
     * @param dir
     * @return Returns "true" on success, "false" otherwise
     */
    private static boolean recursiveDeleteDir(File dir) {
        
        if (dir == null) return false;
        
        // to see if this directory is actually a symbolic link to a directory,
        // we want to get its canonical path - that is, we follow the link to
        // the file it's actually linked to
        File candir;
        try {
            candir = dir.getCanonicalFile();
        } catch (IOException e) {
            return false;
        }
  
        // TODO - but this does not work on Windows
        // (distinguishing between symbolic links and real files)
        // getAbsoluteFile() returns a File with shortened names, e.g. "C:\DOCUME~1\CHRIST~1\LOCALS~1\Temp\ISIM58047"
        // getCanonicalFile() returns expanded names, e.g. "C:\Documents and Settings\Christopher Bruns\Local Settings\Temp\ISIM58047"
        // The File.equals(File) method returns *false* between these two cases
        // (It should return *true*)
        
        // File absdir = dir.getAbsoluteFile();
        
        // a symbolic link has a different canonical path than its actual path,
        // unless it's a link to itself
        // if (! (candir.equals(absdir)) ) {
            // this file is a symbolic link, and there's no reason for us to
            // follow it, because then we might be deleting something outside of
            // the directory we were told to delete
            // return false;
        // }
  
        // Warning: there is a possible race condition here, if the file becomes
        // a symbolic link after the above check, but before the delete.
        
        // now we go through all of the files and subdirectories in the
        // directory and delete them one by one
        File[] files = candir.listFiles();
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                File file = files[i];
  
                // in case this directory is actually a symbolic link, or it's
                // empty, we want to try to delete the link before we try
                // anything
                boolean deleted = file.delete();
                if (!deleted) {
                    // deleting the file failed, so maybe it's a non-empty
                    // directory
                    if (file.isDirectory()) recursiveDeleteDir(file);
  
                    // otherwise, there's nothing else we can do
                }
            }
        }
  
        // now that we tried to clear the directory out, we can try to delete it
        // again
        return dir.delete();  
    }
    
    /**
     * Routine to delete a directory and its contents.
     * Adapted from http://joust.kano.net/weblog/archives/000071.html
     * This is meant to be safer than the recursiveDeleteDir() method, because
     * It does not recursively follow subdirectories.
     * It is intended to remove symbolic links in the dirctory, but not the
     * targets of those links.
     * This method will fail (and return false) if the argument directory
     * returns false.  In this case the contents of the directory may only
     * be partially removed.
     * 
     * @param dir
     * @return Returns "true" on success, "false" otherwise
     */
    private static boolean deleteDir(File dir) {
        
        if (dir == null) return false;
        
        File absdir = dir.getAbsoluteFile();
        
        // now we go through all of the files and subdirectories in the
        // directory and delete them one by one
        File[] files = absdir.listFiles();
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                File file = files[i];
  
                // in case this directory is actually a symbolic link, or it's
                // empty, we want to try to delete the link before we try
                // anything
                // boolean deleted = file.delete();
                file.delete();
                
                // Note: non-empty directories do not get deleted. This is the price
                // of relative safety compared to recursiveDeleteDir
            }
        }
  
        // now that we tried to clear the directory out, we can try to delete it
        // again
        return dir.delete();  
    }
    

    /**
     * Delete working area and all files therein
     *
     */
    private void destroyAllOfMyBeautifulWork() {
        boolean deleteSucceeded = true; // Start optimistic
         
        // First remove inner working directory
        logDialog.append("Deleting " + workingDirectory + "...\n");
        if (! deleteDir(workingDirectory)) deleteSucceeded = false;
        
        // Then remove top level working directory, i.e. the resulte of createTempFile()
        logDialog.append("Deleting " + parentWorkingDirectory + "...\n");
        if (! deleteDir(parentWorkingDirectory)) deleteSucceeded = false;

        if (deleteSucceeded) logDialog.append("Delete succeeded.\n");
        else logDialog.append("Delete failed.\n");
    }
    
    void cleanUpAndExit() {
        destroyAllOfMyBeautifulWork();
        System.exit(0);
    }
    
    public void appendToLog(String logMessage) {
        logDialog.append(logMessage);
    }
    
    public void showLicenseDialog() {
        
        // TODO - put actual licenses in help menu
        final String licenseText = ""

            +interfaceName + " version " + isimInterfaceVersion + "\n"
            +"\n"
            +interfaceName + " is a Java graphical user interface that runs \n"
            +"the programs ABPS and ISIM.\n"
            +"\n"
            +"The " + interfaceName + " Java application is distributed under an \n"
            +"MIT open source license (see Documentation).\n"
            +"\n"
            +"APBS (Adaptive Poisson Boltzmann Solver) is available from \n"
            +"http://apbs.sourceforge.net/\n"
            +"\n"
            +"ISIM (Ion Simulator) is available from \n"
            +"http://mccammon.ucsd.edu/isim/"
           
            ;
        
        JOptionPane.showMessageDialog(this,
                licenseText,
                interfaceName + " Licence",
                JOptionPane.INFORMATION_MESSAGE);
    }

    class BrowserLaunchAction implements ActionListener {
        String urlString;
        BrowserLaunchAction(String u) {urlString = u;}
        public void actionPerformed(ActionEvent e) {
            URL url;
            try {url = new URL(urlString);}            
            catch (MalformedURLException exc) {
                // Show information dialog, so the savvy user will be able to
                // go to the url manually, in case the browser open fails.
                JOptionPane.showConfirmDialog(
                        ISIMWrapper.this, 
                        "Problem opening browser to page " + urlString + "\n" + exc, 
                        "!!! Error opening web browser !!!",
                        JOptionPane.DEFAULT_OPTION, 
                        JOptionPane.ERROR_MESSAGE
                        );
                return;
            }
            try {
                // This only works when started in a web start application
                BasicService bs = (BasicService)ServiceManager.lookup("javax.jnlp.BasicService");
                bs.showDocument(url);
            } 
            catch (UnavailableServiceException exc) {
                // launchErrorConfirmDialog("Problem opening browser to page " + urlString + "\n" + exc,
                // "JNLP Error!");
                try {BrowserLauncher.openURL(urlString);}
                catch (IOException exc2) {
                    JOptionPane.showConfirmDialog(
                            ISIMWrapper.this, 
                            "Problem opening browser to page " + urlString + "\n" + exc, 
                            "!!! Error opening web browser !!!",
                            JOptionPane.DEFAULT_OPTION, 
                            JOptionPane.ERROR_MESSAGE
                            );
                }        
            }
        }
    }

   // accessor for apbs parameters class
    
    public APBSParameters getApbsParameters( ){
       return apbsParameters;
    }

    // accessor for isim parameters class
    
    public ISIMParameters getIsimParameters( ){
       return isimParameters;
    }
}


class ViewLogAction implements ActionListener {
    ISIMWrapper parentFrame;
    public ViewLogAction(ISIMWrapper parentFrame) {
        this.parentFrame = parentFrame;
    }
    public void actionPerformed(ActionEvent e) {
        parentFrame.logDialog.setLocationRelativeTo(parentFrame);
        parentFrame.logDialog.setVisible(true);
    }
}

class AboutAction implements ActionListener {
    ISIMWrapper parentFrame;
    public AboutAction(ISIMWrapper parentFrame) {
        this.parentFrame = parentFrame;
    }
    public void actionPerformed(ActionEvent e) {
        JOptionPane.showMessageDialog(
                parentFrame,

                "" + parentFrame.interfaceName + " version " + 
                parentFrame.isimInterfaceVersion + "\n" +
                "created by Christopher Bruns at SimTK.org\n" +
                "Includes binary executables for APBS and ISIM (see documentation).",

                "About " + parentFrame.interfaceName,

                JOptionPane.PLAIN_MESSAGE);
    }
}

//class ComputeISIM implements ActionListener {
//    ISIMWrapper parentFrame;
//    
//    public ComputeISIM(ISIMWrapper parent) {
//        this.parentFrame = parent;
//    }
//    
//    public void actionPerformed(ActionEvent e) {
//        RunISIMThread thread = 
//            new RunISIMThread(
//                    new ISIMStepParameters(parentFrame.programDirectory, 
//                            parentFrame.workingDirectory,
//                            parentFrame.computeGCMCPotentialTask),
//                    parentFrame.apbsParameters,
//                    parentFrame
//                    );
//        thread.start();
//
//        // And another process to monitor the download process
//        ProgressDialog progressDialog = parentFrame.isimCombinedRow;
//        ProgressManager  progressManager = new ProgressManager(thread, progressDialog);
//        progressManager.start();        
//    }
//}

/**
 *  
  * @author Christopher Bruns
  * 
  * SimulateIons combines functions of ComputeMoleculeAPBS and ComputeISIM
 */
class SimulateIons implements ActionListener {
    ISIMWrapper parentFrame;
    
    public SimulateIons(ISIMWrapper parent) {
        parentFrame = parent;
    }
    
    public void actionPerformed(ActionEvent e) {

        SimulateIonsThread thread = 
            new SimulateIonsThread(
                    new ISIMStepParameters(parentFrame.programDirectory, 
                            parentFrame.workingDirectory,
                            parentFrame.apbsAndIsimTask),
                    parentFrame.apbsParameters,
                    parentFrame);
        thread.start();

        // And another process to monitor the download process
        ProgressDialog progressDialog = parentFrame.isimSimulateRow;
        ProgressManager  progressManager = new ProgressManager(thread, progressDialog);
        progressManager.start();        
        
    }
}

//class ComputeMoleculeAPBS implements ActionListener {
//    ISIMWrapper parentFrame;
//    
//    public ComputeMoleculeAPBS(ISIMWrapper parent) {
//        parentFrame = parent;
//    }
//    
//    public void actionPerformed(ActionEvent e) {
//
//        RunAPBSThread thread = 
//            new RunAPBSThread(
//                    new ISIMStepParameters(parentFrame.programDirectory, 
//                            parentFrame.workingDirectory,
//                            parentFrame.computeMacromoleculePotentialTask),
//                    parentFrame.apbsParameters,
//                    parentFrame);
//        thread.start();
//
//        // And another process to monitor the download process
//        ProgressDialog progressDialog = parentFrame.apbsRow;
//        ProgressManager  progressManager = new ProgressManager(thread, progressDialog);
//        progressManager.start();        
//        
//    }
//}

class ViewIonCountsAction implements ActionListener {
    ISIMWrapper parentFrame;
    IonCountDialog ionCountDialog;
    
    ViewIonCountsAction(ISIMWrapper parentFrame) {
        this.parentFrame = parentFrame;
        ionCountDialog = new IonCountDialog(parentFrame);
    }
    
    public void actionPerformed(ActionEvent e) {
        // Show the user the ion table
        try {
            // Set start time of view counts action
            parentFrame.viewIonCountsTask.setStatus(DependentTask.ACTIVELY_UPDATING,
                    "");

            ionCountDialog.loadAllIonCounts();
            ionCountDialog.setLocationRelativeTo(parentFrame);
            ionCountDialog.setVisible(true);

            // Mark view action complete
            parentFrame.viewIonCountsTask.setStatus(DependentTask.CURRENT_AND_COMPLETE,
                    "");
        } catch (IOException exc) {
            exc.printStackTrace();
            // TODO problem reading ion information files
        }
    }
}

class LoadMoleculeAction implements ActionListener {
    ISIMWrapper parentFrame;

    LoadMoleculeAction(ISIMWrapper parent) {
        parentFrame = parent;
    }

    public void actionPerformed(ActionEvent e) {
        parentFrame.askUserToChooseAMolecule();
    }

}

class QuitAction implements ActionListener {
    ISIMWrapper isimWrapper;
    QuitAction(ISIMWrapper isimWrapper) {
        this.isimWrapper = isimWrapper;
    }
    public void actionPerformed(ActionEvent e) {
        isimWrapper.cleanUpAndExit();
    }
}

//class InspectParametersAction implements ActionListener {
//    APBSParameters parameters;
//    ParameterDialog dialog;
//    JFrame parentFrame;
//    
//    InspectParametersAction(APBSParameters p, JFrame parent) {
//        parameters = p;
//        parentFrame = parent;
//    }
//
//    public void actionPerformed(ActionEvent e) {
//        if (dialog == null) {
//            dialog = new ParameterDialog(parameters, parentFrame);
//        }
//        dialog.setVisible(true);
//    }
//}

class ChooseIonsAction implements ActionListener {
    IonSelectionDialog ionDialog;
    ChooseIonsAction(IonSelectionDialog ionDialog) {
        this.ionDialog = ionDialog;
    }
    public void actionPerformed(ActionEvent e) {
        ionDialog.setVisible(true);
    }    
}

class SetParameterAction implements ActionListener {
	IsimParameterDialog isimParameterDialog;
	SetParameterAction( IsimParameterDialog ionDialog) {
        this.isimParameterDialog = ionDialog;
    }
    public void actionPerformed(ActionEvent e) {
    	isimParameterDialog.setVisible(true);
    }    
}

class IsimTextField extends JTextField {
	
	    static int fixedLabelLength = 50;
	    static int fixedTextLength  = 10;
	    /**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		
		private JLabel label = null;
		GridBagConstraints constraints = new GridBagConstraints();
		
		IsimTextField( String labelString, String initialValue, Container jPanel, 
				       int rowIndex, GridBagLayout gridBagLayout ){
			
			constraints.anchor    = GridBagConstraints.EAST;
			constraints.gridwidth = GridBagConstraints.RELATIVE; //next-to-last
			constraints.fill      = GridBagConstraints.NONE; 
			constraints.gridy     = rowIndex;	
			constraints.weightx   = 0.0;                       //reset to default
	           
	        JPanel rowPanel = new JPanel();
	        jPanel.add( rowPanel );
            // rowPanel.setLayout( new BoxLayout( rowPanel, BoxLayout.X_AXIS) );
            gridBagLayout.setConstraints( rowPanel, constraints );
			if( labelString != null ){
				/*
			   if( labelString.length() < fixedLabelLength ){
				   StringBuffer newLabel = new StringBuffer( labelString );
				   while( newLabel.length() < fixedLabelLength ){
					   newLabel.append( " " );
				   }
				   labelString = newLabel.toString();
			   } */
			   label = new JLabel( labelString, RIGHT );
			   System.out.println( "Label: " + labelString + " " + labelString.length() );
			   if( rowPanel != null ){
				   rowPanel.add( label, constraints );
			   }
			   label.setLabelFor( this );
			}
			if( initialValue != null ){
			   setText( initialValue );
			}
			if( rowPanel != null ){
				
		        constraints.gridwidth = GridBagConstraints.REMAINDER;     //end row
		        constraints.fill      = GridBagConstraints.HORIZONTAL;
		        constraints.weightx    = 1.0;

				rowPanel.add( this, constraints );
			}			
		    setColumns( fixedTextLength );
		
	    }
}

class IntegerTextField extends IsimTextField {

	private static final long serialVersionUID = 1L;
	
	final static String badchars 
       = "`~!@#$%^&*()_+=\\|\"':;?/>.<, ";
    
    
    IntegerTextField( String labelString, int initialValue, Container jPanel, int rowIndex, GridBagLayout gridBagLayout ){
    	super( labelString, Integer.toString( initialValue ), jPanel, rowIndex, gridBagLayout );
    }
    
    public void processKeyEvent(KeyEvent keyEvent ) {

        char c = keyEvent.getKeyChar();

        if( (Character.isLetter(c) && !keyEvent.isAltDown()) 
              || badchars.indexOf(c) > -1 ){
        	keyEvent.consume();
            return;
        }
        if( c == '-' && getDocument().getLength() > 0 ){
        	keyEvent.consume();
        } else {
        	super.processKeyEvent(keyEvent);
        }

    }
}

class DoubleTextField extends IsimTextField {

 	private static final long serialVersionUID = 1L;
	
	final static String badchars 
       = "`~!@#$%^&*()_+=\\|\"':;?/><, ";
    
    
	DoubleTextField( String labelString, double initialValue, Container jPanel, 
			         int rowIndex, GridBagLayout gridBagLayout ){
		super( labelString, Double.toString( initialValue ), jPanel, rowIndex, gridBagLayout );
    }
    
    public void processKeyEvent(KeyEvent keyEvent ) {

        char c = keyEvent.getKeyChar();

        if( ( (Character.isLetter(c) && c != 'e' && c != 'E') && !keyEvent.isAltDown()) 
              || badchars.indexOf(c) > -1 ){
        	keyEvent.consume();
            return;
        }
        super.processKeyEvent(keyEvent);
    }
}

// Interface in which to view and control the parameters
class IsimParameterDialog extends JDialog implements ActionListener {
	
    public static final long serialVersionUID = 01L;
    
    APBSParameters apbsParameters;
    ISIMParameters isimParameters;
    LogDialog logDialog;

    JButton doneButton     = new JButton("Done");
      
    DoubleTextField temperatureTextFieldD, solventPermittivityTextFieldD, moleculePermittivityTextFieldD;
    IntegerTextField randomSeedTextFieldI, totalMonteCarloStepsI;
    
    IsimParameterDialog( APBSParameters inputApbsParameters, ISIMParameters inputIsimParameters, JFrame parentFrame,
    		             LogDialog inputLogDialog ) {

        super( parentFrame, "Simulation Parameters" );
 
        setLocationRelativeTo(parentFrame);
        
        logDialog            = inputLogDialog;
        apbsParameters       = inputApbsParameters;
        isimParameters       = inputIsimParameters;
        GridBagLayout layout = new GridBagLayout();
        
        getContentPane().setLayout( layout );
        
        // Text at the top of the dialog 
        // JPanel headerPanel = new JPanel();
        // headerPanel.add(new JLabel("Simulation parameters"));
        // getContentPane().add(headerPanel);
                
        int rowIndex                     = 0;
        
        //temperatureTextFieldD            = new DoubleTextField( " Temperature(\u00B0K) ", 
        //		                                                apbsParameters.getTemperature(), getContentPane(), rowIndex++, layout );
        
        // solventPermittivityTextFieldD    = new DoubleTextField( " Relative Solvent Permittivity ", 
        // 		                                                apbsParameters.getSolventPermittivity(), getContentPane(), rowIndex++, layout );
        
        randomSeedTextFieldI             = new IntegerTextField( " Random Number Generator Seed ",
                isimParameters.getRandomSeed(), getContentPane(), rowIndex++, layout );
        
        totalMonteCarloStepsI             = new IntegerTextField( " Monte Carlo Steps ",
                isimParameters.getTotalMonteCarloSteps(), getContentPane(), rowIndex++, layout );

        moleculePermittivityTextFieldD   = new DoubleTextField( " Macromolecule Permittivity ", 
        		                                                apbsParameters.getMoleculePermittivity(),
        		                                                getContentPane(), rowIndex++, layout );
        
          
	    GridBagConstraints constraints = new GridBagConstraints();
	    constraints.gridwidth          = 1;
	    constraints.ipadx              = 50;
	    constraints.gridx              = 0;
	    constraints.gridy              = rowIndex++;

        // Buttons
        JPanel buttonPanel = new JPanel();
        buttonPanel.add( doneButton);
        doneButton.addActionListener(this);
        
        getContentPane().add( buttonPanel, constraints );
        
        this.pack();
      
    }
    
       
    // Capture button events and parameter change events here
    boolean automaticDisplayUpdating = false;
    public void actionPerformed(ActionEvent event) {

        if (event.getSource() == doneButton) {
            setVisible(false);
 
            // apbsParameters.setTemperature( (new Double((String) temperatureTextFieldD.getText())).doubleValue() );
            // apbsParameters.setSolventPermittivity((new Double((String) solventPermittivityTextFieldD.getText())).doubleValue() );
            
            apbsParameters.setMoleculePermittivity( (new Double((String) moleculePermittivityTextFieldD.getText())).doubleValue() );

            isimParameters.setRandomSeed( (new Integer((String) randomSeedTextFieldI.getText())).intValue() );
            isimParameters.setTotalMonteCarloSteps( (new Integer((String) totalMonteCarloStepsI.getText())).intValue() );
            
            // logDialog.append( "T=" + temperatureTextFieldD.getText() + "\n" );
            // logDialog.append( "solventPerm=" + solventPermittivityTextFieldD.getText() + "\n" );
            
            logDialog.append( "   molPerm=" + moleculePermittivityTextFieldD.getText() + "\n" );
            logDialog.append( "  MC steps=" + totalMonteCarloStepsI.getText() + "\n" );
            logDialog.append( "randomSeed=" + randomSeedTextFieldI.getText() + "\n" );
        }
    }
    
}

class ISIMLoadMoleculeDialog extends MoleculeAcquisitionMethodDialog {
    ISIMWrapper parentFrame;

    ISIMLoadMoleculeDialog(ISIMWrapper frame, ProgressDialog progressDialog) {
        super(frame, progressDialog);

        this.parentFrame = frame;
        setTitle("Choose Molecule (" + parentFrame.programName + ")");

        setLocationRelativeTo(parentFrame);
        this.setDefaultPdbId("1GRZ"); // Default to an RNA structure
    }
    static final long serialVersionUID = 01L;
    
    public void readStructureFromMoleculeCollection(MoleculeCollection molecules) {
        // Do nothing, we want files, not molecules, for this wrapper
        // This function should never even be called, if we override handleMoleculeURL properly
    }
    
    // Override handleMoleculeURL to manipulate files instead of loading molecules
    protected void handleMoleculeUrl(URL url) throws IOException {
        parentFrame.loadMacromoleculeTask.setStatus(DependentTask.ACTIVELY_UPDATING, "Loading molecule...");

        String loadingMessage = "Loading structure from " + url + "...";
        parentFrame.appendToLog("\n" + loadingMessage + "\n");
        
        CopyPdbFileProcess copyFileProcess = new CopyPdbFileProcess(url, fileLoadObservable);
        copyFileProcess.start();
        
        // And another process to monitor the download process
        ProgressDialog progressDialog = parentFrame.chooseMoleculeRow;
        
        ProgressManager progressManager;
        if (progressDialog == null)
            progressManager = 
                new ProgressManager(
                        copyFileProcess, 
                        parentFrame, 
                        loadingMessage);
        else 
            progressManager = new ProgressManager(copyFileProcess, progressDialog);        

        progressManager.start();        

    }

    boolean isPDBFile(String fileName) {
        // Determine file type from the name
        if (fileName == null) return false;
        
        String name = fileName.toLowerCase();
        if ( name.endsWith(".pdb") ) return true;
        if ( name.endsWith(".pdb1") ) return true;
        if ( name.endsWith(".pdb2") ) return true;
        if ( name.endsWith(".ent") ) return true;
        if ( name.endsWith(".brk") ) return true;

        if ( name.toLowerCase().endsWith(".pqr") ) return false;
        if ( name.toLowerCase().indexOf(".pqr") >= 0 ) return false;

        if ( name.toLowerCase().indexOf(".pdb") >= 0 ) return true;

        return false;
    }
    
    class CopyPdbFileProcess extends LoadPdbUrlProcess {

        CopyPdbFileProcess(URL url, SimpleObservable loadMoleculeObservable) {
            super(url, loadMoleculeObservable);
        }

        // Override loadMolecules to create a file instead of loading a molecule
        protected void loadMolecules() throws IOException, InterruptedException {
            InputStream inStream = getInputStream();
            
            // Determine file name
            String fileName = this.url.getFile();
            // strip off all but the file name (no path)
            int pathEnd = fileName.lastIndexOf("/");
            if (pathEnd >= 0) fileName = fileName.substring(pathEnd + 1);

            // Remove compression suffixes from file name
            fileName = fileName.replaceAll("\\.Z$", "");
            fileName = fileName.replaceAll("\\.gz$", "");
            
            // Create local file of molecular structure
            File outputFile = new File(parentFrame.workingDirectory, fileName);
            OutputStream outStream = new FileOutputStream(outputFile);

            // StreamCopy.copy(inStream, outStream);
            BufferedReader reader = new BufferedReader(new InputStreamReader(inStream));
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outStream));
            String line;
            while ( (line = reader.readLine()) != null) {
                // Skip empty lines
                if (line.length() < 20) continue;
                
                // Remove water molecules
                if (line.substring(17,20).equals("HOH")) continue;
                if (line.substring(17,20).equals("WAT")) continue;
                if (line.substring(17,20).equals("H2O")) continue;
                
                // Remove records that confuse PDB2PQR
                if (line.substring(0,5).equals("LINK ")) continue;
                if (line.substring(0,5).equals("SITE ")) continue;                
                
                writer.write(line);
                writer.write("\n");
            }
            writer.flush();
            inStream.close();
            outStream.close();
            
            // 2 - if PDB format, convert to PQR
            if (isPDBFile(fileName)) {
                parentFrame.convertPDBToPQR(outputFile);            
            }
            else {
                parentFrame.setPqrFile(outputFile);
            }
            
            reactivate();
            
        }
    }
    
}
