/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * 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.android.systemui.shared.recents.model;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;

import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;

import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.ViewDebug;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.util.ArrayUtils;

import java.io.PrintWriter;
import java.util.Objects;

/**
 * A task in the recent tasks list.
 * TODO: Move this into Launcher or see if we can remove now
 */
public class Task {

    public static final String TAG = "Task";

    /**
     * The Task Key represents the unique primary key for the task
     */
    public static class TaskKey implements Parcelable {
        @ViewDebug.ExportedProperty(category="recents")
        public final int id;
        @ViewDebug.ExportedProperty(category="recents")
        public int windowingMode;
        @ViewDebug.ExportedProperty(category="recents")
        @NonNull
        public final Intent baseIntent;
        @ViewDebug.ExportedProperty(category="recents")
        public final int userId;
        @ViewDebug.ExportedProperty(category="recents")
        public long lastActiveTime;

        /**
         * The id of the task was running from which display.
         */
        @ViewDebug.ExportedProperty(category = "recents")
        public final int displayId;

        // The source component name which started this task
        public final ComponentName sourceComponent;

        private int mHashCode;

        public TaskKey(TaskInfo t) {
            ComponentName sourceComponent = t.origActivity != null
                    // Activity alias if there is one
                    ? t.origActivity
                    // The real activity if there is no alias (or the target if there is one)
                    : t.realActivity;
            this.id = t.taskId;
            this.windowingMode = t.configuration.windowConfiguration.getWindowingMode();
            this.baseIntent = t.baseIntent;
            this.sourceComponent = sourceComponent;
            this.userId = t.userId;
            this.lastActiveTime = t.lastActiveTime;
            this.displayId = t.displayId;
            updateHashCode();
        }

        public TaskKey(int id, int windowingMode, @NonNull Intent intent,
                ComponentName sourceComponent, int userId, long lastActiveTime) {
            this.id = id;
            this.windowingMode = windowingMode;
            this.baseIntent = intent;
            this.sourceComponent = sourceComponent;
            this.userId = userId;
            this.lastActiveTime = lastActiveTime;
            this.displayId = DEFAULT_DISPLAY;
            updateHashCode();
        }

        public TaskKey(int id, int windowingMode, @NonNull Intent intent,
                ComponentName sourceComponent, int userId, long lastActiveTime, int displayId) {
            this.id = id;
            this.windowingMode = windowingMode;
            this.baseIntent = intent;
            this.sourceComponent = sourceComponent;
            this.userId = userId;
            this.lastActiveTime = lastActiveTime;
            this.displayId = displayId;
            updateHashCode();
        }

        public void setWindowingMode(int windowingMode) {
            this.windowingMode = windowingMode;
            updateHashCode();
        }

        public ComponentName getComponent() {
            return this.baseIntent.getComponent();
        }

        public String getPackageName() {
            if (this.baseIntent.getComponent() != null) {
                return this.baseIntent.getComponent().getPackageName();
            }
            return this.baseIntent.getPackage();
        }

        public int getId() {
            return id;
        }

        @Override
        public boolean equals(Object o) {
            if (!(o instanceof TaskKey)) {
                return false;
            }
            TaskKey otherKey = (TaskKey) o;
            return id == otherKey.id
                    && windowingMode == otherKey.windowingMode
                    && userId == otherKey.userId;
        }

        @Override
        public int hashCode() {
            return mHashCode;
        }

        @Override
        public String toString() {
            return "id=" + id + " windowingMode=" + windowingMode + " user=" + userId
                    + " lastActiveTime=" + lastActiveTime;
        }

        private void updateHashCode() {
            mHashCode = Objects.hash(id, windowingMode, userId);
        }

        public static final Parcelable.Creator<TaskKey> CREATOR =
                new Parcelable.Creator<TaskKey>() {
                    @Override
                    public TaskKey createFromParcel(Parcel source) {
                        return TaskKey.readFromParcel(source);
                    }

                    @Override
                    public TaskKey[] newArray(int size) {
                        return new TaskKey[size];
                    }
                };

        @Override
        public final void writeToParcel(Parcel parcel, int flags) {
            parcel.writeInt(id);
            parcel.writeInt(windowingMode);
            parcel.writeTypedObject(baseIntent, flags);
            parcel.writeInt(userId);
            parcel.writeLong(lastActiveTime);
            parcel.writeInt(displayId);
            parcel.writeTypedObject(sourceComponent, flags);
        }

        private static TaskKey readFromParcel(Parcel parcel) {
            int id = parcel.readInt();
            int windowingMode = parcel.readInt();
            Intent baseIntent = parcel.readTypedObject(Intent.CREATOR);
            int userId = parcel.readInt();
            long lastActiveTime = parcel.readLong();
            int displayId = parcel.readInt();
            ComponentName sourceComponent = parcel.readTypedObject(ComponentName.CREATOR);

            return new TaskKey(id, windowingMode, baseIntent, sourceComponent, userId,
                    lastActiveTime, displayId);
        }

