/* ExternalProgram.java
 *
 * created: Tue Jan 26 1999
 *
 * This file is part of Artemis
 *
 * Copyright (C) 1998,1999,2000  Genome Research Limited
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header: /nfs/disk222/yeastpub/Repository/powmap/diana/ExternalProgram.java,v 1.11 2000/07/24 11:04:43 kmr Exp $
 **/

package diana;

import AppGlobal;

import uk.ac.sanger.pathogens.*;
import uk.ac.sanger.pathogens.embl.EntryInformationException;

import java.io.*;

/**
 *  Each object of this class represents one external executable or script,
 *  and contains methods for invoking it.
 *
 *  @author Kim Rutherford
 *  @version $Id: ExternalProgram.java,v 1.11 2000/07/24 11:04:43 kmr Exp $
 **/

public abstract class ExternalProgram {
  /**
   *  Create a new ExternalProgram object for the program with given name.
   *  @param name The name of the program.
   **/
  public ExternalProgram (final String name) {
    this.name = name;
  }

  /**
   *  Run this program with the options that were set with setOptions ().
   *  @param features The program will be run for each of these features.
   *  @return The Process object that is running the search or null if there
   *    is nothing to be done (for example if the FeatureVector is empty).
   *  @exception IOException Thrown if an IO error occur while trying to run
   *    the program (eg the program could not be found).
   *  @exception ExternalProgramException Thrown if there is a non-IO error
   *    while attempting to run the program.
   *  @exception ReadOnlyException Thrown if the one features is in a
   *    read-only entry.
   *  @exception EntryInformationException Thrown if there is no qualifier
   *    that can be used to store the output filename
   **/
  public abstract Process run (final FeatureVector features)
      throws IOException, ExternalProgramException, EntryInformationException,
      ReadOnlyException;

  /**
   *  Return the name of this ExternalProgram, as passed to the constructor.
   **/
  public String getName () {
    return name;
  }

  /**
   *  Start a new external program.
   *  @param name the name of the program to start.
   *  @param arguments the arguments to pass to the new program.  (can be null)
   *  @return A Process object for the new program.
   **/
  public static Process startProgram (final String name,
                                      final String [] arguments)
      throws SecurityException, ExternalProgramException, IOException {

    // try to get the program from the artemis directory/jar file
    final InputStream code_stream =
      AppGlobal.class.getResourceAsStream (name);

    if (code_stream == null) {

      // the code isn't in the artemis directory/jar file so just call exec()

      final String [] real_arguments;

      if (arguments == null) {
        real_arguments = new String [1];
      } else {
        real_arguments = new String [arguments.length + 1];

        for (int i = 0 ; i < arguments.length ; ++i) {
          real_arguments[i + 1] = arguments[i];
        }

      }

      real_arguments[0] = name;

      return Runtime.getRuntime ().exec (real_arguments);

//      throw new ExternalProgramException ("could not find this program: " +
//                                          name);
    } else {
      final String [] sh_arguments;

      if (arguments == null) {
        sh_arguments = new String [2];
      } else {
        sh_arguments = new String [arguments.length + 2];

        for (int i = 0 ; i < arguments.length ; ++i) {
          sh_arguments[i + 2] = arguments[i];
        }
      }

      sh_arguments[0] = "/bin/sh";
      sh_arguments[1] = "-s";

      final Process process = Runtime.getRuntime ().exec (sh_arguments);

      final OutputStream out_stream = process.getOutputStream ();

      final int BUFFER_SIZE = 10000;
      final byte [] buffer = new byte [BUFFER_SIZE];

      int read_size = 0;

      while ((read_size = code_stream.read (buffer)) != -1) {
        out_stream.write (buffer, 0, read_size);
      }

      code_stream.close ();
      out_stream.close ();

      return process;
    }
  }

  /**
   *  Read the number to be used with creating the next protein output file.
   *  The number is read from prog_name + "/" + file_counter_filename.
   **/
  protected int getFileNumber ()
      throws IOException {
    try {
      final FileReader file_reader =
        new FileReader (getName () + File.separatorChar +
                        file_counter_filename);

      final BufferedReader reader = new BufferedReader (file_reader);

      // the first line is a comment
      reader.readLine ();

      final String number_line = reader.readLine ();

      try {
        return Integer.parseInt (number_line);
      } catch (NumberFormatException e) {
        throw new IOException ("the " + getName () +
                               "/file_number_counter file is corrupted - " +
                               "please delete it and try again");
      } finally {
        file_reader.close ();
        reader.close ();
      }
    } catch (FileNotFoundException e) {
      // create a new file_number_counter file
      setFileNumber (1);
      return 1;
    }
  }

  /**
   *  Write the given number to the file given by new_file_name.
   **/
  protected void setFileNumber (final int new_file_number)
      throws IOException {
    final FileWriter file_writer =
      new FileWriter (getName () + File.separatorChar +
                      file_counter_filename);

    final PrintWriter print_writer = new PrintWriter (file_writer);

    print_writer.println ("# the file is machine generated - do not edit");

    print_writer.println (new_file_number);

    print_writer.close ();

    file_writer.close ();
  }

  /**
   *  Create a directory path with the given directory_name (if it does not
   *  exist).
   **/
  protected void makeDirectory (final File directory_name)
      throws IOException {
    if (directory_name.exists ()) {
      if (directory_name.isDirectory ()) {
        if (directory_name.canWrite ()) {
          // all is ok
          return;
        } else {
          throw new IOException ("Cannot write to: " +
                                 directory_name.getAbsolutePath ());
        }
      } else {
        throw new IOException (directory_name.getAbsolutePath () +
                               " is not a directory");
      }
    } else {
      directory_name.mkdirs ();
    }
  }

  /**
   *  The string to put at the end of the file that stores the counter used
   *  when creating the protein files to run the search on.  The file contains
   *  a comment line and then a line with a single integer on it.  This file
   *  is updated by setFileNumber () and read with getFileNumber ().
   **/
  final private static String file_counter_filename = "file_number_counter";

  /**
   *  The name of the external program that was passed to the constructor.
   **/
  private String name;
}
