/* 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 Oct 25, 2005
 * Original author: Christopher Bruns
 */
package org.simtk.isimsu;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;
import org.simtk.gui.*;

/**
  * @author Christopher Bruns
  * 
  * Manages external process whose executable is named and stored in a directory of a jar file 

 */
public class JarDefinedExecutableThread extends ExternalProcessThread 
implements MonitoredProcess 
{
    private boolean isUnpackedAlready = false;
    private boolean isCancelled = false;
    private boolean isFailed = false;
    private boolean isSuccessful = false;
    
    /**
     * Make sure executable file exists.  Unpack it if it doesn't.
     * Get the platform-specific command name from a file in the jar file.
     * 
     * @returns Vector of Strings with executable name and initial arguments
     */
    Vector unpackExecutable(
            String jarDirectory, 
            String executableNameFileName,
            File programDirectory
            ) throws IOException {
        
        // TODO getResource returns null on a directory in a jar file
        // URL directoryURL = getClass().getClassLoader().getResource(jarDirectory);
        // System.out.println("Executable directory URL = " + directoryURL);

        // What is the name of the executable?
        // First open a "name file" that contains the name of the executable
        // This indirection is to the variety of names that an executable can have
        URL exeNameURL = getClass().getClassLoader().getResource(jarDirectory + "/" + executableNameFileName);
        if (exeNameURL == null) throw new IOException("Unable to find executable-name file " + executableNameFileName);

        // for debugging
        System.out.println("jar directory = " + jarDirectory);
        System.out.println("executable file name file = " + executableNameFileName);
        System.out.println("program directory = " + programDirectory);
        System.out.println("Exe file name-file URL = " + exeNameURL);
        
        InputStream exeNameStream = exeNameURL.openConnection().getInputStream();
        BufferedReader exeNameReader = new BufferedReader(new InputStreamReader(exeNameStream));

        Vector initialArgs = new Vector();

        // Arguments are on separate lines in the executable name file
        String line;
        while ( (line = exeNameReader.readLine()) != null) {
            initialArgs.add(line);
        }
        if (initialArgs.size() < 1) throw new FileNotFoundException("Failed to parse name file " + executableNameFileName);

        // If the file is already unpacked (for some reason?), we don't need to unpack again.
        if (isUnpackedAlready) return initialArgs;

        // The first LINE of the file-name file contains the name of the executable, e.g. "python"
        // (subsequent lines contain arguments?) e.g. "pdb2pqr.py"
        String exeName = (String) initialArgs.elementAt(0); // e.g. "python" or "pdb2pqr.exe"
        
        // This is no good because it is not there in the case of python on mac and linux
        // File exeFile = new File(programDirectory, exeName);
        // if (exeFile.exists()) return initialArgs; // It's already unpacked
        
        // Unpack directory from jar file

        // Find directory in jar file
            
        // e.g. "jar:file:/C:/cygwin/home/cmbruns/eclipse/ISIM-SU/source/webstart/pdb2pqr_win32.jar!/pdb2pqr"
        // URL   exeNameURL = getClass().getClassLoader().getResource(jarDirectory + "/" + executableNameFileName);
        // URL directoryURL = getClass().getClassLoader().getResource(jarDirectory + "/");
        
        // String directoryURLString = directoryURL.toString();

        // String jarURLEnd = "!/" + jarDirectory; // e.g. "!/pdb2pqr"
        
        String exeNameURLString = exeNameURL.toString();
        try {
            if (exeNameURLString.startsWith("jar:file:")) {
                // Directory is inside of a jar file
                
                JarURLConnection exeNameJuc = (JarURLConnection) exeNameURL.openConnection();
                JarFile jarFile = exeNameJuc.getJarFile();

                // Consider each entry in the jar file, until we find the one we want
                Enumeration entries = jarFile.entries();
                jarEntryLoop: while (entries.hasMoreElements()) {
                    JarEntry entry = (JarEntry) entries.nextElement();

                    // Only look at entries in the "pdb2pqr" directory
                    if (! entry.getName().startsWith(jarDirectory))
                        continue jarEntryLoop;
                    if (entry.isDirectory()) continue jarEntryLoop;
                    String outFileName = entry.getName().substring(jarDirectory.length() + 1);
                    if (outFileName.length() < 1) continue jarEntryLoop;
                    
                    // Copy this file to the program area
                    File outFile = new File(programDirectory, outFileName);
                    StreamCopy.copy(jarFile.getInputStream(entry), new FileOutputStream(outFile));
                    
                    // Try to make executable file executable
                    if (outFile.getName().equals(exeName)) {
                        System.out.println("Found executable " + exeName);
                        try {
                            Process setExecutableBitProcess = Runtime.getRuntime().exec("chmod +x " + exeName, null, programDirectory);
                            setExecutableBitProcess.waitFor();
                            if (setExecutableBitProcess.exitValue() == 0) {
                                System.out.println("Set executable bit succeeded on " + exeName);
                            }
                            else {
                                System.out.println("Set executable bit failed on " + exeName);
                            }
                        }
                        catch (Exception exc) {} // It's OK if attempt fails, e.g. on Windows
                    }
                }
                // if (exeFile.exists()) 

                // Declare success at this point
                isUnpackedAlready = true;
                return initialArgs; // It's already unpacked

                // TODO but the executable might not be there in the case of python
                // else throw new FileNotFoundException("Unable to find executable file " + exeFile + "in directory " + programDirectory + ", even after unpacking " + jarDirectory);
            }
            else {
                setFailed();
                throw new FileNotFoundException("Could not understand URL: " + exeNameURLString);
            }                    
        }
        catch (IOException exc) {
            setFailed();
            throw exc;
        }    
    }

    public void setFailed() {
        isFailed = true;
        isSuccessful = false;
    }

    public void setSuccessful() {
        isSuccessful = true;
        isFailed = false;
    }
    
    
    
    public int getMinimum() {return 0;} // TODO
    public int getMaximum() {return 0;} // TODO
    public int getProgress() {return 0;} // TODO
    public boolean isFailed() {return this.isFailed;}
    public boolean isSuccessful() {return this.isSuccessful;}    
    public synchronized void abort() {
        isCancelled = true;
        interrupt();
    }
}
