// SimpleAnt.java
//
// Created: November 2005
//
// This file is a component of the BioMoby project.
// Copyright Martin Senger (martin.senger@gmail.com).
//

package org.biomoby.service.dashboard;

import org.apache.tools.ant.DemuxInputStream;
import org.apache.tools.ant.DemuxOutputStream;
import org.apache.tools.ant.Main;
import org.apache.tools.ant.Diagnostics;
import org.apache.tools.ant.BuildListener;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.BuildLogger;
import org.apache.tools.ant.input.DefaultInputHandler;
import org.apache.tools.ant.input.InputHandler;

import java.io.File;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;


/**
 * A wrapper that invokes Ant on a <tt>build.xml</tt> file in the
 * current directory. It does not upport any command-line options. The
 * main method is {@link #startAnt} that takes an array of Ant
 * targets, and optionally a list of additional properties. <p>
 *
 * This is a simplified version of the original Ant's
 * <tt>org.apache.tools.ant.Main</tt> file. It is used in the Biomoby
 * Dashboard project. <p>
 *
 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: SimpleAnt.java,v 1.7 2008/03/02 12:45:26 senger Exp $
 */
public class SimpleAnt {

    private static org.apache.commons.logging.Log log =
       org.apache.commons.logging.LogFactory.getLog (SimpleAnt.class);

    /** Our current message output status. Follows Project.MSG_XXX. */
    private int msgOutputLevel = Project.MSG_INFO;

    /** File that we are using for configuration. */
    private File buildFile = new File (Main.DEFAULT_BUILD_FILENAME);

    /** Stream to use for logging. */
    private static PrintStream out = System.out;

    /** Stream that we are using for logging error messages. */
    private static PrintStream err = System.err;

    /** Set of properties that can be used by tasks. */
    private Properties definedProps = new Properties();

    /** Names of classes to add as listeners to project. */
    private Vector<BuildListener> listeners = new Vector<BuildListener> (1);

    /** Indicates whether this build is to support interactive input */
    private boolean allowInput = false;
    private InputHandler inputHandler = new DefaultInputHandler();

    /*********************************************************************
     * Default constructor.
     ********************************************************************/
    public SimpleAnt() {
    }

    /*********************************************************************
     *
     ********************************************************************/
    public void setAllowInput (boolean enabled) {
	allowInput = enabled;
    }

    /*********************************************************************
     *
     ********************************************************************/
    public void setMsgOutputLevel (int level) {
	msgOutputLevel = level;
    }

    /*********************************************************************
     *
     ********************************************************************/
    public synchronized void addBuildListener (BuildListener listener) {
	listeners.addElement (listener);
    }

    /*********************************************************************
     *
     ********************************************************************/
    public synchronized void removeBuildListener (BuildListener listener) {
        listeners.removeElement (listener);
    }

    /*********************************************************************
     * Start Ant to execute given targets, possibly using additional
     * properties.
     ********************************************************************/
    public void startAnt (String[] newTargets,
			  Properties additionalUserProperties)
	throws BuildException {

//         PrintStream logTo = null;
//         if (logTo != null) {
//             out = logTo;
//             err = logTo;
//             System.setOut(out);
//             System.setErr(err);
//         }

	Diagnostics.validateVersion();
	Vector<String> targets = new Vector<String> (newTargets.length);
        for (int i = 0; i < newTargets.length; i++)
	    targets.addElement (newTargets[i]);

        // make sure buildfile exists and is a plain file
        if (! buildFile.exists())
            throw new BuildException ("Buildfile: " + buildFile + " does not exist!");
        if (buildFile.isDirectory())
            throw new BuildException("Buildfile: " + buildFile + " is a dir!");

        if (additionalUserProperties != null) {
            for (Enumeration e = additionalUserProperties.keys();
                    e.hasMoreElements();) {
                String key = (String) e.nextElement();
                String property = additionalUserProperties.getProperty (key);
                definedProps.put (key, property);
            }
        }

	runBuild (targets);

    }

    /*********************************************************************
     * The main job is done here: parse build file and execute targets.
     ********************************************************************/
    private void runBuild (Vector<String> targets)
	throws BuildException {

        final Project project = new Project();
	Throwable error = null;

	// add the listeners (or a default one if no listener registered)
	if (listeners.size() == 0) {
	    DefaultLogger logger = new DefaultLogger();
	    logger.setMessageOutputLevel (msgOutputLevel);
	    logger.setOutputPrintStream (out);
	    logger.setErrorPrintStream (err);
	    addBuildListener (logger);
	}
	for (int i = 0; i < listeners.size(); i++) {
	    BuildListener listener = listeners.elementAt(i);
	    if (listener instanceof BuildLogger)
		((BuildLogger)listener).setMessageOutputLevel (msgOutputLevel);
	    project.setProjectReference (listener);
	    project.addBuildListener (listener);
	}

        try {
	    // add an input handler
	    if (inputHandler == null)
		inputHandler = new DefaultInputHandler();
	    project.setProjectReference (inputHandler);
	    project.setInputHandler (inputHandler);

            PrintStream _err = System.err;
            PrintStream _out = System.out;
            InputStream _in = System.in;

            try {
                if (allowInput) {
                    project.setDefaultInputStream (System.in);
                }
                System.setIn (new DemuxInputStream (project));
                System.setOut (new PrintStream (new DemuxOutputStream (project, false)));
                System.setErr (new PrintStream (new DemuxOutputStream (project, true)));

		project.fireBuildStarted();

                project.init();
                project.setUserProperty ("ant.version", Main.getAntVersion());

                // set user-define properties
                Enumeration e = definedProps.keys();
                while (e.hasMoreElements()) {
                    String arg = (String) e.nextElement();
                    String value = (String) definedProps.get(arg);
                    project.setUserProperty(arg, value);
                }

                project.setUserProperty ("ant.file",
					 buildFile.getAbsolutePath());
                project.setKeepGoingMode (false);
 		ProjectHelper.configureProject (project, buildFile);

                // make sure that we have a target to execute
                if (targets.size() == 0) {
                    if (project.getDefaultTarget() != null) {
                        targets.addElement(project.getDefaultTarget());
                    }
                }

		// a hack: create an artificial target that depends on
		// all wanted targets and does nothing else (this is
		// because maven.xml causes NullPointerException when
		// - some - more targets are executed)
		Target theTarget = new Target();
		for (Enumeration en = targets.elements(); en.hasMoreElements();) {
		    theTarget.addDependency ((String)en.nextElement());
		}
		project.addTarget ("__the__target__", theTarget);
		project.executeTarget ("__the__target__");

            } finally {
                System.setOut (_out);
                System.setErr (_err);
                System.setIn (_in);
            }

        } catch (BuildException e) {
	    error = e;
	    throw e;
        } catch (Throwable e) {
	    error = e;
            throw new BuildException ("Running Ant failed.", e);
        } finally {
 	    if (error != null)
 		log.error (error.toString());
	    project.fireBuildFinished (error);
        }
    }

}
