/*
 * 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.plugins.war;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.maven.plugin.testing.stubs.ArtifactStub;
import org.apache.maven.plugins.war.overlay.DefaultOverlay;
import org.codehaus.plexus.util.FileUtils;

/**
 * @author Stephane Nicoll
 */
public class WarOverlaysTest extends AbstractWarExplodedMojoTest {

    private static File pomFile = new File(getBasedir(), "target/test-classes/unit/waroverlays/default.xml");

    public void setUp() throws Exception {
        super.setUp();
        generateFullOverlayWar("overlay-full-1");
        generateFullOverlayWar("overlay-full-2");
        generateFullOverlayWar("overlay-full-3");
    }

    protected File getPomFile() {
        return pomFile;
    }

    protected File getTestDirectory() {
        return new File(getBasedir(), "target/test-classes/unit/waroverlays");
    }

    public void testEnvironment() throws Exception {
        // see setup
    }

    public void testNoOverlay() throws Exception {
        // setup test data
        final String testId = "no-overlay";
        final File xmlSource = createXMLConfigDir(testId, new String[] {"web.xml"});

        final File webAppDirectory = setUpMojo(testId, null);
        try {
            mojo.setWebXml(new File(xmlSource, "web.xml"));
            mojo.execute();

            // Validate content of the webapp
            assertDefaultContent(webAppDirectory);
            assertWebXml(webAppDirectory);
        } finally {
            cleanDirectory(webAppDirectory);
        }
    }

    public void testDefaultOverlay() throws Exception {
        // setup test data
        final String testId = "default-overlay";

        // Add an overlay
        final ArtifactStub overlay = buildWarOverlayStub("overlay-one");

        final File webAppDirectory = setUpMojo(testId, new ArtifactStub[] {overlay});
        final List<File> assertedFiles = new ArrayList<>();
        try {
            mojo.execute();
            assertedFiles.addAll(assertDefaultContent(webAppDirectory));
            assertedFiles.addAll(assertWebXml(webAppDirectory));
            assertedFiles.addAll(assertCustomContent(
                    webAppDirectory, new String[] {"index.jsp", "login.jsp"}, "overlay file not found"));

            // index and login come from overlay1
            assertOverlayedFile(webAppDirectory, "overlay-one", "index.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-one", "login.jsp");

            // Ok now check that there is no more files/directories
            final FileFilter filter = new FileFilterImpl(webAppDirectory, new String[] {MANIFEST_PATH});
            assertWebAppContent(webAppDirectory, assertedFiles, filter);
        } finally {
            cleanDirectory(webAppDirectory);
        }
    }

    public void testDefaultOverlays() throws Exception {
        // setup test data
        final String testId = "default-overlays";

        // Add an overlay
        final ArtifactStub overlay = buildWarOverlayStub("overlay-one");
        final ArtifactStub overlay2 = buildWarOverlayStub("overlay-two");

        final File webAppDirectory = setUpMojo(testId, new ArtifactStub[] {overlay, overlay2});
        final List<File> assertedFiles = new ArrayList<>();
        try {
            mojo.execute();
            assertedFiles.addAll(assertDefaultContent(webAppDirectory));
            assertedFiles.addAll(assertWebXml(webAppDirectory));
            assertedFiles.addAll(assertCustomContent(
                    webAppDirectory, new String[] {"index.jsp", "login.jsp", "admin.jsp"}, "overlay file not found"));

            // index and login come from overlay1
            assertOverlayedFile(webAppDirectory, "overlay-one", "index.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-one", "login.jsp");

            // admin comes from overlay2
            // index and login comes from overlay1
            assertOverlayedFile(webAppDirectory, "overlay-two", "admin.jsp");

            // Ok now check that there is no more files/directories
            final FileFilter filter = new FileFilterImpl(webAppDirectory, new String[] {MANIFEST_PATH});
            assertWebAppContent(webAppDirectory, assertedFiles, filter);
        } finally {
            cleanDirectory(webAppDirectory);
        }
    }

    /**
     * Merge a dependent WAR when a file in the war source directory overrides one found in the WAR.
     *
     * It also tests completeness of the resulting war as well as the proper order of dependencies.
     *
     * @throws Exception if any error occurs
     */
    public void testScenarioOneWithDefaulSettings() throws Exception {
        // setup test data
        final String testId = "scenario-one-default-settings";

        // Add an overlay
        final ArtifactStub overlay1 = buildWarOverlayStub("overlay-full-1");
        final ArtifactStub overlay2 = buildWarOverlayStub("overlay-full-2");
        final ArtifactStub overlay3 = buildWarOverlayStub("overlay-full-3");

        final File webAppDirectory = setUpMojo(testId, new ArtifactStub[] {overlay1, overlay2, overlay3}, new String[] {
            "org/sample/company/test.jsp", "jsp/b.jsp"
        });

        assertScenariOne(testId, webAppDirectory);
    }

