package com.sun.solarisinstall.panels;

import java.io.*;
import java.util.*;
import com.sun.wizards.core.*;

class PFInstall {
  protected Vector pfOutput = null;
  protected boolean success = false;
  static String baseDir = null;
  static String installDir = null;
  private int EXPORTHOMESLICE = 7;
  private boolean upgrade = false;
  private static String install_type = "";
  private static String backup_media_type = "";

  // constructor
  //
  // Tests the profile, by running pfinstall in dryrun mode.
  //   filename: path to the partition to test
  //
  public PFInstall() {
    this(ProfileConstants.installDir+"/testProfile");
  }

  public PFInstall(String profilePath) {
    String line = null;
    String errline = null;
    String pfFile = "/tmp/pfinstall.out";
    pfOutput = new Vector();
    String swapdisk = " -s /dev/dsk/" + PartitionInfo.getDefaultSwapSlice();
    String swapfile = " -f /swap2 ";
    Profile profile;
   
    
    try {
        String cmd = 
	  "/usr/sbin/install.d/pfinstall -D " +
	  swapdisk + swapfile + profilePath;
        Process p;
        String[] envp = new String[2];
        envp[0] = "LANG=C";
        envp[1] = "LC_ALL=C";
        if (Def.DEBUG)System.out.println(cmd + " started");
        p = Runtime.getRuntime().exec(cmd, envp);
        BufferedReader cmdOutput =
	  new BufferedReader(new InputStreamReader(
		           new BufferedInputStream(p.getInputStream())));
        DataOutputStream pfOutStream = 
	  new DataOutputStream(new BufferedOutputStream(
			   new FileOutputStream(pfFile)));
	BufferedReader errOutput = 
	  new BufferedReader(new InputStreamReader(
		           new BufferedInputStream(p.getErrorStream())));
	parseProfile(profilePath);
	if (install_type.equals("initial_install")) {
	  while (((line = cmdOutput.readLine()) != null) ||
		 (errline = errOutput.readLine()) != null) {
	    if ( errline != null) {
	      System.err.println(errline);
	    }
	    if (line != null) {
	      if (line.startsWith("Disk layout for selected")) {
		do {
		  if (errline != null) {
		    System.err.println(errline);
		  }
		  if (line != null) {
		    if (Def.DEBUG) System.out.println(line);
		    pfOutput.addElement(line);
		    pfOutStream.writeBytes(line+"\n");
		  }
	      } while ((((line = cmdOutput.readLine()) != null) ||
			(errline = errOutput.readLine()) != null) &&
		       (!line.startsWith("Disk layout for unselected")) &&
		       (!line.startsWith("Verifying disk configuration")));
	      }
	      pfOutStream.writeBytes(line+"\n");
	      if (Def.DEBUG) System.out.println(line);
	    }
	  }
	} else 
	  if (install_type.equals("upgrade")) {
	    boolean printedChecking = false;
	    upgrade = true;
	    // no backup media specified, look to see if it required.
	    if (backup_media_type.equals("")) {
	      while (((line = cmdOutput.readLine()) != null) ||
		     (errline = errOutput.readLine()) != null) {
		if (line != null) {
		  if (line.startsWith("File systems with insufficient space")) {
		    do {
		      if (errline != null) {
			System.err.println(errline);
		      }
		      if (line != null) {
			if (Def.DEBUG) System.out.println(line);
			
			pfOutput.addElement(line);
			pfOutStream.writeBytes(line+"\n");
		      }
		    } while ((((line = cmdOutput.readLine()) != null) || 
			      (errline = errOutput.readLine()) != null) && 
			     (!line.startsWith("Remaining file systems")));
		  }
		  if (line.startsWith("Space required in each file system is")) {
		    printedChecking = false;
		  }
		  if (Def.DEBUG && 
		      line.startsWith("Checking file system space") &&
		       !printedChecking) {
		    System.out.println("Checking filesystem space: started");
		    printedChecking = true;
		  } else
		    if (Def.DEBUG && !printedChecking)
		      System.out.println(line);
		  pfOutStream.writeBytes(line+"\n");
		}
	      }
	      // backup specified, look for error message
	    } else {
	      while (((line = cmdOutput.readLine()) != null) ||
		     (errline = errOutput.readLine()) != null) {
		if (line != null) {
		  if (line.startsWith("Space required in each file system is")) {
		    printedChecking = false;
		  }
		  if (Def.DEBUG && 
		      line.startsWith("Checking file system space") &&
		       !printedChecking) {
		    System.out.println("Checking filesystem space: started");
		    printedChecking = true;
		  } else
		    if (Def.DEBUG && !printedChecking)
		      System.out.println(line);
		}
		if (errline != null) {
		  if (errline.startsWith("ERROR: Autolayout was unsuccessful")) {
		    pfOutput.addElement(errline);
		  }
		}
	      }
	    }
	  }
	p.waitFor();

	if (p.exitValue() > 1) {
	  //throw new Exception("Pfinstall failed. Exit stat= " + p.exitValue());
	  if (Def.DEBUG) System.out.println("Pfinstall failed. Exit stat= " + p.exitValue());
	  success = false;
	} else {
	  if (Def.DEBUG) System.out.println(cmd + " ended");
	  success = true;
	}
	dispose(p);
    } catch (IOException ex) {
      success = false;
      System.err.println(ex);
    } catch (InterruptedException iex) {
      success = false;
      System.err.println(iex);
    } catch (Exception ex) {
      success = false;
      System.err.println(ex);
    }
  }


