package org.apache.tools.ant.taskdefs.optional;

import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildListener;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.Task;

import org.apache.tools.ant.taskdefs.Ant;
import org.apache.tools.ant.taskdefs.Property;

import java.util.Enumeration;
import java.util.HashMap;

/**
 * Call another target in the same project when an error occurs.
 *
 *<pre>
 *  <project name="build" default="main" basedir=".">
 *
 *      <taskdef name="antcallAfterError"
 *      classname="org.apache.tools.ant.taskdefs.optional.AntCallAfterError" />
 *
 *      <target name="init">
 *          <property name="mail.subject" value="success" />
 *      </target>
 *
 *      <target name="build" depends="init">
 *          <fail />
 *      </target>
 *
 *      <target name="main">
 *          <antcallAfterError target="mail" sourcetype="project" >
 *              <param name="mail.subject" value="failure" />
 *          </antcallAfterError>
 *          <antcall target="build" />
 *          <antcall target="mail" />
 *      </target>
 *
 *      <target name="mail" depends="init">
 *          <mail
 *              mailhost="enter mailhost"
 *              subject="${mail.subject}"
 *              message="build status: ${mail.subject}"
 *              tolist="enter email address"
 *              from="myname"
 *          />
 *      </target>
 *  </project>
 *</pre>
 *
 * @author Nick Davis <a href="mailto:nick_home_account@yahoo.com">
 * nick_home_account@yahoo.com</a>
 */
public class AntCallAfterError extends Task {

    /**
     * Name of target to call after an error occurs.
     */
    private String targetname;
    /**
     * The type of element which will generate the error.
     * Possible values are project, target, task.
     */
    private String sourcetype;
    /**
     * The name of element which will generate the error.
     * If the sourcetype is project, sourcename will be
     * ignored.
     */
    private String sourcename;
    /**
     * Used to run the target
     */
    private Ant callee;
    /**
     * Prevents recursive calls to the error handling target.
     * (ie the target generates an error)
     */
    private boolean recursiveFlag = false;
    /**
     * Contains initialized state
     */
    private boolean initialized = false;

    /**
     * Sets the name of the target to call when an error ocurs.
     *
     * @param targetname the target name
     */
    public void setTarget( String targetname ) {
        Target target = (Target) getProject().getTargets().get(targetname);
        if ( target == null )
            throw new BuildException( "Target not found: " + targetname );
        
        this.targetname = targetname;
    }

    /**
     * The type of element which will generate the error.
     * Possible values are project, target or task.
     *
     * @param sourcetype type of element which will generate the error.
     */
    public void setSourceType( String sourcetype ) {
        if ( !"project".equals(sourcetype) &&
             !"target".equals(sourcetype) &&
             !"task".equals(sourcetype) ) {
                 
            throw new BuildException
                ( "sourcetype must project or target or task");
        }
            
        this.sourcetype = sourcetype;
    }
    
    /**
     * The name of element which will generate the error.
     * This value is ignored if the sourcetype is project. 
     *
     * @param sourcename name of element which will generate the error.
     */
    public void setSourceName( String sourcename ) {
        this.sourcename = sourcename;
    }
    
    /**
     * Allows nested <param> elements
     */
    public Property createParam() {
        return callee.createProperty();
    }
    
    /**
     * Creates the <code>Ant</code> object which
     * will preform the job of running the target.
     */
    public void init() {
        callee = (Ant) project.createTask("ant");
        callee.setOwningTarget(getOwningTarget());
        callee.setTaskName(getTaskName());
        callee.setLocation(location);
        callee.init();
        initialized = true;
    }
    
    /**
     * Creates a <code>Handler</code> object.
     */
    public void execute() throws BuildException {
        if ( sourcetype == null )
            throw new BuildException( "No sourcetype specified" );
        if ( targetname == null )
            throw new BuildException( "No task specified" );

        if (!initialized) {
            init();
        }

        // Display a message 
        String msg;
        if (sourcename == null) {
            msg = " [" + getTaskName() + "] " +
                targetname + " will be called when an error occurs in any " +
                sourcetype;
        } else {
            msg = " [" + getTaskName() + "] " +
                targetname + " will be called when an error occurs in " +
                sourcetype + ":" + sourcename;
        }
        getProject().log( msg, Project.MSG_VERBOSE );

        // Create and setup the handler
        Handler handler = createHandler( getProject() );
        callee.setDir(project.getBaseDir());
        callee.setAntfile(project.getProperty("ant.file"));
        callee.setTarget(targetname);
        handler.setCallee( callee );
        handler.setSourceType( sourcetype );
        handler.setSourceName( sourcename );
        handler.setTargetName( targetname );
    }

    /**
     * Nested class which will handle build events
     */
    public class Handler implements BuildListener {

        private String sourcetype = null;
        private String sourcename = null;
        private String targetname = null;
        private Ant callee = null;

        /**
         * Default constuctor
         */
        protected Handler() {
        }

        public void setSourceType( String sourcetype ) {
            this.sourcetype = sourcetype;
        }
    
        public void setSourceName( String sourcename ) {
            this.sourcename = sourcename;
        }
        
        public void setTargetName( String targetname ) {
            this.targetname = targetname;
        }
        
        public void setCallee( Ant callee ) {
            this.callee = callee;
        }
        
        /**
         * Fired after the last target has finished. This event
         * will still be thrown if an error occured during the build.
         */
        public void buildFinished(BuildEvent event) {
            
            if ("project".equals(sourcetype)) {
                
                if (event.getException() != null ) {
                    runTask();
                }
            }
        }
        
        /**
         * Fired when a target has finished. This event will
         * still be thrown if an error occured during the build.
         */
        public void targetFinished(BuildEvent event) {
            
            if ("target".equals(sourcetype)) {
                
                if (sourcename == null ||
                    sourcename.equals(event.getTarget().getName() ) ) {
                    
                    if (event.getException() != null ) {
                        runTask();
                    }
                }
            }
        }
        
        /**
         * Fired when a task has finished. This event will still
         * be throw if an error occured during the build.
         */
        public void taskFinished(BuildEvent event) {
            
            if ("task".equals(sourcetype)) {
                
                if (sourcename == null ||
                    sourcename.equals(event.getTask().getTaskName() ) ) {
                    
                    if (event.getException() != null ) {
                        runTask();
                    }
                }
            }
        }
        
        public void taskStarted(BuildEvent event) {}
        public void targetStarted(BuildEvent event) {}
        public void buildStarted(BuildEvent event) {}
        public void messageLogged(BuildEvent event) {}
        
        /**
         * Execute the target.
         */
        protected void runTask() {
            if (recursiveFlag == false) {
                
                String msg = " [" + getTaskName() + "]" +
                    " calling " + targetname;
                getProject().log( "", Project.MSG_VERBOSE );
                getProject().log( msg, Project.MSG_VERBOSE );
                getProject().log( "", Project.MSG_VERBOSE );
            
                recursiveFlag = true;
                
                try {
                    callee.execute();
                } catch(Throwable exc) {
                    getProject().log( exc.toString(), Project.MSG_ERR );
                }
                
                recursiveFlag = false;
                
                msg = " [" + getTaskName() + "] end";
                getProject().log( msg, Project.MSG_VERBOSE );
            }
        }
    }

    /**
     * Creates and registers a <code>Handler</code> object.
     *
     * @param proj the project
     */
    protected Handler createHandler( Project proj )
            throws BuildException {
        Handler handler = new Handler();
        proj.addBuildListener(handler);
        return handler;
    }
}