        @Override
        public int describeContents() {
            return 0;
        }
    }

    @ViewDebug.ExportedProperty(deepExport=true, prefix="key_")
    public TaskKey key;

    /**
     * The icon is the task description icon (if provided), which falls back to the activity icon,
     * which can then fall back to the application icon.
     */
    @Nullable public Drawable icon;
    @Nullable public ThumbnailData thumbnail;
    @ViewDebug.ExportedProperty(category="recents")
    public String title;
    @ViewDebug.ExportedProperty(category="recents")
    public String titleDescription;
    @ViewDebug.ExportedProperty(category="recents")
    public int colorPrimary;
    @ViewDebug.ExportedProperty(category="recents")
    public int colorBackground;

    /**
     * The task description for this task, only used to reload task icons.
     */
    public TaskDescription taskDescription;

    @ViewDebug.ExportedProperty(category="recents")
    public boolean isDockable;

    @ViewDebug.ExportedProperty(category="recents")
    public ComponentName topActivity;

    @ViewDebug.ExportedProperty(category="recents")
    public boolean isLocked;

    public Point positionInParent;

    public Rect appBounds;

    // Last snapshot data, only used for recent tasks
    public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
            new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();

    public Task() {
        // Do nothing
    }

    /**
     * Creates a task object from the provided task info
     */
    public static Task from(TaskKey taskKey, TaskInfo taskInfo, boolean isLocked) {
        ActivityManager.TaskDescription td = taskInfo.taskDescription;
        // Also consider undefined activity type to include tasks in overview right after rebooting
        // the device.
        final boolean isDockable = taskInfo.supportsMultiWindow
                && ArrayUtils.contains(
                        CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, taskInfo.getWindowingMode())
                && (taskInfo.getActivityType() == ACTIVITY_TYPE_UNDEFINED
                || ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()));
        return new Task(taskKey,
                td != null ? td.getPrimaryColor() : 0,
                td != null ? td.getBackgroundColor() : 0, isDockable , isLocked, td,
                taskInfo.topActivity);
    }

    public Task(TaskKey key) {
        this.key = key;
        this.taskDescription = new TaskDescription();
    }

    public Task(Task other) {
        this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
                other.isLocked, other.taskDescription, other.topActivity);
        lastSnapshotData.set(other.lastSnapshotData);
        positionInParent = other.positionInParent;
        appBounds = other.appBounds;
    }

    /**
     * Use {@link Task#Task(Task)}.
     */
    @Deprecated
    public Task(TaskKey key, int colorPrimary, int colorBackground,
            boolean isDockable, boolean isLocked, TaskDescription taskDescription,
            ComponentName topActivity) {
        this.key = key;
        this.colorPrimary = colorPrimary;
        this.colorBackground = colorBackground;
        this.taskDescription = taskDescription;
        this.isDockable = isDockable;
        this.isLocked = isLocked;
        this.topActivity = topActivity;
    }

    /**
     * Returns the top activity component.
     */
    public ComponentName getTopComponent() {
        return topActivity != null
                ? topActivity
                : key.baseIntent.getComponent();
    }

    public void setLastSnapshotData(ActivityManager.RecentTaskInfo rawTask) {
        lastSnapshotData.set(rawTask.lastSnapshotData);
    }

    public TaskKey getKey() {
        return key;
    }

    /**
     * Returns the visible width to height ratio. Returns 0f if snapshot data is not available.
     */
    public float getVisibleThumbnailRatio(boolean clipInsets) {
        if (lastSnapshotData.taskSize == null || lastSnapshotData.contentInsets == null) {
            return 0f;
        }

        float availableWidth = lastSnapshotData.taskSize.x;
        float availableHeight = lastSnapshotData.taskSize.y;
        if (clipInsets) {
            availableWidth -=
                    (lastSnapshotData.contentInsets.left + lastSnapshotData.contentInsets.right);
            availableHeight -=
                    (lastSnapshotData.contentInsets.top + lastSnapshotData.contentInsets.bottom);
        }
        return availableWidth / availableHeight;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }

        if (!(o instanceof Task)) {
            return false;
        }

        // Check that the id matches
        Task t = (Task) o;
        return key.equals(t.key);
    }

    @Override
    public String toString() {
        return "[" + key.toString() + "] " + title;
    }

    public void dump(String prefix, PrintWriter writer) {
        writer.print(prefix); writer.print(key);
        if (!isDockable) {
            writer.print(" dockable=N");
        }
        if (isLocked) {
            writer.print(" locked=Y");
        }
        writer.print(" "); writer.print(title);
        writer.println();
    }
}
