// Copyright 2018 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 androidx.annotation.Nullable;

import java.util.Arrays;

/**
 * TaskTraits are metadata that influence how the TaskSecheduler deals with that task.
 * E.g. the trait can directly or indirectly control task prioritization.
 */
public class TaskTraits {
    // Keep in sync with base::TaskTraitsExtensionStorage:: kInvalidExtensionId
    public static final byte INVALID_EXTENSION_ID = 0;

    // Keep in sync with base::TaskTraitsExtensionStorage::kMaxExtensionId
    public static final int MAX_EXTENSION_ID = 4;

    // Keep in sync with base::TaskTraitsExtensionStorage::kStorageSize
    public static final int EXTENSION_STORAGE_SIZE = 8;

    // Convenience variables explicitly specifying common priorities.
    // These also imply THREAD_POOL unless explicitly overwritten.
    // TODO(1026641): Make destination explicit in Java too.

    // This task will only be scheduled when machine resources are available. Once
    // running, it may be descheduled if higher priority work arrives (in this
    // process or another) and its running on a non-critical thread. This is the
    // lowest possible priority.
    public static final TaskTraits BEST_EFFORT =
            new TaskTraits().taskPriority(TaskPriority.BEST_EFFORT);

    // This is a lowest-priority task which may block, for example non-urgent
    // logging or deletion of temporary files as clean-up.
    public static final TaskTraits BEST_EFFORT_MAY_BLOCK = BEST_EFFORT.mayBlock();

    // This task affects UI or responsiveness of future user interactions. It is
    // not an immediate response to a user interaction. Most tasks are likely to
    // have this priority.
    // Examples:
    // - Updating the UI to reflect progress on a long task.
    // - Loading data that might be shown in the UI after a future user
    //   interaction.
    public static final TaskTraits USER_VISIBLE =
            new TaskTraits().taskPriority(TaskPriority.USER_VISIBLE);

    // USER_VISIBLE + may block.
    public static final TaskTraits USER_VISIBLE_MAY_BLOCK = USER_VISIBLE.mayBlock();

    // This task affects UI immediately after a user interaction.
    // Example: Generating data shown in the UI immediately after a click.
    public static final TaskTraits USER_BLOCKING =
            new TaskTraits().taskPriority(TaskPriority.USER_BLOCKING);

    // USER_BLOCKING + may block.
    public static final TaskTraits USER_BLOCKING_MAY_BLOCK = USER_BLOCKING.mayBlock();

    // A bit like requestAnimationFrame, this task will be posted onto the Choreographer
    // and will be run on the android main thread after the next vsync.
    public static final TaskTraits CHOREOGRAPHER_FRAME = new TaskTraits();
    static {
        CHOREOGRAPHER_FRAME.mIsChoreographerFrame = true;
    }

    // For tasks that should run on the thread pool instead of the main thread.
    // Note that currently also tasks which lack this trait will execute on the
    // thread pool unless a trait for a named thread is given.
    public static final TaskTraits THREAD_POOL =
            new TaskTraits().threadPool().taskPriority(TaskPriority.USER_BLOCKING);
    public static final TaskTraits THREAD_POOL_USER_BLOCKING =
            THREAD_POOL.taskPriority(TaskPriority.USER_BLOCKING);
    public static final TaskTraits THREAD_POOL_USER_VISIBLE =
            THREAD_POOL.taskPriority(TaskPriority.USER_VISIBLE);
    public static final TaskTraits THREAD_POOL_BEST_EFFORT =
            THREAD_POOL.taskPriority(TaskPriority.BEST_EFFORT);

    // For convenience of the JNI code, we use primitive types only.
    // Note shutdown behavior is not supported on android.
    int mPriority;
    boolean mMayBlock;
    boolean mUseThreadPool;
    byte mExtensionId;
    byte mExtensionData[];
    boolean mIsChoreographerFrame;