  public boolean successful() {
    return success;
  }


  //
  // Parse Pfinstall output.
  //   After a successful pfinstall dryrun, the vector pfOutput contains
  //   the section of the output which describes the disk configuration.
  //   For example:
  //
  // Disk layout for selected disks
  //
  // Disk c0t1d0
  //                           Solaris Slice Table
  //       Slice     Start  Cylinder    MB  Preserve  Directory           
  //     -------  --------  --------  ----  --------  ----------      
  //        0         0       586   161        no      /                  
  //        1       586       118    33        no     swap                
  //        2         0      1866   511        no     overlap              
  //        3       704       165    46        no     /var                 
  //        5       869       267    74        no     /opt    
  //
  //       Usable space: 511 MB (1866 cylinders)   Free space: 200 MB
  //
  //  For each selected disk.  This routine will parse this output and
  //  modify the filesys object to contain the proper slice and size info.
  //  If pfinstall describes any filesystems which are not currently
  //  objects those will be created. 
  //
  // boolean def is true if parititoning was set to default and thus
  // disks will be filled.  otherwise we must fill the disks
  //
  public void parsePfinstall(ProfileFile file) {
    if ((!success) || (pfOutput == null)) {
      return;
    }

    if (!upgrade) {
      // First, remove all old partition info:
      Vector oldfsinfo = file.getPartitionList();
      file.clearFilesys();
    
      boolean def = !file.isExplicitPartitioning();
      boolean printDisks = file.getPrintUsedisk();

      DiskInfo dinfo = new DiskInfo();
      Vector usedfs = new Vector();
      Vector disks = new Vector();
      Vector extra = new Vector();
      Hashtable newfs = new Hashtable();
      String line;
      boolean resizeNecessary = false;

      if (! printDisks) {
	file.removeAllUsedisk();
      }

      for (int i=0; i < pfOutput.size(); i++) {
	line = (String) pfOutput.elementAt(i);
	line = line.trim();
      
	if (line.startsWith("Disk c")) {
	  String disk = line.substring(line.indexOf('k')+1).trim();
	
	  // Skip header and pull out slice information
	  while (!((String) pfOutput.elementAt(i)).trim().equals("Solaris Slice Table")) {
	    i++;
	  }
	  i = i + 3;
	  int count = 0;
	  String largest = null;
	  int largesize = 0;
	  int exportsize = 0;
	  while (((line = (String)pfOutput.elementAt(i++)) != null) && (!line.endsWith("MB"))) {
	    line = line.trim();
	  
	    if (line.length() == 0) {
	      continue;
	    }
	    Vector fields = new Vector();
	    fields = getFields(line);
	    if (fields.size() > 5) {
	      String name = (String) fields.elementAt(5);
	      if (!name.equals("overlap")) {
		String slice = (String) fields.elementAt(0);
	      
		int fileSize = Integer.parseInt((String) fields.elementAt(3));

		// Often pfinstall adds one -- if it did this, subtract it
		int oldfssize = 0;
		for (int item=0; item < oldfsinfo.size(); item++) {
		  PartitionInfo pinfo = (PartitionInfo) oldfsinfo.elementAt(item);
		  if (pinfo.getName().equals(name)) {
		    oldfssize = pinfo.getSize();
		    break;
		  }
		}
		if (oldfssize + 1 == fileSize) {
		  fileSize--;
		}
	      
		// Add the file system
		String size = String.valueOf(fileSize);
		file.addFilesys("filesys\t"+disk+"s"+slice+"\t"+size+"\t"+name);
		count++;
	      
		// Keep track of the largest filesystem in case we want to
		// add extra space.  If /export is available we add extra space
		// to that isntead
		try {
		  int sz = Integer.parseInt(size);
		  if (name.equals("/export")) {
		    largesize = -1;
		    largest = name;
		    exportsize = sz;
		  } else if ((largesize != -1) && (sz > largesize)) {
		    largesize = sz;
		    largest = name;
		  }
		} catch (NumberFormatException nfe) {
		  System.err.println("Size is not a number");
		}

		usedfs.addElement(name);
		if (! printDisks) {
		  file.addUsedisk(disk);
		}
		disks.addElement(disk);
	      }
	    }
	  }
	
	  // Use up free space
	  int size = dinfo.getFreeSpace(disk);
	  if ((size >= Profile.MIN_FILESYS_SIZE) && (count < 7)) {
	    newfs.put(disk, new Integer(size));
	  } else if (size > 0) {
	    // Can't create an /export/home filesys because would either be
	    // too small or we're out of disk slices.
	    if (size > 5) {
	      // In this case a resize is necessary even if we're using
	      // default since this means we've reached our 7 slice limit
	      // and have space left or have only a small amount of disk space
	      // left, so we should add it to /export if avail.
	      // or largest otherwise.  If size is really small we assume
	      // it's due errors in calculating usable disk space and do
	      // nothing.
	      resizeNecessary = true;
	    }
	    if (largesize == -1) {
	      largesize = exportsize;
	    }
	    largesize += size;
	    extra.addElement(largest+" "+largesize);
	  }
	}
      }

      // Now we create all the file systems in newfs
      Enumeration enum = newfs.keys();
      while (enum.hasMoreElements()) {
	String disk = (String) enum.nextElement();
	int size = ((Integer) newfs.get(disk)).intValue();

	// Get a unique /export/home file system name
	String basename = "/export/home";
	String name;
	if (usedfs.contains(basename)) {
	  int ehnum = 0;
	  name = basename+String.valueOf(ehnum++);
	  while (usedfs.contains(name)) {
	    name = basename+String.valueOf(ehnum++);
	  }
	} else {
	  name = basename;
	}
	// /export/home is always on slice 7
      
	int slice = EXPORTHOMESLICE;
	file.addFilesys("filesys\t"+disk+"s"+slice+"\t"+size+"\t"+name);
	usedfs.addElement(name);
	if (!disks.contains(disk)) {
	  disks.addElement(disk);
	}
      }

      
      if (! def) {
	// For any disks that are in usedisk but don't have any filessystems
	// on them we add an /export/home partition
	Enumeration e = file.getAllUsedisk();
	while (e.hasMoreElements()) {
	  String disk = (String) e.nextElement();
	  if (!disks.contains(disk)) {
	    // Get a unique /export/home file system name
	    String basename = "/export/home";
	    String name;
	    if (usedfs.contains(basename)) {
	      int ehnum = 0;
	      name = basename+String.valueOf(ehnum++);
	      while (usedfs.contains(name)) {
		name = basename+String.valueOf(ehnum++);
	      }
	    } else {
	      name = basename;
	    }
	  
	    int size = dinfo.getDiskSize(disk);
	    file.addFilesys("filesys\t"+disk+"s7\t"+size+"\t"+name);
	    usedfs.addElement(name);
	  }
	}
      }
      
      /* We now have an almost complete new profile, 
      * but we have to deal with any extra free
      * space on disks where there were no free slices
      * or the space was too small to deserve an
      * extra partitions
      */
      if ((! def) || resizeNecessary) {
	for (int i=0; i < extra.size(); i++) {
	  String name = (String) extra.elementAt(i);
	  String size = name.substring(name.indexOf(' ')).trim();
	  name = name.substring(0, name.indexOf(' ')).trim();
	  try {
	    int sz = Integer.parseInt(size);
	    PartitionInfo fs = file.findFilesys(name);
	    if (fs != null)
	      fs.setSize(sz);
	  } catch (NumberFormatException nfe) {
	    System.err.println("Number format exception parsing 'extra'");
	  }
	}
      }
    } else {
      // Do an upgrade parse
      String line;
      for (int i=0; i < pfOutput.size(); i++) {
	line = (String) pfOutput.elementAt(i);
	line = line.trim();
	if (line.startsWith("/")) {
	  if (Def.DEBUG)
	    System.out.println("PFInstall: setting backupReq to true");
	  file.setBackupReq(true);
	  return;
	} else if (line.startsWith("ERROR: Autolayout was unsuccessful")) {
	  if (Def.DEBUG)
	    System.out.println("PFInstall: backup media specified, autolayout was unsuccessful");
	  file.setBackupReq(true);
	  return;
	}
      }
      file.setBackupReq(false);
    }
  }


