/* *******************************************************************
 * Copyright (c) 1999-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 Eclipse Public License v1.0 
 * which accompanies this distribution and is available at 
 * http://www.eclipse.org/legal/epl-v10.html 
 *  
 * Contributors: 
 *     Xerox/PARC     initial implementation 
 * ******************************************************************/

//XXX INCLUDES CODE FROM ANT -- UNDER APACHE LICENSE
package org.aspectj.internal.tools.ant.taskdefs;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringBufferInputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.taskdefs.Delete;
import org.apache.tools.ant.taskdefs.Expand;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.PatternSet;

public class AJInstaller extends MatchingTask {
    static final String INCLUDE_CLASSES = "$installer$/org/aspectj/*.class";
    static final String MAIN_CLASS = "$installer$.org.aspectj.Main";
    static final String CONTENTS_FILE = "$installer$/org/aspectj/resources/contents.txt";
    private String htmlSrc;
    
    public void setHtmlSrc(String v) { htmlSrc = v; }

    private String resourcesSrc;

    public void setResourcesSrc(String v) { resourcesSrc = v; }

    private String mainclass;

    public void setMainclass(String v) { mainclass = v; }

    private File installerClassJar;

    public void setInstallerclassjar(String v) { 
        installerClassJar = project.resolveFile(v);
    }

    protected List contentsNames = new ArrayList();

    protected long contentsBytes = 0;

    protected void addToContents(File file, String vPath) {
        contentsNames.add(vPath);
        contentsBytes += file.length();
    }

    String[] getFiles(File baseDir) {
        DirectoryScanner ds = new DirectoryScanner();
        setBasedir(baseDir.getAbsolutePath());
        ds.setBasedir(baseDir);
        //ds.setIncludes(new String [] {pattern});
        ds.scan();
        return ds.getIncludedFiles();
    }

    protected Copy getCopyTask() {
        Copy cd = (Copy)project.createTask("copy");
        if (null == cd) {
            log("project.createTask(\"copy\") failed - direct", Project.MSG_VERBOSE);
            cd = new Copy();
            cd.setProject(getProject());
        }
        return cd;
    }
    protected void finishZipOutputStream(ZipOutputStream zOut) throws IOException, BuildException {
        writeContents(zOut);
        writeManifest(zOut);
        File tempDir = setupTempDir();
        String tmp = tempDir.getAbsolutePath();

        // installer class files
        Expand expand = new Expand();
        expand.setProject(getProject());
        expand.setSrc(installerClassJar);
        expand.setDest(new File(tmp));
        PatternSet patterns = new PatternSet();
        patterns.setIncludes(INCLUDE_CLASSES);
        expand.addPatternset(patterns);
        expand.execute();        

        // move the correct resource files into the jar
        Copy cd = getCopyTask();
        fileset = new FileSet();
        fileset.setDir(new File(resourcesSrc));
        fileset.setIncludes("*");
        fileset.setExcludes("contents.txt,properties.txt");
        cd.addFileset(fileset);
        cd.setTodir(new File(tmp+"/$installer$/org/aspectj/resources"));
        cd.execute();
        project.getGlobalFilterSet().addFilter("installer.main.class", this.mainclass);
        Copy cf = getCopyTask();
        fileset = new FileSet();
        fileset.setDir(new File(resourcesSrc));
        fileset.setIncludes("properties.txt");
        cf.setFiltering(true);
        cf.addFileset(fileset);
        cf.setTodir(new File(tmp+"/$installer$/org/aspectj/resources"));
        cf.execute();
        // move the correct resource files into the jar
        cd = getCopyTask();
        fileset = new FileSet();
        fileset.setDir(new File(htmlSrc));
        fileset.setIncludes("*");
        cd.addFileset(fileset);
        cd.setTodir(new File(tmp+"/$installer$/org/aspectj/resources"));
        cd.execute();
        // now move these files into the jar
        setBasedir(tmp);
        writeFiles(zOut, getFiles(tempDir));
        // and delete the tmp dir
        Delete dt = (Delete)project.createTask("delete");
        if (null == dt) {
            dt = new Delete();
            dt.setProject(getProject());
        }
        dt.setDir(tempDir);
        dt.execute();
        tempDir = null;
    }

    static final char NEWLINE = '\n';

    protected void writeContents(ZipOutputStream zOut) throws IOException {
        // write to a StringBuffer
        StringBuffer buf = new StringBuffer();
        buf.append(contentsBytes);
        buf.append(NEWLINE);
        for (Iterator i = contentsNames.iterator(); i.hasNext(); ) {
            String name = (String)i.next();
            buf.append(name);
            buf.append(NEWLINE);
        }
        zipFile(new StringBufferInputStream(buf.toString()), zOut, CONTENTS_FILE, System.currentTimeMillis());
    }

    protected void writeManifest(ZipOutputStream zOut) throws IOException {
        // write to a StringBuffer
        StringBuffer buf = new StringBuffer();
        buf.append("Manifest-Version: 1.0");
        buf.append(NEWLINE);
        buf.append("Main-Class: " + MAIN_CLASS);
        buf.append(NEWLINE);
        zipFile(new StringBufferInputStream(buf.toString()), zOut, "META-INF/MANIFEST.MF", System.currentTimeMillis());
    }

    //XXX cut-and-paste from Zip super-class (under apache license)
    private File zipFile;
    private File baseDir;
    private boolean doCompress = true;
    protected String archiveType = "zip";

    /**
     * This is the name/location of where to
     * create the .zip file.
     */
    public void setZipfile(String zipFilename) {
        zipFile = project.resolveFile(zipFilename);
    }

