/*
 * Copyright (C) 2022 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.quickstep.views;

import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;

import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

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

import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;

/**
 * TaskView that contains all tasks that are part of the desktop.
 */
// TODO(b/249371338): TaskView needs to be refactored to have better support for N tasks.
public class DesktopTaskView extends TaskView {

    /** Flag to indicate whether desktop windowing proto 1 is enabled */
    private static final boolean DESKTOP_IS_PROTO1_ENABLED = SystemProperties.getBoolean(
            "persist.wm.debug.desktop_mode", false);

    /** Flag to indicate whether desktop windowing proto 2 is enabled */
    public static final boolean DESKTOP_IS_PROTO2_ENABLED = SystemProperties.getBoolean(
            "persist.wm.debug.desktop_mode_2", false);

    /** Flags to indicate whether desktop mode is available on the device */
    public static final boolean DESKTOP_MODE_SUPPORTED =
            DESKTOP_IS_PROTO1_ENABLED || DESKTOP_IS_PROTO2_ENABLED;

    private static final String TAG = DesktopTaskView.class.getSimpleName();

    private static final boolean DEBUG = true;

    @NonNull
    private List<Task> mTasks = new ArrayList<>();

    private final ArrayList<TaskThumbnailView> mSnapshotViews = new ArrayList<>();

    /** Maps {@code taskIds} to corresponding {@link TaskThumbnailView}s */
    private final SparseArray<TaskThumbnailView> mSnapshotViewMap = new SparseArray<>();

    private final ArrayList<CancellableTask<?>> mPendingThumbnailRequests = new ArrayList<>();

    private View mBackgroundView;
    private View mEmptyView;

    public DesktopTaskView(Context context) {
        this(context, null);
    }

    public DesktopTaskView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DesktopTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        mBackgroundView = findViewById(R.id.background);
        mEmptyView = findViewById(R.id.empty_view);

        int topMarginPx =
                mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
        FrameLayout.LayoutParams params = (LayoutParams) mBackgroundView.getLayoutParams();
        params.topMargin = topMarginPx;
        mBackgroundView.setLayoutParams(params);

        float[] outerRadii = new float[8];
        Arrays.fill(outerRadii, getTaskCornerRadius());
        RoundRectShape shape = new RoundRectShape(outerRadii, null, null);
        ShapeDrawable background = new ShapeDrawable(shape);
        background.setTint(getResources().getColor(android.R.color.system_neutral2_300,
                getContext().getTheme()));
        // TODO(b/244348395): this should be wallpaper
        mBackgroundView.setBackground(background);

