/*
 * 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.tasks

import com.google.common.collect.Lists
import org.gradle.api.Action
import org.gradle.api.InvalidUserDataException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.internal.AbstractTask
import org.gradle.api.internal.DependencyInjectingInstantiator
import org.gradle.api.internal.TaskInternal
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.internal.project.taskfactory.ITaskFactory
import org.gradle.api.internal.tasks.TaskExecuter
import org.gradle.api.internal.tasks.TaskExecutionContext
import org.gradle.api.internal.tasks.TaskStateInternal
import org.gradle.api.internal.tasks.execution.DefaultTaskExecutionContext
import org.gradle.api.specs.Spec
import org.gradle.internal.Actions
import org.gradle.internal.reflect.Instantiator
import org.gradle.internal.service.DefaultServiceRegistry
import org.gradle.test.fixtures.AbstractProjectBuilderSpec
import org.gradle.util.GUtil
import org.gradle.util.TestUtil

import java.util.concurrent.atomic.AtomicBoolean

import static org.junit.Assert.*

public abstract class AbstractTaskTest extends AbstractProjectBuilderSpec {
    public static final String TEST_TASK_NAME = "testTask"

    protected DefaultServiceRegistry serviceRegistry = new DefaultServiceRegistry()

    protected Instantiator instantiator = new DependencyInjectingInstantiator(serviceRegistry, new DependencyInjectingInstantiator.ConstructorCache())

    public abstract AbstractTask getTask()

    public <T extends AbstractTask> T createTask(Class<T> type) {
        return createTask(type, project, TEST_TASK_NAME)
    }

    public Task createTask(ProjectInternal project, String name) {
        return createTask(getTask().getClass(), project, name)
    }

    public <T extends AbstractTask> T createTask(Class<T> type, ProjectInternal project, String name) {
        Task task = project.getServices().get(ITaskFactory.class).createTask(GUtil.map(Task.TASK_TYPE, type, Task.TASK_NAME, name))
        assertTrue(type.isAssignableFrom(task.getClass()))
        return type.cast(task)
    }

    public void execute(TaskInternal task) {
        project.getServices().get(TaskExecuter.class).execute(task, task.getState(), new DefaultTaskExecutionContext())
        task.getState().rethrowFailure()
    }

    def setup() {
        serviceRegistry.add(Instantiator.class, instantiator)
    }

    def "test Task"() {
        expect:
        getTask().isEnabled()
        TEST_TASK_NAME.equals(getTask().getName())
        getTask().getDescription() == null
        project.is(getTask().getProject())
        getTask().getStandardOutputCapture() != null
        getTask().getInputs() != null
        getTask().getOutputs() != null
        getTask().getOnlyIf() != null
        getTask().getOnlyIf().isSatisfiedBy(getTask())
    }

    def "test Path"() {
        given:
        def childProject = TestUtil.createChildProject(project, "child")
        childProject.getProjectDir().mkdirs()
        def childchildProject = TestUtil.createChildProject(childProject, "childchild")
        childchildProject.getProjectDir().mkdirs()

        and:
        def task = createTask(project, TEST_TASK_NAME)
        def childTask = createTask(childProject, TEST_TASK_NAME)
        def childChildTask = createTask(childchildProject, TEST_TASK_NAME)

        expect:
        task.getPath() == Project.PATH_SEPARATOR + TEST_TASK_NAME
        childTask.getPath() == Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + TEST_TASK_NAME
        childChildTask.getPath() == Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + "childchild" + Project.PATH_SEPARATOR + TEST_TASK_NAME
    }

    def "test toString"() {
        expect:
        "task '" + getTask().getPath() + "'" == getTask().toString()
    }

    def "test deleteAllActions"() {
        given:
        Action<? super Task> action1 = Actions.<Task>doNothing()
        Action<? super Task> action2 = Actions.<Task>doNothing()
        getTask().doLast(action1)
        getTask().doLast(action2)

        expect:
        getTask().is(getTask().deleteAllActions())
        getTask().getActions().isEmpty()
    }

    def "test setActions"() {
        given:
        getTask().deleteAllActions()

        when:
        getTask().getActions().add(Actions.doNothing())
        getTask().getActions().add(Actions.doNothing())

        then:
        2 == getTask().getActions().size()

        when:
        List<Action<? super Task>> actions = Lists.newArrayList()
        actions.add(Actions.doNothing())
        getTask().setActions(actions)

        then:
        1 == getTask().getActions().size()
    }

    def "addAction with null throws"() {
        when:
        getTask().doLast((Closure) null)

        then:
        InvalidUserDataException e = thrown()
        e.message.equals("Action must not be null!")
    }

    def "execute delegates to  TaskExecuter"() {
        given:
        final AbstractTask task = getTask()

        final TaskExecuter executer = Mock(TaskExecuter.class)
        task.setExecuter(executer)

        when:
        task.execute()

        then:
        1 * executer.execute(task, _ as TaskStateInternal, _ as TaskExecutionContext)
    }

    def "test getDescription"() {
        given:
        def testDescription = "testDescription"
        getTask().setDescription(testDescription)

        expect:
        testDescription == getTask().getDescription()
    }

    def "can specify onlyIf predicate using closure"() {
        given:
        def task = getTask()

        expect:
        task.getOnlyIf().isSatisfiedBy(task)

        when:
        task.onlyIf({ false })

        then:
        !task.getOnlyIf().isSatisfiedBy(task)
    }

    def "can specify onlyIf predicate using spec"() {
        given:
        final task = getTask()
        final Spec<Task> spec = Mock(Spec.class)
        spec.isSatisfiedBy(task) >> false

        expect:
        task.getOnlyIf().isSatisfiedBy(task)

        when:
        task.onlyIf(spec)

        then:
        !task.getOnlyIf().isSatisfiedBy(task)
    }

    def "onlyIf predicate is true when task is enabled and all predicates are true"() {
        given:
        final AtomicBoolean condition1 = new AtomicBoolean(true)
        final AtomicBoolean condition2 = new AtomicBoolean(true)

        def task = getTask()
        task.onlyIf(new Spec<Task>() {
            public boolean isSatisfiedBy(Task element) {
                return condition1.get()
            }
        })

        task.onlyIf(new Spec<Task>() {
            public boolean isSatisfiedBy(Task element) {
                return condition2.get()
            }
        })

        expect:
        task.getOnlyIf().isSatisfiedBy(task)

        when:
        task.setEnabled(false)

        then:
        !task.getOnlyIf().isSatisfiedBy(task)

        when:
        task.setEnabled(true)
        condition1.set(false)

        then:
        !task.getOnlyIf().isSatisfiedBy(task)

        when:
        condition1.set(true)
        condition2.set(false)

        then:
        !task.getOnlyIf().isSatisfiedBy(task)

        when:
        condition2.set(true)

        then:
        task.getOnlyIf().isSatisfiedBy(task)
    }

    def "can replace onlyIf spec"() {
        given:
        final AtomicBoolean condition1 = new AtomicBoolean(true)
        final task = getTask()
        final Spec spec = Mock(Spec.class)
        task.onlyIf(spec)
        task.setOnlyIf(new Spec<Task>() {
            public boolean isSatisfiedBy(Task element) {
                return condition1.get()
            }
        })

        expect:
        task.getOnlyIf().isSatisfiedBy(task)

        when:
        task.setEnabled(false)

        then:
        !task.getOnlyIf().isSatisfiedBy(task)

        when:
        task.setEnabled(true)
        condition1.set(false)

        then:
        !task.getOnlyIf().isSatisfiedBy(task)

        when:
        condition1.set(true)

        then:
        task.getOnlyIf().isSatisfiedBy(task)
    }
}
