package org.apache.maven.plugin.testing;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.File;
import java.io.InputStream;
import java.util.Map;

import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.MojoExecutionEvent;
import org.apache.maven.execution.MojoExecutionListener;
import org.apache.maven.execution.scope.internal.MojoExecutionScope;
import org.apache.maven.plugin.Mojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.session.scope.internal.SessionScope;
import org.codehaus.plexus.ContainerConfiguration;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.junit.Assert;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

/**
 * {@link TestRule} for usage with Junit-4.10ff. This is just a wrapper for an embedded 
 * {@link AbstractMojoTestCase}, so all <tt>protected</tt> methods of the TestCase are 
 * exhibited as <tt>public</tt> in the rule. You may annotate single tests methods with 
 * {@link WithoutMojo} to prevent the rule from firing.
 *
 * @author Mirko Friedenhagen
 * @version $Id$
 * @since 2.2
 */
public class MojoRule
    implements TestRule
{
    private final AbstractMojoTestCase testCase;
    
    public MojoRule() 
    {
        this( new AbstractMojoTestCase() {} );
    }

    public MojoRule(AbstractMojoTestCase testCase)
    {
        this.testCase = testCase;
    }

    /**
     * May be overridden in the implementation to do stuff <em>after</em> the embedded test case 
     * is set up but <em>before</em> the current test is actually run.
     *
     * @throws Throwable
     */
    protected void before() throws Throwable
    {
        
    }
    
    /**
     * May be overridden in the implementation to do stuff after the current test was run.
     */
    protected void after() 
    {
        
    }

    public InputStream getPublicDescriptorStream()
        throws Exception
    {
        return testCase.getPublicDescriptorStream();
    }

    public String getPluginDescriptorPath()
    {
        return testCase.getPluginDescriptorPath();
    }

    public String getPluginDescriptorLocation()
    {
        return testCase.getPluginDescriptorLocation();
    }

    public void setupContainer()
    {
        testCase.setupContainer();
    }

    public ContainerConfiguration setupContainerConfiguration()
    {
        return testCase.setupContainerConfiguration();
    }
    
    public PlexusContainer getContainer()
    {
        return testCase.getContainer();
    }    
    
    /**
     * Lookup the mojo leveraging the subproject pom
     *
     * @param goal
     * @param pluginPom
     * @return a Mojo instance
     * @throws Exception
     */
    public Mojo lookupMojo( String goal, String pluginPom )
        throws Exception
    {
        return testCase.lookupMojo( goal, pluginPom );
    }

    /**
     * Lookup an empty mojo
     *
     * @param goal
     * @param pluginPom
     * @return a Mojo instance
     * @throws Exception
     */
    public Mojo lookupEmptyMojo( String goal, String pluginPom )
        throws Exception
    {
        return testCase.lookupEmptyMojo( goal, new File( pluginPom ) );
    }

    /**
     * Lookup the mojo leveraging the actual subprojects pom
     *
     * @param goal
     * @param pom
     * @return a Mojo instance
     * @throws Exception
     */
    public Mojo lookupMojo( String goal, File pom )
        throws Exception
    {
        return testCase.lookupMojo( goal, pom );
    }

    /**
     * Lookup the mojo leveraging the actual subprojects pom
     *
     * @param goal
     * @param pom
     * @return a Mojo instance
     * @throws Exception
     */
    public Mojo lookupEmptyMojo( String goal, File pom )
        throws Exception
    {
        return testCase.lookupEmptyMojo( goal, pom );
    }

    public Mojo lookupMojo( String groupId, String artifactId, String version, String goal,
                               PlexusConfiguration pluginConfiguration )
        throws Exception
    {
        return testCase.lookupMojo( groupId, artifactId, version, goal, pluginConfiguration );
    }

    public Mojo lookupConfiguredMojo( MavenProject project, String goal )
        throws Exception
    {
        return testCase.lookupConfiguredMojo( project, goal );
    }

    public Mojo lookupConfiguredMojo( MavenSession session, MojoExecution execution )
        throws Exception, ComponentConfigurationException
    {
        return testCase.lookupConfiguredMojo( session, execution );
    }

    public MavenSession newMavenSession( MavenProject project )
    {
        return testCase.newMavenSession( project );
    }

    public MojoExecution newMojoExecution( String goal )
    {
        return testCase.newMojoExecution( goal );
    }

    public PlexusConfiguration extractPluginConfiguration( String artifactId, File pom )
        throws Exception
    {
        return testCase.extractPluginConfiguration( artifactId, pom );
    }

    public PlexusConfiguration extractPluginConfiguration( String artifactId, Xpp3Dom pomDom )
        throws Exception
    {
        return testCase.extractPluginConfiguration( artifactId, pomDom );
    }

    public Mojo configureMojo( Mojo mojo, String artifactId, File pom )
        throws Exception
    {
        return testCase.configureMojo( mojo, artifactId, pom );
    }

    public Mojo configureMojo( Mojo mojo, PlexusConfiguration pluginConfiguration )
        throws Exception
    {
        return testCase.configureMojo( mojo, pluginConfiguration );
    }

    /**
     * Convenience method to obtain the value of a variable on a mojo that might not have a getter.
     *
     * NOTE: the caller is responsible for casting to to what the desired type is.
     *
     * @param object
     * @param variable
     * @return object value of variable
     * @throws IllegalArgumentException
     */
    public Object getVariableValueFromObject( Object object, String variable )
        throws IllegalAccessException
    {
        return testCase.getVariableValueFromObject( object, variable );
    }

    /**
     * Convenience method to obtain all variables and values from the mojo (including its superclasses)
     *
     * Note: the values in the map are of type Object so the caller is responsible for casting to desired types.
     *
     * @param object
     * @return map of variable names and values
     */
    public Map<String, Object> getVariablesAndValuesFromObject( Object object )
        throws IllegalAccessException
    {
        return testCase.getVariablesAndValuesFromObject( object );
    }

    /**
     * Convenience method to obtain all variables and values from the mojo (including its superclasses)
     *
     * Note: the values in the map are of type Object so the caller is responsible for casting to desired types.
     *
     * @param clazz
     * @param object
     * @return map of variable names and values
     */
    public Map<String, Object> getVariablesAndValuesFromObject( Class<?> clazz, Object object )
        throws IllegalAccessException
    {
        return testCase.getVariablesAndValuesFromObject( clazz, object );
    }

    /**
     * Convenience method to set values to variables in objects that don't have setters
     *
     * @param object
     * @param variable
     * @param value
     * @throws IllegalAccessException
     */
    public void setVariableValueToObject( Object object, String variable, Object value )
        throws IllegalAccessException
    {
        testCase.setVariableValueToObject( object, variable, value );
    }

    @Override
    public Statement apply(final Statement base, Description description) {
        if (description.getAnnotation(WithoutMojo.class) != null) // skip.
        {
            return base;
        }
        return new Statement() 
        {
            @Override
            public void evaluate() throws Throwable 
            {
                testCase.setUp();
                before();
                try 
                {
                    base.evaluate();
                } 
                finally 
                {
                    after();
                }
            }            
        };       
    }

    /**
     * @since 3.1.0
     */
    public MavenProject readMavenProject( File basedir )
        throws Exception
    {
        File pom = new File( basedir, "pom.xml" );
        MavenExecutionRequest request = new DefaultMavenExecutionRequest();
        request.setBaseDirectory( basedir );
        ProjectBuildingRequest configuration = request.getProjectBuildingRequest();
        configuration.setRepositorySession( new DefaultRepositorySystemSession() );
        MavenProject project = lookup( ProjectBuilder.class ).build( pom, configuration ).getProject();
        Assert.assertNotNull( project );
        return project;
    }

    /**
     * @since 3.1.0
     */
    public void executeMojo( File basedir, String goal )
        throws Exception
    {
        MavenProject project = readMavenProject( basedir );
        MavenSession session = newMavenSession( project );
        MojoExecution execution = newMojoExecution( goal );
        executeMojo( session, project, execution );
    }

    /**
     * @since 3.1.0
     */
    public Mojo lookupConfiguredMojo( File basedir, String goal )
        throws Exception, ComponentConfigurationException
    {
        MavenProject project = readMavenProject( basedir );
        MavenSession session = newMavenSession( project );
        MojoExecution execution = newMojoExecution( goal );
        return lookupConfiguredMojo( session, execution );
    }

    /**
     * @since 3.1.0
     */
    public final <T> T lookup( final Class<T> role )
        throws ComponentLookupException
    {
        return getContainer().lookup( role );
    }

    /**
     * @since 3.2.0
     */
    public void executeMojo( MavenProject project, String goal, Xpp3Dom... parameters )
        throws Exception
    {
        MavenSession session = newMavenSession( project );
        executeMojo( session, project, goal, parameters );
    }

    /**
     * @since 3.2.0
     */
    public void executeMojo( MavenSession session, MavenProject project, String goal, Xpp3Dom... parameters )
        throws Exception
    {
        MojoExecution execution = newMojoExecution( goal );
        if ( parameters != null )
        {
            Xpp3Dom configuration = execution.getConfiguration();
            for ( Xpp3Dom parameter : parameters )
            {
                configuration.addChild( parameter );
            }
        }
        executeMojo( session, project, execution );
    }

    /**
     * @since 3.2.0
     */
    public void executeMojo( MavenSession session, MavenProject project, MojoExecution execution )
        throws Exception
    {
        SessionScope sessionScope = lookup( SessionScope.class );
        try
        {
            sessionScope.enter();
            sessionScope.seed( MavenSession.class, session );

            MojoExecutionScope executionScope = lookup( MojoExecutionScope.class );
            try
            {
                executionScope.enter();

                executionScope.seed( MavenProject.class, project );
                executionScope.seed( MojoExecution.class, execution );

                Mojo mojo = lookupConfiguredMojo( session, execution );
                mojo.execute();

                MojoExecutionEvent event = new MojoExecutionEvent( session, project, execution, mojo );
                for ( MojoExecutionListener listener : getContainer().lookupList( MojoExecutionListener.class ) )
                {
                    listener.afterMojoExecutionSuccess( event );
                }
            }
            finally
            {
                executionScope.exit();
            }
        }
        finally
        {
            sessionScope.exit();
        }
    }

}
