/*
 * Copyright (C) 2019 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.inputconsumers;

import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.HOME;
import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.NEW_TASK;
import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.RECENTS;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Bundle;

import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.BaseSwipeUpHandler;
import com.android.quickstep.MultiStateCallback;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SwipeSharedState;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.util.ObjectWrapper;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.InputConsumerController;

public class FallbackNoButtonInputConsumer extends
        BaseSwipeUpHandler<RecentsActivity, FallbackRecentsView> {

    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null;

    private static int getFlagForIndex(int index, String name) {
        if (DEBUG_STATES) {
            STATE_NAMES[index] = name;
        }
        return 1 << index;
    }

    private static final int STATE_RECENTS_PRESENT =
            getFlagForIndex(0, "STATE_RECENTS_PRESENT");
    private static final int STATE_HANDLER_INVALIDATED =
            getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");

    private static final int STATE_GESTURE_CANCELLED =
            getFlagForIndex(2, "STATE_GESTURE_CANCELLED");
    private static final int STATE_GESTURE_COMPLETED =
            getFlagForIndex(3, "STATE_GESTURE_COMPLETED");
    private static final int STATE_APP_CONTROLLER_RECEIVED =
            getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");

    public enum GestureEndTarget {
        HOME(3, 100, 1),
        RECENTS(1, 300, 0),
        LAST_TASK(0, 150, 1),
        NEW_TASK(0, 150, 1);

        private final float mEndProgress;
        private final long mDurationMultiplier;
        private final float mLauncherAlpha;

        GestureEndTarget(float endProgress, long durationMultiplier, float launcherAlpha) {
            mEndProgress = endProgress;
            mDurationMultiplier = durationMultiplier;
            mLauncherAlpha = launcherAlpha;
        }
    }

    private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);

    private boolean mIsMotionPaused = false;
    private GestureEndTarget mEndTarget;

    private final boolean mInQuickSwitchMode;
    private final boolean mContinuingLastGesture;
    private final boolean mRunningOverHome;
    private final boolean mSwipeUpOverHome;

    private final RunningTaskInfo mRunningTaskInfo;

    private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
    private RunningWindowAnim mFinishAnimation;

    public FallbackNoButtonInputConsumer(Context context,
            OverviewComponentObserver overviewComponentObserver,
            RunningTaskInfo runningTaskInfo, RecentsModel recentsModel,
            InputConsumerController inputConsumer,
            boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
        super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
        mLauncherAlpha.value = 1;

        mRunningTaskInfo = runningTaskInfo;
        mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
        mContinuingLastGesture = continuingLastGesture;
        mRunningOverHome = ActivityManagerWrapper.isHomeTask(runningTaskInfo);
        mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;

        if (mSwipeUpOverHome) {
            mClipAnimationHelper.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value);
        } else {
            mClipAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
        }

        initStateCallbacks();
    }

    private void initStateCallbacks() {
        mStateCallback = new MultiStateCallback(STATE_NAMES);

        mStateCallback.addCallback(STATE_HANDLER_INVALIDATED,
                this::onHandlerInvalidated);
        mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
                this::onHandlerInvalidatedWithRecents);

        mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
                this::finishAnimationTargetSetAnimationComplete);

        if (mInQuickSwitchMode) {
            mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
                            | STATE_RECENTS_PRESENT,
                    this::finishAnimationTargetSet);
        } else {
            mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
                    this::finishAnimationTargetSet);
        }
    }

    private void onLauncherAlphaChanged() {
        if (mRecentsAnimationWrapper.targetSet != null && mEndTarget == null) {
            applyTransformUnchecked();
        }
    }

    @Override
    protected boolean onActivityInit(final RecentsActivity activity, Boolean alreadyOnHome) {
        mActivity = activity;
        mRecentsView = activity.getOverviewPanel();
        linkRecentsViewScroll();
        mRecentsView.setDisallowScrollToClearAll(true);
        mRecentsView.getClearAllButton().setVisibilityAlpha(0);

        mRecentsView.setZoomProgress(1);

        if (!mContinuingLastGesture) {
            if (mRunningOverHome) {
                mRecentsView.onGestureAnimationStart(mRunningTaskInfo);
            } else {
                mRecentsView.onGestureAnimationStart(mRunningTaskId);
            }
        }
        setStateOnUiThread(STATE_RECENTS_PRESENT);
        return true;
    }

    @Override
    protected boolean moveWindowWithRecentsScroll() {
        return mInQuickSwitchMode;
    }

    @Override
    public void initWhenReady() {
        if (mInQuickSwitchMode) {
            // Only init if we are in quickswitch mode
            super.initWhenReady();
        }
    }

    @Override
    public void updateDisplacement(float displacement) {
        if (!mInQuickSwitchMode) {
            super.updateDisplacement(displacement);
        }
    }

    @Override
    protected InputConsumer createNewInputProxyHandler() {
        // Just consume all input on the active task
        return InputConsumer.NO_OP;
    }

    @Override
    public void onMotionPauseChanged(boolean isPaused) {
        if (!mInQuickSwitchMode) {
            mIsMotionPaused = isPaused;
            mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
                    .setDuration(150).start();
            performHapticFeedback();
        }
    }

    @Override
    public Intent getLaunchIntent() {
        if (mInQuickSwitchMode || mSwipeUpOverHome) {
            return mOverviewComponentObserver.getOverviewIntent();
        } else {
            return mOverviewComponentObserver.getHomeIntent();
        }
    }

    @Override
    public void updateFinalShift() {
        mTransformParams.setProgress(mCurrentShift.value);
        mRecentsAnimationWrapper.setWindowThresholdCrossed(!mInQuickSwitchMode
                && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD));
        if (mRecentsAnimationWrapper.targetSet != null) {
            applyTransformUnchecked();
        }
    }

    @Override
    public void onGestureCancelled() {
        updateDisplacement(0);
        mEndTarget = LAST_TASK;
        setStateOnUiThread(STATE_GESTURE_CANCELLED);
    }

    @Override
    public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
        mEndVelocityPxPerMs.set(0, velocity.y / 1000);
        if (mInQuickSwitchMode) {
            // For now set it to non-null, it will be reset before starting the animation
            mEndTarget = LAST_TASK;
        } else {
            float flingThreshold = mContext.getResources()
                    .getDimension(R.dimen.quickstep_fling_threshold_velocity);
            boolean isFling = Math.abs(endVelocity) > flingThreshold;

            if (isFling) {
                mEndTarget = endVelocity < 0 ? HOME : LAST_TASK;
            } else if (mIsMotionPaused) {
                mEndTarget = RECENTS;
            } else {
                mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
            }
        }
        setStateOnUiThread(STATE_GESTURE_COMPLETED);
    }

    @Override
    public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
        if (mInQuickSwitchMode && mEndTarget != null) {
            sharedState.canGestureBeContinued = true;
            sharedState.goingToLauncher = false;

            mCanceled = true;
            mCurrentShift.cancelAnimation();
            if (mFinishAnimation != null) {
                mFinishAnimation.cancel();
            }

            if (mRecentsView != null) {
                if (mFinishingRecentsAnimationForNewTaskId != -1) {
                    TaskView newRunningTaskView = mRecentsView.getTaskView(
                            mFinishingRecentsAnimationForNewTaskId);
                    int newRunningTaskId = newRunningTaskView != null
                            ? newRunningTaskView.getTask().key.id
                            : -1;
                    mRecentsView.setCurrentTask(newRunningTaskId);
                    sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
                }
                mRecentsView.setOnScrollChangeListener(null);
            }
        } else {
            setStateOnUiThread(STATE_HANDLER_INVALIDATED);
        }
    }

    private void onHandlerInvalidated() {
        mActivityInitListener.unregister();
        if (mGestureEndCallback != null) {
            mGestureEndCallback.run();
        }
        if (mFinishAnimation != null) {
            mFinishAnimation.end();
        }
    }

    private void onHandlerInvalidatedWithRecents() {
        mRecentsView.onGestureAnimationEnd();
        mRecentsView.setDisallowScrollToClearAll(false);
        mRecentsView.getClearAllButton().setVisibilityAlpha(1);
    }

    private void finishAnimationTargetSetAnimationComplete() {
        switch (mEndTarget) {
            case HOME: {
                if (mSwipeUpOverHome) {
                    mRecentsAnimationWrapper.finish(false, null, false);
                    // Send a home intent to clear the task stack
                    mContext.startActivity(mOverviewComponentObserver.getHomeIntent());
                } else {
                    mRecentsAnimationWrapper.finish(true, null, true);
                }
                break;
            }
            case LAST_TASK:
                mRecentsAnimationWrapper.finish(false, null, false);
                break;
            case RECENTS: {
                if (mSwipeUpOverHome) {
                    mRecentsAnimationWrapper.finish(true, null, true);
                    break;
                }

                ThumbnailData thumbnail =
                        mRecentsAnimationWrapper.targetSet.controller.screenshotTask(mRunningTaskId);
                mRecentsAnimationWrapper.setDeferCancelUntilNextTransition(true /* defer */,
                        false /* screenshot */);

                ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
                ActivityOptionsCompat.setFreezeRecentTasksList(options);

                Bundle extras = new Bundle();
                extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail));
                extras.putInt(EXTRA_TASK_ID, mRunningTaskId);

                Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent())
                        .putExtras(extras);
                mContext.startActivity(intent, options.toBundle());
                mRecentsAnimationWrapper.targetSet.controller.cleanupScreenshot();
                break;
            }
            case NEW_TASK: {
                startNewTask(STATE_HANDLER_INVALIDATED, b -> {});
                break;
            }
        }

        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
    }

    private void finishAnimationTargetSet() {
        if (mInQuickSwitchMode) {
            // Recalculate the end target, some views might have been initialized after
            // gesture has ended.
            if (mRecentsView == null || !mRecentsAnimationWrapper.hasTargets()) {
                mEndTarget = LAST_TASK;
            } else {
                final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
                final int taskToLaunch = mRecentsView.getNextPage();
                mEndTarget = (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
                        ? NEW_TASK : LAST_TASK;
            }
        }

        float endProgress = mEndTarget.mEndProgress;
        long duration = (long) (mEndTarget.mDurationMultiplier *
                Math.abs(endProgress - mCurrentShift.value));
        if (mRecentsView != null) {
            duration = Math.max(duration, mRecentsView.getScroller().getDuration());
        }
        if (mCurrentShift.value != endProgress || mInQuickSwitchMode) {
            AnimationSuccessListener endListener = new AnimationSuccessListener() {

                @Override
                public void onAnimationSuccess(Animator animator) {
                    finishAnimationTargetSetAnimationComplete();
                    mFinishAnimation = null;
                }
            };

            if (mEndTarget == HOME && !mRunningOverHome) {
                RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
                anim.addAnimatorListener(endListener);
                anim.start(mEndVelocityPxPerMs);
                mFinishAnimation = RunningWindowAnim.wrap(anim);
            } else {

                AnimatorSet anim = new AnimatorSet();
                anim.play(mLauncherAlpha.animateToValue(
                        mLauncherAlpha.value, mEndTarget.mLauncherAlpha));
                anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));

                anim.setDuration(duration);
                anim.addListener(endListener);
                anim.start();
                mFinishAnimation = RunningWindowAnim.wrap(anim);
            }

        } else {
            finishAnimationTargetSetAnimationComplete();
        }
    }

    @Override
    public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
        super.onRecentsAnimationStart(targetSet);
        mRecentsAnimationWrapper.enableInputConsumer();

        if (mRunningOverHome) {
            mClipAnimationHelper.prepareAnimation(mDp, true);
        }
        applyTransformUnchecked();

        setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
    }

    @Override
    public void onRecentsAnimationCanceled() {
        mRecentsAnimationWrapper.setController(null);
        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
    }

    /**
     * Creates an animation that transforms the current app window into the home app.
     * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
     */
    private RectFSpringAnim createWindowAnimationToHome(float startProgress, long duration) {
        HomeAnimationFactory factory = new HomeAnimationFactory() {
            @Override
            public RectF getWindowTargetRect() {
                return HomeAnimationFactory.getDefaultWindowTargetRect(mDp);
            }

            @Override
            public AnimatorPlaybackController createActivityAnimationToHome() {
                AnimatorSet anim = new AnimatorSet();
                anim.play(mLauncherAlpha.animateToValue(mLauncherAlpha.value, 1));
                anim.setDuration(duration);
                return AnimatorPlaybackController.wrap(anim, duration);
            }
        };
        return createWindowAnimationToHome(startProgress, factory);
    }
}
