/*
 * 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.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.util.JUnit4GroovyMockery
import org.gradle.util.TemporaryFolder
import org.gradle.util.TestFile
import org.jmock.integration.junit4.JMock
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 CopySpecImplTest {

    @Rule public TemporaryFolder testDir = new TemporaryFolder();
    private TestFile baseFile = testDir.dir
    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
    private final FileResolver fileResolver = context.mock(FileResolver);
    private final CopySpecImpl spec = new CopySpecImpl(fileResolver)

    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() {
        CopySpecImpl child = spec.from('source') {
        }

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

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

        assertThat(child, not(sameInstance(spec)))
        assertEquals(['source1', 'source2'], 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() {
        CopySpecImpl other1 = new CopySpecImpl(fileResolver)
        CopySpecImpl other2 = new CopySpecImpl(fileResolver)

        spec.with other1, other2
        assertTrue(spec.sourcePaths.empty)
        assertThat(spec.childSpecs.size(), equalTo(2))
    }
    
    @Test public void testWithSpecSource() {
        CopyActionImpl source = new CopyActionImpl(fileResolver, null)

        spec.with source
        assertTrue(spec.sourcePaths.empty)
        assertThat(spec.childSpecs.size(), equalTo(1))
    }

    @Test public void testWithSpecInheritsDestinationPathFromParent() {
        CopySpecImpl other = new CopySpecImpl(fileResolver)
        other.into 'other'

        spec.into 'spec'
        spec.with other

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

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

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

    @Test public void testGetAllSpecsReturnsBreadthwiseTraverseOfSpecs() {
        CopySpecImpl child = spec.into('somedir') { }
        CopySpecImpl grandchild = child.into('somedir') { }
        CopySpecImpl child2 = spec.into('somedir') { }

        assertThat(spec.allSpecs, equalTo([spec, child, grandchild, child2]))
    }

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

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

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

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

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

        child.into 'child'

        CopySpecImpl grandchild = child.from('somedir') { }
        assertThat(grandchild.destPath, equalTo(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() {
        CopySpecImpl 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 = 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() {
        CopySpecImpl 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 = 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 testChildUsesCaseSensitiveFlagFromParentAsDefault() {
        CopySpecImpl child = spec.from('dir') {}
        assertTrue(child.caseSensitive)
        assertTrue(child.patternSet.caseSensitive)

        spec.caseSensitive = false
        assertFalse(child.caseSensitive)
        assertFalse(child.patternSet.caseSensitive)

        child.caseSensitive = true
        assertTrue(child.caseSensitive)
        assertTrue(child.patternSet.caseSensitive)
    }

    @Test public void testChildUsesIncludeEmptyDirsFlagFromParentAsDefault() {
        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
        CopySpecImpl childSpec = spec.from('src') {
            eachFile childAction
        }

        assertThat(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

        CopySpecImpl child = spec.from('src') { }
        org.junit.Assert.assertEquals(0x1, child.fileMode)
        org.junit.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())
    }
}

