/* *******************************************************************
 * Copyright (c) 2000-2001 Xerox Corporation, 
 *               2002 Palo Alto Research Center, Incorporated (PARC).
 * All rights reserved. 
 * This program and the accompanying materials are made available 
 * under the terms of the Common Public License v1.0 
 * which accompanies this distribution and is available at 
 * http://www.eclipse.org/legal/cpl-v10.html 
 *  
 * Contributors: 
 *     Xerox/PARC     initial implementation 
 * ******************************************************************/



package $installer$.org.aspectj;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;

/**
 * Invoke the Installer gui.
 * There are two ways to run without GUI by passing parameters to main:
 * <ol>
 * <li>pass <code>-text {pathToPropertiesFile}</code>:
 * <ul>
 * <li> "-text".equals(arg[0])</li>
 * <li> arg[1] is the path to a properties file which defines
 *   name="output.dir" value="{path to output dir}"
 *   name="context.javaPath" value="{path to JDKDIR}", i.e,.
 *   <pre>output.dir=c:/latest
 *   "context.javaPath=c:/apps/jdk1.3.1</pre></li>
 * <li>outputDir must be created and empty (i.e., no overwriting</li>
 * <li>the VM being invoked should be the target vm</li>
 * </ul>
 * </li>
 * <li>pass <code>-to {pathToTargetDir}</code>:
 * <ul>
 * <li> "-to".equals(arg[0])</li>
 * <li> arg[1] is the path to a writable install directory.
 * </li>
 * </ul>
 * </li>
 */
public class Main {
    public static void main(String[] args) {
        Options.loadArgs(args);
        boolean hasGui = true;
        Properties properties = new Properties();
        InputStream istream = null;
        try {
            istream = Main.class.getResourceAsStream(Installer.RESOURCE_DIR + "/properties.txt");
            if (istream == null) {
                System.err.println("unable to load properties.txt using Main.class - exiting");
                Main.exit(-1);
            }
            properties.load(istream);
            // when running outside GUI, load values into properties 
            // so that property-value resolution works
            // (otherwise, could just set values below).
            // XXX not sure if this indirection is actually needed.
            if (null != Options.textProperties) {
                istream.close();
                istream = new FileInputStream(Options.textProperties);
                properties.load(istream);
                hasGui = false;
            } else if (null != Options.targetDir) {
                String path = null;
                try {
                    path = Options.targetDir.getCanonicalPath();
                } catch (IOException e) {
                    path = Options.targetDir.getAbsolutePath();
                }
                String javaPath 
                    = ConfigureLauncherPane.getDefaultJavaHomeLocation();
                if (null == javaPath) {
                    System.err.println("using GUI - unable to find java");
                } else {
                    properties.setProperty("output.dir", path);
                    properties.setProperty("context.javaPath", javaPath);
                    hasGui = false;
                }
            }
        } catch (IOException ioe) {
            handleException(ioe);
        } finally {
            if (null != istream) {
                try {istream.close();}
                catch (IOException e){} // ignore
            }
        }

        try {
            String className = (String)properties.get("installer.main.class");
            //UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            Installer installer = (Installer)Class.forName(className).newInstance();
            InstallContext installerContext = new InstallContext(properties);
            installerContext.setHasGui(hasGui);
            installer.setContext(installerContext);
            if (installerContext.hasGui()) { // let context force whether or not to run gui
                installer.runGUI();
            } else {
                // set output dir and java path in context after minimal validation
                String propName = "output.dir";
                String propValue = properties.getProperty(propName);
                if (null == propValue) {
                    throw new Exception("expecting property " + propName);
                }
                String outputDirName = propValue;
                propName = "context.javaPath";
                propValue = properties.getProperty(propName);
                if (null == propValue) {
                    throw new Exception("expecting property " + propName);
                }
                String javaPath = propValue;
                File outputDir = new File(outputDirName);
                if (! outputDir.isDirectory()) {
                    throw new Exception("not a dir outputDirName: " + outputDirName
                                        + " dir: " + outputDir);
                }
                if (! outputDir.canWrite()) {
                    throw new Exception("cannot write outputDirName: " + outputDirName
                                        + " dir: " + outputDir);
                }
                InstallContext context = installer.getContext(); // todo: why not use installerContext?
                context.setOutputDir(outputDir);
                context.javaPath = new File(javaPath);
                // todo: check javaPath for ... bin/java? lib/rt.jar?
                if (! outputDir.isDirectory() || ! outputDir.canRead()) {
                    throw new Exception("invalid javaPath: " + javaPath);
                }
                // directly set context and run
                WizardPane.setContext(installerContext);
                installer.run();
            }
        } catch (Exception e) {
            handleException(e);
        }
    }

    public static void handleException(Throwable e) {
        System.out.println("internal error: " + e.toString());
        e.printStackTrace();
        Main.exit(-1);
    }

    /** indirection for System.exit - todo apply cleanup here as necessary */
    public static void exit(int value) {
        System.exit(value);
    }
} // class Main

class Options {
    public static boolean verbose = false;
    public static String textProperties = null;
    public static File targetDir = null;
    public static boolean forceError1 = false;
    public static boolean forceError2 = false;
    public static boolean forceHandConfigure = false;

    public static void loadArgs(String[] args) {
        if (args == null) return;
        for (int i=0; i<args.length; i++) {
            String arg = args[i];
            if (arg == null) continue;

            if (arg.equals("-verbose")) verbose = true;
            else if (arg.equals("-forceError1")) forceError1 = true;
            else if (arg.equals("-forceError2")) forceError2 = true;
            else if (arg.equals("-forceHandConfigure")) forceHandConfigure = true;
            else if (arg.equals("-text")) {
                if (i+1 < args.length) {
                    textProperties = args[++i];
                }
            }
            else if (arg.equals("-to")) {
                String next = "no argument";
                if (i+1 < args.length) {
                    next = args[++i];
                    File targDir = new File(next);
                    if (targDir.isDirectory() && targDir.canWrite()) {
                        targetDir = targDir;
                    }
                }
                if (null == targetDir) {
                    System.err.println("invalid -to dir: " + next);
                }
            }
        }
    }
}

/** tools installer installs the entire 1.1+ distribution */
class ToolsInstaller extends Installer {
    public String getTitle() { return "Installer for AspectJ(TM)"; }

    public String getPrefix() { return "tools"; }

    public String getReadmeFilename() {
        return "README-AspectJ.html";
    }

    public ToolsInstaller() {
        InstallPane installPane = new InstallPane(true);
        setInstallPane(installPane);
        panes = new WizardPane[] {
          new IntroPane(),
          new ConfigureLauncherPane(),
          new LocationPane(),
          installPane,
          new FinishPane()
        };
    }
}

class DocsInstaller extends Installer {
    public String getTitle() { return "AspectJ(TM) Documentation and Examples Installer"; }

    public String getPrefix() { return "docs"; }