    /**
     * Tests that specifying the overlay explicitely has the same behavior as the default (i.e. order, etc).
     *
     * The default project is not specified in this case so it is processed first by default
     *
     * @throws Exception if an error occurs
     */
    public void testScenarioOneWithOverlaySettings() throws Exception {
        // setup test data
        final String testId = "scenario-one-overlay-settings";

        // Add an overlay
        final ArtifactStub overlay1 = buildWarOverlayStub("overlay-full-1");
        final ArtifactStub overlay2 = buildWarOverlayStub("overlay-full-2");
        final ArtifactStub overlay3 = buildWarOverlayStub("overlay-full-3");

        final File webAppDirectory = setUpMojo(testId, new ArtifactStub[] {overlay1, overlay2, overlay3}, new String[] {
            "org/sample/company/test.jsp", "jsp/b.jsp"
        });

        // Add the tags
        final List<Overlay> overlays = new ArrayList<>();
        overlays.add(new DefaultOverlay(overlay1));
        overlays.add(new DefaultOverlay(overlay2));
        overlays.add(new DefaultOverlay(overlay3));
        mojo.setOverlays(overlays);

        // current project ignored. Should be on top of the list
        assertScenariOne(testId, webAppDirectory);
    }

    /**
     * Tests that specifying the overlay explicitely has the same behavior as the default (i.e. order, etc).
     *
     * The default project is explicitely specified so this should match the default.
     *
     * @throws Exception if an error occurs
     */
    public void testScenarioOneWithFullSettings() throws Exception {
        // setup test data
        final String testId = "scenario-one-full-settings";

        // Add an overlay
        final ArtifactStub overlay1 = buildWarOverlayStub("overlay-full-1");
        final ArtifactStub overlay2 = buildWarOverlayStub("overlay-full-2");
        final ArtifactStub overlay3 = buildWarOverlayStub("overlay-full-3");

        final File webAppDirectory = setUpMojo(testId, new ArtifactStub[] {overlay1, overlay2, overlay3}, new String[] {
            "org/sample/company/test.jsp", "jsp/b.jsp"
        });

        // Add the tags
        final List<Overlay> overlays = new ArrayList<>();

        // Add the default project explicitely
        overlays.add(mojo.getCurrentProjectOverlay());

        // Other overlays
        overlays.add(new DefaultOverlay(overlay1));
        overlays.add(new DefaultOverlay(overlay2));
        overlays.add(new DefaultOverlay(overlay3));
        mojo.setOverlays(overlays);

        // current project ignored. Should be on top of the list
        assertScenariOne(testId, webAppDirectory);
    }

    /**
     * Runs the mojo and asserts a scenerio with 3 overlays and no includes/excludes settings.
     *
     * @param testId thie id of the test
     * @param webAppDirectory the webapp directory
     * @throws Exception if an exception occurs
     */
    private void assertScenariOne(String testId, File webAppDirectory) throws Exception {
        final List<File> assertedFiles = new ArrayList<>();
        try {
            mojo.execute();
            assertedFiles.addAll(assertWebXml(webAppDirectory));
            assertedFiles.addAll(assertCustomContent(
                    webAppDirectory,
                    new String[] {
                        "jsp/a.jsp",
                        "jsp/b.jsp",
                        "jsp/c.jsp",
                        "jsp/d/a.jsp",
                        "jsp/d/b.jsp",
                        "jsp/d/c.jsp",
                        "org/sample/company/test.jsp",
                        "WEB-INF/classes/a.clazz",
                        "WEB-INF/classes/b.clazz",
                        "WEB-INF/classes/c.clazz",
                        "WEB-INF/lib/a.jar",
                        "WEB-INF/lib/b.jar",
                        "WEB-INF/lib/c.jar"
                    },
                    "overlay file not found"));

            // Those files should come from the source webapp without any config
            assertDefaultFileContent(testId, webAppDirectory, "jsp/b.jsp");
            assertDefaultFileContent(testId, webAppDirectory, "org/sample/company/test.jsp");

            // Everything else comes from overlay1 (order of addition in the dependencies)
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "jsp/a.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "jsp/c.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "jsp/d/a.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "jsp/d/b.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "jsp/d/c.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "WEB-INF/web.xml");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "WEB-INF/classes/a.clazz");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "WEB-INF/classes/b.clazz");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "WEB-INF/classes/c.clazz");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "WEB-INF/lib/a.jar");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "WEB-INF/lib/b.jar");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "WEB-INF/lib/c.jar");

            // Ok now check that there is no more files/directories
            final FileFilter filter = new FileFilterImpl(webAppDirectory, new String[] {MANIFEST_PATH});
            assertWebAppContent(webAppDirectory, assertedFiles, filter);
        } finally {
            cleanDirectory(webAppDirectory);
        }
    }

