// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.base.task;

import android.util.Pair;

import org.chromium.base.TraceEvent;

import java.util.LinkedList;

import javax.annotation.concurrent.GuardedBy;

/**
 * Allows chaining multiple tasks on arbitrary threads, with the next task posted when one
 * completes.
 *
 * How this differs from SequencedTaskRunner:
 * Deferred posting of subsequent tasks allows more time for Android framework tasks to run
 * (e.g. input events). As such, this class really only makes sense when submitting tasks to
 * SingleThreadTaskRunners.
 *
 * Threading:
 * - This class is threadsafe and all methods may be called from any thread.
 * - Tasks may run with arbitrary TaskTraits, unless tasks are coalesced, in which case all tasks
 *   must run on the same thread.
 */
public class ChainedTasks {
    private final LinkedList<Pair<Integer, Runnable>> mTasks = new LinkedList<>();

    @GuardedBy("mTasks")
    private boolean mFinalized;

    private volatile boolean mCanceled;
    private int mIterationIdForTesting = PostTask.sTestIterationForTesting;

    private final Runnable mRunAndPost =
            new Runnable() {
                @Override
                @SuppressWarnings("NoDynamicStringsInTraceEventCheck")
                public void run() {
                    if (mIterationIdForTesting != PostTask.sTestIterationForTesting) {
                        cancel();
                    }
                    if (mCanceled) return;

                    Pair<Integer, Runnable> pair = mTasks.pop();
                    try (TraceEvent e =
                            TraceEvent.scoped(
                                    "ChainedTask.run: " + pair.second.getClass().getName())) {
                        pair.second.run();
                    }
                    if (!mTasks.isEmpty()) PostTask.postTask(mTasks.peek().first, this);
                }
            };

    /**
     * Adds a task to the list of tasks to run. Cannot be called once {@link start()} has been
     * called.
     */
    public void add(@TaskTraits int traits, Runnable task) {
        assert mIterationIdForTesting == PostTask.sTestIterationForTesting;

        synchronized (mTasks) {
            assert !mFinalized : "Must not call add() after start()";
            mTasks.add(new Pair<>(traits, task));
        }
    }

    /** Cancels the remaining tasks. */
    public void cancel() {
        synchronized (mTasks) {
            mFinalized = true;
            mCanceled = true;
        }
    }

    /**
     * Posts or runs all the tasks, one by one.
     *
     * @param coalesceTasks if false, posts the tasks. Otherwise run them in a single task. If
     * called on the thread matching the TaskTraits, will block and run all tasks synchronously.
     */
    public void start(final boolean coalesceTasks) {
        synchronized (mTasks) {
            assert !mFinalized : "Cannot call start() several times";
            mFinalized = true;
        }
        if (mTasks.isEmpty()) return;
        if (coalesceTasks) {
            @TaskTraits int traits = mTasks.peek().first;
            PostTask.runOrPostTask(
                    traits,
                    () -> {
                        for (Pair<Integer, Runnable> pair : mTasks) {
                            assert PostTask.canRunTaskImmediately(pair.first);
                            pair.second.run();
                            if (mCanceled) return;
                        }
                    });
        } else {
            PostTask.postTask(mTasks.peek().first, mRunAndPost);
        }
    }
}
