/*
 * 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.
 */
package org.apache.maven.archetype.mojos;

import javax.inject.Inject;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import org.apache.maven.archetype.ArchetypeCreationRequest;
import org.apache.maven.archetype.ArchetypeCreationResult;
import org.apache.maven.archetype.ArchetypeManager;
import org.apache.maven.archetype.common.Constants;
import org.apache.maven.archetype.ui.creation.ArchetypeCreationConfigurator;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.PropertyUtils;
import org.codehaus.plexus.util.StringUtils;

/**
 * <p>
 * Creates an archetype project from the current project.
 * </p>
 * <p>
 * This goal reads your source and resource files, the values of its parameters,
 * and properties you specify in a <code>.property</code> file, and uses them to
 * create a Maven archetype project using the maven-archetype packaging.
 * If you build the resulting project, it will create the archetype. You can then
 * use this archetype to create new projects that resemble the original.
 * </p>
 * <p>
 * The maven-archetype-plugin uses Velocity to expand template files, and this documentation
 * talks about 'Velocity Properties', which are values substituted into Velocity templates.
 * See <a href="http://velocity.apache.org/engine/devel/user-guide.html">The Velocity User's Guide</a>
 * for more information.
 * </p>
 * <p>
 * This goal modifies the text of the files of the current project to form the Velocity template files
 * that make up the archetype.
 * </p>
 * <dl>
 * <dt>GAV</dt><dd>The GAV values for the current project are replaced by properties: groupId, artifactId, and version.
 * The user chooses new values for these when generating a project from the archetype.</dd>
 * <dt>package</dt><dd>All the files under one specified Java (or cognate) package are relocated to a project
 * that the user chooses when generating a project. References to the class name are replaced by a property reference. For
 * example, if the current project's sources are in the package <code>org.apache.saltedpeanuts</code>, then
 * any example of the string <code>org.apache.saltedpeanuts</code> is replaced with the Velocity property
 * reference <code>${packageName}</code>. When the user generates a project, this is in turn replaced by
 * his or her choice of a package.
 * </dd>
 * <dt>custom properties</dt><dd>You may identify additional strings that should be replaced by parameters.
 * To add custom properties, you must use the <code>propertyFile</code> parameter to specify a property file.
 * See the documentation for <code>propertyFile</code> for the details.
 * </dd>
 * <dt>integration tests</dt><dd>You may also specify a set of integration tests to be executed right after
 * you create archetype from the project. Each test consists of a separate folder under src/it/projects folder and
 * in there you specify <code>archetype.properties</code>, <code>goal.txt</code> and <code>verify.groovy</code> files.
 * The <code>archetype.properties</code> file is the file used to generate a new project from the newly created archetype
 * and the <code>goal.txt</code> a single-line file to specify the maven goal to invoke after generation of the test-project.
 * Finally the <code>verify.groovy</code> is a groovy file that you can use to specify your assertions on the generated project.
 * </dd>
 * </dl>
 * <p>
 * Note that you may need to edit the results of this goal. This goal has no way to exclude unwanted files,
 * or add copyright notices to the Velocity templates, or add more complex elements to the archetype metadata file.
 * </p>
 * <p>
 * This goal also generates a simple integration-test that exercises the generated archetype.
 * </p>
 *
 * @author rafale
 */
@Mojo(name = "create-from-project", requiresProject = true, aggregator = true)
@Execute(phase = LifecyclePhase.GENERATE_SOURCES)
public class CreateArchetypeFromProjectMojo extends AbstractMojo {

    /**
     * Enable the interactive mode to define the archetype from the project.
     */
    @Parameter(property = "interactive", defaultValue = "false")
    private boolean interactive;

    /**
     * File extensions which are checked for project's text files (vs binary files).
     */
    @Parameter(property = "archetype.filteredExtentions")
    private String archetypeFilteredExtentions;

    /**
     * Directory names which are checked for project's sources main package.
     */
    @Parameter(property = "archetype.languages")
    private String archetypeLanguages;

    /**
     * Velocity templates encoding.
     */
    @Parameter(property = "archetype.encoding", defaultValue = "UTF-8")
    private String defaultEncoding;

