package org.apache.maven.script.ant;

/*
 * 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 org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.ContextEnabled;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.path.PathTranslator;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.PropertyHelper;
import org.apache.tools.ant.types.Path;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.zip.ZipUnArchiver;
import org.codehaus.plexus.component.MapOrientedComponent;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.component.factory.ant.AntComponentExecutionException;
import org.codehaus.plexus.component.factory.ant.AntScriptInvoker;
import org.codehaus.plexus.component.repository.ComponentRequirement;
import org.codehaus.plexus.logging.LogEnabled;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.util.StringUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AntMojoWrapper
    extends AbstractMojo
    implements ContextEnabled, MapOrientedComponent, LogEnabled
{

    private Map<String, Object> pluginContext;
    
    private final AntScriptInvoker scriptInvoker;

    private Project antProject;

    private MavenProject mavenProject;

    private MojoExecution mojoExecution;

    private MavenSession session;
    
    private PathTranslator pathTranslator;

    private Logger logger;
    
    private transient List<String> unconstructedParts = new ArrayList<String>();

    public AntMojoWrapper( AntScriptInvoker scriptInvoker )
    {
        this.scriptInvoker = scriptInvoker;
    }

    public void execute()
        throws MojoExecutionException
    {
        if ( antProject == null )
        {
            antProject = scriptInvoker.getProject();
        }
        
        Map<String, Object> allConfig = new HashMap<String, Object>();
        if ( pluginContext != null && !pluginContext.isEmpty() )
        {
            allConfig.putAll( pluginContext );
        }
        
        @SuppressWarnings( "unchecked" )
        Map<String, PathTranslator> refs = scriptInvoker.getReferences();
        if ( refs != null )
        {
            allConfig.putAll( refs );
            
            for ( Map.Entry<String, PathTranslator> entry : refs.entrySet() )
            {
                if ( entry.getKey().startsWith( PathTranslator.class.getName() ) )
                {
                    pathTranslator = entry.getValue();
                }
            }
        }

        mavenProject = (MavenProject) allConfig.get( "project" );
        
        mojoExecution = (MojoExecution) allConfig.get( "mojoExecution" );
        
        session = (MavenSession) allConfig.get( "session" );
        
        unpackFileBasedResources();

        addClasspathReferences();
        
        if ( logger.isDebugEnabled() && !unconstructedParts.isEmpty() )
        {
            StringBuffer buffer = new StringBuffer();
            
            buffer.append( "The following standard Maven Ant-mojo support objects could not be created:\n\n" );
            
            for ( String part : unconstructedParts )
            {
                buffer.append( "\n-  " ).append( part );
            }
            
            buffer.append( "\n\nMaven project, session, mojo-execution, or path-translation parameter information is " );
            buffer.append( "\nmissing from this mojo's plugin descriptor." );
            buffer.append( "\n\nPerhaps this Ant-based mojo depends on maven-script-ant < 2.1.0, " );
            buffer.append( "or used maven-plugin-tools-ant < 2.2 during release?\n\n" );
            
            logger.debug( buffer.toString() );
        }

        try
        {
            scriptInvoker.invoke();
        }
        catch ( AntComponentExecutionException e )
        {
            throw new MojoExecutionException( "Failed to execute: " + e.getMessage(), e );
        }
        
        unconstructedParts.clear();
    }

    public void setPluginContext( Map pluginContext )
    {
        this.pluginContext = pluginContext;
    }

    public Map getPluginContext()
    {
        return pluginContext;
    }

    public void addComponentRequirement( ComponentRequirement requirementDescriptor, Object requirementValue )
        throws ComponentConfigurationException
    {
        scriptInvoker.addComponentRequirement( requirementDescriptor, requirementValue );
    }

    public void setComponentConfiguration( Map componentConfiguration )
        throws ComponentConfigurationException
    {
        scriptInvoker.setComponentConfiguration( componentConfiguration );
        antProject = scriptInvoker.getProject();
    }

    private void unpackFileBasedResources()
        throws MojoExecutionException
    {
        if ( mojoExecution == null || mavenProject == null )
        {
            unconstructedParts.add( "Unpacked Ant build scripts (in Maven build directory)." );
            
            return;
        }
        
        // What we need to write out any resources in the plugin to the target directory of the
        // mavenProject using the Ant-based plugin:
        //
        // 1. Need a reference to the plugin JAR itself
        // 2. Need a reference to the ${basedir} of the mavenProject

        PluginDescriptor pluginDescriptor = mojoExecution.getMojoDescriptor().getPluginDescriptor();
        
        File pluginJar = pluginDescriptor.getPluginArtifact().getFile();

        String resourcesPath = pluginDescriptor.getArtifactId();

        File outputDirectory = new File( mavenProject.getBuild().getDirectory() );

        try
        {
            UnArchiver ua = new ZipUnArchiver( pluginJar );

            ua.extract( resourcesPath, outputDirectory );
        }
        catch ( ArchiverException e )
        {
            throw new MojoExecutionException( "Error extracting resources from your Ant-based plugin.", e );
        }
    }

    private void addClasspathReferences()
        throws MojoExecutionException
    {
        try
        {
            if ( mavenProject != null && session != null && pathTranslator != null )
            {
                ExpressionEvaluator exprEvaluator =
                    new PluginParameterExpressionEvaluator( session, mojoExecution, pathTranslator, logger, mavenProject,
                                                            mavenProject.getProperties() );
                
                PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper( antProject );
                propertyHelper.setNext( new AntPropertyHelper( exprEvaluator, mavenProject.getArtifacts(), getLog() ) );
            }
            else
            {
                unconstructedParts.add( "Maven parameter expression evaluator for Ant properties." );
            }

            @SuppressWarnings( "unchecked" )
            Map<String, Object> references = scriptInvoker.getReferences();

            if ( mavenProject != null )
            {

                // Compile classpath
                Path p = new Path( antProject );

                p.setPath( StringUtils.join( mavenProject.getCompileClasspathElements().iterator(), File.pathSeparator ) );

                /* maven.dependency.classpath it's deprecated as it's equal to maven.compile.classpath */
                references.put( "maven.dependency.classpath", p );
                antProject.addReference( "maven.dependency.classpath", p );
                
                references.put( "maven.compile.classpath", p );
                antProject.addReference( "maven.compile.classpath", p );

                // Runtime classpath
                p = new Path( antProject );

                p.setPath( StringUtils.join( mavenProject.getRuntimeClasspathElements().iterator(), File.pathSeparator ) );

                references.put( "maven.runtime.classpath", p );
                antProject.addReference( "maven.runtime.classpath", p );

                // Test classpath
                p = new Path( antProject );

                p.setPath( StringUtils.join( mavenProject.getTestClasspathElements().iterator(), File.pathSeparator ) );

                references.put( "maven.test.classpath", p );
                antProject.addReference( "maven.test.classpath", p );

            }
            else
            {
                unconstructedParts.add( "Maven standard project-based classpath references." );
            }
            
            if ( mojoExecution != null )
            {
                // Plugin dependency classpath

                Path p = getPathFromArtifacts( mojoExecution.getMojoDescriptor().getPluginDescriptor().getArtifacts(), antProject );
                
                references.put( "maven.plugin.classpath", p );
                antProject.addReference( "maven.plugin.classpath", p );
            }
            else
            {
                unconstructedParts.add( "Maven standard plugin-based classpath references." );
            }
        }
        catch ( DependencyResolutionRequiredException e )
        {
            throw new MojoExecutionException( "Error creating classpath references for Ant-based plugin scripts.", e  );
        }
    }

    public Path getPathFromArtifacts( Collection<Artifact> artifacts, Project antProject )
        throws DependencyResolutionRequiredException
    {
        List<String> list = new ArrayList<String>( artifacts.size() );

        for ( Artifact a : artifacts )
        {
            File file = a.getFile();

            if ( file == null )
            {
                throw new DependencyResolutionRequiredException( a );
            }

            list.add( file.getPath() );
        }

        Path p = new Path( antProject );

        p.setPath( StringUtils.join( list.iterator(), File.pathSeparator ) );

        return p;
    }

    public Project getAntProject()
    {
        return antProject;
    }

    public void setAntProject( Project antProject )
    {
        this.antProject = antProject;
    }

    public MavenProject getMavenProject()
    {
        return mavenProject;
    }

    public void setMavenProject( MavenProject mavenProject )
    {
        this.mavenProject = mavenProject;
    }

    public MojoExecution getMojoExecution()
    {
        return mojoExecution;
    }

    public void setMojoExecution( MojoExecution mojoExecution )
    {
        this.mojoExecution = mojoExecution;
    }

    public MavenSession getSession()
    {
        return session;
    }

    public void setSession( MavenSession session )
    {
        this.session = session;
    }

    public PathTranslator getPathTranslator()
    {
        return pathTranslator;
    }

    public void setPathTranslator( PathTranslator pathTranslator )
    {
        this.pathTranslator = pathTranslator;
    }

    public AntScriptInvoker getScriptInvoker()
    {
        return scriptInvoker;
    }

    public void enableLogging( Logger logger )
    {
        this.logger = logger;
    }
}
