package org.apache.maven.shared.model.fileset.util;

/*
 * 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.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.maven.shared.model.fileset.FileSet;
import org.apache.maven.shared.utils.cli.CommandLineException;
import org.apache.maven.shared.utils.cli.Commandline;
import org.apache.maven.shared.utils.io.FileUtils;

import junit.framework.TestCase;

/**
 * Test the FileSet
 *
 * @version $Id: FileSetUtilsTest.java 1716893 2015-11-27 16:09:51Z khmarbaise $
 */
public class FileSetUtilsTest
    extends TestCase
{
    private Set<File> testDirectories = new HashSet<File>();

    private Set<File> linkFiles = new HashSet<File>();

    /** {@inheritDoc} */
    public void tearDown()
        throws IOException
    {
        for ( Iterator<File> it = linkFiles.iterator(); it.hasNext(); )
        {
            File linkFile = it.next();

            linkFile.delete();
        }

        for ( Iterator<File> it = testDirectories.iterator(); it.hasNext(); )
        {
            File dir = it.next();

            FileUtils.deleteDirectory( dir );
        }
    }

    /**
     * @throws IOException if any
     */
    public void testGetIncludedFiles()
        throws IOException
    {
        File directory = setupTestDirectory( "testGetIncludedFiles" );

        FileSet set = new FileSet();
        set.setDirectory( directory.getPath() );
        set.addInclude( "**/included.txt" );

        FileSetManager fileSetManager = new FileSetManager();

        String[] included = fileSetManager.getIncludedFiles( set );

        assertEquals( 1, included.length );
    }

    /**
     * @throws IOException if any
     * @throws InterruptedException if any
     * @throws org.apache.maven.shared.utils.cli.CommandLineException if any
     */
    public void testIncludesDontFollowSymlinks()
        throws IOException, InterruptedException, CommandLineException
    {
        File directory = setupTestDirectory( "testIncludesDontFollowSymlinks" );
        File subdir = new File( directory, directory.getName() );

        if ( !createSymlink( directory, subdir ) )
        {
            // assume failure to create a sym link is because the system does not support them
            // and not because the sym link creation failed.
            return;
        }

        FileSet set = new FileSet();
        set.setDirectory( directory.getPath() );
        set.addInclude( "**/included.txt" );
        set.setFollowSymlinks( false );

        FileSetManager fileSetManager = new FileSetManager();

        String[] included = fileSetManager.getIncludedFiles( set );

        assertEquals( 1, included.length );
    }

    /**
     * @throws IOException if any
     * @throws InterruptedException if any
     * @throws CommandLineException if any
     */
    public void testDeleteDontFollowSymlinks()
        throws IOException, InterruptedException, CommandLineException
    {
        File directory = setupTestDirectory( "testDeleteDontFollowSymlinks" );
        File subdir = new File( directory, directory.getName() );

        if ( !createSymlink( directory, subdir ) )
        {
            // assume failure to create a sym link is because the system does not support them
            // and not because the sym link creation failed.
            return;
        }

        FileSet set = new FileSet();
        set.setDirectory( directory.getPath() );
        set.addInclude( "**/included.txt" );
        set.addInclude( "**/" + subdir.getName() );
        set.setFollowSymlinks( false );

        FileSetManager fileSetManager = new FileSetManager();

        fileSetManager.delete( set );

        assertFalse( subdir.exists() );
    }

    /**
     * @throws IOException if any
     */
    public void testDelete()
        throws IOException
    {
        File directory = setupTestDirectory( "testDelete" );
        File subdirFile = new File( directory, "subdir/excluded.txt" );

        FileSet set = new FileSet();
        set.setDirectory( directory.getPath() );
        set.addInclude( "**/included.txt" );
        set.addInclude( "**/subdir" );

        FileSetManager fileSetManager = new FileSetManager();

        fileSetManager.delete( set );

        assertFalse( "file in marked subdirectory still exists.", subdirFile.exists() );
    }

    /**
     * @throws Exception if any
     */
    public void testDeleteDanglingSymlink()
        throws Exception
    {
        File directory = setupTestDirectory( "testDeleteDanglingSymlink" );
        File targetFile = new File( directory, "test.txt" );
        File linkFile = new File( directory, "symlink" );

        if ( !createSymlink( targetFile, linkFile ) )
        {
            // symlinks apparently not supported, skip test
            return;
        }
        targetFile.delete();

        FileSet set = new FileSet();
        set.setDirectory( directory.getPath() );
        set.addInclude( "**" );

        FileSetManager fileSetManager = new FileSetManager();

        fileSetManager.delete( set );

        assertFalse( "directory still exists", directory.exists() );
    }

    /**
     * @throws Exception if any
     */
    public void testDeleteExcludeParentOfExcludedFile()
        throws Exception
    {
        File directory = setupTestDirectory( "testDeleteExcludeParentOfExcludedFile" );

        FileSet set = new FileSet();
        set.setDirectory( directory.getPath() );
        set.addExclude( "*excluded*" );
        set.setFollowSymlinks( true );

        FileSetManager fileSetManager = new FileSetManager();

        fileSetManager.delete( set );

        assertTrue( "excluded file has been deleted", new File( directory, "excluded.txt" ).exists() );
    }

    /**
     * @throws Exception if any
     */
    public void testDeleteExcludeParentOfExcludedDir()
        throws Exception
    {
        File directory = setupTestDirectory( "testDeleteExcludeParentOfExcludedDir" );

        FileSet set = new FileSet();
        set.setDirectory( directory.getPath() );
        set.addExclude( "*excluded*" );
        set.setFollowSymlinks( true );

        FileSetManager fileSetManager = new FileSetManager();

        fileSetManager.delete( set );

        assertTrue( "excluded directory has been deleted", new File( directory, "excluded" ).exists() );
    }

    /**
     * @throws Exception if any
     */
    public void testDeleteExcludeFollowSymlinks()
        throws Exception
    {
        File directory = setupTestDirectory( "testDeleteExcludeFollowSymlinks" );

        FileSet set = new FileSet();
        set.setDirectory( directory.getPath() );
        set.addExclude( "*excluded*" );
        set.setFollowSymlinks( true );

        FileSetManager fileSetManager = new FileSetManager();

        fileSetManager.delete( set );

        assertTrue( "excluded file has been deleted", new File( directory, "excluded.txt" ).exists() );
        assertTrue( "excluded directory has been deleted", new File( directory, "excluded" ).exists() );
        assertFalse( "included file has not been deleted", new File( directory, "included.txt" ).exists() );
    }

    /**
     * @throws Exception if any
     */
    public void testDeleteExcludeDontFollowSymlinks()
        throws Exception
    {
        File directory = setupTestDirectory( "testDeleteExcludeDontFollowSymlinks" );

        FileSet set = new FileSet();
        set.setDirectory( directory.getPath() );
        set.addExclude( "*excluded*" );
        set.setFollowSymlinks( false );

        FileSetManager fileSetManager = new FileSetManager();

        fileSetManager.delete( set );

        assertTrue( "excluded file has been deleted", new File( directory, "excluded.txt" ).exists() );
        assertTrue( "excluded directory has been deleted", new File( directory, "excluded" ).exists() );
        assertFalse( "included file has not been deleted", new File( directory, "included.txt" ).exists() );
    }

    /**
     * @throws Exception if any
     */
    public void testDeleteDontFollowSymlinksButDeleteThem()
        throws Exception
    {
        File directory = setupTestDirectory( "testDeleteDontFollowSymlinksButDeleteThem" );

        createSymlink( new File( directory, "excluded" ), new File( directory, "dirlink" ) );
        createSymlink( new File( directory, "excluded.txt" ), new File( directory, "filelink" ) );
        createSymlink( new File( directory, "excluded" ), new File( directory, "dir0/dirlink" ) );
        createSymlink( new File( directory, "excluded.txt" ), new File( directory, "dir1/filelink" ) );

        FileSet set = new FileSet();
        set.setDirectory( directory.getPath() );
        set.addExclude( "*excluded*" );
        set.setFollowSymlinks( false );

        FileSetManager fileSetManager = new FileSetManager();

        fileSetManager.delete( set );

        assertTrue( "excluded file has been deleted", new File( directory, "excluded.txt" ).exists() );
        assertTrue( "excluded directory has been deleted", new File( directory, "excluded" ).exists() );
        assertFalse( "included dirlink has not been deleted", new File( directory, "dirlink" ).exists() );
        assertFalse( "included filelink has not been deleted", new File( directory, "filelink" ).exists() );
        assertFalse( "included directory has not been deleted", new File( directory, "dir0" ).exists() );
        assertFalse( "included directory has not been deleted", new File( directory, "dir1" ).exists() );
    }

    /**
     * @param target The target file/directory of the symlink, must not be <code>null</code>.
     * @param link The link to create, must not be <code>null</code>.
     * @return
     * @throws InterruptedException
     * @throws CommandLineException
     */
    private boolean createSymlink( File target, File link )
        throws InterruptedException, CommandLineException
    {
        if ( link.exists() )
        {
            link.delete();
        }

        Commandline cli = new Commandline();
        cli.setExecutable( "ln" );
        cli.createArg().setValue( "-s" );
        cli.createArg().setValue( target.getPath() );
        cli.createArg().setValue( link.getPath() );

        int result = cli.execute().waitFor();

        linkFiles.add( link );

        return result == 0;
    }

    /**
     * @param directoryName
     * @return
     * @throws IOException
     */
    private File setupTestDirectory( String directoryName )
        throws IOException
    {
        URL sourceResource = getClass().getClassLoader().getResource( directoryName );

        if ( sourceResource == null )
        {
            fail( "Source directory for test: " + directoryName + " cannot be found." );
        }

        File sourceDir = new File( URLDecoder.decode( sourceResource.getPath(), "UTF-8" ) );

        String basedir = System.getProperty( "basedir", System.getProperty( "user.dir" ) );
        String testBase = System.getProperty( "testBase", "target/test-directories" );

        File testDir = new File( basedir, testBase + "/" + directoryName );
        testDir.mkdirs();

        FileUtils.copyDirectoryStructure( sourceDir, testDir );

        testDirectories.add( testDir );

        return testDir;
    }
}