    /**
     * Create a partial archetype.
     */
    @Parameter(property = "archetype.partialArchetype")
    private boolean partialArchetype = false;

    /**
     * Create pom's velocity templates with CDATA preservation. This uses the <code>String.replaceAll()</code>
     * method and risks to have some overly replacement capabilities (beware of '1.0' value).
     */
    @Parameter(property = "archetype.preserveCData")
    private boolean preserveCData = false;

    /**
     * POMs in archetype are created with their initial parent.
     * This property is ignored when preserveCData is true.
     */
    @Parameter(property = "archetype.keepParent")
    private boolean keepParent = true;

    /**
     * The Maven project to create an archetype from.
     */
    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject project;

    /**
     * The property file that holds the plugin configuration. If this is provided, then
     * the plugin reads properties from here. The properties in here can be standard
     * properties listed below or custom properties for this archetype. The standard properties
     * are below. Several of them overlap parameters of this goal; it's better to just
     * set the parameter.
     * <p/>
     * <dl><dt>package</dt><dd>See the packageName parameter.</dd>
     * <dt>archetype.languages</dt><dd>See the archetypeLanguages parameter.</dd>
     * <dt>groupId</dt><dd>The default groupId of the generated project.</dd>
     * <dt>artifactId</dt><dd>The default artifactId of the generated project.</dd>
     * <dt>version</dt><dd>The default version of the generated project.</dd>
     * <dt>excludePatterns</dt><dd>A comma-separated list of paths that will not be included in the resulting
     * archetype.</dd>
     * <dt>archetype.filteredExtensions</dt><dd>See the filteredExensions parameter.</dd>
     * </dl>
     * <strong>Custom Properties</strong>
     * <p>
     * Custom properties allow you to replace some constant values in the project's files
     * with Velocity macro references. When a user generates a project from your archetype
     * he or she gets the opportunity to replace the value from the source project.
     * </p>
     * <p>
     * Custom property names <strong>may not contain the '.' character</strong>.
     * </p>
     * <p>
     * For example, if you include a line like the following in your property file:
     * <pre>cxf-version=2.5.1-SNAPSHOT</pre>
     * the plugin will search your files for the <code>2.5.1-SNAPSHOT</code> string and
     * replace them with references to a velocity macro <code>cxf-version</cpde>. It will
     * then list <code>cxf-version</code> as a <code>requiredProperty</code> in the
     * <code>archetype-metadata.xml</code>, with <code>2.5.1-SNAPSHOT</code> as the default value.
     * </p>
     */
    @Parameter(defaultValue = "archetype.properties", property = "archetype.properties")
    private File propertyFile;

    /**
     * The property telling which phase to call on the generated archetype.
     * Interesting values are: <code>package</code>, <code>integration-test</code>, <code>install</code> and <code>deploy</code>.
     */
    @Parameter(property = "archetype.postPhase", defaultValue = "package")
    private String archetypePostPhase;

    /**
     * The directory where the archetype should be created.
     */
    @Parameter(defaultValue = "${project.build.directory}/generated-sources/archetype", property = "outputDirectory")
    private File outputDirectory;

    @Parameter(property = "testMode")
    private boolean testMode;

    /**
     * The package name for Java source files to be incorporated in the archetype and
     * and relocated to the package that the user selects.
     */
    @Parameter(property = "packageName")
    private String packageName; // Find a better way to resolve the package!!! enforce usage of the configurator

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

    private ArchetypeCreationConfigurator configurator;

    private ArchetypeManager manager;

    @Inject
    public CreateArchetypeFromProjectMojo(ArchetypeCreationConfigurator configurator, ArchetypeManager manager) {
        this.configurator = configurator;
        this.manager = manager;
    }

