package com.sun.solarisinstall.postinstall.tasks;

import java.io.*;
import java.util.*;
import java.lang.*;
import com.sun.wizards.core.*;
import com.sun.wizards.builder.resolver.*;
import com.sun.install.tasks.*;
import com.sun.install.products.*;
import com.sun.install.InstallResources;

/**
 * The ScriptUnit is used to run a script at install time. The unit
 * is added to a Product tree like other install units (e.g. PkgUnit).
 * At install time, it retrieves the script, then instantiates a RunCmd
 * object to run the script. Note that uninstall is pretty meaningless
 * for this unit.
 *
 * @author Linh Vuong
 * @version 1999.02.25
 */
public class ScriptUnit extends FileUnit
{
  /**
   * A list of scripts that have been run by this unit.
   */
  private Vector runScripts = new Vector();

  /**
   * This list serves as the list of scripts to be run
   * 
   * The files vector looks like this:
   *
   *<pre><blockquote>
   * Vector { Vector { path, name1, name2, ..., nameN},
   *          Vector { path, name1, name2, ..., nameN},
   *          ...
   *          Vector { path, name1, name2, ..., nameN},
   *     ...}
   *</blockquote></pre>
   */
  private Vector files = new Vector();

  /**
   * Permissions of files.  Not remembered once archive is written.
   * (Carried over from FileUnit. Not used. Should be removed)
   */
  private transient Hashtable forcedPermissions = new Hashtable();

  /**
   * The total length of time this unit will take during install
   * (for now equate this to total file size of scripts)
   */
  private int unitSize = 0;

  /**
   * Create a ScriptUnit
   */
  public ScriptUnit()
  {
  }

  /**  
   * Update this  file.
   * @param targets The target properties (set via <code>setProperty()</code>) to update.
   * @return <code>true</code> if the refresh was sucessful, <code>false</code>otherwise.
   .e.*/
  public boolean refresh(Vector targets)
  {
    return true;
  }
    
  /**
   * Install (i.e. run) the scripts
   *
   * @param state		The wizard state.
   * @return Whether the install was successful or not.
   */
  public boolean install(WizardState state)
  {
    /*
     * Get the progress space from the wizard state.
     */
    int subtotal = (Integer.valueOf
                 (((String)state.getData("install.subtotal")))).intValue();
    int total=(Integer.valueOf
                 (((String)state.getData("install.total")))).intValue();

    Product installProduct = (Product)state.getData(InstallConstants.productTree);
    ProductTask task = (ProductTask) installProduct.getTask();


    int installCount = 0;
    int fileCount = 0;
    // calculate total number of files to be installed
    for(int pathCount = 0; pathCount < files.size(); pathCount++)
      fileCount += ((Vector)files.elementAt(pathCount)).size() - 1;

    for(int pathCount = 0; pathCount < files.size(); pathCount++)
    {
      Vector fileVector = (Vector)files.elementAt(pathCount);
      String path = (String)fileVector.elementAt(0);
      for (int index = 1; index < fileVector.size(); index++)
      {
        String filename = (String)fileVector.elementAt(index);
        /*
         * denormalize name
         */
        filename = filename.replace('/', File.separatorChar);
        /*
         * Ready to install
         */
        if (!Product.DRY_RUN)
        {
          RunCmd cmd = new RunCmd("/usr/bin/sh " +
            path + File.separator + filename,
            true);	// collect output while running
          if (cmd.getResult() != 0)
          {
            Product.writeToLog(state, "install.logfile",
              MsgCat.getString("com.sun.install.Install",
              InstallResources.MSG_PRODUCT_INSTALL_FAILED)+
                ": "+ filename);
	   
            /*
             * Indicate that the installation failed.
             */
            return(false);
          }
          /*
           * Add the filename to the list of run scripts
           */
          runScripts.insertElementAt(filename, 0);
          Product.writeToLog(state, "install.logfile",
          MsgCat.getString("com.sun.install.Install",
            InstallResources.MSG_INSTALLED)+
              ": "+ filename);
        }
        /*
         * Update the progress and the subtotal.
         */
        double percent = (double)(++installCount) / (double)fileCount;
        if (task != null)
          task.updateProgress((int)(((double)(subtotal+percent)/total)*100.0));
        else
           System.out.println("ScriptUnit: null Task");
      }
    }
    
    /*
     * Done installing files, update progress
     */
    subtotal++;
    state.setData("install.subtotal",String.valueOf(subtotal));
    
    return(true);
  }

  /**
   * Uninstall this Leaf.  This is run by the uninstaller at uninstall
   * time. 
   *
   * @return Whether the uninstall was sucessful or not.
   */
  public boolean uninstall()
  {
    WizardState state = getState();
    /*
     * Get the progress space from the wizard state.
     */
    int subtotal = (Integer.valueOf
                 (((String)state.getData("install.subtotal")))).intValue();
    int total=(Integer.valueOf
                 (((String)state.getData("install.total")))).intValue();
    ProductTask task = (ProductTask)((Product)state.getData(InstallConstants.productTree)).getTask();

    /*
     * Calculate the number of files to be removed.
     */
    int fileCount = runScripts.size();
    /*
     * Try to remove all of the files that were supposed to be installed.  The base
     * directory is the directory where the uninstall.class file was found.
     */
    int filesRemoved = 0;
    for(int index = 0; index < fileCount; index++)
    {
      Object[] msgArgs = {runScripts.elementAt(index)};
      Product.writeToLog(state, "uninstall.logfile",
        MsgCat.getString("com.sun.install.Install", 
          InstallResources.MSG_UNINSTALLING_NOTICE, msgArgs));
      filesRemoved++;
 
      /*
       * Update the progress.
       */
      double percent = (double)(filesRemoved) / (double)fileCount;
      if (task != null)
        task.updateProgress((int)(((double)(subtotal+percent)/total)*100.0));
      else
        System.out.println("ScriptUnit: null Task");
    }
    subtotal++;
    state.setData("install.subtotal",String.valueOf(subtotal));

    return(true);
  }