    public void testOverlaysIncludesExcludesWithMultipleDefinitions() throws Exception {
        // setup test data
        final String testId = "overlays-includes-excludes-multiple-defs";

        // Add an overlay
        final ArtifactStub overlay1 = buildWarOverlayStub("overlay-full-1");
        final ArtifactStub overlay2 = buildWarOverlayStub("overlay-full-2");
        final ArtifactStub overlay3 = buildWarOverlayStub("overlay-full-3");

        final File webAppDirectory = setUpMojo(testId, new ArtifactStub[] {overlay1, overlay2, overlay3}, new String[] {
            "org/sample/company/test.jsp", "jsp/b.jsp"
        });

        Overlay over1 = new DefaultOverlay(overlay3);
        over1.setExcludes("**/a.*,**/c.*,**/*.xml");

        Overlay over2 = new DefaultOverlay(overlay1);
        over2.setIncludes("jsp/d/*");
        over2.setExcludes("jsp/d/a.jsp");

        Overlay over3 = new DefaultOverlay(overlay3);
        over3.setIncludes("**/*.jsp");

        Overlay over4 = new DefaultOverlay(overlay2);

        mojo.setOverlays(new LinkedList<>());
        mojo.addOverlay(over1);
        mojo.addOverlay(over2);
        mojo.addOverlay(over3);
        mojo.addOverlay(mojo.getCurrentProjectOverlay());
        mojo.addOverlay(over4);

        final List<File> assertedFiles = new ArrayList<>();
        try {
            mojo.execute();
            assertedFiles.addAll(assertWebXml(webAppDirectory));
            assertedFiles.addAll(assertCustomContent(
                    webAppDirectory,
                    new String[] {
                        "jsp/a.jsp",
                        "jsp/b.jsp",
                        "jsp/c.jsp",
                        "jsp/d/a.jsp",
                        "jsp/d/b.jsp",
                        "jsp/d/c.jsp",
                        "org/sample/company/test.jsp",
                        "WEB-INF/classes/a.clazz",
                        "WEB-INF/classes/b.clazz",
                        "WEB-INF/classes/c.clazz",
                        "WEB-INF/lib/a.jar",
                        "WEB-INF/lib/b.jar",
                        "WEB-INF/lib/c.jar"
                    },
                    "overlay file not found"));

            assertOverlayedFile(webAppDirectory, "overlay-full-3", "jsp/a.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-3", "jsp/b.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-3", "jsp/c.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-3", "jsp/d/a.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-3", "jsp/d/b.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "jsp/d/c.jsp");
            assertDefaultFileContent(testId, webAppDirectory, "org/sample/company/test.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-2", "WEB-INF/web.xml");
            assertOverlayedFile(webAppDirectory, "overlay-full-2", "WEB-INF/classes/a.clazz");
            assertOverlayedFile(webAppDirectory, "overlay-full-3", "WEB-INF/classes/b.clazz");
            assertOverlayedFile(webAppDirectory, "overlay-full-2", "WEB-INF/classes/c.clazz");
            assertOverlayedFile(webAppDirectory, "overlay-full-2", "WEB-INF/lib/a.jar");
            assertOverlayedFile(webAppDirectory, "overlay-full-3", "WEB-INF/lib/b.jar");
            assertOverlayedFile(webAppDirectory, "overlay-full-2", "WEB-INF/lib/c.jar");

            // Ok now check that there is no more files/directories
            final FileFilter filter = new FileFilterImpl(webAppDirectory, new String[] {MANIFEST_PATH});
            assertWebAppContent(webAppDirectory, assertedFiles, filter);
        } finally {
            cleanDirectory(webAppDirectory);
        }
    }

    public void testOverlaysIncludesExcludesWithMultipleDefinitions2() throws Exception {
        // setup test data
        final String testId = "overlays-includes-excludes-multiple-defs2";

        // Add an overlay
        final ArtifactStub overlay1 = buildWarOverlayStub("overlay-full-1");
        final ArtifactStub overlay2 = buildWarOverlayStub("overlay-full-2");
        final ArtifactStub overlay3 = buildWarOverlayStub("overlay-full-3");

        final File webAppDirectory = setUpMojo(testId, new ArtifactStub[] {overlay1, overlay2, overlay3}, new String[] {
            "org/sample/company/test.jsp", "jsp/b.jsp"
        });

        Overlay over1 = new DefaultOverlay(overlay3);
        over1.setExcludes("**/a.*,**/c.*,**/*.xml,jsp/b.jsp");

        Overlay over2 = new DefaultOverlay(overlay1);
        over2.setIncludes("jsp/d/*");
        over2.setExcludes("jsp/d/a.jsp");

        Overlay over3 = new DefaultOverlay(overlay3);
        over3.setIncludes("**/*.jsp");
        over3.setExcludes("jsp/b.jsp");

        Overlay over4 = new DefaultOverlay(overlay2);

        mojo.setOverlays(new LinkedList<>());
        mojo.addOverlay(over1);
        mojo.addOverlay(over2);
        mojo.addOverlay(over3);
        mojo.addOverlay(mojo.getCurrentProjectOverlay());
        mojo.addOverlay(over4);

        final List<File> assertedFiles = new ArrayList<>();
        try {
            mojo.execute();
            assertedFiles.addAll(assertWebXml(webAppDirectory));
            assertedFiles.addAll(assertCustomContent(
                    webAppDirectory,
                    new String[] {
                        "jsp/a.jsp",
                        "jsp/b.jsp",
                        "jsp/c.jsp",
                        "jsp/d/a.jsp",
                        "jsp/d/b.jsp",
                        "jsp/d/c.jsp",
                        "org/sample/company/test.jsp",
                        "WEB-INF/classes/a.clazz",
                        "WEB-INF/classes/b.clazz",
                        "WEB-INF/classes/c.clazz",
                        "WEB-INF/lib/a.jar",
                        "WEB-INF/lib/b.jar",
                        "WEB-INF/lib/c.jar"
                    },
                    "overlay file not found"));

            assertOverlayedFile(webAppDirectory, "overlay-full-3", "jsp/a.jsp");
            assertDefaultFileContent(testId, webAppDirectory, "jsp/b.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-3", "jsp/c.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-3", "jsp/d/a.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-3", "jsp/d/b.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-1", "jsp/d/c.jsp");
            assertDefaultFileContent(testId, webAppDirectory, "org/sample/company/test.jsp");
            assertOverlayedFile(webAppDirectory, "overlay-full-2", "WEB-INF/web.xml");
            assertOverlayedFile(webAppDirectory, "overlay-full-2", "WEB-INF/classes/a.clazz");
            assertOverlayedFile(webAppDirectory, "overlay-full-3", "WEB-INF/classes/b.clazz");
            assertOverlayedFile(webAppDirectory, "overlay-full-2", "WEB-INF/classes/c.clazz");
            assertOverlayedFile(webAppDirectory, "overlay-full-2", "WEB-INF/lib/a.jar");
            assertOverlayedFile(webAppDirectory, "overlay-full-3", "WEB-INF/lib/b.jar");
            assertOverlayedFile(webAppDirectory, "overlay-full-2", "WEB-INF/lib/c.jar");

            // Ok now check that there is no more files/directories
            final FileFilter filter = new FileFilterImpl(webAppDirectory, new String[] {MANIFEST_PATH});
            assertWebAppContent(webAppDirectory, assertedFiles, filter);
        } finally {
            cleanDirectory(webAppDirectory);
        }
    }

    // Helpers

    /**
     * Asserts that the content of an overlayed file is correct.
     *
     * Note that the {@code filePath} is relative to both the webapp directory and the overlayed directory, defined by
     * the {@code overlayId}.
     *
     * @param webAppDirectory the webapp directory
     * @param overlayId the id of the overlay
     * @param filePath the relative path
     * @throws IOException if an error occurred while reading the files
     */
    protected void assertOverlayedFile(File webAppDirectory, String overlayId, String filePath) throws IOException {
        final File webAppFile = new File(webAppDirectory, filePath);
        final File overlayFile = getOverlayFile(overlayId, filePath);
        assertEquals(
                "Wrong content for overlayed file " + filePath,
                FileUtils.fileRead(overlayFile),
                FileUtils.fileRead(webAppFile));
    }

    /**
     * Asserts that the content of an overlayed file is correct.
     *
     * Note that the {@code filePath} is relative to both the webapp directory and the overlayed directory, defined by
     * the {@code overlayId}.
     *
     * @param testId te id of the test
     * @param webAppDirectory the webapp directory
     * @param filePath the relative path
     * @throws IOException if an error occurred while reading the files
     */
    protected void assertDefaultFileContent(String testId, File webAppDirectory, String filePath) throws Exception {
        final File webAppFile = new File(webAppDirectory, filePath);
        final File sourceFile = new File(getWebAppSource(testId), filePath);
        final String expectedContent = sourceFile.toString();
        assertEquals("Wrong content for file " + filePath, expectedContent, FileUtils.fileRead(webAppFile));
    }

    protected ArtifactStub generateSimpleWarArtifactStub(String id) throws Exception {
        return buildWarOverlayStub(id);
    }
}
