/*
 * Copyright 2010 the original author or authors.
 *
 * Licensed 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.gradle.api.internal.file.copy

import org.apache.tools.ant.filters.HeadFilter
import org.apache.tools.ant.filters.StripJavaComments
import org.gradle.api.Action
import org.gradle.api.file.CopySpec
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.file.FileTree
import org.gradle.api.file.RelativePath
import org.gradle.api.internal.file.FileResolver
import org.gradle.api.specs.Spec
import org.gradle.api.tasks.util.PatternSet
import org.gradle.internal.Actions
import org.gradle.internal.reflect.DirectInstantiator
import org.gradle.internal.reflect.Instantiator
import org.gradle.test.fixtures.file.TestFile
import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
import org.gradle.util.JUnit4GroovyMockery
import org.jmock.integration.junit4.JMock
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

import static org.hamcrest.Matchers.*
import static org.junit.Assert.*

@RunWith(JMock)
public class DefaultCopySpecTest {

    @Rule
    public TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
    private TestFile baseFile = testDir.testDirectory
    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
    private final FileResolver fileResolver = context.mock(FileResolver);
    private final Instantiator instantiator = new DirectInstantiator()
    private final DefaultCopySpec spec = new DefaultCopySpec(fileResolver, instantiator)

    private List<String> getTestSourceFileNames() {
        ['first', 'second']
    }

    private List<File> getAbsoluteTestSources() {
        testSourceFileNames.collect { new File(baseFile, it) }
    }

    @Test
    public void testAbsoluteFromList() {
        List<File> sources = getAbsoluteTestSources();
        spec.from(sources);
        assertEquals([sources], spec.sourcePaths as List);
    }

    @Test
    public void testFromArray() {
        List<File> sources = getAbsoluteTestSources();
        spec.from(sources as File[]);
        assertEquals(sources, spec.sourcePaths as List);
    }

    @Test
    public void testSourceWithClosure() {
        CopySpec child = spec.from('source') {
        }

        assertThat(child, not(sameInstance(spec as CopySpec)))
        assertEquals(['source'], unpackWrapper(child).sourcePaths as List);
    }

    @Test
    public void testMultipleSourcesWithClosure() {
        CopySpec child = spec.from(['source1', 'source2']) {
        }

        assertThat(child, not(sameInstance(spec as CopySpec)))
        assertEquals(['source1', 'source2'], unpackWrapper(child).sourcePaths.flatten() as List);
    }

    @Test
    public void testDefaultDestinationPathForRootSpec() {
        assertThat(spec.destPath, equalTo(new RelativePath(false)))
    }

    @Test
    public void testInto() {
        spec.into 'spec'
        assertThat(spec.destPath, equalTo(new RelativePath(false, 'spec')))
        spec.into '/'
        assertThat(spec.destPath, equalTo(new RelativePath(false)))
    }

    @Test
    public void testIntoWithAClosure() {
        spec.into { 'spec' }
        assertThat(spec.destPath, equalTo(new RelativePath(false, 'spec')))
        spec.into { return { 'spec' } }
        assertThat(spec.destPath, equalTo(new RelativePath(false, 'spec')))
    }

    @Test
    public void testWithSpec() {
        DefaultCopySpec other1 = new DefaultCopySpec(fileResolver, instantiator)
        DefaultCopySpec other2 = new DefaultCopySpec(fileResolver, instantiator)

        spec.with other1, other2
        assertTrue(spec.sourcePaths.empty)
        assertThat(spec.childSpecs.size(), equalTo(2))
    }

    @Test
    public void testWithSpecInheritsDestinationPathFromParent() {
        DefaultCopySpec other = new DefaultCopySpec(fileResolver, instantiator)
        other.into 'other'

        spec.into 'spec'
        spec.with other

        CopySpecInternal child = spec.childSpecs[0]
        assertThat(child.destPath, equalTo(new RelativePath(false, 'spec', 'other')))
    }

    @Test
    public void testDestinationWithClosure() {
        CopySpec child = spec.into('target') {
        }

        assertThat(child, not(sameInstance(spec as CopySpec)))
        assertThat(unpackWrapper(child).destPath, equalTo(new RelativePath(false, 'target')))
    }

    @Test
    public void testRootSpecHasRootPathAsDestination() {
        assertThat(spec.destPath, equalTo(new RelativePath(false)))
    }

    @Test
    public void testChildSpecResolvesIntoArgRelativeToParentDestinationDir() {
        CopySpec child = spec.from('somedir') { into 'child' }
        assertThat(unpackWrapper(child).destPath, equalTo(new RelativePath(false, 'child')))

        CopySpec grandchild = child.from('somedir') { into 'grandchild' }
        assertThat(unpackWrapper(grandchild).destPath, equalTo(new RelativePath(false, 'child', 'grandchild')))

        grandchild.into '/grandchild'
        assertThat(unpackWrapper(grandchild).destPath, equalTo(new RelativePath(false, 'grandchild')))
    }

    @Test
    public void testChildSpecUsesParentDestinationPathAsDefault() {
        CopySpec child = spec.from('somedir') {}
        assertThat(unpackWrapper(child).destPath, equalTo(spec.destPath))

        child.into 'child'

        CopySpec grandchild = child.from('somedir') {}
        assertThat(unpackWrapper(grandchild).destPath, equalTo(unpackWrapper(child).destPath))
    }

    @Test
    public void testSourceIsFilteredTreeOfSources() {
        spec.from 'a'
        spec.from 'b'

        def filteredTree = context.mock(FileTree, 'filtered')

        context.checking {
            one(fileResolver).resolveFilesAsTree(['a', 'b'] as Set)
            def tree = context.mock(FileTree, 'source')
            will(returnValue(tree))
            one(tree).matching(withParam(equalTo(spec.patternSet)))
            will(returnValue(filteredTree))
        }

        assertThat(spec.source, sameInstance(filteredTree))
    }

    @Test
    public void testChildUsesPatternsFromParent() {
        CopySpec child = spec.from('dir') {}
        Spec specInclude = [:] as Spec
        Spec specExclude = [:] as Spec
        Spec childInclude = [:] as Spec
        Spec childExclude = [:] as Spec

        spec.include('parent-include')
        spec.exclude('parent-exclude')
        spec.include(specInclude)
        spec.exclude(specExclude)
        child.include('child-include')
        child.exclude('child-exclude')
        child.include(childInclude)
        child.exclude(childExclude)

        PatternSet patterns = unpackWrapper(child).patternSet
        assertThat(patterns.includes, equalTo(['parent-include', 'child-include'] as Set))
        assertThat(patterns.excludes, equalTo(['parent-exclude', 'child-exclude'] as Set))
        assertThat(patterns.includeSpecs, equalTo([specInclude, childInclude] as Set))
        assertThat(patterns.excludeSpecs, equalTo([specExclude, childExclude] as Set))
    }

    @Test
    public void testChildUsesParentPatternsAsDefault() {
        CopySpec child = spec.from('dir') {}
        Spec specInclude = [:] as Spec
        Spec specExclude = [:] as Spec

        spec.include('parent-include')
        spec.exclude('parent-exclude')
        spec.include(specInclude)
        spec.exclude(specExclude)

        PatternSet patterns = unpackWrapper(child).patternSet
        assertThat(patterns.includes, equalTo(['parent-include'] as Set))
        assertThat(patterns.excludes, equalTo(['parent-exclude'] as Set))
        assertThat(patterns.includeSpecs, equalTo([specInclude] as Set))
        assertThat(patterns.excludeSpecs, equalTo([specExclude] as Set))
    }

    @Test
    public void caseSensitiveFlagDefaultsToTrue() {
        assert spec.caseSensitive
        assert spec.patternSet.caseSensitive
    }

    @Test
    public void childUsesCaseSensitiveFlagFromParentAsDefault() {
        def child = spec.from('dir') {}
        def realChild = (child as CopySpecWrapper).delegate as DefaultCopySpec

        assert child.caseSensitive
        assert realChild.patternSet.caseSensitive

        spec.caseSensitive = false
        assert !child.caseSensitive
        assert !realChild.patternSet.caseSensitive

        child.caseSensitive = true
        assert child.caseSensitive
        assert realChild.patternSet.caseSensitive
    }

    @Test
    public void includeEmptyDirsFlagDefaultsToTrue() {
        assert spec.includeEmptyDirs
    }

    @Test
    public void childUsesIncludeEmptyDirsFlagFromParentAsDefault() {
        def child = spec.from('dir') {}

        assert child.includeEmptyDirs

        spec.includeEmptyDirs = false
        assert !child.includeEmptyDirs

        child.includeEmptyDirs = true
        assert child.includeEmptyDirs
    }

    @Test
    public void testNoArgFilter() {
        spec.filter(StripJavaComments)
        assertThat(spec.allCopyActions.size(), equalTo(1))
    }

    @Test
    public void testArgFilter() {
        spec.filter(HeadFilter, lines: 15, skip: 2)
        assertThat(spec.allCopyActions.size(), equalTo(1))
    }

    @Test
    public void testExpand() {
        spec.expand(version: '1.2', skip: 2)
        assertThat(spec.allCopyActions.size(), equalTo(1))
    }

    @Test
    public void testTwoFilters() {
        spec.filter(StripJavaComments)
        spec.filter(HeadFilter, lines: 15, skip: 2)

        assertThat(spec.allCopyActions.size(), equalTo(2))
    }

    @Test
    public void testAddsStringNameTransformerToActions() {
        spec.rename("regexp", "replacement")

        assertThat(spec.allCopyActions.size(), equalTo(1))
        assertThat(spec.allCopyActions[0], instanceOf(RenamingCopyAction))
        assertThat(spec.allCopyActions[0].transformer, instanceOf(RegExpNameMapper))
    }

    @Test
    public void testAddsPatternNameTransformerToActions() {
        spec.rename(/regexp/, "replacement")

        assertThat(spec.allCopyActions.size(), equalTo(1))
        assertThat(spec.allCopyActions[0], instanceOf(RenamingCopyAction))
        assertThat(spec.allCopyActions[0].transformer, instanceOf(RegExpNameMapper))
    }

    @Test
    public void testAddsClosureToActions() {
        spec.rename {}

        assertThat(spec.allCopyActions.size(), equalTo(1))
        assertThat(spec.allCopyActions[0], instanceOf(RenamingCopyAction))
    }

    @Test
    public void testAddAction() {
        def action = context.mock(Action)
        spec.eachFile(action)

        assertThat(spec.allCopyActions, equalTo([action]))
    }

    @Test
    public void testAddActionAsClosure() {
        def action = {}
        spec.eachFile(action)

        assertThat(spec.allCopyActions.size(), equalTo(1))
    }

    @Test
    public void testSpecInheritsActionsFromParent() {
        Action parentAction = context.mock(Action, 'parent')
        Action childAction = context.mock(Action, 'child')

        spec.eachFile parentAction
        CopySpec childSpec = spec.from('src') {
            eachFile childAction
        }

        assertThat(unpackWrapper(childSpec).allCopyActions, equalTo([parentAction, childAction]))
    }

    @Test
    public void testHasNoPermissionsByDefault() {
        assert spec.fileMode == null
        assert spec.dirMode == null
    }

    @Test
    public void testInheritsPermissionsFromParent() {
        spec.fileMode = 0x1
        spec.dirMode = 0x2

        CopySpec child = spec.from('src') {}
        Assert.assertEquals(0x1, child.fileMode)
        Assert.assertEquals(0x2, child.dirMode)
    }

    @Test
    public void testHasNoSourceByDefault() {
        assertFalse(spec.hasSource())
    }

    @Test
    public void testHasSourceWhenSpecHasSource() {
        spec.from 'source'
        assertTrue(spec.hasSource())
    }

    @Test
    public void testHasSourceWhenChildSpecHasSource() {
        spec.from('source') {}
        assertTrue(spec.hasSource())
    }

    @Test
    public void duplicatesStrategyDefaultsToInclude() {
        assert spec.duplicatesStrategy == DuplicatesStrategy.INCLUDE
    }

    @Test
    public void childInheritsDuplicatesStrategyFromParent() {
        def child = spec.from('dir') {}

        assert child.duplicatesStrategy == DuplicatesStrategy.INCLUDE

        spec.duplicatesStrategy = 'EXCLUDE'
        assert child.duplicatesStrategy == DuplicatesStrategy.EXCLUDE

        child.duplicatesStrategy = 'INCLUDE'
        assert child.duplicatesStrategy == DuplicatesStrategy.INCLUDE
    }

    @Test
    public void testMatchingCreatesAppropriateAction() {
        spec.filesMatching "root/**/a*", Actions.doNothing()
        assertEquals(1, spec.allCopyActions.size())
        assertThat(spec.allCopyActions[0], instanceOf(MatchingCopyAction))

        Spec<RelativePath> matchSpec = spec.allCopyActions[0].matchSpec
        assertTrue(matchSpec.isSatisfiedBy(RelativePath.parse(true, '/root/folder/abc')))
        assertTrue(matchSpec.isSatisfiedBy(RelativePath.parse(true, '/root/abc')))
        assertFalse(matchSpec.isSatisfiedBy(RelativePath.parse(true, '/notRoot/abc')))
        assertFalse(matchSpec.isSatisfiedBy(RelativePath.parse(true, '/root/bbc')))
        assertFalse(matchSpec.isSatisfiedBy(RelativePath.parse(true, '/notRoot/bbc')))
    }

    @Test
    public void testNotMatchingCreatesAppropriateAction() {
        // no path component starting with an a
        spec.filesNotMatching("**/a*/**", Actions.doNothing())
        assertEquals(1, spec.allCopyActions.size())
        assertThat(spec.allCopyActions[0], instanceOf(MatchingCopyAction))

        Spec<RelativePath> matchSpec = spec.allCopyActions[0].matchSpec
        assertTrue(matchSpec.isSatisfiedBy(RelativePath.parse(true, 'root/folder1/folder2')))
        assertTrue(matchSpec.isSatisfiedBy(RelativePath.parse(true, 'modules/project1')))
        assertFalse(matchSpec.isSatisfiedBy(RelativePath.parse(true, 'archives/folder/file')))
        assertFalse(matchSpec.isSatisfiedBy(RelativePath.parse(true, 'root/archives/file')))
        assertFalse(matchSpec.isSatisfiedBy(RelativePath.parse(true, 'root/folder/abc')))
    }

    @Test
    void "Add spec as first child"() {
        DefaultCopySpec child1 = spec.addFirst()
        assert child1 != null
        assert spec.childSpecs.size() == 1
        assert spec.childSpecs[0] == child1
        DefaultCopySpec child2 = spec.addFirst()
        assert child2 != null
        assert spec.childSpecs.size() == 2
        assert spec.childSpecs[0] == child2
        assert spec.childSpecs[1] == child1
    }

    @Test
    void "Add spec in between two child specs if given child exists"() {
        DefaultCopySpec child1 = spec.addChild()
        DefaultCopySpec child2 = spec.addChild()
        assert child1 != null
        assert child2 != null
        assert spec.childSpecs.size() == 2
        assert spec.childSpecs[0] == child1
        assert spec.childSpecs[1] == child2
        DefaultCopySpec child3 = spec.addChildBeforeSpec(child2)
        assert child3 != null
        assert spec.childSpecs.size() == 3
        assert spec.childSpecs[0] == child1
        assert spec.childSpecs[1] == child3
        assert spec.childSpecs[2] == child2
    }

    @Test
    void "Add spec in between two child specs if given child does not exist"() {
        DefaultCopySpec child1 = spec.addChild()
        DefaultCopySpec child2 = spec.addChild()
        assert child1 != null
        assert child2 != null
        assert spec.childSpecs.size() == 2
        assert spec.childSpecs[0] == child1
        assert spec.childSpecs[1] == child2
        DefaultCopySpec unknownChild = new DefaultCopySpec(fileResolver, instantiator)
        DefaultCopySpec child3 = spec.addChildBeforeSpec(unknownChild)
        assert child3 != null
        assert spec.childSpecs.size() == 3
        assert spec.childSpecs[0] == child1
        assert spec.childSpecs[1] == child2
        assert spec.childSpecs[2] == child3
    }

    @Test
    void "Add spec in between two child specs if given child is null"() {
        DefaultCopySpec child1 = spec.addChild()
        DefaultCopySpec child2 = spec.addChild()
        assert child1 != null
        assert child2 != null
        assert spec.childSpecs.size() == 2
        assert spec.childSpecs[0] == child1
        assert spec.childSpecs[1] == child2
        DefaultCopySpec child3 = spec.addChildBeforeSpec(null)
        assert child3 != null
        assert spec.childSpecs.size() == 3
        assert spec.childSpecs[0] == child1
        assert spec.childSpecs[1] == child2
        assert spec.childSpecs[2] == child3
    }

    DefaultCopySpec unpackWrapper(CopySpec copySpec) {
        (copySpec as CopySpecWrapper).delegate as DefaultCopySpec
    }
}

