// Copyright (c) 2004-2005 Sun Microsystems, Inc. All rights reserved. Use is
// subject to license terms.
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the Lesser 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

package org.jdesktop.jdic.screensaver.autogen;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Properties;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.tools.ant.*;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.FileSet;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Generates .nsi installer script for the screensavers in the
 * configuration directory. 
 * <p>
 * Steps through each screensaver by looking at the XML files present in
 * the configuration directory.  Autogenerates a single .nsi file which
 * can be compiled with nsis (see 
 * <a href="http://nsis.sourceforge.net/">http://nsis.sourceforge.net/</a>)
 * to generate a Winodws installer for the screensavers.
 * <p>
 * A template .nsi file is used (from the resources package) and the following
 * substitutions are made:
 * <ul>
 *   <li>[[product_name]] - Name of product</li>
 *   <li>[[product_version]] - Version of product</li>
 *   <li>[[product_publisher]] - Publisher of product</li>
 *   <li>[[product_web_site]] - Web site of product</li>
 *   <li>[[license_file]] - Path of license file</li>
 *   <li>[[outfile]] - Name of output file (e.g. Setup.exe)</li>
 *   <li>[[saverbeans_sdk]] - Path of SaverBeans SDK</li>
 *   <li>[[jogl_files]] - JOGL files</li>
 *   <li>[[screensaver_sections]] - NSI Sections for each screensaver
 *     (autogenerated from info in screensaver config files)</li>
 *   <li>[[screensaver_descriptions]] - NSI sections for descriptions of
 *     each screensaver.</li>
 *   <li>[[screensaver_delete]] - NSI sections for deleting files during
 *     uninstall.</li>
 * </ul>
 * <p>
 * Note: The installer script generated will only work for the local system.
 * It should not be checked into CVS or distributed.
 *
 * @author Mark Roth
 */
public class NsiGeneratorTask 
    extends Task 
{
    /** Name of the product, to display in installer */
    private String productName;
    
    /** Version of product, to display in installer */
    private String productVersion;
    
    /** Publisher of product, to display in installer */
    private String productPublisher;
    
    /** Web site of product, to display in installer */
    private String productWebSite;
    
    /** Path of license file */
    private File licenseFile;
    
    /** Name of output file (e.g. Setup.exe) */
    private String outputFilename;
    
    /** Path to SaverBeans SDK */
    private File saverbeansSdkPath;
    
    /** True if this is a JOGL screensaver, or false if not */
    private boolean jogl;
    
    /** The fileset of configuration files */
    private FileSet confFileSet;
    
    /** The file to store the final .nsi script to */
    private File outFile;
    
    /** The directory containing the screensaver .jar files */
    private File jarDir;
    
    /** The directory containing the screensaver .scr files */
    private File scrDir;
    
    /** Initial section number in .nsi file */
    private static final int INITIAL_SECTION_NUMBER = 2;
    
    /** Line terminator to use */
    private static final String CRLF = "\r\n";
    
    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    public String getProductVersion() {
        return productVersion;
    }

    public void setProductVersion(String productVersion) {
        this.productVersion = productVersion;
    }

    public String getProductPublisher() {
        return productPublisher;
    }

    public void setProductPublisher(String productPublisher) {
        this.productPublisher = productPublisher;
    }

    public String getProductWebSite() {
        return productWebSite;
    }

    public void setProductWebSite(String productWebSite) {
        this.productWebSite = productWebSite;
    }

    public File getLicenseFile() {
        return licenseFile;
    }

    public void setLicenseFile(File licenseFile) {
        this.licenseFile = licenseFile;
    }

    public String getOutputFilename() {
        return outputFilename;
    }

    public void setOutputFilename(String outputFilename) {
        this.outputFilename = outputFilename;
    }

    public File getSaverbeansSdkPath() {
        return saverbeansSdkPath;
    }

    public void setSaverbeansSdkPath(File saverbeansSdkPath) {
        this.saverbeansSdkPath = saverbeansSdkPath;
    }
    
    public boolean isJogl() {
        return this.jogl;
    }
    
    public void setJogl(boolean jogl) {
        this.jogl = jogl;
    }
    
    public void addFileSet(FileSet confFileSet) {
        this.confFileSet = confFileSet;
    }
    
    public void setOutFile(File outFile) {
        this.outFile = outFile;
    }
    
    public File getOutFile() {
        return this.outFile;
    }
    
    public void setJarDir(File jarDir) {
        this.jarDir = jarDir;
    }
    
    public File getJarDir() {
        return this.jarDir;
    }
    
    public void setScrDir(File scrDir) {
        this.scrDir = scrDir;
    }
    
    public File getScrDir() {
        return this.scrDir;
    }
    
    public void execute() 
        throws BuildException 
    {
    
        if(productName == null) {
            throw new BuildException("productName parameter is required.");
        }
    
        if(productVersion == null) {
            throw new BuildException("productVersion parameter is required.");
        }
    
        if(productPublisher == null) {
            throw new BuildException("productPublisher parameter is required.");
        }
    
        if(productWebSite == null) {
            throw new BuildException("productWebSite parameter is required.");
        }
    
        if((licenseFile == null) || !licenseFile.exists()) {
            throw new BuildException(
                "licenseFile not specified or path does not exist.");
        }
    
        if(outputFilename == null) {
            throw new BuildException("outputFilename parameter is required.");
        }
    
        if((saverbeansSdkPath == null) || !saverbeansSdkPath.isDirectory()) {
            throw new BuildException(
                "saverbeansSdkPath parameter not specified or path " +
                "does not exist.");
        }
    
        if((jarDir == null) || !jarDir.isDirectory()) {
            throw new BuildException(
                "jarDir parameter not specified or path " +
                "does not exist.");
        }
    
        if((scrDir == null) || !scrDir.isDirectory()) {
            throw new BuildException(
                "scrDir parameter not specified or path " +
                "does not exist.");
        }
    
        if(confFileSet == null) {
            throw new BuildException(
                "FileSet of configuration files is required.");
        }
        
        if(outFile == null) {
            throw new BuildException("outFile parameter is required.");
        }
        
        File outFileDir = outFile.getParentFile();
        if(!outFileDir.isDirectory()) {
            throw new BuildException("Directory " + 
                outFileDir.getAbsolutePath() + " does not exist.");
        }
        
        DirectoryScanner confScanner = 
            confFileSet.getDirectoryScanner(getProject());
        String[] confFiles = confScanner.getIncludedFiles();
            
        // Keep track of all screensavers so we can build our Makefile
        Properties savers = new Properties();
        
        log("Building Windows installer script...");
        try {
            DocumentBuilderFactory builderFactory = 
                DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = builderFactory.newDocumentBuilder();

            Properties variables = new Properties();
            variables.put("product_name", productName);
            variables.put("product_version", productVersion);
            variables.put("product_publisher", productPublisher);
            variables.put("product_web_site", productWebSite);
            variables.put("license_file", licenseFile.getAbsolutePath());
            variables.put("outfile", outputFilename);
            variables.put("saverbeans_sdk", 
                saverbeansSdkPath.getAbsolutePath());
            File buildDir = new File(getProject().getBaseDir(), "build");
            if(!buildDir.isDirectory()) {
                throw new BuildException(
                    "Could not find build directory " + 
                    buildDir.getAbsolutePath() + 
                    ".  Be sure to build the screensavers before " +
                    "building the installer.");
            }
            File joglNativeDir = new File(buildDir, "win32-jogl");
            if(jogl) {
                variables.put("jogl_files", 
                    "File \"" + joglNativeDir.getAbsolutePath() + 
                        "\\jogl_cg.dll\"" + CRLF +
                    "File \"" + joglNativeDir.getAbsolutePath() + 
                        "\\jogl.dll\"" + CRLF +
                    "File \"" + saverbeansSdkPath.getAbsolutePath() + 
                        "\\lib\\jogl\\jogl.jar\"" + CRLF
                );
            }
            else {
                variables.put("jogl_files", "");
            }
            StringBuffer screensaverSections = new StringBuffer();
            StringBuffer screensaverDescriptions = new StringBuffer();
            StringBuffer screensaverDelete = new StringBuffer();
            
            int sectionNumber = INITIAL_SECTION_NUMBER;
            NumberFormat integerFormat = DecimalFormat.getIntegerInstance();
            integerFormat.setMinimumIntegerDigits(2);
            
            for( int i = 0; i < confFiles.length; i++ ) {
                log("  - Processing " + confFiles[i] + "...");
                
                try {
                    File confFile = new File(confScanner.getBasedir(),
                        confFiles[i]);
                    InputStream in = new FileInputStream(confFile);
                    Document document = builder.parse(new InputSource(in));
                    HackConfigScanner scanner = new HackConfigScanner(
                        document);
                    scanner.visitDocument();
                    in.close();
                    HackConfig config = scanner.getHackConfig();
                    String name = config.getName();
                    String label = config.getLabel();
                    String description = config.getDescription();
                    String sectionLabel = "SEC" + 
                        integerFormat.format(sectionNumber);
                    
                    // Find -jar argument:
                    String jarArg = null;
                    ArrayList options = config.getOptions();
                    for(int j = 0; j < options.size(); j++) {
                        HackConfig.Option opt =
                            (HackConfig.Option)options.get(j);
                        if(opt instanceof HackConfig.CommandOption) {
                            HackConfig.CommandOption cmd = 
                                (HackConfig.CommandOption)opt;
                            String arg = cmd.getArg();
                            if(arg.startsWith("-jar ")) {
                                jarArg = arg.substring(5);
                            }
                        }
                    }
                    if(jarArg == null) {
                        throw new BuildException("Error: " + 
                            confFiles[i] + " has no command with " +
                            "-jar argument");
                    }
                    
                    // Create section for this saver:
                    screensaverSections.append(
                        "Section \"" + label + "\" " + sectionLabel + CRLF + 
                        "  File \"" + jarDir.getAbsolutePath() + 
                            File.separator + jarArg + "\"" + CRLF + 
                        "  File \"" + scrDir.getAbsolutePath() + 
                            File.separator + name + ".scr\"" + CRLF + 
                        "SectionEnd" + CRLF
                    );
                    
                    // Create description for this saver:
                    if(description == null) description = "";
                    description = nsiQuote(description.trim());
                    screensaverDescriptions.append(
                        "!insertmacro MUI_DESCRIPTION_TEXT ${" + 
                            sectionLabel + "} \"" + description + "\"" + CRLF
                    );
                    
                    // Create delete section for this saver:
                    screensaverDelete.append(
                        "Delete \"$INSTDIR" + 
                            File.separator + jarArg + "\"" + CRLF +
                        "Delete \"$INSTDIR" + 
                            File.separator + name + ".scr\"" + CRLF
                    );
                }
                catch( IOException e ) {
                    throw new BuildException( e );
                }
                catch( SAXException e ) {
                    throw new BuildException( e );
                }
                sectionNumber++;
            }
            variables.put("screensaver_sections", 
                screensaverSections.toString());
            variables.put("screensaver_descriptions",
                screensaverDescriptions.toString());
            variables.put("screensaver_delete", 
                screensaverDelete.toString());
            
            Utilities.copyFileAndSubstitute(outFile,
                "/org/jdesktop/jdic/screensaver/autogen/resources/win32/" +
                "saverbeans-screensaver-pack.nsi.template", 
                variables, "[[", "]]", true);
        }
        catch( ParserConfigurationException e ) {
            throw new BuildException( e );
        }
        catch( IOException e ) {
            throw new BuildException( e );
        }
    }
    
    /**
     * Quotes the given String in a format suitable for NSI
     */
    private String nsiQuote(String s) {
        // $\" doesn't seem to work for description macro!
        //s = s.replaceAll("\\\"", "\\$\\\\\"");
        s = s.replaceAll("\\\"", "''");
        s = s.replaceAll("\n", "\\$\\\\r\\$\\\\n");
        return s;
    }
}