    public DocsInstaller() {
        InstallPane installPane = new InstallPane(false);
        setInstallPane(installPane);
        panes = new WizardPane[] {
          new IntroPane(),
          new LocationPane(),
          installPane,
          new FinishPane()
        };
    }
}

class AJDEForJBuilderInstaller extends Installer {
    public String getTitle() { return "AspectJ(TM) Support for JBuilder"; }

    public String getPrefix() { return "ajdeForJBuilder"; }

    public AJDEForJBuilderInstaller() {
        InstallPane installPane = new InstallPane(false);
        setInstallPane(installPane);
        panes = new WizardPane[] {
          new IntroPane(),
          new LocationPane() {
              public String getDefaultLocation() {
                if (context.onWindows()) {
                    // check some default locations
                    String[] paths = { "c:\\JBuilder6\\lib\\ext",
                        "c:\\apps\\JBuilder6\\lib\\ext",
                        "c:\\Program Files\\JBuilder6\\lib\\ext" };
                    int pathIndex = 0;
                    for (; pathIndex < paths.length; pathIndex++) {
                        if (new File(paths[pathIndex]).exists()) return paths[pathIndex];
                    }
                    return "c:\\JBuilder6\\lib\\ext";
                } else {
                    return "/usr/JBuilder6/lib/ext";
                }
              }

              /**
               * Make sure that the old jar file gets removed.
               */
              public void verify() {
                File jbuilder = new File(location.getText() + "/../../lib/jbuilder.jar");
                if (!jbuilder.exists() && hasGui()) {
                    int ret = JOptionPane.showConfirmDialog
                        (frame,
                         "The location you specified does not seem to be a " +
                         "valid JBuilder install directory." +
                         " Continue?", "Confirm Install",
                         JOptionPane.YES_NO_OPTION,
                         JOptionPane.QUESTION_MESSAGE);
                    if (ret != JOptionPane.YES_OPTION) {
                        Main.exit(-1);
                    } else {
                        // do nothing
                    }
                }

                File oldFile = new File(location.getText() + "/ajbuilder.jar");
                if (oldFile.exists() && hasGui()) {
                    int ret = JOptionPane.showConfirmDialog(frame,
                                                            "This old version of AJDE for JBuilder (\"ajbuilder.jar\") exists" +
                                                            " and must be removed from the install directory." +
                                                            " OK to delete?", "Confirm Delete",
                                                            JOptionPane.YES_NO_OPTION,
                                                            JOptionPane.QUESTION_MESSAGE);
                    if (ret != JOptionPane.YES_OPTION) {
                        Main.exit(-1);
                    } else {
                        oldFile.delete();
                    }
                }
              }
          },
          installPane,
          new FinishPane()
        };
    }
}

class AJDEForForteInstaller extends Installer {
    public String getTitle() { return "AspectJ(TM) Support for Forte 4J"; }

    public String getPrefix() { return "ajdeForForte"; }

    private String installLoc = "";

    public AJDEForForteInstaller() {
        InstallPane installPane = new InstallPane(false);
        setInstallPane(installPane);
        panes = new WizardPane[] {
          new IntroPane(),
          new LocationPane() {
              public String getDefaultLocation() {
                if (context.onWindows()) {
                    // check some default locations
                    String[] paths = { "c:\\forte4j\\modules",
                        "c:\\apps\\forte4j\\modules",
                        "c:\\Program Files\\forte4j\\modules" };
                    int pathIndex = 0;
                    for (; pathIndex < paths.length; pathIndex++) {
                        if (new File(paths[pathIndex]).exists()) return paths[pathIndex];
                    }
                    return "c:\\forte4j\\modules";
                } else {
                    return "/usr/forte4j/modules";
                }
              }

            public void verify() {
                File forte = new File(location.getText() + "/../lib/openide.jar");
                installLoc = location.getText();
                if (!forte.exists() && hasGui()) {
                    int ret =
                        JOptionPane.showConfirmDialog
                        (frame,
                         "The location you specified does not seem to be a " +
                         "valid Forte install directory." +
                         " Continue?", "Confirm Install",
                         JOptionPane.YES_NO_OPTION,
                         JOptionPane.QUESTION_MESSAGE);
                    if (ret != JOptionPane.YES_OPTION) {
                        Main.exit(-1);
                    } else {
                        // do nothing
                    }
                }
              }
          },
          installPane,
          new FinishPane() {
            public void finalActions() { // todo verify dir ../lib/ext exists?
                // !!! this should be done with two install locations, not by moving a file
                new File(installLoc + "/../lib/ext/aspectjrt.jar").delete();
                new File(installLoc + "/aspectjrt.jar").renameTo(new File((installLoc + "/../lib/ext/aspectjrt.jar")));
                new File(installLoc + "/aspectjrt.jar").delete();
            }
          }
        };
    }
}

class SrcInstaller extends Installer {
    public String getTitle() { return "AspectJ(TM) Compiler and Core Tools Sources Installer"; }

    public String getPrefix() { return "sources"; }

    public SrcInstaller() {
        InstallPane installPane = new InstallPane(false);
        setInstallPane(installPane);
        panes = new WizardPane[] {
          new IntroPane(),
          new LocationPane(),
          installPane,
          new FinishPane()
        };
    }
}

abstract class Installer {
    static final String EXIT_MESSAGE = "Are you sure you want to cancel the installation?";
    static final String EXIT_TITLE = "Exiting installer";
    /** relative directory in jar from package $installer$.org.aspectj
        for loading resources - todo must be tracked during build */
    public static final String RESOURCE_DIR = "resources";

    JFrame frame;
    InstallContext context;
    /** special pane that actually does the installation */
    InstallPane installPane;

    public Installer() {
    }

    protected void setInstallPane(InstallPane installPane) {
        this.installPane = installPane;
    }
    public InstallPane getInstallPane() {
        return installPane;
    }

    /** directly run the install pane, if any */
    public void run() {
        if (null != installPane) {
            installPane.run();
        }
    }

    public abstract String getPrefix();

    public String getReadmeFilename() {
        return "README-" + getPrefix().toUpperCase()+".html";
    }

    public void setContext(InstallContext context) {
        this.context = context;
        context.installer = this;
    }

    public InstallContext getContext() {
        return context;
    }

    public String getTitle() { return "AspectJ(TM) Installer"; }
    public int getWidth() { return 640; }
    public int getHeight() { return 460; }

    protected WizardPane[] panes = new WizardPane[0];
    public WizardPane[] getPanes() {
        return panes;
    }

    public int findPaneIndex(WizardPane pane) {
        for (int i = 0; i < panes.length; i++) {
            if (panes[i] == pane) return i;
        }
        return -1;
    }

    Component header, footer, body;

