package org.apache.maven.plugins.war.overlay;

/*
 * 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.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.plugins.war.Overlay;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.StringUtils;

/**
 * Manages the overlays.
 *
 * @author Stephane Nicoll
 * @version $Id: OverlayManager.java 1781709 2017-02-04 21:11:45Z michaelo $
 */
public class OverlayManager
{
    private final List<Overlay> overlays;

    private final MavenProject project;

    private final List<Artifact> artifactsOverlays;

    /**
     * Creates a manager with the specified overlays.
     * 
     * Note that the list is potentially updated by the manager so a new list is created based on the overlays.
     *
     * @param overlays the overlays
     * @param project the maven project
     * @param defaultIncludes the default includes to use
     * @param defaultExcludes the default excludes to use
     * @param currentProjectOverlay the overlay for the current project
     * @throws InvalidOverlayConfigurationException if the config is invalid
     */
    public OverlayManager( List<Overlay> overlays, MavenProject project, String[] defaultIncludes,
                           String[] defaultExcludes, Overlay currentProjectOverlay )
        throws InvalidOverlayConfigurationException
    {
        this.overlays = new ArrayList<Overlay>();
        if ( overlays != null )
        {
            this.overlays.addAll( overlays );
        }
        this.project = project;

        this.artifactsOverlays = getOverlaysAsArtifacts();

        // Initialize
        initialize( defaultIncludes, defaultExcludes, currentProjectOverlay );

    }

    /**
     * Returns the resolved overlays.
     *
     * @return the overlays
     */
    public List<Overlay> getOverlays()
    {
        return overlays;
    }

    /**
     * Returns the id of the resolved overlays.
     *
     * @return the overlay ids
     */
    public List<String> getOverlayIds()
    {
        final List<String> result = new ArrayList<String>();
        for ( Overlay overlay : overlays )
        {
            result.add( overlay.getId() );
        }
        return result;

    }

    /**
     * Initializes the manager and validates the overlays configuration.
     *
     * @param defaultIncludes the default includes to use
     * @param defaultExcludes the default excludes to use
     * @param currentProjectOverlay the overlay for the current project
     * @throws InvalidOverlayConfigurationException if the configuration is invalid
     */
    void initialize( String[] defaultIncludes, String[] defaultExcludes, Overlay currentProjectOverlay )
        throws InvalidOverlayConfigurationException
    {

        // Build the list of configured artifacts and makes sure that each overlay
        // refer to a valid artifact
        final List<Artifact> configuredWarArtifacts = new ArrayList<Artifact>();
        final ListIterator<Overlay> it = overlays.listIterator();
        while ( it.hasNext() )
        {
            Overlay overlay = it.next();
            if ( overlay == null )
            {
                throw new InvalidOverlayConfigurationException( "overlay could not be null." );
            }
            // If it's the current project, return the project instance
            if ( overlay.isCurrentProject() )
            {
                overlay = currentProjectOverlay;
                it.set( overlay );
            }
            // default includes/excludes - only if the overlay uses the default settings
            if ( Arrays.equals( Overlay.DEFAULT_INCLUDES, overlay.getIncludes() )
                && Arrays.equals( Overlay.DEFAULT_EXCLUDES, overlay.getExcludes() ) )
            {
                overlay.setIncludes( defaultIncludes );
                overlay.setExcludes( defaultExcludes );
            }

            final Artifact artifact = getAssociatedArtifact( overlay );
            if ( artifact != null )
            {
                configuredWarArtifacts.add( artifact );
                overlay.setArtifact( artifact );
            }
        }

        // Build the list of missing overlays
        for ( Artifact artifact : artifactsOverlays )
        {
            if ( !configuredWarArtifacts.contains( artifact ) )
            {
                // Add a default overlay for the given artifact which will be applied after
                // the ones that have been configured
                overlays.add( new DefaultOverlay( artifact, defaultIncludes, defaultExcludes ) );
            }
        }

        // Final validation, make sure that the current project is in there. Otherwise add it first
        for ( Overlay overlay : overlays )
        {
            if ( overlay.equals( currentProjectOverlay ) )
            {
                return;
            }
        }
        overlays.add( 0, currentProjectOverlay );
    }

    /**
     * Returns the Artifact associated to the specified overlay.
     * 
     * If the overlay defines the current project, <tt>null</tt> is returned. If no artifact could not be found for the
     * overlay a InvalidOverlayConfigurationException is thrown.
     *
     * @param overlay an overlay
     * @return the artifact associated to the overlay
     * @throws org.apache.maven.plugins.war.overlay.InvalidOverlayConfigurationException if the overlay does not have an
     *             associated artifact
     */
    Artifact getAssociatedArtifact( final Overlay overlay )
        throws InvalidOverlayConfigurationException
    {
        if ( overlay.isCurrentProject() )
        {
            return null;
        }

        for ( Artifact artifact : artifactsOverlays )
        {
            // Handle classifier dependencies properly (clash management)
            if ( compareOverlayWithArtifact( overlay, artifact ) )
            {
                return artifact;
            }
        }

        // maybe its a project dependencies zip or an other type
        Set<Artifact> projectArtifacts = this.project.getDependencyArtifacts();
        if ( projectArtifacts != null )
        {
            for ( Artifact artifact : projectArtifacts )
            {
                if ( compareOverlayWithArtifact( overlay, artifact ) )
                {
                    return artifact;
                }
            }
        }
        // CHECKSTYLE_OFF: LineLength
        throw new InvalidOverlayConfigurationException( "overlay [" + overlay + "] is not a dependency of the project." );
        // CHECKSTYLE_ON: LineLength

    }

    /**
     * Compare groupId && artifactId && type && classifier.
     *
     * @param overlay the overlay
     * @param artifact the artifact
     * @return boolean true if equals
     */
    private boolean compareOverlayWithArtifact( Overlay overlay, Artifact artifact )
    {
        return ( StringUtils.equals( overlay.getGroupId(), artifact.getGroupId() )
            && StringUtils.equals( overlay.getArtifactId(), artifact.getArtifactId() )
            && StringUtils.equals( overlay.getType(), artifact.getType() )
        // MWAR-241 Make sure to treat null and "" as equal when comparing the classifier
        && StringUtils.equals( StringUtils.defaultString( overlay.getClassifier() ),
                               StringUtils.defaultString( artifact.getClassifier() ) ) );
    }

    /**
     * Returns a list of WAR {@link org.apache.maven.artifact.Artifact} describing the overlays of the current project.
     *
     * @return the overlays as artifacts objects
     */
    private List<Artifact> getOverlaysAsArtifacts()
    {
        ScopeArtifactFilter filter = new ScopeArtifactFilter( Artifact.SCOPE_RUNTIME );
        final Set<Artifact> artifacts = project.getArtifacts();

        final List<Artifact> result = new ArrayList<Artifact>();
        for ( Artifact artifact : artifacts )
        {
            if ( !artifact.isOptional() && filter.include( artifact ) && ( "war".equals( artifact.getType() ) ) )
            {
                result.add( artifact );
            }
        }
        return result;
    }
}
