namespace tf {

/** @page StaticTasking Static Tasking

This chapter demonstrates how to create a static task dependency graph. Static tasking captures the static parallel structure of a decomposition and is defined only by the program itself.
It has a flat task hierarchy and cannot spawn new tasks from a running dependency graph.

@tableofcontents

@section CreateATaskDependencyGraph Create a Task Dependency Graph

A task in %Taskflow is a @em callable object for which the operation @std_invoke is applicable.
It can be either 
a functor, a lambda expression, a bind expression, or a class objects with @c operator() overloaded.
All tasks are created from tf::Taskflow, the class that manages a task dependency graph.
%Taskflow provides two methods, tf::Taskflow::placeholder and tf::Taskflow::emplace to create a task.

@code{.cpp}
1: tf::Taskflow taskflow;
2: tf::Task A = taskflow.placeholder();
3: tf::Task B = taskflow.emplace([] () { std::cout << "task B\n"; });
4:
5: auto [D, E, F] = taskflow.emplace(
6:   [](){ std::cout << "Task A\n"; },
7:   [](){ std::cout << "Task B\n"; },
8:   [](){ std::cout << "Task C\n"; }
9: );
@endcode

Debrief:
@li Line 1 creates a taskflow object, or a @em graph
@li Line 2 creates a placeholder task without work (i.e., callable)
@li Line 3 creates a task from a given callable object and returns a task handle
@li Lines 5-9 create three tasks in one call using C++ structured binding coupled with std::tuple

Each time you create a task, 
the taskflow object creates a node in the task graph
and returns a task handle of type tf::Task.
A task handle is a lightweight object
that wraps up a particular node in a graph
and provides a set of methods for you to assign different attributes to the task
such as adding dependencies, naming, and assigning a new work.

@code{.cpp}
 1: tf::Taskflow taskflow;
 2: tf::Task A = taskflow.emplace([] () { std::cout << "create a task A\n"; });
 3: tf::Task B = taskflow.emplace([] () { std::cout << "create a task B\n"; });
 4:
 5: A.name("TaskA");
 6: A.work([] () { std::cout << "reassign A to a new callable\n"; });
 7: A.precede(B);
 8:
 9: std::cout << A.name() << std::endl;            // TaskA
10: std::cout << A.num_successors() << std::endl;  // 1
11: std::cout << A.num_dependents() << std::endl;  // 0
12: 
13: std::cout << B.num_successors() << std::endl;  // 0
14: std::cout << B.num_dependents() << std::endl;  // 1
@endcode

Debrief:
@li Line 1 creates a taskflow object
@li Lines 2-3 create two tasks A and B
@li Lines 5-6 assign a name and a work to task A, and add a precedence link to task B
@li Line 7 adds a dependency link from A to B
@li Lines 9-14 dump the task attributes 

%Taskflow uses general-purpose polymorphic function wrapper, std::function,
to store and invoke a callable in a task.
You need to follow its contract to create a task.
For example, the callable to construct a task must be copyable,
and thus the code below won't compile:

@code{.cpp}
taskflow.emplace([ptr=std::make_unique<int>(1)](){
  std::cout << "captured unique pointer is not copyable";
});
@endcode


@section VisualizeATaskDependencyGraph Visualize a Task Dependency Graph

You can dump a taskflow to a DOT format and visualize the graph using free online tools such as <a href="https://dreampuf.github.io/GraphvizOnline/">GraphvizOnline</a> and <a href="http://www.webgraphviz.com/">WebGraphviz</a>.

@code{.cpp}
 1: #include <taskflow/taskflow.hpp>
 2:
 3: int main() {
 4: 
 5:   tf::Taskflow taskflow;
 6:
 7:   // create a task dependency graph
 8:   tf::Task A = taskflow.emplace([] () { std::cout << "Task A\n"; });
 9:   tf::Task B = taskflow.emplace([] () { std::cout << "Task B\n"; });
10:   tf::Task C = taskflow.emplace([] () { std::cout << "Task C\n"; });
11:   tf::Task D = taskflow.emplace([] () { std::cout << "Task D\n"; });
12:
13:   // add dependency links
14:   A.precede(B); 
15:   A.precede(C);
16:   B.precede(D);
17:   C.precede(D);
18:
19:   taskflow.dump(std::cout);
20: }
@endcode

Debrief:
@li Line 5 creates a taskflow object
@li Lines 8-11 create four tasks
@li Lines 14-17 add four task dependencies
@li Line 19 dumps the taskflow in the DOT format through standard output

<!-- @image html images/simple.svg width=40% -->
@dotfile images/simple.dot


@section ModifyTaskAttributes Modify Task Attributes

This example demonstrates how to modify a task's attributes using methods defined in
the task handler.

@code{.cpp}
 1: #include <taskflow/taskflow.hpp>
 2:
 3: int main() {
 4:
 5:   tf::Taskflow taskflow;
 6:
 7:   std::vector<tf::Task> tasks = { 
 8:     taskflow.placeholder(),         // create a task with no work
 9:     taskflow.placeholder()          // create a task with no work
10:   };
11:
12:   tasks[0].name("This is Task 0");
13:   tasks[1].name("This is Task 1");
14:   tasks[0].precede(tasks[1]);
15:
16:   for(auto task : tasks) {          // print out each task's attributes
17:     std::cout << task.name() << ": "
18:               << "num_dependents=" << task.num_dependents() << ", "
19:               << "num_successors=" << task.num_successors() << '\n';
20:   }
21:
22:   taskflow.dump(std::cout);         // dump the taskflow graph
23:
24:   tasks[0].work([](){ std::cout << "got a new work!\n"; });
25:   tasks[1].work([](){ std::cout << "got a new work!\n"; });
26:
27:   return 0;
28: }
@endcode

The output of this program looks like the following:

@code{.sh}
This is Task 0: num_dependents=0, num_successors=1
This is Task 1: num_dependents=1, num_successors=0
digraph Taskflow {
"This is Task 1";
"This is Task 0";
"This is Task 0" -> "This is Task 1";
}
@endcode

Debrief:
@li Line 5 creates a taskflow object
@li Lines 7-10 create two placeholder tasks with no works and stores the corresponding task handles in a vector
@li Lines 12-13 name the two tasks with human-readable strings 
@li Line 14 adds a dependency link from the first task to the second task
@li Lines 16-20 print out the name of each task, the number of dependents, and the number of successors
@li Line 22 dumps the task dependency graph to a @GraphVizOnline format (dot)
@li Lines 24-25 assign a new target to each task

You can change the name and work of a task at anytime before running the graph.
The later assignment overwrites the previous values.

@section TraverseAdjacentTasks Traverse Adjacent Tasks

You can iterate the successor list and the dependent list of a task by using tf::Task::for_each_successor
and tf::Task::for_each_dependent, respectively.
Each method takes a lambda and applies it to a successor or a dependent being traversed.

@code{.cpp}
// traverse all successors of my_task
my_task.for_each_successor([s=0] (tf::Task successor) mutable {
  std::cout << "successor " << s++ << '\n';
});

// traverse all dependents of my_task
my_task.for_each_dependent([d=0] (tf::Task dependent) mutable {
  std::cout << "dependent " << d++ << '\n';
});
@endcode

@section AttachUserDataToATask Attach User Data to a Task

You can attach custom data to a task using tf::Task::data(void*)
and access it using tf::Task::data().
Each node in a taskflow is associated with a C-styled data pointer
(i.e., `void*`) you can use to point to user data and access it in the body
of a task callable.
The following example attaches an integer to a task and accesses that integer
through capturing the data in the callable.

@code{.cpp}
int my_data = 5;
tf::Task task = taskflow.placeholder();
task.data(&my_data)
    .work([task](){
      int my_date = *static_cast<int*>(task.data());
      std::cout << "my_data: " << my_data; 
    });
@endcode

Notice that you need to create a placeholder task first before assigning it
a work callable. Only this way can you capture that task in the lambda
and access its attached data in the lambda body.

@attention
It is your responsibility to ensure that the attached data stay alive
during the execution of its task.


@section UnderstandTheLifetimeOfATask Understand the Lifetime of a Task

A task lives with its graph and belongs to only a graph at a time, 
and is not destroyed until the graph gets cleaned up.
The lifetime of a task refers to the user-given callable object,
including captured values.
As long as the graph is alive,
all the associated tasks exist.

@attention
It is your responsibility to keep tasks and graph alive during their execution.


@section MoveATaskflow Move a Taskflow

You can construct or assign a taskflow from a @em moved taskflow.
Moving a taskflow to another will result in transferring the underlying graph 
data structures from one to the other.

@code{.cpp}
tf::Taskflow taskflow1, taskflow3;

taskflow1.emplace([](){});

// move-construct taskflow2 from taskflow1
tf::Taskflow taskflow2(std::move(taskflow1));
assert(taskflow2.num_tasks() == 1 && taskflow1.num_tasks() == 0);

// move-assign taskflow3 to taskflow2
taskflow3 = std::move(taskflow2);
assert(taskflow3.num_tasks() == 1 && taskflow2.num_tasks() == 0);
@endcode

You can only move a taskflow to another while that taskflow is not being
run by an executor.
Moving a running taskflow can result in undefined behavior.
Please see @ref ExecuteATaskflowWithTransferredOwnership for more details.

*/

}