  public Vector getDiskList() {
    if (pfOutput == null) {
      return null;
    }

    Vector v = new Vector();
    String line;
    for (int i=0; i < pfOutput.size(); i++) {
      line = (String) pfOutput.elementAt(i);
      line = line.trim();
      
      if (line.startsWith("Disk c")) {
	String disk = line.substring(line.indexOf('k')+1).trim();
	v.addElement(disk);
      }
    }

    return(v);
  }


  public Vector getFields(String line) {
     int endPos = 0;
     Vector fields  = new Vector();
     String field;

     while (endPos != -1) {
       line = line.substring(endPos, line.length());
       line = line.trim();
       endPos = line.indexOf(' ');

       if (endPos != -1) 
           field = line.substring(0, endPos);
       else
           field = line.substring(0, line.length());
       fields.addElement(field.trim());
    }
     return fields;
  }

  private void dispose( Process p)
    {
      try {
	p.getInputStream().close();
	p.getOutputStream().close();
	p.getErrorStream().close();
	p.destroy();
      } catch (IOException ex) {
	p.destroy();
      }
    }

  private void parseProfile(String profilePath) {
    String line;
    File profileFile = new File(profilePath);
    BufferedReader profileRdr;
    FileInputStream profileStream;
    try {
      profileStream = new FileInputStream(profileFile);
      profileRdr = new BufferedReader(new InputStreamReader(profileStream));
      while (((line = profileRdr.readLine()) != null)) {
	if (line.startsWith("install_type")){
	  StringTokenizer tok = new StringTokenizer(line);
	  String dummy = tok.nextToken();
	  install_type = tok.nextToken();
	  install_type = install_type.trim();
	}
	if (line.startsWith("backup_media")) {
	  StringTokenizer tok = new StringTokenizer(line);
	  String dummy = tok.nextToken();
	  backup_media_type = tok.nextToken();
	  backup_media_type = backup_media_type.trim();
	}
      }
    } catch(IOException e) {
      System.err.println("IOException in parseProfile()");
    }
  }
}
