/*
 * 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.
 */
package org.apache.brooklyn.util.core.task;

import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import org.apache.brooklyn.api.mgmt.ExecutionManager;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.util.time.Duration;

import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.util.concurrent.ExecutionList;
import com.google.common.util.concurrent.ListenableFuture;

/**
 * All tasks being passed to the {@link ExecutionManager} should implement this.
 * Users are strongly encouraged to use (or extend) {@link BasicTask}, rather than
 * implementing a task from scratch.
 * 
 * The methods on this interface will change in subsequent releases. Because this is
 * marked as beta, the normal deprecation policy for these methods does not apply.
 * 
 * @author aled
 */
@Beta
public interface TaskInternal<T> extends Task<T> {
    
    /** sets the internal future object used to record the association to a job submitted to an {@link ExecutorService} */
    void initInternalFuture(ListenableFuture<T> result);

    /** returns the underlying future where this task's results will come in; see {@link #initInternalFuture(ListenableFuture)} */
    Future<T> getInternalFuture();
    
    /** if the job is queued for submission (e.g. by another task) it can indicate that fact (and time) here;
     * note tasks can (and often are) submitted without any queueing, in which case this value may be -1 */
    long getQueuedTimeUtc();
    
    boolean isQueuedOrSubmitted();
    boolean isQueuedAndNotSubmitted();
    boolean isQueued();

    /** marks the task as queued for execution */
    void markQueued();

    boolean cancel();
    
    boolean blockUntilStarted(Duration timeout);

    /** allows a task user to specify why a task is blocked; for use immediately before a blocking/wait,
     * and typically cleared immediately afterwards; referenced by management api to inspect a task
     * which is blocking
     * <p>
     * returns previous details, in case caller wishes to recall and restore it (e.g. if it is doing a sub-blocking)
     */
    String setBlockingDetails(String blockingDetails);

    /** as {@link #setBlockingDetails(String)} but records a task which is blocking,
     * for use e.g. in a gui to navigate to the current active subtask
     * <p>
     * returns previous blocking task, in case caller wishes to recall and restore it
     */
    Task<?> setBlockingTask(Task<?> blockingTask);
    
    void resetBlockingDetails();
    
    void resetBlockingTask();

    /** returns a textual message giving details while the task is blocked */
    String getBlockingDetails();
    
    /** returns a task that this task is blocked on */
    Task<?> getBlockingTask();
    
    void setExtraStatusText(Object extraStatus);
    
    Object getExtraStatusText();

    /** On task completion (or cancellation) runs the listeners which have been registered using 
     * {@link #addListener(Runnable, java.util.concurrent.Executor)}. */
    void runListeners();

    void setEndTimeUtc(long val);

    void setThread(Thread thread);

    Callable<T> getJob();
    
    void setJob(Callable<T> job);

    ExecutionList getListeners();

    void setSubmitTimeUtc(long currentTimeMillis);

    void setSubmittedByTask(Task<?> task);
    
    Set<Object> getMutableTags();

    void setStartTimeUtc(long currentTimeMillis);

    void applyTagModifier(Function<Set<Object>,Void> modifier);
    
    /** if a task is a proxy for another one (used mainly for internal tasks),
     * this returns the "real" task represented by this one */
    Task<?> getProxyTarget();

    /** clearer semantics around cancellation; may be promoted to {@link Task} if we  */
    @Beta
    public boolean cancel(TaskCancellationMode mode);
    
    @Beta
    public static class TaskCancellationMode {
        public static final TaskCancellationMode DO_NOT_INTERRUPT = new TaskCancellationMode(false, false, false);
        public static final TaskCancellationMode INTERRUPT_TASK_BUT_NOT_SUBMITTED_TASKS = new TaskCancellationMode(true, false, false);
        public static final TaskCancellationMode INTERRUPT_TASK_AND_DEPENDENT_SUBMITTED_TASKS = new TaskCancellationMode(true, true, false);
        public static final TaskCancellationMode INTERRUPT_TASK_AND_ALL_SUBMITTED_TASKS = new TaskCancellationMode(true, true, true);
        
        private final boolean allowedToInterruptTask, 
            allowedToInterruptDependentSubmittedTasks, 
            allowedToInterruptAllSubmittedTasks;
        
        private TaskCancellationMode(boolean mayInterruptIfRunning, boolean interruptSubmittedTransients, boolean interruptAllSubmitted) {
            this.allowedToInterruptTask = mayInterruptIfRunning;
            this.allowedToInterruptDependentSubmittedTasks = interruptSubmittedTransients;
            this.allowedToInterruptAllSubmittedTasks = interruptAllSubmitted;
            
            // if dependent isn't set, then all shouldn't be set
            assert !(this.allowedToInterruptAllSubmittedTasks && !this.allowedToInterruptDependentSubmittedTasks);
        }
        
        public boolean isAllowedToInterruptTask() { return allowedToInterruptTask; }
        /** Implementation-dependent what "dependent" means in this context, 
         * e.g. may be linked to a "transient" tag (that's what Brooklyn does) */ 
        public boolean isAllowedToInterruptDependentSubmittedTasks() { return allowedToInterruptDependentSubmittedTasks; }
        public boolean isAllowedToInterruptAllSubmittedTasks() { return allowedToInterruptAllSubmittedTasks; }
        
        @Override
        public String toString() {
            return Objects.toStringHelper(this).add("interruptTask", allowedToInterruptTask)
                .add("interruptDependentSubmitted", allowedToInterruptDependentSubmittedTasks)
                .add("interruptAllSubmitted", allowedToInterruptAllSubmittedTasks)
                .toString();
        }
    }
    
}