    /**
     * This is the base directory to look in for
     * things to zip.
     */
    public void setBasedir(String baseDirname) {
        baseDir = project.resolveFile(baseDirname);
    }

    /**
     * Sets whether we want to compress the files or only store them.
     */
    public void setCompress(String compress) {
        doCompress = Project.toBoolean(compress);
    }

    protected void initZipOutputStream(ZipOutputStream zOut)
        throws IOException, BuildException
    {
    }

    protected void zipDir(File dir, ZipOutputStream zOut, String vPath)
        throws IOException
    {
    }

    protected void zipFile(InputStream in, ZipOutputStream zOut, String vPath,
                           long lastModified)
        throws IOException
    {
        ZipEntry ze = new ZipEntry(vPath);
        ze.setTime(lastModified);

        /*
         * XXX ZipOutputStream.putEntry expects the ZipEntry to know its
         * size and the CRC sum before you start writing the data when using
         * STORED mode.
         *
         * This forces us to process the data twice.
         *
         * I couldn't find any documentation on this, just found out by try
         * and error.
         */
        if (!doCompress) {
            long size = 0;
            CRC32 cal = new CRC32();
            if (!in.markSupported()) {
                // Store data into a byte[]
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                byte[] buffer = new byte[8 * 1024];
                int count = 0;
                do {
                    size += count;
                    cal.update(buffer, 0, count);
                    bos.write(buffer, 0, count);
                    count = in.read(buffer, 0, buffer.length);
                } while (count != -1);
                in = new ByteArrayInputStream(bos.toByteArray());
            } else {
                in.mark(Integer.MAX_VALUE);
                byte[] buffer = new byte[8 * 1024];
                int count = 0;
                do {
                    size += count;
                    cal.update(buffer, 0, count);
                    count = in.read(buffer, 0, buffer.length);
                } while (count != -1);
                in.reset();
            }
            ze.setSize(size);
            ze.setCrc(cal.getValue());
        }
        zOut.putNextEntry(ze);
        byte[] buffer = new byte[8 * 1024];
        int count = 0;
        do {
            zOut.write(buffer, 0, count);
            count = in.read(buffer, 0, buffer.length);
        } while (count != -1);
    }

    protected void zipFile(File file, ZipOutputStream zOut, String vPath)
        throws IOException
    {
        if ( !vPath.startsWith("$installer$") ) {
            addToContents(file, vPath);
        }
        FileInputStream fIn = new FileInputStream(file);
        try {
            zipFile(fIn, zOut, vPath, file.lastModified());
        } finally {
            fIn.close();
        }
    }
    private File setupTempDir() throws BuildException {
        File tmpDirF = null;
        File tmpDir = null;
        try {
            tmpDirF = File.createTempFile("tgz", ".di");
            tmpDir = new File(tmpDirF.getParentFile(), "AJInstaller");
            tmpDirF.delete();
        } catch (IOException e) {
            // retrying below
        }
        if (null == tmpDir || !tmpDir.mkdirs()) {
            tmpDir = new File("AJInstaller.finishZipOutputStream.tmp");
            if (!tmpDir.mkdirs()) {
                throw new BuildException("unable to make temp dir");
            }
        }
        return tmpDir;
    }
    
    public void execute() throws BuildException {
        if (installerClassJar == null) {
            throw new BuildException("installerClassJar attribute must be set!");
        }
        if (!installerClassJar.canRead() 
            || !installerClassJar.getPath().endsWith(".jar")) {
            throw new BuildException("not readable jar:" + installerClassJar);
        }
//        if (installerClassDir == null) {
//            throw new BuildException("installerClassDir attribute must be set!");
//        }
//        if (!installerClassDir.exists()) {
//            throw new BuildException("no such directory: installerClassDir=" + installerClassDir);
//        }
        if (baseDir == null) {
            throw new BuildException("basedir attribute must be set!");
        }
        if (!baseDir.exists()) {
            throw new BuildException("basedir does not exist!");
        }
        DirectoryScanner ds = super.getDirectoryScanner(baseDir);
        String[] files = ds.getIncludedFiles();
        String[] dirs  = ds.getIncludedDirectories();
        log("Building installer: "+ zipFile.getAbsolutePath());
        ZipOutputStream zOut = null;
        try {
            zOut = new ZipOutputStream(new FileOutputStream(zipFile));
            if (doCompress) {
                zOut.setMethod(ZipOutputStream.DEFLATED);
            } else {
                zOut.setMethod(ZipOutputStream.STORED);
            }
            initZipOutputStream(zOut);
            writeDirs(zOut, dirs);
            writeFiles(zOut, files);
            finishZipOutputStream(zOut); // deletes temp dir
        } catch (IOException ioe) {
            String msg = "Problem creating " + archiveType + " " + ioe.getMessage();
            throw new BuildException(msg, ioe, location);
        } finally {
            if (zOut != null) {
                try {
                    // close up
                    zOut.close();
                }
                catch (IOException e) {}
            }
        }
    }

    protected void writeDirs(ZipOutputStream zOut, String[] dirs) throws IOException {
        for (int i = 0; i < dirs.length; i++) {
            File f = new File(baseDir,dirs[i]);
            String name = dirs[i].replace(File.separatorChar,'/')+"/";
            zipDir(f, zOut, name);
        }
    }

    protected void writeFiles(ZipOutputStream zOut, String[] files) throws IOException {
        for (int i = 0; i < files.length; i++) {
            File f = new File(baseDir,files[i]);
            String name = files[i].replace(File.separatorChar,'/');
            zipFile(f, zOut, name);
        }
    }

}