        Drawable icon = getResources().getDrawable(R.drawable.ic_desktop, getContext().getTheme());
        Drawable iconBackground = getResources().getDrawable(R.drawable.bg_circle,
                getContext().getTheme());
        mIconView.setDrawable(new LayerDrawable(new Drawable[]{iconBackground, icon}));
    }

    @Override
    protected void updateBorderBounds(Rect bounds) {
        bounds.set(mBackgroundView.getLeft(), mBackgroundView.getTop(), mBackgroundView.getRight(),
                mBackgroundView.getBottom());
    }

    @Override
    public void bind(Task task, RecentsOrientedState orientedState) {
        bind(Collections.singletonList(task), orientedState);
    }

    /**
     * Updates this desktop task to the gives task list defined in {@code tasks}
     */
    public void bind(List<Task> tasks, RecentsOrientedState orientedState) {
        if (DEBUG) {
            StringBuilder sb = new StringBuilder();
            sb.append("bind tasks=").append(tasks.size()).append("\n");
            for (Task task : tasks) {
                sb.append(" key=").append(task.key).append("\n");
            }
            Log.d(TAG, sb.toString());
        }
        cancelPendingLoadTasks();

        mTasks = new ArrayList<>(tasks);
        mSnapshotViewMap.clear();

        // Ensure there are equal number of snapshot views and tasks.
        // More tasks than views, add views. More views than tasks, remove views.
        // TODO(b/251586230): use a ViewPool for creating TaskThumbnailViews
        if (mSnapshotViews.size() > mTasks.size()) {
            int diff = mSnapshotViews.size() - mTasks.size();
            for (int i = 0; i < diff; i++) {
                TaskThumbnailView snapshotView = mSnapshotViews.remove(0);
                removeView(snapshotView);
            }
        } else if (mSnapshotViews.size() < mTasks.size()) {
            int diff = mTasks.size() - mSnapshotViews.size();
            for (int i = 0; i < diff; i++) {
                TaskThumbnailView snapshotView = new TaskThumbnailView(getContext());
                mSnapshotViews.add(snapshotView);
                addView(snapshotView, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
            }
        }

        for (int i = 0; i < mTasks.size(); i++) {
            Task task = mTasks.get(i);
            TaskThumbnailView snapshotView = mSnapshotViews.get(i);
            snapshotView.bind(task);
            mSnapshotViewMap.put(task.key.id, snapshotView);
        }

        mEmptyView.setVisibility(mTasks.isEmpty() ? View.VISIBLE : View.GONE);

        updateTaskIdContainer();
        updateTaskIdAttributeContainer();

        setOrientationState(orientedState);
    }

    private void updateTaskIdContainer() {
        // TODO(b/249371338): TaskView expects the array to have at least 2 elements.
        // At least 2 elements in the array
        mTaskIdContainer = new int[Math.max(mTasks.size(), 2)];
        for (int i = 0; i < mTasks.size(); i++) {
            mTaskIdContainer[i] = mTasks.get(i).key.id;
        }
    }

    private void updateTaskIdAttributeContainer() {
        // TODO(b/249371338): TaskView expects the array to have at least 2 elements.
        // At least 2 elements in the array
        mTaskIdAttributeContainer = new TaskIdAttributeContainer[Math.max(mTasks.size(), 2)];
        for (int i = 0; i < mTasks.size(); i++) {
            Task task = mTasks.get(i);
            TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
            mTaskIdAttributeContainer[i] = createAttributeContainer(task, thumbnailView);
        }
    }

    private TaskIdAttributeContainer createAttributeContainer(Task task,
            TaskThumbnailView thumbnailView) {
        return new TaskIdAttributeContainer(task, thumbnailView, null, STAGE_POSITION_UNDEFINED);
    }

    @Nullable
    @Override
    public Task getTask() {
        // TODO(b/249371338): returning first task. This won't work well with multiple tasks.
        return mTasks.size() > 0 ? mTasks.get(0) : null;
    }

    @Override
    public TaskThumbnailView getThumbnail() {
        // TODO(b/249371338): returning single thumbnail. This won't work well with multiple tasks.
        Task task = getTask();
        if (task != null) {
            return mSnapshotViewMap.get(task.key.id);
        }
        // Return the place holder snapshot views. Callers expect this to be non-null
        return mSnapshotView;
    }

    @Override
    public boolean containsTaskId(int taskId) {
        // Thumbnail map contains taskId -> thumbnail map. Use the keys for contains
        return mSnapshotViewMap.contains(taskId);
    }

    @Override
    public void onTaskListVisibilityChanged(boolean visible, int changes) {
        cancelPendingLoadTasks();
        if (visible) {
            RecentsModel model = RecentsModel.INSTANCE.get(getContext());
            TaskThumbnailCache thumbnailCache = model.getThumbnailCache();

            if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
                for (Task task : mTasks) {
                    CancellableTask<?> thumbLoadRequest =
                            thumbnailCache.updateThumbnailInBackground(task, thumbnailData -> {
                                TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
                                if (thumbnailView != null) {
                                    thumbnailView.setThumbnail(task, thumbnailData);
                                }
                            });
                    if (thumbLoadRequest != null) {
                        mPendingThumbnailRequests.add(thumbLoadRequest);
                    }
                }
            }
        } else {
            if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
                for (Task task : mTasks) {
                    TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
                    if (thumbnailView != null) {
                        thumbnailView.setThumbnail(null, null);
                    }
                    // Reset the task thumbnail ref
                    task.thumbnail = null;
                }
            }
        }
    }

    @Override
    protected void setThumbnailOrientation(RecentsOrientedState orientationState) {
        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
        int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;

        LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
        snapshotParams.topMargin = thumbnailTopMargin;

        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
            TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
            thumbnailView.setLayoutParams(snapshotParams);
        }
    }

    @Override
    protected void cancelPendingLoadTasks() {
        for (CancellableTask<?> cancellableTask : mPendingThumbnailRequests) {
            cancellableTask.cancel();
        }
        mPendingThumbnailRequests.clear();
    }

    @Override
    public boolean offerTouchToChildren(MotionEvent event) {
        return false;
    }

    @Override
    protected boolean showTaskMenuWithContainer(IconView iconView) {
        return false;
    }

    @Override
    public RunnableList launchTasks() {
        SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps();
        Launcher.getLauncher(mActivity).getStateManager().goToState(NORMAL, false /* animated */);
        return null;
    }

    @Nullable
    @Override
    public RunnableList launchTaskAnimated() {
        return launchTasks();
    }

    @Override
    public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
        launchTasks();
        callback.accept(true);
    }

    @Override
    public boolean isDesktopTask() {
        return true;
    }

    @Override
    void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
        // Sets new thumbnails based on the incoming data and refreshes the rest.
        // Create a copy of the thumbnail map, so we can track thumbnails that need refreshing.
        SparseArray<TaskThumbnailView> thumbnailsToRefresh = mSnapshotViewMap.clone();
        if (thumbnailDatas != null) {
            for (Task task : mTasks) {
                int key = task.key.id;
                TaskThumbnailView thumbnailView = thumbnailsToRefresh.get(key);
                ThumbnailData thumbnailData = thumbnailDatas.get(key);
                if (thumbnailView != null && thumbnailData != null) {
                    thumbnailView.setThumbnail(task, thumbnailData);
                    // Remove this thumbnail from the list that should be refreshed.
                    thumbnailsToRefresh.remove(key);
                }
            }
        }

        // Refresh the rest that were not updated.
        for (int i = 0; i < thumbnailsToRefresh.size(); i++) {
            thumbnailsToRefresh.valueAt(i).refresh();
        }
    }

    @Override
    public TaskThumbnailView[] getThumbnails() {
        TaskThumbnailView[] thumbnails = new TaskThumbnailView[mSnapshotViewMap.size()];
        for (int i = 0; i < thumbnails.length; i++) {
            thumbnails[i] = mSnapshotViewMap.valueAt(i);
        }
        return thumbnails;
    }

    @Override
    public void onRecycle() {
        resetPersistentViewTransforms();
        // Clear any references to the thumbnail (it will be re-read either from the cache or the
        // system on next bind)
        for (Task task : mTasks) {
            TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
            if (thumbnailView != null) {
                thumbnailView.setThumbnail(task, null);
            }
        }
        setOverlayEnabled(false);
        onTaskListVisibilityChanged(false);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int containerWidth = MeasureSpec.getSize(widthMeasureSpec);
        int containerHeight = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(containerWidth, containerHeight);

        int thumbnailTopMarginPx = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
        containerHeight -= thumbnailTopMarginPx;

        int thumbnails = mSnapshotViewMap.size();
        if (thumbnails == 0) {
            return;
        }

        int windowWidth = mActivity.getDeviceProfile().widthPx;
        int windowHeight = mActivity.getDeviceProfile().heightPx;

        float scaleWidth = containerWidth / (float) windowWidth;
        float scaleHeight = containerHeight / (float) windowHeight;

        if (DEBUG) {
            Log.d(TAG,
                    "onMeasure: container=[" + containerWidth + "," + containerHeight + "] window=["
                            + windowWidth + "," + windowHeight + "] scale=[" + scaleWidth + ","
                            + scaleHeight + "]");
        }

        // Desktop tile is a shrunk down version of launcher and freeform task thumbnails.
        for (int i = 0; i < mTasks.size(); i++) {
            Task task = mTasks.get(i);
            Rect taskSize = task.appBounds;
            if (taskSize == null) {
                // Default to quarter of the desktop if we did not get app bounds.
                taskSize = new Rect(0, 0, windowWidth / 4, windowHeight / 4);
            }

            int thumbWidth = (int) (taskSize.width() * scaleWidth);
            int thumbHeight = (int) (taskSize.height() * scaleHeight);

            TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
            if (thumbnailView != null) {
                thumbnailView.measure(MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY));

                // Position the task to the same position as it would be on the desktop
                Point positionInParent = task.positionInParent;
                if (positionInParent == null) {
                    positionInParent = new Point(0, 0);
                }
                int taskX = (int) (positionInParent.x * scaleWidth);
                int taskY = (int) (positionInParent.y * scaleHeight);
                // move task down by margin size
                taskY += thumbnailTopMarginPx;
                thumbnailView.setX(taskX);
                thumbnailView.setY(taskY);

                if (DEBUG) {
                    Log.d(TAG, "onMeasure: task=" + task.key + " thumb=[" + thumbWidth + ","
                            + thumbHeight + "]" + " pos=[" + taskX + "," + taskY + "]");
                }
            }
        }
    }

    @Override
    public void setOverlayEnabled(boolean overlayEnabled) {
        // Intentional no-op to prevent setting smart actions overlay on thumbnails
    }

    @Override
    public void setFullscreenProgress(float progress) {
        // TODO(b/249371338): this copies parent implementation and makes it work for N thumbs
        progress = Utilities.boundToRange(progress, 0, 1);
        mFullscreenProgress = progress;
        if (mFullscreenProgress > 0) {
            // Don't show background while we are transitioning to/from fullscreen
            mBackgroundView.setVisibility(INVISIBLE);
        } else {
            mBackgroundView.setVisibility(VISIBLE);
        }
        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
            TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
            thumbnailView.getTaskOverlay().setFullscreenProgress(progress);
            updateSnapshotRadius();
        }
    }

    @Override
    protected void updateSnapshotRadius() {
        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
            mSnapshotViewMap.valueAt(i).setFullscreenParams(mCurrentFullscreenParams);
        }
    }

    @Override
    protected void setIconsAndBannersTransitionProgress(float progress, boolean invert) {
        // no-op
    }

    @Override
    public void setColorTint(float amount, int tintColor) {
        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
            mSnapshotViewMap.valueAt(i).setDimAlpha(amount);
        }
    }

    @Override
    protected void applyThumbnailSplashAlpha() {
        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
            mSnapshotViewMap.valueAt(i).setSplashAlpha(mTaskThumbnailSplashAlpha);
        }
    }

    @Override
    void setThumbnailVisibility(int visibility) {
        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
            mSnapshotViewMap.valueAt(i).setVisibility(visibility);
        }
    }

    @Override
    protected boolean confirmSecondSplitSelectApp() {
        // Desktop tile can't be in split screen
        return false;
    }
}
