package org.codehaus.mojo.appassembler;

/*
 * The MIT License
 *
 * Copyright (c) 2006-2013, The Codehaus
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.filtering.MavenFilteringException;
import org.apache.maven.shared.filtering.MavenResourcesExecution;
import org.apache.maven.shared.filtering.MavenResourcesFiltering;
import org.codehaus.mojo.appassembler.daemon.DaemonGeneratorException;
import org.codehaus.mojo.appassembler.daemon.DaemonGeneratorService;
import org.codehaus.mojo.appassembler.daemon.script.Platform;
import org.codehaus.plexus.util.FileUtils;

/**
 * This is intended to collect all generic parts of the script generating Mojos assemble and generate-daemons into a
 * single class. A first step of hopefully merging the two into one some day.
 *
 * @author Dennis Lundberg
 */
public abstract class AbstractScriptGeneratorMojo
    extends AbstractAppAssemblerMojo
{
    // -----------------------------------------------------------------------
    // Parameters
    // -----------------------------------------------------------------------

    /**
     * Setup file in <code>$BASEDIR/bin</code> to be called prior to execution.
     * <p>
     * <b>Note:</b> only for the <code>jsw</code> platform. If this optional environment file also sets up
     * WRAPPER_CONF_OVERRIDES variable, it will be passed into JSW native launcher's command line arguments to override
     * wrapper.conf's properties. See http://wrapper.tanukisoftware.com/doc/english/props-command-line.html for details.
     * </p>
     *
     * @since 1.2.3 (generate-daemons)
     */
    @Parameter
    protected String environmentSetupFileName;

    /**
     * You can define a license header file which will be used instead the default header in the generated scripts.
     *
     * @since 1.2
     */
    @Parameter
    protected File licenseHeaderFile;

    /**
     * The unix template of the generated script. It can be a file or resource path. If not given, an internal one is
     * used. Use with care since it is not guaranteed to be compatible with new plugin release.
     *
     * @since 1.3
     */
    @Parameter( property = "unixScriptTemplate" )
    protected String unixScriptTemplate;

    /**
     * Sometimes it happens that you have many dependencies which means having a very long classpath, and
     * becomes too long (in particular on Windows based platforms). This option can help in such
     * situation. If you activate this option, your classpath contains only a <a href=
     * "http://docs.oracle.com/javase/6/docs/technotes/tools/windows/classpath.html" >classpath wildcard</a> (REPO/*).
     * But be aware that this works only in combination with Java 1.6 and above and with {@link #repositoryLayout}
     * <code>flat</code>.
     *
     * @since 1.2.3 (assemble), 1.3.1 (generate-daemons)
     */
    @Parameter( defaultValue = "false" )
    protected boolean useWildcardClassPath;

    /**
     * The windows template of the generated script. It can be a file or resource path. If not given, an internal one is
     * used. Use with care since it is not guaranteed to be compatible with new plugin release.
     *
     * @since 1.3
     */
    @Parameter( property = "windowsScriptTemplate" )
    protected String windowsScriptTemplate;

    // -----------------------------------------------------------------------
    // Read-only Parameters
    // -----------------------------------------------------------------------

    @Parameter( defaultValue = "${project.runtimeArtifacts}", readonly = true )
    protected List<Artifact> artifacts;

    @Parameter( defaultValue = "${project}", readonly = true, required = true )
    protected MavenProject mavenProject;

    /**
     * Set to <code>false</code> to skip repository generation.
     */
    @Parameter( defaultValue = "true" )
    protected boolean generateRepository;

    /**
     * The name of the target directory for configuration files. Prior to version 1.7 this value defaults to 'conf' for
     * assemble goal and 'etc' for generate-daemons
     */
    @Parameter( defaultValue = "etc" )
    protected String configurationDirectory;

    /**
     * The source directory for configuration files.
     *
     * @since 1.1
     */
    @Parameter( defaultValue = "src/main/config" )
    protected File configurationSourceDirectory;

    /**
     * If the source configuration directory should be filtered when copied to the configured
     * <code>configurationDirectory</code>. This configuration can be replaced by a more generic
     * <code>preAssembleDirectory</code> option
     *
     * @since 1.8
     */
    @Parameter( defaultValue = "false" )
    protected boolean filterConfigurationDirectory;

    /**
     * If the source configuration directory should be copied to the configured <code>configurationDirectory</code>.
     *
     * @since 1.1 (assemble), 1.7 (generate-daemons)
     */
    @Parameter( defaultValue = "false" )
    protected boolean copyConfigurationDirectory;

    /**
     * If exists, copy this directory's contents to the configured <code>assembleDirectory</code> with option to filter.
     * This configuration can replace <i>copyConfigurationDirectory</i>
     *
     * @since 1.10
     */
    @Parameter
    protected File preAssembleDirectory;

    /**
     * If the source pre-assemble directory should be filtered when copied to the configured
     * <code>assembleDirectory</code>.
     *
     * @since 1.10
     */
    @Parameter( defaultValue = "false" )
    protected boolean filterPreAssembleDirectory;

    /**
     * Location under base directory where all of files non-recursively are added before the generated classpath. Java
     * 6+ only since it uses wildcard classpath format. This is a convenient way to have user to add artifacts that not
     * possible to be part of final assembly such as LGPL/GPL artifacts
     *
     * @since 1.6
     */
    @Parameter
    protected String endorsedDir;

    /**
     * Project build filters.
     *
     * @since 1.8
     */
    @Parameter( defaultValue = "${project.build.filters}", readonly = true )
    protected List<String> buildFilters;

    /**
     * The character encoding scheme to be applied when filtering the source configuration directory.
     *
     * @since 1.8
     */
    @Parameter( defaultValue = "${project.build.sourceEncoding}" )
    protected String encoding;

    /**
     * Expressions preceded with this String won't be interpolated. <code>\${foo}</code> will be replaced with
     * <code>${foo}</code>.
     *
     * @since 1.8
     */
    @Parameter
    protected String escapeString;

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

    /**
     * The logs directory for the assembled project. If you specify a value for this parameter an empty directory with
     * the name given will be created.
     *
     * @since 1.8
     */
    @Parameter( property = "logsDirectory" )
    protected String logsDirectory;

    /**
     * The temp directory of the assembled project. If you specify a value for this parameter an empty directory with
     * the name given will be created.
     *
     * @since 1.8
     */
    @Parameter( property = "tempDirectory" )
    protected String tempDirectory;

    /**
     * The file extensions to use for bin files. The file extensions are stored in a Map that uses the platform name as
     * key. To change the file extension for Unix bin files to ".sh" use this configuration:
     *
     * <pre>
     *          &lt;binFileExtensions&gt;
     *            &lt;unix&gt;.sh&lt;/unix&gt;
     *          &lt;/binFileExtensions&gt;
     * </pre>
     *
     * @since 1.1 for {@link AssembleMojo} and 2.0.0 for {@link GenerateDaemonsMojo}
     */
    @Parameter
    protected Map<String, String> binFileExtensions;

    // -----------------------------------------------------------------------
    // Components
    // -----------------------------------------------------------------------

    @Component
    protected DaemonGeneratorService daemonGeneratorService;

    /**
     * The filtering component used when copying the source configuration directory.
     *
     * @since 1.8
     */
    @Component( role = MavenResourcesFiltering.class, hint = "default" )
    protected MavenResourcesFiltering mavenResourcesFiltering;

    protected void doCopyConfigurationDirectory( final String targetDirectory )
        throws MojoFailureException
    {
        getLog().debug( "copying configuration directory." );

        // Create a Resource from the configuration source directory
        Resource resource = new Resource();
        resource.setDirectory( configurationSourceDirectory.getAbsolutePath() );
        resource.setFiltering( filterConfigurationDirectory );
        resource.setTargetPath( configurationDirectory );
        List<Resource> resources = new ArrayList<Resource>();
        resources.add( resource );

        MavenResourcesExecution mavenResourcesExecution =
            new MavenResourcesExecution( resources, new File( targetDirectory ), mavenProject, encoding, buildFilters,
                                         Collections.<String>emptyList(), session );

        mavenResourcesExecution.setEscapeString( escapeString );
        // Include empty directories, to be backwards compatible
        mavenResourcesExecution.setIncludeEmptyDirs( true );
        mavenResourcesExecution.setUseDefaultFilterWrappers( true );

        // @todo Possible future enhancements
        // mavenResourcesExecution.setEscapedBackslashesInFilePath( escapedBackslashesInFilePath );
        // mavenResourcesExecution.setOverwrite( overwrite );
        // mavenResourcesExecution.setSupportMultiLineFiltering( supportMultiLineFiltering );

        try
        {
            getLog().debug( "Will try to copy configuration files from "
                                + configurationSourceDirectory.getAbsolutePath() + " to " + targetDirectory
                                + FileUtils.FS + configurationDirectory );

            // Use a MavenResourcesFiltering component to filter and copy the configuration files
            mavenResourcesFiltering.filterResources( mavenResourcesExecution );
        }
        catch ( MavenFilteringException mfe )
        {
            throw new MojoFailureException( "Failed to copy/filter the configuration files." );
        }
    }

    protected void doCopyPreAssembleDirectory( final String targetDirectory )
        throws MojoFailureException
    {
        // Create a Resource from the configuration source directory
        Resource resource = new Resource();
        resource.setDirectory( preAssembleDirectory.getAbsolutePath() );
        resource.setFiltering( filterPreAssembleDirectory );
        List<Resource> resources = new ArrayList<Resource>();
        resources.add( resource );

        MavenResourcesExecution mavenResourcesExecution =
            new MavenResourcesExecution( resources, new File( targetDirectory ), mavenProject, encoding, buildFilters,
                                         Collections.<String>emptyList(), session );

        mavenResourcesExecution.setEscapeString( escapeString );
        // Include empty directories, to be backwards compatible
        mavenResourcesExecution.setIncludeEmptyDirs( true );
        mavenResourcesExecution.setUseDefaultFilterWrappers( true );

        try
        {
            getLog().info( "Copy pre-assemble files from " + this.preAssembleDirectory.getAbsolutePath() + " to "
                                + targetDirectory );

            // Use a MavenResourcesFiltering component to filter and copy the configuration files
            mavenResourcesFiltering.filterResources( mavenResourcesExecution );
        }
        catch ( MavenFilteringException mfe )
        {
            throw new MojoFailureException( "Failed to copy/filter the configuration files." );
        }
    }

    // -----------------------------------------------------------------------
    // Protected helper methods.
    // -----------------------------------------------------------------------

    protected void doCreateExtraDirectories( File targetDirectory )
        throws MojoFailureException
    {
        if ( this.logsDirectory != null )
        {
            File logsDirectory = new File( targetDirectory, this.logsDirectory );
            if ( logsDirectory.exists() && !logsDirectory.delete() )
            {
                throw new MojoFailureException( "Failed to delete existing directory for log files." );
            }
            boolean success = logsDirectory.mkdirs();
            if ( !success )
            {
                throw new MojoFailureException( "Failed to create directory for log files." );
            }
        }
        if ( this.tempDirectory != null )
        {
            File tempDirectory = new File( targetDirectory, this.tempDirectory );
            if ( tempDirectory.exists() && !tempDirectory.delete() )
            {
                throw new MojoFailureException( "Failed to delete existing directory for temp files." );
            }
            boolean success = tempDirectory.mkdirs();
            if ( !success )
            {
                throw new MojoFailureException( "Failed to create directory for temp files." );
            }
        }
    }

    protected void installDependencies( final String outputDirectory, final String repositoryName )
        throws MojoExecutionException, MojoFailureException
    {
        if ( generateRepository )
        {
            // The repo where the jar files will be installed
            ArtifactRepository artifactRepository =
                artifactRepositoryFactory.createDeploymentArtifactRepository( "appassembler", "file://"
                    + outputDirectory + "/" + repositoryName, getArtifactRepositoryLayout(), false );

            for ( Artifact artifact : artifacts )
            {
                installArtifact( artifact, artifactRepository, this.useTimestampInSnapshotFileName );
            }

            // install the project's artifact in the new repository
            installArtifact( projectArtifact, artifactRepository );
        }
    }

    /**
     * Set the extensions for bin files for the supported platforms. The values are taken from the Mojo's
     * <code>binFileExtensions</code> parameter.
     * @throws  MojoFailureException in case of an error.
     */
    protected void setBinFileExtensions()
            throws MojoFailureException
    {
        if ( binFileExtensions != null )
        {
            for ( String platformName : binFileExtensions.keySet() )
            {

                try
                {
                    Platform platform = Platform.getInstance( platformName );
                    platform.setBinFileExtension( binFileExtensions.get( platformName ) );
                }
                catch ( DaemonGeneratorException e )
                {
                    getLog().warn( "Unable to set the bin file extension for " + platformName, e );
                }
            }
        }
    }
}