    public void runGUI() {
        frame = new JFrame(getTitle());
        WindowListener wl = new WindowAdapter() {
            public void windowClosing(WindowEvent arg0) {
                Main.exit(-1); // -1 unless exiting through done button
            }
        };
        frame.addWindowListener(wl);

        if (Options.forceError1) {
            throw new RuntimeException("forced error1 for testing purposes");
        }

        Dimension size = Toolkit.getDefaultToolkit().getScreenSize();

        int x = (int)(size.getWidth() - getWidth()) / 2;
        int y = (int)(size.getHeight() - getHeight()) / 2;

        //include a few sanity checks on starting position
        if (x < 0) x = 0;
        if (x > 600) x = 600;
        if (y < 0) y = 0;
        if (y > 400) y = 400;

        frame.setLocation(x, y);
        frame.setSize(getWidth(), getHeight());
        moveToPane(getPanes()[0]);
        frame.setVisible(true);
    }

    public void moveToPane(WizardPane pane) {
        WizardPane.setContext(this.context);

        Dimension size = frame.getContentPane().getSize();

        JPanel contents = new JPanel();
        contents.setLayout(new BorderLayout());
        header = makeHeader();
        contents.add(header, BorderLayout.NORTH);

        body = pane.getPanel();
        contents.add(body, BorderLayout.CENTER);

        footer = pane.getButtons();
        contents.add(footer, BorderLayout.SOUTH);

        contents.revalidate();
        contents.setSize(size);

        frame.setContentPane(contents);

        //XXX deal with threading here?
        pane.run();
    }

    public Icon loadImage(String name) {
        return new javax.swing.ImageIcon(this.getClass().getResource(name));
    }

    public Component makeHeader() {
        return new JLabel(loadImage(Installer.RESOURCE_DIR + "/aspectjBanner.gif"));
    }

    public ActionListener makeNextAction(final WizardPane pane) {
        int nextPaneIndex = findPaneIndex(pane) + 1;
        if (nextPaneIndex >= getPanes().length) {
            return null;
        }

        final WizardPane nextPane = getPanes()[nextPaneIndex];
        return new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                pane.finish();
                moveToPane(nextPane);
            }
        };
    }

    public ActionListener makeBackAction(final WizardPane pane) {
        int nextPaneIndex = findPaneIndex(pane) - 1;
        if (nextPaneIndex < 0) {
            return null;
        }

        final WizardPane nextPane = getPanes()[nextPaneIndex];
        return new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                moveToPane(nextPane);
            }
        };
    }

    public ActionListener makeCancelAction(WizardPane pane) {
        return new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                int ret = JOptionPane.showConfirmDialog(frame, EXIT_MESSAGE, EXIT_TITLE,
                                                        JOptionPane.YES_NO_OPTION,
                                                        JOptionPane.QUESTION_MESSAGE);
                if (ret == JOptionPane.YES_OPTION) {
                    Main.exit(-1);
                }
            }
        };
    }
    public ActionListener makeFinishAction(WizardPane pane) {
        return new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                Main.exit(0);
            }
        };
    }
}



    // willing to go up to 3 levels deep to find either jre or jdk

    // jre\[*\]lib\ext
    // jdk*\lib\tools.jar

    /*****
    final static int MAX_DEPTH = 4;
    public static void findPaths(String prefix, File currentDir, int currentDepth) {
        if (currentDepth > MAX_DEPTH) return;
        if (!currentDir.exists() || !currentDir.isDirectory()) return;
        File [] files = currentDir.listFiles();
        if (files == null) return;
        for (int i=0; i<files.length; i++) {
            if (files[i] == null) continue;
            if (!files[i].isDirectory()) continue;
            if (files[i].getName().startsWith(prefix)) {
                System.out.println("found: " + files[i]);
            } else {
                findPaths(prefix, files[i], currentDepth + 1);
            }
        }
    }

    public static void findPaths(String prefix) {
        File [] files = File.listRoots();
        for (int i=1; i<files.length; i++) {
            if (!files[i].isDirectory()) continue;
            if (files[i].getName().toLowerCase().startsWith(prefix)) {
                System.out.println("found: " + files[i]);
            } else {
                findPaths(prefix, files[i], 1);
            }
        }
    }
    *****/

class InstallContext {
    public InstallContext(Map properties) {
        this.properties = properties;
        properties.put("user.home", System.getProperty("user.home"));
        //System.out.println("new install context");
    }

    private File outputDir;
    public void setOutputDir(File outputDir) {
        this.outputDir = outputDir;

        properties.put("installer.output.dir", outputDir.getAbsolutePath());
        properties.put("installer.output.dir.bin", new File(outputDir, "bin").getAbsolutePath());
        properties.put("installer.output.dir.doc", new File(outputDir, "doc").getAbsolutePath());
        properties.put("installer.output.aspectjrt", new File(new File(outputDir, "lib"), "aspectjrt.jar").getAbsolutePath());
        properties.put("installer.output.readme", new File(outputDir, installer.getReadmeFilename()).getAbsolutePath());
    }

    public File getOutputDir() {
        return outputDir;
    }

    private boolean hasGui;
    public File javaPath;
    public File toolsJarPath;

    public Installer installer;

    private Map properties;

    public boolean hasGui() {
        return hasGui;
    }

    public void setHasGui(boolean hasGui) {
        if (this.hasGui != hasGui) {
            this.hasGui = hasGui;
        }
    }

    public Font getFont() {
        return new Font("Serif", Font.PLAIN, 14);
    }

    public String getOS() {
        return System.getProperty("os.name");
    }

    public boolean onOS2() {
        return getOS().equals("OS2") || getOS().equals("OS/2");
    }

    public boolean onWindows() {
        return getOS().startsWith("Windows") || onOS2();
    }

    public boolean onWindowsPro() {
        return getOS().equals("Windows NT") || getOS().equals("Windows 2000");
    }

    public boolean onMacintosh() {
        return getOS().startsWith("Mac");
    }

    public boolean onUnix() {
        return !onMacintosh() && !onWindows();
    }

    static final String[] TEXT_EXTENSIONS = {
        ".txt", ".text", ".htm", ".html", ".java", ".ajava", "README", ".lst"
    };

    public boolean isTextFile(File file) {
        String name = file.getName();

        for (int i=0; i < TEXT_EXTENSIONS.length; i++) {
            if (name.endsWith(TEXT_EXTENSIONS[i])) return true;
        }

        return false;
    }

    public void handleException(Throwable e) {
        System.out.println("internal error: " + e.toString());
        e.printStackTrace();
        if (hasGui()) {
            JOptionPane.showMessageDialog(installer.frame, e.toString(),
            "Unexpected Exception", JOptionPane.ERROR_MESSAGE);
        }
    }

    final static String OVERWRITE_MESSAGE = "Overwrite file ";
    final static String OVERWRITE_TITLE = "Overwrite?";

    final static String[] OVERWRITE_OPTIONS = {
        "Yes", "No", "Yes to all" //, "No to all"
    };

    final static int OVERWRITE_YES = 0;
    final static int OVERWRITE_NO = 1;
    final static int OVERWRITE_ALL = 2;
    //final static int OVERWRITE_NONE = 3;

