package org.codehaus.mojo.gwt;

/*
 * 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 static org.apache.maven.artifact.Artifact.SCOPE_COMPILE;
import static org.apache.maven.artifact.Artifact.SCOPE_RUNTIME;

import org.apache.commons.io.IOUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.artifact.MavenMetadataSource;
import org.codehaus.plexus.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * Abstract Support class for all GWT-related operations.
 * <p>
 * Provide methods to build classpath for GWT SDK tools.
 *
 * @author <a href="mailto:nicolas@apache.org">Nicolas De Loof</a>
 * @version $Id$
 */
public abstract class AbstractGwtMojo
    extends AbstractMojo
{
    protected static final String GWT_USER = "org.gwtproject:gwt-user";

    protected static final String GWT_DEV = "org.gwtproject:gwt-dev";

    /** GWT artifacts groupId */
    public static final String GWT_GROUP_ID = "org.gwtproject";

    // --- Some Maven tools ----------------------------------------------------

    @Parameter(defaultValue = "${plugin.artifactMap}", required = true, readonly = true)
    private Map<String, Artifact> pluginArtifactMap;

    @Component
    private MavenProjectBuilder projectBuilder;

    @Component
    protected ArtifactResolver resolver;

    @Component
    protected ArtifactFactory artifactFactory;

    @Component
    protected ClasspathBuilder classpathBuilder;

    // --- Some MavenSession related structures --------------------------------

    @Parameter(defaultValue = "${localRepository}", required = true, readonly = true)
    protected ArtifactRepository localRepository;

    @Parameter(defaultValue = "${project.pluginArtifactRepositories}", required = true, readonly = true)
    protected List<ArtifactRepository> remoteRepositories;

    @Component
    protected ArtifactMetadataSource artifactMetadataSource;

    /**
     * The maven project descriptor
     */
    @Parameter(defaultValue = "${project}", required = true, readonly = true)
    private MavenProject project;

    @Parameter(defaultValue = "${session}", required = true, readonly = true)
    protected MavenSession session;

    // --- Plugin parameters ---------------------------------------------------

    /**
     * Folder where generated-source will be created (automatically added to compile classpath).
     */
    @Parameter(defaultValue = "${project.build.directory}/generated-sources/gwt", required = true)
    private File generateDirectory;

    /**
     * Location on filesystem where GWT will write output files (-out option to GWTCompiler).
     */
    @Parameter(property = "gwt.war", defaultValue="${project.build.directory}/${project.build.finalName}", alias = "outputDirectory")
    private File webappDirectory;

    /**
     * Prefix to prepend to module names inside {@code webappDirectory} or in URLs in DevMode.
     * <p>
     * Could also be seen as a suffix to {@code webappDirectory}.
     */
    @Parameter(property = "gwt.modulePathPrefix")
    protected String modulePathPrefix;

    /**
     * Location of the web application static resources (same as maven-war-plugin parameter)
     */
    @Parameter(defaultValue="${basedir}/src/main/webapp")
    protected File warSourceDirectory;

    /**
     * Select the place where GWT application is built. In <code>inplace</code> mode, the warSourceDirectory is used to
     * match the same use case of the {@link war:inplace
     * http://maven.apache.org/plugins/maven-war-plugin/inplace-mojo.html} goal.
     */
    @Parameter(defaultValue = "false", property = "gwt.inplace")
    private boolean inplace;

    /**
     * The forked command line will use gwt sdk jars first in classpath.
     * see issue http://code.google.com/p/google-web-toolkit/issues/detail?id=5290
     *
     * @since 2.1.0-1
     * @deprecated tweak your dependencies and/or split your project with a client-only module
     */
    @Deprecated
    @Parameter(defaultValue = "false", property = "gwt.gwtSdkFirstInClasspath")
    protected boolean gwtSdkFirstInClasspath;

    public File getOutputDirectory()
    {
        File out = inplace ? warSourceDirectory : webappDirectory;
        if ( !StringUtils.isBlank( modulePathPrefix ) ) 
        {
            out = new File(out, modulePathPrefix);
        }
        return out;
    }

    /**
     * Add classpath elements to a classpath URL set
     *
     * @param elements the initial URL set
     * @param urls the urls to add
     * @param startPosition the position to insert URLS
     * @return full classpath URL set
     * @throws MojoExecutionException some error occured
     */
    protected int addClasspathElements( Collection<?> elements, URL[] urls, int startPosition )
        throws MojoExecutionException
    {
        for ( Object object : elements )
        {
            try
            {
                if ( object instanceof Artifact )
                {
                    urls[startPosition] = ( (Artifact) object ).getFile().toURI().toURL();
                }
                else if ( object instanceof Resource )
                {
                    urls[startPosition] = new File( ( (Resource) object ).getDirectory() ).toURI().toURL();
                }
                else
                {
                    urls[startPosition] = new File( (String) object ).toURI().toURL();
                }
            }
            catch ( MalformedURLException e )
            {
                throw new MojoExecutionException(
                                                  "Failed to convert original classpath element " + object + " to URL.",
                                                  e );
            }
            startPosition++;
        }
        return startPosition;
    }


    /**
     * Build the GWT classpath for the specified scope
     *
     * @param scope Artifact.SCOPE_COMPILE or Artifact.SCOPE_TEST
     * @return a collection of dependencies as Files for the specified scope.
     * @throws MojoExecutionException if classPath building failed
     */
    public Collection<File> getClasspath( String scope )
        throws MojoExecutionException
    {
        try
        {
            Collection<File> files = classpathBuilder.buildClasspathList( getProject(), scope, getProjectArtifacts(), isGenerator() );

            if ( getLog().isDebugEnabled() )
            {
                getLog().debug( "GWT SDK execution classpath :" );
                for ( File f : files )
                {
                    getLog().debug( "   " + f.getAbsolutePath() );
                }
            }
            return files;
        }
        catch ( ClasspathBuilderException e )
        {
            throw new MojoExecutionException( e.getMessage(), e );
        }
    }


    /**
     * Whether to use processed resources and compiled classes ({@code false}), or raw resources ({@code true }).
     */
    protected boolean isGenerator() {
        return false;
    }

    protected Collection<File> getGwtDevJar() throws MojoExecutionException
    {
        return getJarFiles( GWT_DEV, true );
    }

    protected Collection<File> getGwtUserJar() throws MojoExecutionException
    {
        return getJarFiles( GWT_USER, true );
    }

    protected Collection<File> getJarFiles(String artifactId, boolean detectProjectDependencies) throws MojoExecutionException
    {
        checkGwtUserVersion();

        if (detectProjectDependencies) for (Artifact artifact : project.getArtifacts()) {
            String dependencyKey = ArtifactUtils.versionlessKey(artifact.getGroupId(), artifact.getArtifactId());
            // if the project contains this GWT lib do not returns any jar, maven resolution should do that
            if (dependencyKey.equals(artifactId)) return Collections.emptyList();
        }

        Artifact rootArtifact = pluginArtifactMap.get( artifactId );

        ArtifactResolutionResult result;
        try
        {
            // Code shamelessly copied from exec-maven-plugin.
            MavenProject rootProject =
                            this.projectBuilder.buildFromRepository( rootArtifact, this.remoteRepositories,
                                                                     this.localRepository );
            List<Dependency> dependencies = rootProject.getDependencies();
            Set<Artifact> dependencyArtifacts =
                            MavenMetadataSource.createArtifacts( artifactFactory, dependencies, null, null, null );
            dependencyArtifacts.add( rootProject.getArtifact() );
            result = resolver.resolveTransitively( dependencyArtifacts, rootArtifact,
                                                   Collections.EMPTY_MAP, localRepository,
                                                   remoteRepositories, artifactMetadataSource,
                                                   null, Collections.emptyList());
        }
        catch (Exception e)
        {
            throw new MojoExecutionException( "Failed to resolve artifact", e);
        }

        Collection<Artifact> resolved = result.getArtifacts();
        Collection<File> files = new ArrayList<File>(resolved.size() + 1 );
        files.add( rootArtifact.getFile() );
        for ( Artifact artifact : resolved )
        {
            files.add( artifact.getFile() );
        }

        return files;
    }

    /**
     * Check gwt-user dependency matches plugin version
     */
    private void checkGwtUserVersion() throws MojoExecutionException
    {
        InputStream inputStream = Thread.currentThread().getContextClassLoader()
            .getResourceAsStream( "org/codehaus/mojo/gwt/mojoGwtVersion.properties" );
        Properties properties = new Properties();
        try
        {
            properties.load( inputStream );

        }
        catch (IOException e)
        {
            throw new MojoExecutionException( "Failed to load plugin properties", e );
        }
        finally
        {
            IOUtils.closeQuietly( inputStream );
        }

        Artifact gwtUser = project.getArtifactMap().get( GWT_USER );
        if (gwtUser != null)
        {
            String mojoGwtVersion = properties.getProperty( "gwt.version" );
            //ComparableVersion with an up2date maven version
            ArtifactVersion mojoGwtArtifactVersion = new DefaultArtifactVersion( mojoGwtVersion );
            ArtifactVersion userGwtArtifactVersion = new DefaultArtifactVersion( gwtUser.getVersion() );
            if ( userGwtArtifactVersion.compareTo( mojoGwtArtifactVersion ) < 0 )
            {
                getLog().warn( "Your project declares dependency on gwt-user " + gwtUser.getVersion()
                                   + ". This plugin is designed for at least gwt version " + mojoGwtVersion );
            }
        }
    }

    protected Artifact resolve( String groupId, String artifactId, String version, String type, String classifier )
        throws MojoExecutionException
    {
        // return project.getArtifactMap().get( groupId + ":" + artifactId );

        Artifact artifact = artifactFactory.createArtifactWithClassifier( groupId, artifactId, version, type, classifier );
        try
        {
            resolver.resolve(artifact, remoteRepositories, localRepository);
        }
        catch ( ArtifactNotFoundException e )
        {
            throw new MojoExecutionException( "artifact not found - " + e.getMessage(), e );
        }
        catch ( ArtifactResolutionException e )
        {
            throw new MojoExecutionException( "artifact resolver problem - " + e.getMessage(), e );
        }
        return artifact;
    }

    /**
     * @param path file to add to the project compile directories
     */
    protected void addCompileSourceRoot( File path )
    {
        getProject().addCompileSourceRoot( path.getAbsolutePath() );
    }

    /**
     * @return the project
     */
    public MavenProject getProject()
    {
        return project;
    }


    public ArtifactRepository getLocalRepository()
    {
        return this.localRepository;
    }

    public List<ArtifactRepository> getRemoteRepositories()
    {
        return this.remoteRepositories;
    }

    protected File setupGenerateDirectory() {
        if ( !generateDirectory.exists() )
        {
            getLog().debug( "Creating target directory " + generateDirectory.getAbsolutePath() );
            generateDirectory.mkdirs();
        }
        getLog().debug( "Add compile source root " + generateDirectory.getAbsolutePath() );
        addCompileSourceRoot( generateDirectory );
        return generateDirectory;
    }

    public File getGenerateDirectory()
    {
        if ( !generateDirectory.exists() )
        {
            getLog().debug( "Creating target directory " + generateDirectory.getAbsolutePath() );
            generateDirectory.mkdirs();
        }
        return generateDirectory;
    }

    public Set<Artifact> getProjectArtifacts()
    {
        return project.getArtifacts();
    }

    public Set<Artifact> getProjectRuntimeArtifacts()
    {
        Set<Artifact> artifacts = new HashSet<Artifact>();
        for (Artifact projectArtifact : project.getArtifacts() )
        {
            String scope = projectArtifact.getScope();
            if ( SCOPE_RUNTIME.equals( scope )
              || SCOPE_COMPILE.equals( scope ) )
            {
                artifacts.add( projectArtifact );
            }

        }
        return artifacts;
    }


}
