/*
 * Copyright 2012 LinkedIn, Inc
 *
 * 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 com.linkedin.parseq;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;


/**
 * This class provides a set of factory methods for create common
 * {@link Task}s.
 *
 * @author Chris Pettitt (cpettitt@linkedin.com)
 * @author Chi Chan (ckchan@linkedin.com)
 */
public class Tasks {
  private Tasks() {
  }

  /**
   * Creates a new {@link Task} that have a value of type Void. Because the
   * returned task has no value, it is typically used to produce side-effects.
   *
   * @deprecated  As of 2.0.0, replaced by {@link Task#action(String, com.linkedin.parseq.function.Action) Task.action}
   * @param name a name that describes the action
   * @param runnable the action that will be executed when the task is run
   * @return the new task
   * @see Task#action(String, com.linkedin.parseq.function.Action) Task.action
   */
  @Deprecated
  public static Task<Void> action(final String name, final Runnable runnable) {
    return Task.action(name, runnable::run);
  }

  /**
   * Creates a new {@link Task} that's value will be set to the value returned
   * from the supplied callable. This task is useful when doing basic
   * computation that does not require asynchrony. It is not appropriate for
   * long running or blocking tasks.
   *
   * @deprecated  As of 2.0.0, replaced by {@link Task#callable(String, Callable) Task.callable}
   * @param name a name that describes the action
   * @param callable the callable to execute when this task is run
   * @param <T> the type of the return value for this task
   * @return the new task
   * @see Task#callable(String, Callable) Task.callable
   */
  @Deprecated
  public static <T> Task<T> callable(final String name, final Callable<? extends T> callable) {
    return Task.callable(name, () -> callable.call());
  }

  /**
   * Creates a new {@link Task} that's value will be set to the value returned
   * from the supplied callable. This task is useful when doing basic
   * computation that does not require asynchrony. It is not appropriate for
   * long running or blocking tasks.
   *
   * @deprecated  As of 2.0.0, replaced by {@link Task#callable(String, Callable) Task.callable}
   * @param name a name that describes the action
   * @param callable the callable to execute when this task is run
   * @param <T> the type of the return value for this task
   * @return the new task
   * @see Task#callable(String, Callable) Task.callable
   */
  @Deprecated
  public static <T> Task<T> callable(final String name, final ThrowableCallable<? extends T> callable) {
    return new CallableTask<T>(name, callable);
  }

  /**
   * Creates a new task that will run the given tasks sequentially (e.g.
   * task1 will be finished before task2 starts). The value of the seq task will
   * be the result of the last task in the sequence.
   *
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<T> task2) {
    return vaseq(task1, task2);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<T> task3) {
    return vaseq(task1, task2, task3);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<T> task4) {
    return vaseq(task1, task2, task3, task4);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<?> task4, Task<T> task5) {
    return vaseq(task1, task2, task3, task4, task5);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<?> task4, Task<?> task5,
      Task<T> task6) {
    return vaseq(task1, task2, task3, task4, task5, task6);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<?> task4, Task<?> task5,
      Task<?> task6, Task<T> task7) {
    return vaseq(task1, task2, task3, task4, task5, task6, task7);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<?> task4, Task<?> task5,
      Task<?> task6, Task<?> task7, Task<T> task8) {
    return vaseq(task1, task2, task3, task4, task5, task6, task7, task8);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<?> task4, Task<?> task5,
      Task<?> task6, Task<?> task7, Task<?> task8, Task<T> task9) {
    return vaseq(task1, task2, task3, task4, task5, task6, task7, task8, task9);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<?> task4, Task<?> task5,
      Task<?> task6, Task<?> task7, Task<?> task8, Task<?> task9, Task<T> task10) {
    return vaseq(task1, task2, task3, task4, task5, task6, task7, task8, task9, task10);
  }

  /**
   * Creates a new task that, when run, will run each of the supplied tasks in
   * order. The value of this new task is the value of the last task in the
   * sequence. <strong>This method is not type-safe</strong>.
   *
   * @param tasks the tasks to run sequentially
   * @param <T> the result value for the sequence of tasks
   * @return a new task that will run the given tasks sequentially
   */
  @SuppressWarnings("deprecation")
  public static <T> Task<T> seq(final Iterable<? extends Task<?>> tasks) {
    return new SeqTask<T>("seq", tasks);
  }

  /**
   * Creates a task that will run another task as a side effect once the primary task
   * completes successfully. The side effect will not be run if the primary task fails or
   * is canceled. The entire task is marked done once the base task completes, even if
   * the side effect has not been run.
   *
   * @param parent the primary task.
   * @param sideEffect the side effect of the primary task.
   * @param <T> the result value of the parent task, and the resulting task.
   * @return a new task that will be done once parent completes, but has the given side effect.
   * @deprecated  As of 2.0.0, replaced by {@link Task#withSideEffect(String, com.linkedin.parseq.function.Function1) Task.withSideEffect}
   * @see Task#withSideEffect(String, com.linkedin.parseq.function.Function1) Task.withSideEffect
   */
  @Deprecated
  public static <T> Task<T> withSideEffect(final Task<T> parent, final Task<?> sideEffect) {
    return parent.withSideEffect(t -> sideEffect);
  }