    int overwriteState = OVERWRITE_NO;
    boolean shouldOverwrite(final File file) {
        //System.out.println("overwrite: " + file + " state " + overwriteState);
        if (overwriteState == OVERWRITE_ALL) return true;
        //if (overwriteState == OVERWRITE_NONE) return false;

        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    int ret = JOptionPane.showOptionDialog(installer.frame,
                          OVERWRITE_MESSAGE+file.getPath(), OVERWRITE_TITLE,
                          JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
                          OVERWRITE_OPTIONS, OVERWRITE_OPTIONS[OVERWRITE_YES]);

                    overwriteState = ret;
                }
            });
        } catch (InvocationTargetException ite) {
            handleException(ite.getTargetException());
        } catch (InterruptedException ie) {
        }

        return overwriteState == OVERWRITE_YES || overwriteState == OVERWRITE_ALL;
    }


    public Map getProperties() {
        return properties;
    }
}

abstract class WizardPane {
    static InstallContext context;

    protected JButton backButton = null;
    protected JButton nextButton = null;
    protected JButton cancelButton = null;

    public static void setContext(InstallContext con) { 
        context = con; 
    }

    public abstract JPanel makePanel();

    protected JTextArea makeTextArea(String data) {
        JTextArea text = new JTextArea(data);
        text.setOpaque(false);
        text.setFont(context.getFont());
        text.setEditable(false);
        return text;
    }

    /** @return false only if there is an InstallContext saying there is no GUI */
    protected boolean hasGui() {
        final InstallContext icontext = context;
        return ((null == icontext) || icontext.hasGui());
    }

    public static String stringFromStream(InputStream stream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "US-ASCII"));

        StringBuffer ret = new StringBuffer();
        int data;
        while ( (data = reader.read()) != -1) {
            ret.append( (char)data );
        }
        return ret.toString();
    }

    public static String removeHead(String text) {
        int startIndex = text.indexOf("<head>");
        int stopIndex = text.indexOf("</head>");
        if (startIndex == -1 || stopIndex == -1) return text;
        stopIndex += 7;
        return text.substring(0, startIndex) + text.substring(stopIndex);
    }

    static String styleHeader = "<head></head>";/*<STYLE TYPE=\"text/css\"><!--\n" +
    " h2 {\n" +
	"  	font-size: x-large;\n" +
    "   font-family: Serif;\n" +
    "   font-weight: normal;\n" +
    " }\n" +
    " p {\n" +
    "   font-family: Serif;\n" +
    "   font-weight: normal;\n" +
    //"   color:black;\n" +
    "}</head>\n";*/

    public static String applyProperties(String text, Map map) {
        // ${name} -> map.get(name).toString()
        int lastIndex = 0;
        StringBuffer buf = new StringBuffer();

        int startIndex;
        while ( (startIndex = text.indexOf("${", lastIndex)) != -1) {
            int endIndex = text.indexOf('}', startIndex);
            //XXX internal error here
            if (endIndex == -1) break;
            buf.append(text.substring(lastIndex, startIndex));
            String key = text.substring(startIndex+2, endIndex);
            lastIndex = endIndex+1;
            Object replaceText = map.get(key);
            //System.out.println("key: " + key + " -> " + replaceText);
            if (replaceText == null) replaceText = "NOT_FOUND";
            buf.append(replaceText.toString());
        }
        buf.append(text.substring(lastIndex));

        return buf.toString();
    }

    public static String applyProperties(String text) {
        return applyProperties(text, context.getProperties());
    }

    protected String loadText(String filename) {
        String fullname = Installer.RESOURCE_DIR + "/" + filename;
        //context.installer.getPrefix() + "-" + filename;

        try {
            String text = stringFromStream(getClass().getResourceAsStream(fullname));
            text = styleHeader + removeHead(text);
            text = applyProperties(text);
            //System.out.println(text);
            return text;
        } catch (IOException e) {
            context.handleException(e);
            return "";
        }
    }

    protected JEditorPane makeHTMLArea(String filename) {
        JEditorPane editorPane = new JEditorPane("text/html", loadText(filename));
        /* {
            public void paint(Graphics g) {
                Graphics2D g2 = (Graphics2D)g;
                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                super.paint(g2);
            }
        };*/
        editorPane.setEditable(false);
        editorPane.setOpaque(false);
        return editorPane;
    }

    protected void setHTMLArea(JEditorPane pane, String filename) {
        pane.setText(loadText(filename));
    }


    protected JPanel makeLocationBox(String label, JTextField textField, JButton browseButton) {
        JPanel box = new JPanel();
        box.setLayout(new BoxLayout(box, BoxLayout.X_AXIS));

        textField.setFont(context.getFont());
        textField.selectAll();
        box.add(textField);

        box.add(browseButton);
        Border border = BorderFactory.createTitledBorder(label);
        final int INSET = 8;
        border = new CompoundBorder(border, new EmptyBorder(1, INSET, INSET, INSET));
        box.setBorder(border);

        return box;
    }


    private JPanel panel = null;
    public JPanel getPanel() {
        if (panel == null) {
            panel = makePanel();
            panel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
        }
        return panel;
    }

    protected void setListener(JButton button, ActionListener listener) {
        if (listener == null) {
            button.setEnabled(false);
        } else {
            button.addActionListener(listener);
        }
    }

    protected Component makeButtons(Installer installer) {
        JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));

        backButton = new JButton("Back");
        setListener(backButton, installer.makeBackAction(this));
        panel.add(backButton);

        nextButton = new JButton("Next");
        setListener(nextButton, installer.makeNextAction(this));
        panel.add(nextButton); //.setDefaultCapable(true);

        JLabel space = new JLabel();
        space.setPreferredSize(new Dimension(20, 0));
        panel.add(space);

        cancelButton = new JButton("Cancel");
        setListener(cancelButton, installer.makeCancelAction(this));
        panel.add(cancelButton);

        return panel;
    }

    private Component buttons = null;
    public Component getButtons() {
        if (buttons == null) {
            buttons = makeButtons(context.installer);
            //buttons.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
        }
        context.installer.frame.getRootPane().setDefaultButton(nextButton);
        return buttons;
    }

    public void finish() {
        if (Options.forceError2) {
            throw new RuntimeException("forced error2 for testing purposes");
        }
    }
    public void run() {}
}

class IntroPane extends WizardPane {
    public JPanel makePanel() {
        Component text = makeHTMLArea("intro.html");

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(text);
        return panel;
    }
}

class FinishPane extends WizardPane {
    public JPanel makePanel() {
        Component text = makeHTMLArea("finish.html");

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(text);
        finalActions();
        return panel;
    }

    public Component makeButtons(Installer installer) {
        Component ret = super.makeButtons(installer);
        nextButton.setText("Finish");
        nextButton.setEnabled(true);
        nextButton.addActionListener(installer.makeFinishAction(this));
        backButton.setEnabled(false);
        cancelButton.setEnabled(false);
        return ret;
    }