  /**
   * Adds the specified file or directory to the list of files to be installed
   * 
   * @param path The path to the desired file(s) to be added. 
   *             This can be a file or a directory.  If it is a directory, this
   *             method will recursively add all files in that directory,
   *             and the files will be laid down in the same relative directories
   *             as when collected.  The <code>path</code>argument will not be
   *             remembered.
   *
   * @param filename The relative path to the desired file to be added. 
   *             This can be a file or a directory.  If it is a directory, this
   *             method will recursively add all files in that directory.
   *      The filenames should be specified relative
   *      the the supplied path parameter.  
   *
   * @param forcedPermissions A hashtable consisting of filenames and
   *      the desired permissions or <code>null</code> if no forced
   *      permissions should be applied..  These permissions will
   *      override any other permissions for this file at
   *      install-time.
   *
   */
  public void addFile(String path, String fileName, Hashtable forcedPermissions)
    {
      File file = new File(path, fileName);
      if(file.exists() && file.canRead())
	{
	  if(file.isFile())
	    {
	      addRegularFile(path, fileName, forcedPermissions);
	    }
	  else if(file.isDirectory())
	    {
	      addDirectory(path, fileName, forcedPermissions);
	    }
	}
    }

  /**
   * Add the specified file from the specified path.
   *
   * @param path The path to this directory containing this file
   * @param filename The filename of the file to be added (must be a
   *        file, rather than a directory)
   *
   * @param forcedPermissions A hashtable consisting of filenames and
   *     the desired permissions or <code>null</code> if no forced 
   *     permissions should be applied..  These permissions will
   *     override any other permissions for this file at
   *     install-time.  The filename should be specified relative
   *     to the supplied path parameter.  
   */
  private void addRegularFile(String path, String filename, Hashtable forcedPermissions)
    {
      Vector pathVector = getPathVector(path);
      if (forcedPermissions != null)
      {
	 Enumeration forcedKeys = forcedPermissions.keys();
	 while (forcedKeys.hasMoreElements())
	 {
	    String key = (String)forcedKeys.nextElement();
	    this.forcedPermissions.put(key, forcedPermissions.get(key));
	 }
      }

      /*
       * Create a new vector with the specified path.  
       */
      if(pathVector == null)
	{
	  pathVector = new Vector();
	  pathVector.addElement(path);
	  files.addElement(pathVector);
	}

      /*
       * Create file, normalized
       */
      filename = filename.replace(File.separatorChar, '/');
      pathVector.addElement(filename);
    }

  /**
   * Add the specified directory from the specified path.
   * For example, to specify the directory "/net/lando/export/dist"
   * you would pass "/net/lando/export" as the path, and "dist" as
   * the filename.
   *
   * @param path The path to the directory containing this directory.
   * @param dirname The name of the directory to be added.
   * @param forcedPermissions A hashtable consisting of filenames and
   *      the desired permissions or <code>null</code> if no forced *
   *      permissions should be appliet..  These permissions will
   *      override any other permissions for this file at
   *      install-time.  The filenames should be specified relative
   *      the the supplied path parameter.  
   */
  private void addDirectory(String path, String dirname, Hashtable forcedPermissions)
    {
      /*
       * Read the directory, add all the files from the directory,
       * then read all directories recursively.
       */
      File dir = new File(path, dirname);
      String[] dirFiles = dir.list();
      if(dirFiles != null)
	{
	  for(int index = 0; index < dirFiles.length; index++)
	    {
	      File file = new File(path + File.separator + dirname + File.separator + dirFiles[index]); 
	      if(file.isFile())
		{
		  /*
		   * Add this file to the resource.
		   */
		  addRegularFile(path, dirname + File.separator + dirFiles[index], forcedPermissions);
		}
	      else if(file.isDirectory())
		{
		  /*
		   * Recursively add all files from the directory.
		   */
		  addDirectory(path, dirname + File.separator + dirFiles[index], forcedPermissions);
		}
	    }
	}
    }


  /**
   * Retrieves the vector that describes the files in the specified
   * path from the <code>files</code> vector.
   *
   * @param path The path contained at the beginning of the desired vector.
   * @return The desired vector with the files from the specified path.  */
  private Vector getPathVector(String path)
    {
      for(int index = 0; index < files.size(); index++)
	{
	  Vector pathVector = (Vector)files.elementAt(index);
	  if(pathVector.size() > 0)
	    {
	      String testPath = (String)pathVector.elementAt(0);
	      if(testPath.equals(path))
		{
		  return(pathVector);
		}
	    }
	}
      return(null);
    }

  /**
   * Represent this object as a string.
   *
   * @return A description of this object.
   */
  public String toString()
  {
    return "ScriptUnit";
  }

  /**
   * Add the runtime resources required by this class.
   *
   * @param resourceVector The vector containing all of the required resources
   *                       for this class
   */
  public void addRuntimeResources(Vector resourceVector)
    {
      super.addRuntimeResources(resourceVector);

      resourceVector.addElement(new String[] {null, 
	"com.sun.solarisinstall.postinstall.tasks.ScriptUnit"});

      resourceVector.addElement(new String[] {null, 
	"com.sun.install.products.RunCmd"});
    }
}
