/*
 * 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 15, 2005
 * Original author: Christopher Bruns
 */
package org.simtk.isimsu;

import java.io.*;
import java.util.*;

import org.simtk.gui.*;
import java.text.*; // DecimalFormat

/**
 * 
  * @author Christopher Bruns
  * 
  * class to manage the running of the program APBS on a molecule
 */
public class RunAPBSThread extends JarDefinedExecutableThread 
implements MonitoredProcess
{
    // ISIMTask apbsTask;
    ISIMWrapper isimWrapper;
    APBSParameters apbsParameters;
    ISIMStepParameters stepParameters;
    
    // Constructor
    RunAPBSThread(ISIMStepParameters s, APBSParameters p, ISIMWrapper isimWrapper) {
        stepParameters = s;
        apbsParameters = p;
        this.isimWrapper = isimWrapper;
    }
    
    public void run() {
        
        isimWrapper.appendToLog("Starting APBS task...\n");

        ISIMTask task = stepParameters.task;
        
        // Don't do anything if it appears that someone else is already doing this task
        if (task.isRunning()) return;
        
        isimWrapper.appendToLog("Unpacking APBS executable...\n");

        Vector initialArgs = new Vector();
        try {
            initialArgs = unpackExecutable("dir_apbs", 
                    "apbs_executable_name",
                    stepParameters.programDirectory);
        } catch (IOException exc) {
            reportFailure(exc, "error unpacking APBS executable");
            return;
        }
        
//        
//        // See if apbs is already installed
        File apbsExecutableFile = new File(stepParameters.programDirectory, (String) initialArgs.elementAt(0));
//        // If not, copy it from the jar file
//        if (! apbsExecutableFile.exists()) {
//            try {
//                // TODO get rid of all the APBSBinary stuff
//                // Use a mechanism like the file based mechanisim of pdb2pqr
//                OutputStream apbsOut = new FileOutputStream(apbsExecutableFile);
//                InputStream apbsIn = APBSBinary.getJarURL().openStream();
//                StreamCopy.copy(apbsIn, apbsOut);
//            }
//            catch (IOException exc) {
//                exc.printStackTrace();
//                System.exit(1);
//            }
//        }

        isimWrapper.appendToLog("Creating APBS input file...\n");

        File workDir = stepParameters.workingDirectory;
        

        // 1 - create apbs input file
        File apbsInputFile = new File(workDir, "apbs_molecule.in");
        // Create an APBS input file
        String apbsInputString = createAPBSInput();

        isimWrapper.appendToLog("APBS input file:\n");
        isimWrapper.appendToLog("\n\n********************\n\n");
        isimWrapper.appendToLog(apbsInputString);
        isimWrapper.appendToLog("\n\n******************\n\n");

        task.setStatus(DependentTask.ACTIVELY_UPDATING, "computing NLPB...");
        
        try {
            BufferedWriter apbsInWriter = new BufferedWriter(new FileWriter(apbsInputFile));
            apbsInWriter.write(apbsInputString);
            apbsInWriter.close();

        } catch (IOException exc) {
            exc.printStackTrace();
            task.setStatus(DependentTask.FAILED, "I/O error on APBS input file");
            return;
        }
        
        // 2 - run apbs
        // Run the APBS command

        isimWrapper.appendToLog("Running APBS...\n");

        String apbsCommand = apbsExecutableFile.getAbsolutePath();

        String apbsArg1 = apbsInputFile.getName();
        String[] apbsArgs = {apbsCommand, apbsArg1};
        
        // Process apbsProcess = Runtime.getRuntime().exec(apbsArgs, null, tempDir);
        ExternalProcessManager apbsProcess = new ExternalProcessManager(apbsArgs, workDir);
        long startSeconds = new Date().getTime() / 1000;
        apbsProcess.start();

        // Wait for process to end
        try {
            apbsProcess.join();
            
            // Test whether the process worked
            if (apbsProcess.exception != null) reportFailure(apbsProcess.exception, "");
            else {                
                // This is the only point of success

                // Report the time taken
                long finishSeconds = new Date().getTime() / 1000;
                long elapsedSeconds = finishSeconds - startSeconds;

                long elapsedMinutes = elapsedSeconds / 60;
                elapsedSeconds = elapsedSeconds % 60;

                long elapsedHours = elapsedMinutes / 60;
                elapsedMinutes = elapsedMinutes % 60;

                NumberFormat twoDigits = new DecimalFormat();
                twoDigits.setMinimumIntegerDigits(2);
                twoDigits.setMaximumIntegerDigits(2);
                twoDigits.setMaximumFractionDigits(0);
                
                String elapsedTime = 
                    "" + twoDigits.format(elapsedHours) + 
                    ":" + twoDigits.format(elapsedMinutes) + 
                    ":" + twoDigits.format(elapsedSeconds);

                task.setStatus(DependentTask.CURRENT_AND_COMPLETE, elapsedTime);
                logProcessOutput(apbsProcess);
                isimWrapper.appendToLog("APBS process completed successfully\n");
            }
            return;
        }
        catch (InterruptedException exc) {
            logProcessOutput(apbsProcess);
            reportFailure(exc, "process interrupted");
            return;
        }
    }
    
    private void logProcessOutput(ExternalProcessManager process) {
        isimWrapper.appendToLog("Standard error stream: \n\n");
        isimWrapper.appendToLog("************************************ \n\n");
        isimWrapper.appendToLog(process.getStdErr());
        isimWrapper.appendToLog("\n\n************************************ \n\n");
        
        isimWrapper.appendToLog("Standard output stream: \n\n");
        isimWrapper.appendToLog("************************************ \n\n");
        isimWrapper.appendToLog(process.getStdOut());
        isimWrapper.appendToLog("\n\n************************************ \n\n");    
    }
    
    String createAPBSInput() {
        // String pqrFileName = apbsParameters.pqrInputFile.getAbsolutePath(); // danger - might contain spaces

        String pqrFileName = apbsParameters.getPqrInputFile().getName();
        String moleculePotentialFileName = apbsParameters.getPotentialOutputFile().getName();
        // Remove ".dx" suffix, apbs will add it automatically
        moleculePotentialFileName = moleculePotentialFileName.replaceAll("\\.dx$", "");
        
        DecimalFormat threeDecimalPlaces = new DecimalFormat("#.###");
        
        int dimeX = apbsParameters.getDimension()[0];
        int dimeY = apbsParameters.getDimension()[1];
        int dimeZ = apbsParameters.getDimension()[2];
        
        String glenX = threeDecimalPlaces.format(apbsParameters.getGridLength()[0]);
        String glenY = threeDecimalPlaces.format(apbsParameters.getGridLength()[1]);
        String glenZ = threeDecimalPlaces.format(apbsParameters.getGridLength()[2]);
        
        return 
            "read \n" +
            "    mol pqr " + pqrFileName + "\n" +
            "end\n" +
            "\n" +
            "elec\n" +
            "    mg-manual\n" +
            
            // dime options are 65, 97, 129, and 161 (for nlev = 4)
            "    dime  "+dimeX+" "+dimeY+" "+dimeZ+"\n" +
            "    nlev 4\n" +
            "    glen "+glenX+" "+glenY+" "+glenZ+"\n" +
            "    gcent mol 1\n" +
            "    mol 1\n" +
            "    npbe\n" +
            "    bcfl sdh\n" +
            "    ion  1 0.000 2.0   # Zero ionic strength for initial run\n" +
            "    ion -1 0.000 2.0\n" +
            "    pdie " + threeDecimalPlaces.format(apbsParameters.getMoleculePermittivity()) + "\n" +
            "    sdie " + threeDecimalPlaces.format(apbsParameters.getSolventPermittivity()) + "\n" +
            "    srfm smol\n" +
            "    chgm spl2\n" +
            "    srad 1.4\n" +
            "    swin 0.3\n" +
            "    temp " + threeDecimalPlaces.format(apbsParameters.getTemperature()) + "\n" +
            "    gamma 0.105\n" +
            "    calcenergy no\n" +
            "    calcforce no\n" +
            "    write pot dx " + moleculePotentialFileName + "\n" +
            "end\n" +
            "\n" +
            "quit\n";
    }

    void reportFailure(Exception exc, String msg) {
        exc.printStackTrace();
        stepParameters.task.setStatus(DependentTask.FAILED, msg);
        isimWrapper.appendToLog("APBS process failed: " + msg + "\n");;
        return;        
    }
}