  /**
   * Creates a new task that will run the given tasks in parallel (e.g. task1
   * can be executed at the same time as task2). When all tasks complete
   * successfully, you can use {@link com.linkedin.parseq.ParTask#get()} to
   * get a list of the results. If at least one task failed, then this task will
   * also be marked as failed. Use {@link com.linkedin.parseq.ParTask#getTasks()}
   * or {@link com.linkedin.parseq.ParTask#getSuccessful()} to get results in
   * this case.
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2) {
    return vapar(task1, task2);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3) {
    return vapar(task1, task2, task3);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4) {
    return vapar(task1, task2, task3, task4);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4, Task<? extends T> task5) {
    return vapar(task1, task2, task3, task4, task5);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4, Task<? extends T> task5, Task<? extends T> task6) {
    return vapar(task1, task2, task3, task4, task5, task6);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4, Task<? extends T> task5, Task<? extends T> task6, Task<? extends T> task7) {
    return vapar(task1, task2, task3, task4, task5, task6, task7);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4, Task<? extends T> task5, Task<? extends T> task6, Task<? extends T> task7,
      Task<? extends T> task8) {
    return vapar(task1, task2, task3, task4, task5, task6, task7, task8);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4, Task<? extends T> task5, Task<? extends T> task6, Task<? extends T> task7,
      Task<? extends T> task8, Task<? extends T> task9) {
    return vapar(task1, task2, task3, task4, task5, task6, task7, task8, task9);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4, Task<? extends T> task5, Task<? extends T> task6, Task<? extends T> task7,
      Task<? extends T> task8, Task<? extends T> task9, Task<? extends T> task10) {
    return vapar(task1, task2, task3, task4, task5, task6, task7, task8, task9, task10);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Iterable) Task.par}
   * as a safer alternative that does not throw an IllegalArgumentException for
   * an empty Iterable of tasks.
   * @see Task#par(Iterable) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(final Iterable<? extends Task<? extends T>> tasks) {
    return new ParTaskImpl<T>("par", tasks);
  }

  /**
   * Creates a new task that wraps the given task. If the given task finishes
   * before the timeout occurs then this task takes on the value of the task.
   * If the task does not complete in the given time then this task will
   * have a TimeoutException. The wrapped task may be cancelled when a timeout
   * occurs.
   *
   * @param time the time to wait before timing out
   * @param unit the units for the time
   * @param task the task to wrap
   * @param <T> the value type for the task
   * @return the new timeout task
   * @deprecated  As of 2.0.0, replaced by {@link Task#withTimeout(long, TimeUnit) Task.withTimeout}.
   * @see Task#withTimeout(long, TimeUnit) Task.withTimeout
   */
  @Deprecated
  public static <T> Task<T> timeoutWithError(final long time, final TimeUnit unit, final Task<T> task) {
    return task.withTimeout(time, unit);
  }

  /**
   * This method is similar to {@link #timeoutWithError(long, java.util.concurrent.TimeUnit, Task)},
   * but it also takes a name that will be set on the returned task.
   *
   * @param name the name of this task
   * @param time the time to wait before timing out
   * @param unit the units for the time
   * @param task the task to wrap
   * @param <T> the value type for the task
   * @return the new timeout task
   * @deprecated  As of 2.0.0, replaced by {@link Task#withTimeout(long, TimeUnit) Task.withTimeout}.
   * @see Task#withTimeout(long, TimeUnit) Task.withTimeout
   */
  @Deprecated
  public static <T> Task<T> timeoutWithError(final String name, final long time, final TimeUnit unit,
      final Task<T> task) {
    return task.withTimeout(time, unit);
  }

  /**
   * Creates a new task that will run the given array of tasks sequentially
   * (e.g. tasks[0] will be finished before tasks[1] is run). <strong>This
   * method is not type-safe</strong> - prefer one of the {@code seq} options
   * when possible. The value of this new task is the value of the last task in
   * the sequence.
   *
   * @param tasks the tasks to run sequentially
   * @param <T> the result value for the sequence of tasks
   * @return a new task that will run the given tasks sequentially
   */
  @SuppressWarnings("deprecation")
  private static <T> Task<T> vaseq(final Task<?>... tasks) {
    return new SeqTask<T>("seq", Arrays.asList(tasks));
  }

  /**
   * Creates a new task that will run each of the supplied tasks in parallel (e.g.
   * tasks[0] can be run at the same time as task2). <strong>This method is not
   * type-safe</strong> - prefer one of the {@code par} options when possible.
   * <p>
   * When all tasks complete successfully, you can use
   * {@link com.linkedin.parseq.ParTask#get()} to get a list of the results. If
   * at least one task failed, then this task will also be marked as failed. Use
   * {@link com.linkedin.parseq.ParTask#getTasks()} or
   * {@link com.linkedin.parseq.ParTask#getSuccessful()} to get results in this
   * case.
   *
   * @param tasks the tasks to run in parallel
   * @return a new task that will run the given tasks in parallel
   */
  private static <T> ParTask<T> vapar(final Task<?>... tasks) {
    final List<Task<T>> taskList = new ArrayList<Task<T>>(tasks.length);
    for (Task<?> task : tasks) {
      @SuppressWarnings("unchecked")
      final Task<T> typedTask = (Task<T>) task;
      taskList.add(typedTask);
    }
    return par(taskList);
  }
}
