/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included with this distribution in
 * the LICENSE file.
 */
package org.apache.ant.project;

import java.util.ArrayList;
import java.util.Iterator;
import org.apache.ant.AntException;
import org.apache.ant.configuration.Configuration;
import org.apache.ant.tasklet.DefaultTaskletContext;
import org.apache.ant.tasklet.TaskletContext;
import org.apache.ant.tasklet.engine.DefaultTaskletEngine;
import org.apache.ant.tasklet.engine.TaskletEngine;
import org.apache.ant.util.Condition;
import org.apache.avalon.AbstractLoggable;
import org.apache.avalon.Composer;
import org.apache.avalon.ComponentManager;
import org.apache.avalon.DefaultComponentManager;
import org.apache.avalon.ComponentManagerException;
import org.apache.avalon.DefaultComponentManager;
import org.apache.avalon.Disposable;
import org.apache.avalon.Initializable;
import org.apache.log.Logger;

/**
 * This is the default implementation of ProjectEngine.
 * 
 * @author <a href="mailto:donaldp@apache.org">Peter Donald</a>
 */
public class DefaultProjectEngine
    extends AbstractLoggable
    implements ProjectEngine, Composer
{
    protected TaskletEngine            m_taskletEngine;
    protected ProjectListenerSupport   m_listenerSupport = new ProjectListenerSupport();
    protected DefaultComponentManager  m_componentManager;
    
    /**
     * Add a listener to project events.
     *
     * @param listener the listener
     */
    public void addProjectListener( final ProjectListener listener )
    {
        m_listenerSupport.addProjectListener( listener );
    }
    
    /**
     * Remove a listener from project events.
     *
     * @param listener the listener
     */
    public void removeProjectListener( final ProjectListener listener )
    {
        m_listenerSupport.removeProjectListener( listener );
    }
    
    /**
     * Retrieve relevent services needed for engine.
     *
     * @param componentManager the ComponentManager
     * @exception ComponentManagerException if an error occurs
     */
    public void compose( final ComponentManager componentManager )
        throws ComponentManagerException
    {
        m_componentManager = (DefaultComponentManager)componentManager;
        m_taskletEngine = (TaskletEngine)componentManager.
            lookup( "org.apache.ant.tasklet.engine.TaskletEngine" );
    }

    /**
     * Execute a target in a particular project.
     * Execute in the project context.
     *
     * @param project the Project
     * @param target the name of the target
     * @exception AntException if an error occurs
     */    
    public void execute( final Project project, final String target )
        throws AntException
    {
        //HACK: should do this a better way !!!!!!
        m_componentManager.put( "org.apache.ant.project.Project", project );

        final TaskletContext context = project.getContext();

        final String projectName = (String)context.getProperty( Project.PROJECT );

        m_listenerSupport.projectStarted( projectName );

        //context = new DefaultTaskletContext( context );

        executeTargetWork( "<init>", project.getImplicitTarget(), context );

        execute( project, target, context );

        m_listenerSupport.projectFinished();
    }

    /**
     * Execute a target in a particular project, in a particular context.
     *
     * @param project the Project
     * @param target the name of the target
     * @param context the context
     * @exception AntException if an error occurs
     */
    public void execute( Project project, String target, TaskletContext context )
        throws AntException
    {
        execute( project, target, context, new ArrayList() );
    }

    /**
     * Helper method to execute a target.
     *
     * @param project the Project
     * @param target the name of the target
     * @param context the context
     * @param done the list of targets already executed in current run
     * @exception AntException if an error occurs
     */
    protected void execute( final Project project, 
                            final String targetName, 
                            final TaskletContext context,
                            final ArrayList done )
        throws AntException
    {
        final Target target = project.getTarget( targetName );

        if( null == target )
        {
            throw new AntException( "Unable to find target " + targetName );
        }
        
        //add target to list of targets executed
        done.add( targetName );

        //execute all dependencies
        final Iterator dependencies = target.getDependencies();
        while( dependencies.hasNext() )
        {
            final String dependency = (String)dependencies.next();
            if( !done.contains( dependency ) )
            {
                execute( project, dependency, context, done );
            }
        }

        executeTarget( targetName, target, context );
    }

    /**
     * Method to execute a particular target instance.
     *
     * @param targetName the name of target
     * @param target the target
     * @param context the context in which to execute
     * @exception AntException if an error occurs
     */
    protected void executeTarget( final String targetName, 
                                  final Target target, 
                                  final TaskletContext context )
        throws AntException
    {
        //is this necessary ? I think not but ....
        // NO it isn't because you set target name and project has already been provided
        //m_componentManager.put( "org.apache.ant.project.Target", target );

        //create project context and set target name
        final TaskletContext targetContext = new DefaultTaskletContext( context );
        targetContext.setProperty( Project.TARGET, targetName );
        
        //notify listeners
        m_listenerSupport.targetStarted( targetName );

        //actually do the execution work 
        executeTargetWork( targetName, target, targetContext );
        
        //notify listeners
        m_listenerSupport.targetFinished();
    }

    /**
     * Do the work associated with target.
     * ie execute all tasks
     *
     * @param name the name of target
     * @param target the target
     * @param context the context
     */
    protected void executeTargetWork( final String name, 
                                      final Target target, 
                                      final TaskletContext context )
    {
        //check the condition associated with target.
        //if it is not satisfied then skip target
        final Condition condition = target.getCondition();
        if( null != condition )
        {
            if( false == condition.evaluate( context ) )
            {
                getLogger().debug( "Skipping target " + name + 
                                   " as it does not satisfy condition" );
                return;
            }
        }

        getLogger().debug( "Executing target " + name );

        //execute all tasks assciated with target
        final Iterator tasks = target.getTasks();
        while( tasks.hasNext() )
        {
            final Configuration task = (Configuration)tasks.next();
            executeTask( task, context );
        }
    }

    /**
     * Execute a task.
     *
     * @param task the task definition
     * @param context the context 
     * @exception AntException if an error occurs
     */
    protected void executeTask( final Configuration task, final TaskletContext context )
        throws AntException
    {
        final String name = task.getName();
        getLogger().debug( "Executing task " + name );

        //Set up context for task...

        //is Only necessary if we are multi-threaded
        //final TaskletContext targetContext = new DefaultTaskletContext( context );

        //is setting name even necessary ???
        context.setProperty( TaskletContext.NAME, name );

        //notify listeners
        m_listenerSupport.taskletStarted( name );

        //run task
        m_taskletEngine.execute( task, context );

        //notify listeners task has ended
        m_listenerSupport.taskletFinished();
    }
}
