/**
 * Copyright (C) 2015 The Authors.
 */
package dk.itu.kelvin.thread;

// General utilities
import java.util.Map;
import java.util.Queue;

// Concurrency utilities
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentHashMap;

// JavaFX concurrency utilities
import javafx.concurrent.Task;

/**
 * Task queue class.
 *
 * @version 1.0.0
 */
public final class TaskQueue {
  static {
    // Initialize the map containing the task groups. We use a concurrent hash
    // map since it will potentially be modified from multiple threads at the
    // same time.
    //
    // If the task queue is never used, this statement will never run thus
    // ensuring that we don't allocate memory to a map which is never used.
    TaskQueue.groups = new ConcurrentHashMap<>();
  }

  /**
   * The map containing the different groups mapped to their task queues.
   */
  private static Map<String, Queue<FunctionalTask>> groups;

  /**
   * Don't allow instantiation of the class.
   *
   * Since the class only contains static fields and methods, we never want to
   * instantiate the class. We therefore define a private constructor so that
   * noone can create instances of the class other than the class itself.
   *
   * NB: This does not make the class a singleton. In fact, there never exists
   * an instance of the class since not even the class instantiates itself.
   */
  private TaskQueue() {
    super();
  }

  /**
   * Register a new task belonging to the specified group.
   *
   * @param group The group that the task belongs to.
   * @param task  The task to perform.
   */
  public static void register(final String group, final FunctionalTask task) {
    if (group == null || task == null) {
      return;
    }

    // Initialize the group if it hasn't been initialized yet.
    if (!TaskQueue.groups.containsKey(group)) {
      // A concurrent linked queue is used as the task queue will potentially be
      // modified from several threads at the same time. This will for example
      // be the case if a new task is added before `start()` has finished
      // running all the tasks in the queue.
      TaskQueue.groups.put(group, new ConcurrentLinkedQueue<FunctionalTask>());
    }

    TaskQueue.groups.get(group).add(task);
  }

  /**
   * Run the specified task on a separate thread.
   *
   * @param task The task to perform.
   */
  public static void run(final FunctionalTask task) {
    // Encapsulate the FunctionalTask in a JavaFX Task.
    Task<Void> runner = new Task<Void>() {
      @Override
      public Void call() {
        // Run the task.
        task.run();

        // We're done here.
        return null;
      }
    };

    // Start the task on a separate thread.
    new Thread(runner).start();
  }

  /**
   * Start all tasks belonging to the specified group.
   *
   * @param group The group whose tasks to run.
   */
  public static void start(final String group) {
    if (group == null || !TaskQueue.groups.containsKey(group)) {
      return;
    }

    // Boot a new thread whose only purpose is to dequeue and run tasks from the
    // task queue. If there are a lot of tasks to run, this ensures that the
    // main thread isn't blocked.
    TaskQueue.run(() -> {
      // Get the task queue for the specified group.
      Queue<FunctionalTask> tasks = TaskQueue.groups.get(group);

      // While there are tasks left in the queue, get the next one and run it.
      while (!tasks.isEmpty()) {
        TaskQueue.run(tasks.poll());
      }

      // Remove the group from the map of groups. No loitering!
      TaskQueue.groups.remove(group);
    });
  }

  /**
   * A functional interface for defining asynchronous tasks.
   */
  @FunctionalInterface
  public interface FunctionalTask {
    /**
     * Run the task.
     */
    void run();
  }
}

@@ -1585,17 +1585,17 @@
 ram task
- 
+s
  The tas
@@ -1587,32 +1587,33 @@
 m tasks The task
+s
  to perform.%0A   
@@ -1645,16 +1645,21 @@
 egister(
+%0A    
 final St
@@ -1669,16 +1669,20 @@
 g group,
+%0A   
  final F
@@ -1686,37 +1686,44 @@
 l FunctionalTask
+...
  task
+s%0A  
 ) %7B%0A    if (grou
@@ -1739,24 +1739,25 @@
  %7C%7C task
+s
  == null
 ) %7B%0A    
@@ -1748,16 +1748,37 @@
  == null
+ %7C%7C tasks.length == 0
 ) %7B%0A    
@@ -2279,35 +2279,118 @@
     
-TaskQueue.groups.get(group)
+Queue%3CFunctionalTask%3E queue = TaskQueue.groups.get(group);%0A%0A    for (FunctionalTask task: tasks) %7B%0A      queue
 .add
@@ -2397,16 +2397,22 @@
 (task);%0A
+    %7D%0A
   %7D%0A%0A  /
@@ -3455,21 +3455,21 @@
 alTask%3E 
-tasks
+queue
  = TaskQ
@@ -3586,21 +3586,21 @@
 while (!
-tasks
+queue
 .isEmpty
@@ -3627,21 +3627,21 @@
 eue.run(
-tasks
+queue
 .poll())

Allow registering multiple tasks at once.