    // Derive custom traits from existing trait constants.
    private TaskTraits() {
        // Assume USER_BLOCKING by default.
        mPriority = TaskPriority.USER_BLOCKING;
    }

    private TaskTraits(TaskTraits other) {
        mPriority = other.mPriority;
        mMayBlock = other.mMayBlock;
        mUseThreadPool = other.mUseThreadPool;
        mExtensionId = other.mExtensionId;
        mExtensionData = other.mExtensionData;
    }

    public TaskTraits taskPriority(int taskPriority) {
        TaskTraits taskTraits = new TaskTraits(this);
        taskTraits.mPriority = taskPriority;
        return taskTraits;
    }

    /**
     * Tasks with this trait may block. This includes but is not limited to tasks that wait on
     * synchronous file I/O operations: read or write a file from disk, interact with a pipe or a
     * socket, rename or delete a file, enumerate files in a directory, etc. This trait isn't
     * required for the mere use of locks. The thread pool uses this property to work out if
     * additional threads are required.
     */
    public TaskTraits mayBlock() {
        TaskTraits taskTraits = new TaskTraits(this);
        taskTraits.mMayBlock = true;
        return taskTraits;
    }

    public TaskTraits threadPool() {
        TaskTraits taskTraits = new TaskTraits(this);
        taskTraits.mUseThreadPool = true;
        return taskTraits;
    }

    /**
     * @return true if this task is using some TaskTraits extension.
     */
    public boolean hasExtension() {
        return mExtensionId != INVALID_EXTENSION_ID;
    }

    /**
     * Tries to extract the extension for the given descriptor from this traits.
     *
     * @return Extension instance or null if the traits do not contain the requested extension
     */
    public <Extension> Extension getExtension(TaskTraitsExtensionDescriptor<Extension> descriptor) {
        if (mExtensionId == descriptor.getId()) {
            return descriptor.fromSerializedData(mExtensionData);
        } else {
            return null;
        }
    }

    public <Extension> TaskTraits withExtension(
            TaskTraitsExtensionDescriptor<Extension> descriptor, Extension extension) {
        int id = descriptor.getId();
        byte[] data = descriptor.toSerializedData(extension);
        assert id > INVALID_EXTENSION_ID && id <= MAX_EXTENSION_ID;
        assert data.length <= EXTENSION_STORAGE_SIZE;

        TaskTraits taskTraits = new TaskTraits(this);
        taskTraits.mExtensionId = (byte) id;
        taskTraits.mExtensionData = data;
        return taskTraits;
    }

    /**
     * Returns a TaskTraits with an explicit destination.
     *
     * The C++ side enforces that a destination _must_ be specified. The Java
     * side loosely considers lack of destination as implying THREAD_POOL
     * destination.
     * TODO(1026641): Bring the Java side inline with the C++ side.
     */
    public TaskTraits withExplicitDestination() {
        if (!mUseThreadPool && !hasExtension()) {
            return this.threadPool();
        }
        return this;
    }

    @Override
    public boolean equals(@Nullable Object object) {
        if (object == this) {
            return true;
        } else if (object instanceof TaskTraits) {
            TaskTraits other = (TaskTraits) object;
            return mPriority == other.mPriority && mMayBlock == other.mMayBlock
                    && mUseThreadPool == other.mUseThreadPool && mExtensionId == other.mExtensionId
                    && Arrays.equals(mExtensionData, other.mExtensionData)
                    && mIsChoreographerFrame == other.mIsChoreographerFrame;
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        int hash = 31;
        hash = 37 * hash + mPriority;
        hash = 37 * hash + (mMayBlock ? 0 : 1);
        hash = 37 * hash + (mUseThreadPool ? 0 : 1);
        hash = 37 * hash + (int) mExtensionId;
        hash = 37 * hash + Arrays.hashCode(mExtensionData);
        hash = 37 * hash + (mIsChoreographerFrame ? 0 : 1);
        return hash;
    }
}
