/*
 * Copyright 2007, 2008 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.tasks

import org.gradle.api.provider.Provider
import org.gradle.internal.typeconversion.UnsupportedNotationException
import spock.lang.Specification

import java.util.concurrent.Callable
import org.gradle.api.Buildable
import org.gradle.api.Task
import org.gradle.api.tasks.TaskDependency
import static org.gradle.util.WrapUtil.toSet
import org.gradle.api.GradleException

public class DefaultTaskDependencyTest extends Specification {
    private final TaskResolver resolver = Mock(TaskResolver.class)
    private final DefaultTaskDependency dependency = new DefaultTaskDependency(resolver)
    private Task task
    private Task otherTask

    def setup() {
        task = Mock(Task)
        otherTask = Mock(Task)
    }

    def "has no dependencies by default"() {
        expect:
        dependency.getDependencies(task) == emptySet()
    }

    def "can depend on char sequence"() {
        def input = new StringBuilder("other")

        given:
        1 * resolver.resolveTask("other") >> otherTask

        when:
        dependency.add(input)

        then:
        dependency.getDependencies(task) == toSet(otherTask)
    }

    def "can depend on a task instance"() {
        when:
        dependency.add(otherTask)

        then:
        dependency.getDependencies(task)== toSet(otherTask)
    }

    def "can depend on a task dependency"() {
        def otherDependency = Mock(TaskDependency)

        given:
        1 * otherDependency.getDependencies(task) >> toSet(otherTask)

        when:
        dependency.add(otherDependency);

        then:
        dependency.getDependencies(task) == toSet(otherTask)
    }

    def "can depend on a closure"() {
        when:
        dependency.add({Task suppliedTask ->
            assert suppliedTask == task
            otherTask
        })

        then:
        dependency.getDependencies(task) == toSet(otherTask)
    }

    def "can depend on a closure that returns null"() {
        when:
        dependency.add({ null })

        then:
        dependency.getDependencies(task) == emptySet()
    }

    def "can depend on a buildable"() {
        Buildable buildable = Mock(Buildable)
        TaskDependency otherDependency = Mock(TaskDependency)

        given:
        1 * buildable.getBuildDependencies() >> otherDependency
        1 * otherDependency.getDependencies(task) >> toSet(otherTask)

        when:
        dependency.add(buildable)

        then:
        dependency.getDependencies(task) == toSet(otherTask)
    }

    def "can depend on an iterable"() {
        List tasks = [otherTask]
        Iterable iterable = { tasks.iterator() } as Iterable

        when:
        dependency.add(iterable)

        then:
        dependency.getDependencies(task) == toSet(otherTask)
    }

    def "can depend on a callable"() {
        Callable callable = Mock(Callable)

        given:
        1 * callable.call() >> otherTask

        when:
        dependency.add(callable)

        then:
        dependency.getDependencies(task) == toSet(otherTask)
    }

    def "can depend on a callable that returns null"() {
        Callable callable = Mock(Callable)

        given:
        1 * callable.call() >> null

        when:
        dependency.add(callable)

        then:
        dependency.getDependencies(task) == emptySet()
    }

    def "can depend on a task dependency container"() {
        TaskDependencyContainer dep = Mock(TaskDependencyContainer)

        given:
        1 * dep.visitDependencies(_) >> { args -> args[0].add(otherTask) }

        when:
        dependency.add(dep)

        then:
        dependency.getDependencies(task) == toSet(otherTask)
    }

    def "fails for other types"() {
        when:
        dependency.add(12)
        dependency.getDependencies(task)

        then:
        def e = thrown(GradleException)
        e.cause instanceof UnsupportedNotationException
        e.cause.message.startsWith "Cannot convert 12 to a task."
    }

    def "fails for char sequence when no resolver provided"() {
        DefaultTaskDependency dependency = new DefaultTaskDependency()
        StringBuffer dep = new StringBuffer("task")

        when:
        dependency.add(dep)
        dependency.getDependencies(task)

        then:
        def e = thrown(GradleException)
        e.cause instanceof UnsupportedNotationException
        e.cause.message.startsWith "Cannot convert $dep to a task."
    }

    def "produces sensible error when a provider is of the wrong type"() {
        Provider provider = Mock(Provider)

        when:
        dependency.add(provider)
        dependency.getDependencies(task)

        then:
        def e = thrown(GradleException)
        e.cause instanceof UnsupportedNotationException
        e.cause.message.startsWith "Cannot convert Provider $provider to a task."
    }

    def "flattens collections"() {
        when:
        dependency.add(toSet(otherTask))

        then:
        dependency.getDependencies(task) == toSet(otherTask)
    }

    def "flattens maps"() {
        when:
        dependency.add([key: otherTask])

        then:
        dependency.getDependencies(task) == toSet(otherTask)
    }

    def "flattens arrays"() {
        when:
        dependency.add([[otherTask] as Task[]])

        then:
        dependency.getDependencies(task) == toSet(otherTask)
    }

    def "can nest iterables and maps and closures and callables"() {
        Map nestedMap = [task: otherTask]
        Iterable nestedCollection = [nestedMap]
        Callable nestedCallable = {nestedCollection} as Callable
        Closure nestedClosure = {nestedCallable}
        List collection = [nestedClosure]
        Closure closure = {collection}
        Object[] array = [closure] as Object[]
        Map map = [key: array]
        Callable callable = {map} as Callable

        when:
        dependency.add(callable)

        then:
        dependency.getDependencies(task) == toSet(otherTask)
    }

    static emptySet() {
        return [] as Set
    }
}