    public void finalActions() { }
}


class LocationPane extends WizardPane implements ActionListener {
    //XXX need more sophisticated default location finding
    //XXX would like to find the place they last chose...
    public String getDefaultLocation() {
        if (context.onWindows()) {
            //XXX hard-coded majorminor version needs to be fixed by 1.1 release
            return "c:\\aspectj1.2";
        } else {
            return new File(System.getProperty("user.home"), "aspectj1.2").getAbsolutePath();
        }
    }

    protected JTextField location;

    public JPanel makePanel() {
        Component text = makeHTMLArea("location.html");

        location = new JTextField(getDefaultLocation());
        JButton browse = new JButton("Browse...");
        browse.addActionListener(this);

        JPanel locationBox = makeLocationBox("installation directory", location, browse);

        GridBagLayout bag = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();

        JPanel panel = new JPanel(bag);
        c.fill = GridBagConstraints.BOTH;
        c.weightx = 1.0;
        c.gridwidth = GridBagConstraints.REMAINDER;
        bag.setConstraints(text, c);
        panel.add(text);

        c.weighty = 1.0;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.gridwidth = GridBagConstraints.REMAINDER;
        bag.setConstraints(locationBox, c);
        panel.add(locationBox);

        //XXX set next button to read install
        //context.nextButton.setText("Install");

        return panel;
    }

    public Component makeButtons(Installer installer) {
        Component ret = super.makeButtons(installer);
        nextButton.setText("Install");
        return ret;
    }

    public void actionPerformed(ActionEvent e) {
        JFileChooser chooser = new JFileChooser(); // {
        //    public void approveSelection() {
        //        System.out.println("approved selection");
        //    }
        //}; //field.getText());
        chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        int returnVal = chooser.showDialog(location, "Select");
        if(returnVal == JFileChooser.APPROVE_OPTION) {
            File file = chooser.getSelectedFile();
            if (!file.isDirectory()) file = file.getParentFile();
            String name = file.getPath();
            location.setText(name);
            location.selectAll();
        }
    }

    /**
     * Override to do any additional checks.
     */
    protected void verify() {}

    public void finish() {
        verify();
        context.setOutputDir(new File(location.getText()));
    }
}

class ConfigureLauncherPane extends WizardPane {
    /*
    //XXX check that the returned file is valid
    public String getDefaultJavaLocation() {
        String name = "java";
        if (context.onWindows()) name += ".exe";

        if (Options.verbose) {
            System.out.println("java.home: " + System.getProperty("java.home"));
            System.out.println("  java: " + new File(new File(System.getProperty("java.home"), "bin"), name));
            System.out.println("  java: " + new File(new File(System.getProperty("java.home"), "bin"), name).getPath());
        }

        return new File(new File(System.getProperty("java.home"), "bin"), name).getPath();
    }
    */

    public static String getDefaultJavaHomeLocation() {
        if (!Options.forceHandConfigure) {
            File javaHome = findJavaHome();
            if (javaHome != null) return javaHome.getPath();
        }
        return null;
    }

    public void chooseFile(JTextField field) {
        JFileChooser chooser = new JFileChooser(); //field.getText());
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        int returnVal = chooser.showDialog(field, "Select");
        if(returnVal == JFileChooser.APPROVE_OPTION) {
            String name = chooser.getSelectedFile().getPath();
            field.setText(name);
            field.selectAll();
        }
    }


    public ActionListener makeJavaLocationBrowseListener() {
        return new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                chooseFile(javaLocation);
            }
        };
    }

//     public ActionListener makeToolsJarLocationBrowseListener() {
//         return new ActionListener() {
//             public void actionPerformed(ActionEvent e) {
//                 chooseFile(toolsJarLocation);
//             }
//         };
//     }


    private JTextField javaLocation;
    //private JTextField toolsJarLocation;

    public JPanel makePanel() {
        String javaPath = getDefaultJavaHomeLocation();
        //String toolsJarPath = getDefaultToolsJarLocation();

        Component text;
        if (javaPath == null) {
            javaPath = "<java home not found>";
            text = makeHTMLArea("configure-hand.html");
        } else {
            text = makeHTMLArea("configure-auto.html");
        }

        javaLocation = new JTextField(javaPath);
        JButton javaLocationBrowse = new JButton("Browse...");
        javaLocationBrowse.addActionListener(makeJavaLocationBrowseListener());

        JPanel javaLocationBox = makeLocationBox("java home directory", javaLocation, javaLocationBrowse);


//         toolsJarLocation = new JTextField(toolsJarPath);
//         JButton toolsJarLocationBrowse = new JButton("Browse...");
//         toolsJarLocationBrowse.addActionListener(makeToolsJarLocationBrowseListener());

//         JPanel toolsJarLocationBox = makeLocationBox("full path to tools.jar", toolsJarLocation, toolsJarLocationBrowse);

        GridBagLayout bag = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();

        JPanel panel = new JPanel(bag);
        c.fill = GridBagConstraints.BOTH;
        c.weightx = 1.0;
        c.weighty = 1.0;
        //c.ipady = 10;
        c.gridwidth = GridBagConstraints.REMAINDER;
        bag.setConstraints(text, c);
        panel.add(text);

        c.weighty = 0.0;
        //c.fill = GridBagConstraints.VERTICAL;
        bag.setConstraints(javaLocationBox, c);
        panel.add(javaLocationBox);

//         c.weighty = 0.25;
//         JLabel space = new JLabel();
//         bag.setConstraints(space, c);
//         panel.add(space);

//         c.weighty = 0.0;
//         bag.setConstraints(toolsJarLocationBox, c);
//         panel.add(toolsJarLocationBox);

        c.weighty = 0.5;
        JLabel space = new JLabel();
        bag.setConstraints(space, c);
        panel.add(space);

        return panel;
    }

    public void finish() {
        context.javaPath = new File(javaLocation.getText());
//         context.toolsJarPath = new File(toolsJarLocation.getText());

        //XXX need much more work on helping the user get these paths right
//         if (context.javaPath.isDirectory()) {
//             context.javaPath = new File(context.javaPath, "java");
//         }
//         if (context.toolsJarPath.isDirectory()) {
//             context.toolsJarPath = new File(context.toolsJarPath, "tools.jar");
//         }
    }


    //XXX add user.home to prefixes in a rational way
    public static final String[] windowsPaths = {
        "c:\\jdk", "c:\\apps\\jdk", "${user.home}\\jdk"
    };

    public static final String[] unixPaths = {
        "/usr/local/bin/jdk", "/usr/bin/jdk", "/usr/bin/jdk", "${user.home}/jdk"
    };

    public static final String[] suffixes = {
        "1.3.1", "1.3", "1.2", "13", "12", "2", "", "1.4"
    };

    public static boolean windows = true;

    public static boolean isLegalJavaHome(File home) {
        File bin = new File(home, "bin");
        return new File(bin, "java").isFile() || new File(bin, "java.exe").isFile();
    }

    public static boolean isLegalJDKHome(File home) {
        File lib = new File(home, "lib");
        return new File(lib, "tools.jar").isFile();
    }


    public static File findJavaHome() {
        String s = System.getProperty("java.home");
        File javaHome = null;
        if (s != null) {
            javaHome = new File(s);
            if (isLegalJDKHome(javaHome)) return javaHome;
            if (isLegalJavaHome(javaHome)) {
                File parent = javaHome.getParentFile();
                if (parent != null && isLegalJDKHome(parent)) return parent;
            }
        }

        String[] paths;
        if (windows) {
            paths = windowsPaths;
        } else {
            paths = unixPaths;
        }

        for (int suffixIndex = 0; suffixIndex < suffixes.length; suffixIndex++) {
            String suffix = suffixes[suffixIndex];
            for (int prefixIndex = 0; prefixIndex < paths.length; prefixIndex++) {
                String prefix = paths[prefixIndex];
                prefix = applyProperties(prefix);
                File test = new File(prefix+suffix);
                if (isLegalJavaHome(test)) {
                    if (isLegalJDKHome(test)) return test;
                    else if (javaHome == null) javaHome = test;
                }
            }
        }
        return javaHome;
    }
}


