/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Ant", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.tools.ant.taskdefs.optional.sitraka.coverage;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.FileWriter;
import java.io.File;
import java.io.OutputStream;
import java.io.IOException;
import java.util.Vector;
import java.util.Random;
import java.util.Hashtable;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.CommandlineJava;
import org.apache.tools.ant.types.Commandline;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.taskdefs.Execute;
import org.apache.tools.ant.taskdefs.LogStreamHandler;

/**
 * Convenient task to run Sitraka JProbe Coverage from Ant.
 * Options are pretty numerous, you'd better check the manual for a full
 * descriptions of options. (not that simple since they differ from the online
 * help, from the usage command line and from the examples...)
 * <p>
 * For additional information, visit <a href="http://www.sitraka.com">www.sitraka.com</a>
 *
 * @author <a href="sbailliez@imediation.com">Stephane Bailliez</a>
 */
public class Coverage extends Task {

    protected File home;

    protected Commandline cmdl = new Commandline();

    protected CommandlineJava cmdlJava = new CommandlineJava();

    protected String function = "coverage";

    protected String seedName;

    protected File javaHome;

    protected File javaExe;

    protected String vm;

    protected boolean applet = false;

    /** this is a somewhat annoying thing, set it to never */
    protected String exitPrompt = "never";

    protected Filters filters = new Filters();

    protected Triggers triggers;

    protected String finalSnapshot = "coverage";

    protected String recordFromStart = "coverage";

    protected File snapshotDir;

    protected File workingDir;

    protected boolean trackNatives = false;

    protected Socket socket;

    protected int warnLevel = 0;

    protected Vector filesets = new Vector();

    //--------- setters used via reflection --

    /** set the coverage home directory where are libraries, jars and jplauncher */
    public void setHome(File value){
        home = value;
    }

    /** seed name for snapshot file. can be null, default to snap */
    public void setSeedname(String value){
        seedName = value;
    }

    public void setJavahome(File value){
        javaHome = value;
    }

    public void setJavaexe(File value){
        javaExe = value;
    }

    /** jdk117, jdk118 or java2, can be null, default to java2 */
    public void setVm(String value) {
        vm = value;
    }

    /** default to false unless file is htm or html */
    public void setApplet(boolean value){
        applet = value;
    }

    /** always, error, never */
    public void setExitprompt(String value){
        exitPrompt = value;
    }

    public Filters createFilters(){
        return filters;
    }

    public Triggers createTriggers(){
        if (triggers == null) {
            triggers = new Triggers();
        }
        return triggers;
    }

    public Socket createSocket(){
        if (socket == null ) {
            socket = new Socket();
        }
        return socket;
    }

    /** none, coverage, all. can be null, default to none */
    public void setFinalsnapshot(String value){
        if (!"coverage".equals(value) && !"none".equals(value) && !"all".equals(value)) {
            throw new BuildException("'finalsnapshot' invalid. Should be one of 'all', 'coverage', 'none'");
        }
        finalSnapshot = value;
    }

    /** all, coverage, none */
    public void setRecordfromstart(String value) throws BuildException {
        if (!"coverage".equals(value) && !"none".equals(value) && !"all".equals(value)) {
            throw new BuildException("'recordfromstart' invalid. Should be one of 'all', 'coverage', 'none'");
        }
        recordFromStart = value;
    }

    public void setWarnlevel(Integer value){
        warnLevel = value.intValue();
    }

    public void setSnapshotdir(File value){
        snapshotDir = value;
    }

    public void setWorkingdir(File value){
        workingDir = value;
    }

    public void setTracknatives(boolean value){
        trackNatives = value;
    }

    //

    /** the jvm arguments */
    public Commandline.Argument createJvmarg() {
        return cmdlJava.createVmArgument();
    }

    /** the command arguments */
    public Commandline.Argument createArg() {
        return cmdlJava.createArgument();
    }

    /** classpath to run the files */
    public Path createClasspath() {
        return cmdlJava.createClasspath(project).createPath();
    }

    /** classname to run as standalone or runner for filesets */
    public void setClassname(String value){
        cmdlJava.setClassname(value);
    }

    /** the classnames to execute */
    public void addFileset(FileSet fs){
        filesets.addElement(fs);
    }


    //---------------- the tedious job begins here

    public Coverage(){
    }

    /** execute the jplauncher by providing a parameter file */
    public void execute() throws BuildException {
        checkOptions();
        File paramfile = createParamFile();
        try {
            // we need to run Coverage from his directory due to dll/jar issues
            cmdl.setExecutable( new File(home, "jplauncher").getAbsolutePath() );
            cmdl.createArgument().setValue("-jp_input=" + paramfile.getAbsolutePath());
            
            // use the custom handler for stdin issues
            LogStreamHandler handler = new CoverageStreamHandler(this);
            Execute exec = new Execute( handler );
            log(cmdl.toString(), Project.MSG_VERBOSE);
            exec.setCommandline(cmdl.getCommandline());
            int exitValue = exec.execute();
            if (exitValue != 0) {
                throw new BuildException("JProbe Coverage failed (" + exitValue + ")");
            }
        } catch (IOException e){
            throw new BuildException("Failed to execute JProbe Coverage.", e);
        } finally {
            //@todo should be removed once switched to JDK1.2
            paramfile.delete();
        }       
    }

