/* Copyright (c) 2005 Stanford University and Christopher Bruns
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including 
 * without limitation the rights to use, copy, modify, merge, publish, 
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject
 * to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/*
 * Created on Mar 10, 2006
 * Original author: Christopher Bruns
 */
package org.simtk.isimsu;

import java.io.*;
import java.util.Vector;
import java.util.regex.*;

public class RunPsizeThread extends JarDefinedExecutableThread {
    ISIMWrapper parentFrame;
    File pqrInputFile;
    APBSParameters apbsParameters;
    ISIMStepParameters stepParams;
    
    public RunPsizeThread(
            ISIMStepParameters stepParams,
            APBSParameters apbsParameters,
            File pqrInputFile, 
            ISIMWrapper parentFrame 
            ) {
        this.pqrInputFile = pqrInputFile;
        this.parentFrame = parentFrame;
        this.apbsParameters = apbsParameters;
        this.stepParams = stepParams;
    }
    
    public void run() {
        parentFrame.appendToLog("Running psize.py...\n");

        if ( (pqrInputFile != null) && (pqrInputFile.exists()) ) {

            // Get the platform-specific command name from a file in the jar file
            Vector initialArgs = new Vector();
            // The first line of the name file = first entry in initial args = executable name, e.g. "python"
            try {
                initialArgs = unpackExecutable("psize_dir", "psize_executable_name", stepParams.programDirectory);
            } catch (IOException exc) {
                reportFailure(exc);
            }

            // If the executable is local to the working directory, expand to fully qualified name
            String exeFileName = (String) initialArgs.elementAt(0);
            File exeFile = new File( stepParams.programDirectory, exeFileName);
            if (exeFile.exists()) 
                exeFileName = exeFile.getAbsolutePath();

            // Generate the full command line for the system call
            Vector cmdArgs = new Vector();
            cmdArgs.addElement(exeFileName); // begin with the executable name, e.g. "python"
            // Put later arguments from executable name into command path
            for (int i = 1; i < initialArgs.size(); i ++) {
                cmdArgs.addElement((String) initialArgs.elementAt(i));
            }

            // Increase maximum memory size - parallel computation is not supported yet
            // This argument must come before the structure file name
            cmdArgs.addElement("--GMEMCEIL=2000");
            
            // Add file name argument to command line
            cmdArgs.addElement(pqrInputFile.getName()); // name of structure file we want to measure

            // Convert to a string array for use by ExternalProcessManager
            String[] psizeCommand = new String[cmdArgs.size()];
            psizeCommand = (String[]) cmdArgs.toArray(psizeCommand);
            
            // Run the command using a "system" call in yet another thread
            ExternalProcessManager programThread = new ExternalProcessManager(psizeCommand, stepParams.workingDirectory);
            programThread.start(); // Run the program

            try { 
                // wait for it to finish
                programThread.join();
                
                String psizeOutput = programThread.getStdOut();

                parentFrame.appendToLog("\n\nNOTE: The recommended settings from\n"+
                        "psize are NOT necessarily exactly what will be used when\n"+
                        "running APBS.\n\n");

                parentFrame.appendToLog("\n\nPsize Standard Output:\n\n");
                parentFrame.appendToLog(psizeOutput);
                parentFrame.appendToLog("\n\nPsize Standard Error:\n\n");
                parentFrame.appendToLog(programThread.getStdErr());

                // Parse the standard output to get the molecule dimensions
                // Examine one line at a time
                // Approach using "split" (my original regex wasn't working
                String lineBreakRegex = "[\n\r\f\u0085\u2028\u2029]+";
                Pattern lineBreakPattern = Pattern.compile(lineBreakRegex);
                String[] psizeLines = lineBreakPattern.split(psizeOutput);

                // Coarse grid dims = 140.740 x 105.550 x 148.369 A
                String cgridRegex = "^\\s*Coarse grid dims = ([0-9.]+) x ([0-9.]+) x ([0-9.]+) A\\s*$";
                Pattern cgridPattern = Pattern.compile(cgridRegex);
                
                for (int i = 0; i < psizeLines.length; i++) {
                    String line = psizeLines[i];
                    
                    // System.out.println("Split: " + line);
                    
                    // TODO actually parse the lines
                    Matcher cgridMatcher = cgridPattern.matcher(line);
                    if (cgridMatcher.find()) {
                        double gridX = new Double(cgridMatcher.group(1)).doubleValue();
                        double gridY = new Double(cgridMatcher.group(2)).doubleValue();
                        double gridZ = new Double(cgridMatcher.group(3)).doubleValue();

                        // System.out.println("grid = " + gridX + ", " + gridY + ", " + gridZ);

                        // Because we are going to run ISIM in a sphere, we want an
                        // enclosing *cube* for APBS.  Use the longest grid length for
                        // the cube edge length, so we can be certain of enclosing the molecule.
                        
                        double maxGrid = Math.max(gridX, gridY);
                        maxGrid = Math.max(maxGrid, gridZ);
                        
                        apbsParameters.setGridLength(maxGrid, maxGrid, maxGrid);
                    }
                    
                }
                
                if (programThread.exception != null) {
                    // FAILURE of process
                    reportFailure(programThread.exception);
                }                
                return;
            }
            catch (InterruptedException exc) {
                reportFailure(exc);
                return;
            }
        }
    }

    void reportFailure(Exception exc) {
        exc.printStackTrace();
        setFailed();
        return;        
    }
    
    public void setFailed(String msg) {
        super.setFailed();
        stepParams.task.setStatus(DependentTask.FAILED, msg);
    }
}