class InstallPane extends WizardPane {
    private JProgressBar progressBar;
    private JTextField progressItem;
    private JEditorPane message;

    private boolean makeLaunchScripts = false;

    public InstallPane(boolean makeLaunchScripts) {
        this.makeLaunchScripts = makeLaunchScripts;
    }

    public JPanel makePanel() {
        message = makeHTMLArea("install-start.html");

        progressBar = new JProgressBar();

        progressItem = new JTextField();
        progressItem.setOpaque(false);
        progressItem.setFont(context.getFont());
        progressItem.setEditable(false);


        GridBagLayout bag = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();

        JPanel panel = new JPanel(bag);
        c.fill = GridBagConstraints.BOTH;
        c.weightx = 1.0;
        c.weighty = 1.0;
        //c.ipady = 10;
        c.gridwidth = GridBagConstraints.REMAINDER;
        bag.setConstraints(message, c);
        panel.add(message);

        c.weighty = 0.0;
        //c.fill = GridBagConstraints.VERTICAL;
        bag.setConstraints(progressBar, c);
        panel.add(progressBar);

        c.weighty = 0.1;
        JLabel space = new JLabel();
        bag.setConstraints(space, c);
        panel.add(space);

        c.weighty = 0.0;
        bag.setConstraints(progressItem, c);
        panel.add(progressItem);

        c.weighty = 0.5;
        space = new JLabel();
        bag.setConstraints(space, c);
        panel.add(space);

        return panel;
    }

    class InstallRunner implements Runnable {
        public InstallRunner() {}

        public void run() {
            try {
                new CurrentJarUnpacker(context, InstallPane.this).unpack(Installer.RESOURCE_DIR + "/contents.txt", context.getOutputDir());

                if (makeLaunchScripts) {
                    LaunchScriptMaker lsm = new LaunchScriptMaker(context);
                    lsm.writeScript("ajc");
                    lsm.writeScript("ajdoc");
                    //lsm.writeScript("ajdb");
                    lsm.writeScript("ajbrowser");
                    
                    // While the experimental aj(.bat) launch script is 
                    // shipped in the docs path
                    lsm.writeAJLaunchScript();
                }
                if (hasGui()) {
                  progressBar.setValue(100);
                  setHTMLArea(message, "install-finish.html");
                }
            } catch (IOException ioe) {
                context.handleException(ioe);
            }

            if (hasGui()) {
                cancelButton.setEnabled(false);
                nextButton.setEnabled(true);
            }
        }
    }

    public Component makeButtons(Installer installer) {
        Component ret = super.makeButtons(installer);
        //nextButton.setText("Finish");
        nextButton.setEnabled(false);
        //nextButton.addActionListener(installer.makeFinishAction(this));
        backButton.setEnabled(false);
        return ret;
    }


    public void run() {
        Thread thread = new Thread(new InstallRunner());
        thread.start();
    }

    public void progressMessage(final String message) {
        if (!hasGui()) {
            return;
        }
        try {
            //XXX performance tradeoff between invokeAndWait and invokeLater...
            SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        progressItem.setText(message);
                    }
                });
        } catch (InvocationTargetException ite) {
            context.handleException(ite.getTargetException());
        } catch (InterruptedException ie) {
        }
    }

    int nBytes = 0;
    int bytesWritten = 0;
    public void progressBytesWritten(int bytes) {
        if (!hasGui()) {
            return;
        }
        bytesWritten += bytes;
        final int PCT = (int)(100.0 *  bytesWritten / nBytes);
        //System.out.println("bytesWritten: " + bytesWritten);
        try {
            //XXX performance tradeoff between invokeAndWait and invokeLater...
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    progressBar.setValue(PCT);
                }
            });
        } catch (InvocationTargetException ite) {
            context.handleException(ite.getTargetException());
        } catch (InterruptedException ie) {
        }
    }
}

class CurrentJarUnpacker {
    InstallContext context;
    InstallPane installPane;

    public CurrentJarUnpacker(InstallContext context, InstallPane installPane) {
        this.context = context;
        this.installPane = installPane;
    }

    public File makeOutputFile(String name, File outputFile) {
        int index;
        int lastIndex = 0;

        while ( (index = name.indexOf('/', lastIndex)) != -1) {
            outputFile = new File(outputFile, name.substring(lastIndex, index));
            lastIndex = index + 1;
        }

        return new File(outputFile, name.substring(lastIndex));
    }

    final static int BUF_SIZE = 4096;

    public void writeStream(InputStream zis, File outputFile) throws IOException {
        if (outputFile.exists()) {
            if (!context.shouldOverwrite(outputFile)) return;
        }

        installPane.progressMessage("writing " + outputFile.getAbsolutePath());

        outputFile.getParentFile().mkdirs();

        if (context.isTextFile(outputFile)) {
            writeTextStream(zis, outputFile);
        } else {
            writeBinaryStream(zis, outputFile);
        }
    }

    public void writeBinaryStream(InputStream zis, File outputFile) throws IOException {
        byte[] buffer = new byte[BUF_SIZE];
        int nRead = 0;

        OutputStream os = new BufferedOutputStream(new FileOutputStream(outputFile));

        while ( (nRead = zis.read(buffer)) != -1) {
            os.write(buffer, 0, nRead);
            installPane.progressBytesWritten(nRead);
        }
        os.close();
    }