    @Override
    public void execute() throws MojoExecutionException {

        Properties executionProperties = new Properties(session.getSystemProperties());
        executionProperties.putAll(session.getUserProperties());

        try {
            if (propertyFile != null) {
                propertyFile.getParentFile().mkdirs();
            }

            List<String> languages = getLanguages(archetypeLanguages, propertyFile);

            Properties properties = configurator.configureArchetypeCreation(
                    project, interactive, executionProperties, propertyFile, languages);

            List<String> filtereds = getFilteredExtensions(archetypeFilteredExtentions, propertyFile);

            ArchetypeCreationRequest request = new ArchetypeCreationRequest()
                    .setDefaultEncoding(defaultEncoding)
                    .setProject(project)
                    /* Used when in interactive mode */ .setProperties(properties)
                    .setLanguages(languages)
                    /* Should be refactored to use some ant patterns */ .setFiltereds(filtereds)
                    /* This should be correctly handled */ .setPreserveCData(preserveCData)
                    .setKeepParent(keepParent)
                    .setPartialArchetype(partialArchetype)
                    .setLocalRepositoryBasedir(
                            session.getRepositorySession().getLocalRepository().getBasedir())
                    /* this should be resolved and asked for user to verify */ .setPackageName(packageName)
                    .setPostPhase(archetypePostPhase)
                    .setOutputDirectory(outputDirectory)
                    .setSettingsFile(session.getRequest().getUserSettingsFile());

            ArchetypeCreationResult result = manager.createArchetypeFromProject(request);

            if (result.getCause() != null) {
                throw new MojoExecutionException(result.getCause().getMessage(), result.getCause());
            }

            getLog().info("Archetype project created in " + outputDirectory);

            if (testMode) {
                // Now here a properties file would be useful to write so that we could automate
                // some functional tests where we string together an:
                //
                // archetype create from project -> deploy it into a test repo
                // project create from archetype -> use the repository we deployed to archetype to
                // generate
                // test the output
                //
                // This of course would be strung together from the outside.
            }

        } catch (Exception ex) {
            throw new MojoExecutionException(ex.getMessage(), ex);
        }
    }

    private List<String> getFilteredExtensions(String archetypeFilteredExtentions, File propertyFile)
            throws IOException {
        List<String> filteredExtensions = new ArrayList<>();

        if (archetypeFilteredExtentions != null && !archetypeFilteredExtentions.isEmpty()) {
            filteredExtensions.addAll(Arrays.asList(StringUtils.split(archetypeFilteredExtentions, ",")));

            getLog().debug("Found in command line extensions = " + filteredExtensions);
        }

        if (filteredExtensions.isEmpty() && propertyFile != null && propertyFile.exists()) {
            Properties properties = PropertyUtils.loadProperties(propertyFile);

            String extensions = properties.getProperty(Constants.ARCHETYPE_FILTERED_EXTENSIONS);
            if (extensions != null && !extensions.isEmpty()) {
                filteredExtensions.addAll(Arrays.asList(StringUtils.split(extensions, ",")));
            }

            getLog().debug("Found in propertyFile " + propertyFile.getName() + " extensions = " + filteredExtensions);
        }

        if (filteredExtensions.isEmpty()) {
            filteredExtensions.addAll(Constants.DEFAULT_FILTERED_EXTENSIONS);

            getLog().debug("Using default extensions = " + filteredExtensions);
        }

        return filteredExtensions;
    }

    private List<String> getLanguages(String archetypeLanguages, File propertyFile) throws IOException {
        List<String> resultingLanguages = new ArrayList<>();

        if (archetypeLanguages != null && !archetypeLanguages.isEmpty()) {
            resultingLanguages.addAll(Arrays.asList(StringUtils.split(archetypeLanguages, ",")));

            getLog().debug("Found in command line languages = " + resultingLanguages);
        }

        if (resultingLanguages.isEmpty() && propertyFile != null && propertyFile.exists()) {
            Properties properties = PropertyUtils.loadProperties(propertyFile);

            String languages = properties.getProperty(Constants.ARCHETYPE_LANGUAGES);
            if (languages != null && !languages.isEmpty()) {
                resultingLanguages.addAll(Arrays.asList(StringUtils.split(languages, ",")));
            }

            getLog().debug("Found in propertyFile " + propertyFile.getName() + " languages = " + resultingLanguages);
        }

        if (resultingLanguages.isEmpty()) {
            resultingLanguages.addAll(Constants.DEFAULT_LANGUAGES);

            getLog().debug("Using default languages = " + resultingLanguages);
        }

        return resultingLanguages;
    }
}