    /** wheck what is necessary to check, Coverage will do the job for us */
    protected void checkOptions() throws BuildException {
        // check coverage home
        if (home == null || !home.isDirectory() || !home.exists()) {
            throw new BuildException("Invalid home directory. Must point to JProbe Coverage home directory");
        }
        home = project.resolveFile( home.getPath() );

        // make sure snapshot dir exists and is resolved
        if (snapshotDir == null) {
            snapshotDir = new File(".");
        }
        snapshotDir = project.resolveFile(snapshotDir.getPath());
        if (!snapshotDir.isDirectory() || !snapshotDir.exists()) {
            throw new BuildException("Snapshot directory does not exists :" + snapshotDir);
        }

        // check for info.
        /*
        if ("java2".equals(vm) && javaHome == null && javaExe == null) {
            throw new BuildException("'javahome' or 'javaexe' must be set if using 'java2' vm");
        } */
    }

    /**
     * return the command line parameters. Parameters can either be passed
     * to the command line and stored to a file (then use the -jp_input=<filename>)
     * if they are too numerous.
     */
    protected String[] getParameters(){
        Vector params = new Vector();
        params.addElement("-jp_function=" + function);
        if (vm != null) {
            params.addElement("-jp_vm=" + vm);
        }
        if (javaHome != null) {
            params.addElement("-jp_java_home" + project.resolveFile(javaHome.getPath()));
        }
        if (javaExe != null) {
            params.addElement("-jp_java_exe" + project.resolveFile(javaExe.getPath()));
        }
        if (workingDir != null) {
            params.addElement("-jp_working_dir=" + project.resolveFile(workingDir.getPath()) );
        }
        params.addElement("-jp_snapshot_dir=" + snapshotDir.getPath() );
        params.addElement("-jp_record_from_start=" + recordFromStart);
        params.addElement("-jp_warn=" + warnLevel);
        if (seedName != null) {
            params.addElement("-jp_output_file=" + seedName);
        }
        params.addElement("-jp_filter=" + filters.toString() );
        if (triggers != null) {
            params.addElement("-jp_trigger=" + triggers.toString() );
        }
        if (finalSnapshot != null) {
            params.addElement("-jp_final_snapshot=" + finalSnapshot);
        }
        params.addElement("-jp_exit_prompt=" + exitPrompt);
        //params.addElement("-jp_append=" + append);
        params.addElement("-jp_track_natives=" + trackNatives);
        //.... now the jvm
        // arguments
        String[] vmargs = cmdlJava.getVmCommand().getArguments();
        for (int i = 0; i < vmargs.length; i++) {
            params.addElement(vmargs[i]);
        }
        // classpath
        Path classpath = cmdlJava.getClasspath();
        if (classpath != null && classpath.size() > 0) {
            params.addElement("-classpath " + classpath.toString());
        }
        // classname (runner or standalone)
        if (cmdlJava.getClassname() != null) {
            params.addElement(cmdlJava.getClassname());
        }
        // arguments for classname
        String[] args = cmdlJava.getJavaCommand().getArguments();
        for (int i = 0; i < args.length; i++) {
            params.addElement(args[i]);
        }
        
        String[] array = new String[params.size()];
        params.copyInto(array);
        return array;
    }   
    
    
    /**
     * create the parameter file from the given options. The file is
     * created with a random name in the current directory.
     * @return the file object where are written the configuration to run
     * JProbe Coverage
     * @throws BuildException thrown if something bad happens while writing
     * the arguments to the file.
     */
    protected File createParamFile() throws BuildException {
        //@todo change this when switching to JDK 1.2 and use File.createTmpFile()
        File file = createTmpFile();
        log("Creating parameter file: " + file, Project.MSG_VERBOSE);
        
        // options need to be one per line in the parameter file
        // so write them all in a single string
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        String[] params = getParameters();
        for (int i = 0; i < params.length; i++){
            pw.println(params[i]);
        }
        pw.flush();     
        log("JProbe Coverage parameters:\n" + sw.toString(), Project.MSG_VERBOSE);
        
        // now write them to the file
        FileWriter fw = null;
        try {
            fw = new FileWriter(file);
            fw.write(sw.toString());
            fw.flush();
        } catch (IOException e){
            throw new BuildException("Could not write parameter file " + file, e);
        } finally {
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException ignored){}
            }
        }
        return file;
    }

    /** create a temporary file in the current dir (For JDK1.1 support) */
    protected File createTmpFile(){
        final long rand = (new Random(System.currentTimeMillis())).nextLong();
        File file = new File("jpcoverage" + rand + ".tmp");
        return file;
    }

    /** specific pumper to avoid those nasty stdin issues */
    static class CoverageStreamHandler extends LogStreamHandler {
        CoverageStreamHandler(Task task){
            super(task, Project.MSG_INFO, Project.MSG_WARN);
        }
        /**
         * there are some issues concerning all JProbe executable
         * In our case a 'Press ENTER to close this window..." will
         * be displayed in the current window waiting for enter.
         * So I'm closing the stream right away to avoid problems.
         */
        public void setProcessInputStream(OutputStream os) {
            try {
                os.close();
            } catch (IOException ignored){
            }
        }
    }

}