    public void writeTextStream(InputStream zis, File outputFile) throws IOException {
        BufferedWriter os = new BufferedWriter(new FileWriter(outputFile));
        BufferedReader r = new BufferedReader(new InputStreamReader(zis, "US-ASCII"));

        String l;
        while ( (l = r.readLine()) != null) {
            os.write(l);
            os.newLine();
            installPane.progressBytesWritten(l.length() + 1);
        }
        os.close();
    }

    public void writeResource(String name, File outputDir) throws IOException {
        File outputFile = makeOutputFile(name, outputDir);
        //System.out.println("finding name: " + name);
        writeStream(getClass().getResourceAsStream("/" + name), outputFile);
    }

    public void unpack(String contentsName, File outputDir) throws IOException {
        URL url = getClass().getResource(contentsName);
        InputStream stream = url.openStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "US-ASCII"));

        String line = reader.readLine();
        installPane.nBytes = Integer.parseInt(line);

        while ( (line = reader.readLine()) != null) {
            writeResource(line, outputDir);
        }

        installPane.progressMessage("done writing");
    }
}

class LaunchScriptMaker {
    static final String toolsPackage = "org.aspectj.tools";

    InstallContext context;
    public LaunchScriptMaker(InstallContext context) {
        this.context = context;
    }

    /**
	 * 
	 */
	public void writeAJLaunchScript() throws IOException {
		String name = "aj";
		if (!context.onUnix()) {
			if (context.onOS2()) {
				name += ".cmd";
			} else if (context.onWindows()) {
				name += ".bat";
			}
		}

		File destDir = new File(context.getOutputDir(), "doc/examples/ltw");
		destDir.mkdirs();
		File file = new File(destDir, name);

		PrintStream ps = getPrintStream(file);
		writeAJLaunchScriptContent(ps);
		ps.close();

		if (context.onUnix()) {
			makeExecutable(file);
		}
	}

	/**
	 * @param ps
	 */
	private void writeAJLaunchScriptContent(PrintStream ps) {
		if (context.onUnix()) {
			writeUnixHeader(ps);
			writeAJUnixLaunchLine(ps);
		}
		else {
			writeWindowsHeader(ps);
			writeAJWindowsLaunchLine(ps);
		}
	}

	/**
	 * @param ps
	 */
	private void writeAJWindowsLaunchLine(PrintStream ps) {
		ps.println(
			"\"%JAVA_HOME%\\bin\\java\" -classpath " +
			"\"%ASPECTJ_HOME%\\lib\\aspectjtools.jar\"" +
			" \"-Djava.system.class.loader=org.aspectj.weaver.WeavingURLClassLoader\"" +
			" \"-Daj.class.path=%ASPECTPATH%;%CLASSPATH%\"" +
			" \"-Daj.aspect.path=%ASPECTPATH%\"" +
			" " + makeScriptArgs(false));
	}

	/**
	 * @param ps
	 */
	private void writeAJUnixLaunchLine(PrintStream ps) {
		String sep = File.pathSeparator;
		ps.println(
			"\"$JAVA_HOME/bin/java\" -classpath" +
		    " \"$ASPECTJ_HOME/lib/aspectjtools.jar\"" +
		    " \"-Djava.system.class.loader=org.aspectj.weaver.WeavingURLClassLoader\"" +
		    " \"-Daj.class.path=$ASPECTPATH:$CLASSPATH\"" +
		    " \"-Daj.aspect.path=$ASPECTPATH\"" +
		    " " +
			makeScriptArgs(true));
	}

	private void writeWindowsHeader(PrintStream ps) {
        ps.println("@echo off");
        ps.println("REM This file generated by AspectJ installer");
        ps.println("REM Created on "+new java.util.Date()+" by "+
                   System.getProperty("user.name"));
        ps.println("");
        ps.println("if \"%JAVA_HOME%\" == \"\" set JAVA_HOME=" +
                   context.javaPath.getAbsolutePath());
        ps.println("if \"%ASPECTJ_HOME%\" == \"\" set ASPECTJ_HOME=" +
                   context.getOutputDir().getAbsolutePath());
        ps.println("");

        ps.println("if exist \"%JAVA_HOME%\\bin\\java.exe\" goto haveJava");
        ps.println("if exist \"%JAVA_HOME%\\bin\\java.bat\" goto haveJava");
        ps.println("if exist \"%JAVA_HOME%\\bin\\java\" goto haveJava");

        ps.println("echo java does not exist as %JAVA_HOME%\\bin\\java");
        ps.println("echo please fix the JAVA_HOME environment variable");
        ps.println(":haveJava");
    }

	private void writeWindowsLaunchLine(String className, PrintStream ps) {
		ps.println("\"%JAVA_HOME%\\bin\\java\" -classpath " +
		//                   "\"%ASPECTJ_HOME%\\lib\\aspectjtools.jar;%CLASSPATH%\""+
		"\"%ASPECTJ_HOME%\\lib\\aspectjtools.jar;%JAVA_HOME%\\lib\\tools.jar;%CLASSPATH%\""
			+ " -Xmx64M "
			+ className
		+ //" -defaultClasspath " + "\"%CLASSPATH%\"" +
		" " + makeScriptArgs(false));
	}

    private void writeUnixHeader(PrintStream ps) {
        File binsh = new File(File.separator+"bin", "sh");
        if (binsh.canRead()) {
            ps.println("#!"+binsh.getPath());
        }
        ps.println("# This file generated by AspectJ installer");
        ps.println("# Created on "+new java.util.Date()+" by "+
                   System.getProperty("user.name"));
        ps.println("");
        ps.println("if [ \"$JAVA_HOME\" = \"\" ] ; then JAVA_HOME=" +
                   quote(true, false, context.javaPath.getAbsolutePath()));
        ps.println("fi");
        ps.println("if [ \"$ASPECTJ_HOME\" = \"\" ] ; then ASPECTJ_HOME=" +
                   quote(true, false, context.getOutputDir()));
        ps.println("fi");
        ps.println("");
    }

	private void writeUnixLaunchLine(
		String className,
		PrintStream ps) {
		String sep = File.pathSeparator;
		ps.println(
			"\"$JAVA_HOME/bin/java\" -classpath "
				+ "\"$ASPECTJ_HOME/lib/aspectjtools.jar"
				+ sep
				+ "$JAVA_HOME/lib/tools.jar"
				+ sep
				+ "$CLASSPATH\""
				+ " -Xmx64M "
				+ className
				+ " "
				+ makeScriptArgs(true));
	}

    private void makeExecutable(File file) {
        try {
            Runtime curRuntime = Runtime.getRuntime();
            curRuntime.exec("chmod 777 " + quote(true, false, file));
        } catch (Throwable t) {
            // ignore any errors that occur while trying to chmod
        }
    }



    private String makeScriptArgs(boolean unixStyle) {
        if (unixStyle) {
            return "\"$@\"";
        } else if (context.onWindowsPro()) {
            return "%*";
        } else {
            return "%1 %2 %3 %4 %5 %6 %7 %8 %9";
        }
    }

    private String quote(boolean unixStyle, boolean forceQuotes, File file) {
        return quote(unixStyle, forceQuotes, file.getAbsolutePath());
    }
    private String quote(boolean unixStyle, boolean forceQuotes, String s) {
        if (context.onWindows() && unixStyle) {
            s = s.replace('\\', '/');
        }

        if (!forceQuotes && s.indexOf(' ') == -1) return s;
        return "\""+s+"\"";
    }

    private File makeScriptFile(String name, boolean unixStyle) throws IOException {
        if (!unixStyle) {
            if (context.onOS2()) {
                name += ".cmd";
            } else if (context.onWindows()) {
                name += ".bat";
            }
        }

        //XXX probably want a context.getOutputBinDir()
        File bindir = new File(context.getOutputDir(), "bin");
        bindir.mkdirs();
        File file = new File(bindir, name);
        return file;
    }

    private PrintStream getPrintStream(File file) throws IOException {
        return new PrintStream(new BufferedOutputStream(new FileOutputStream(file)));
    }

    String makeClassPathVar(boolean unixStyle) {
        if (unixStyle) {
            return "$CLASSPATH";
        } else {
            return "%CLASSPATH%";
        }
    }

    public String makeClassPath(boolean unixStyle) throws IOException {
        return context.toolsJarPath.getAbsolutePath() + File.pathSeparator +
               //XXX want context.getOutputLibDir()
            new File(new File(context.getOutputDir(), "lib"), "aspectjtools.jar").getAbsolutePath()
            + File.pathSeparator + makeClassPathVar(unixStyle);
    }

    public void writeScript(String className, PrintStream ps, boolean unixStyle) throws IOException {
		if (unixStyle) {
			writeUnixHeader(ps);
			writeUnixLaunchLine(className, ps);
		}
		else {
			writeWindowsHeader(ps);
			writeWindowsLaunchLine(className, ps);
		}

        /*
        ps.print(quote(unixStyle, false, context.javaPath.getAbsolutePath()));
        ps.print(" ");
        ps.print("-classpath ");
        ps.print(quote(unixStyle, true, makeClassPath(unixStyle)));
        ps.print(" ");
        ps.print("-Xmx64M ");
        ps.print(className);
        ps.print(" ");
        ps.print(makeScriptArgs(unixStyle));
        */
    }

    public void writeScript(String className, boolean unixStyle) throws IOException {
        File file = makeScriptFile(className, unixStyle);
        if (!checkExistingFile(file)) return;
        PrintStream ps = getPrintStream(file);
        writeScript(toolsPackage + '.' + className + ".Main", ps, unixStyle);
        ps.close();
        //??? unixStyle vs. onUnix()
        if (context.onUnix()) makeExecutable(file);
    }

    public boolean checkExistingFile(File file) {
        if (!file.exists()) return true;

        return context.shouldOverwrite(file);
    }

    /*
    final static String OVERWRITE_MESSAGE = "Overwrite launch script ";
    final static String OVERWRITE_TITLE = "Overwrite?";

    final static String[] OVERWRITE_OPTIONS = {
        "Yes", "No", "Yes to all", "No to all"
    };

    final static int OVERWRITE_YES = 0;
    final static int OVERWRITE_NO = 1;
    final static int OVERWRITE_ALL = 2;
    final static int OVERWRITE_NONE = 3;

    int overwriteState = OVERWRITE_NO;
    boolean shouldOverwrite(final File file) {
        if (overwriteState == OVERWRITE_ALL) return true;
        if (overwriteState == OVERWRITE_NONE) return false;

        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    int ret = JOptionPane.showOptionDialog(context.installer.frame,
                          OVERWRITE_MESSAGE+file.getPath(), OVERWRITE_TITLE,
                          JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
                          OVERWRITE_OPTIONS, OVERWRITE_OPTIONS[OVERWRITE_YES]);

                    overwriteState = ret;
                }
            });
        } catch (InvocationTargetException ite) {
            context.handleException(ite.getTargetException());
        } catch (InterruptedException ie) {
        }

        return overwriteState == OVERWRITE_YES || overwriteState == OVERWRITE_ALL;
    }
    */


    public void writeScript(String className) throws IOException {
        writeScript(className, true);
        if (context.onWindows()) {
            writeScript(className, false);
        }
    }
}


class JarUnpacker {
    InstallContext context;
    InstallPane installPane;

    public JarUnpacker(InstallContext context, InstallPane installPane) {
        this.context = context;
        this.installPane = installPane;
    }

    public File makeOutputFile(String name, File outputFile) {
        int index;
        int lastIndex = 0;

        while ( (index = name.indexOf('/', lastIndex)) != -1) {
            outputFile = new File(outputFile, name.substring(lastIndex, index));
            lastIndex = index + 1;
        }

        return new File(outputFile, name.substring(lastIndex));
    }

    final static int BUF_SIZE = 4096;

    public void writeStream(ZipInputStream zis, File outputFile) throws IOException {
        if (outputFile.exists()) {
            if (!context.shouldOverwrite(outputFile)) return;
        }

        installPane.progressMessage("writing " + outputFile.getAbsolutePath());

        outputFile.getParentFile().mkdirs();

        if (context.isTextFile(outputFile)) {
            writeTextStream(zis, outputFile);
        } else {
            writeBinaryStream(zis, outputFile);
        }
    }

    public void writeBinaryStream(ZipInputStream zis, File outputFile) throws IOException {
        byte[] buffer = new byte[BUF_SIZE];
        int nRead = 0;

        OutputStream os = new BufferedOutputStream(new FileOutputStream(outputFile));

        while ( (nRead = zis.read(buffer)) != -1) {
            os.write(buffer, 0, nRead);
            installPane.progressBytesWritten(nRead);
        }
        os.close();
    }

    public void writeTextStream(ZipInputStream zis, File outputFile) throws IOException {
        BufferedWriter os = new BufferedWriter(new FileWriter(outputFile));
        BufferedReader r = new BufferedReader(new InputStreamReader(zis, "US-ASCII"));

        String l;
        while ( (l = r.readLine()) != null) {
            os.write(l);
            os.newLine();
            installPane.progressBytesWritten(l.length() + 1);
        }
        os.close();
    }

    public void writeEntry(ZipInputStream zis, ZipEntry entry, File outputDir) throws IOException {
        if (entry.isDirectory()) return;

        String name = entry.getName();
        File outputFile = makeOutputFile(name, outputDir);
        writeStream(zis, outputFile);
    }

    public void unpack(String jarName, File outputDir) throws IOException {
        URL url = getClass().getResource(jarName);
        InputStream stream = url.openStream();
        ZipInputStream zis = new ZipInputStream(stream);
//        int i = 0;

        ZipEntry entry;
        while ((entry = zis.getNextEntry()) != null) {
//            final String name = entry.getName();
            writeEntry(zis, entry, outputDir);
            //
        }
        installPane.progressMessage("done writing");
    }
}

