/*
 * 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.launcher3.uioverrides.touchcontrollers;

import static com.android.app.animation.Interpolators.DECELERATE_3;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.newSingleUseCancelListener;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_ALPHA;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;

import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.view.MotionEvent;
import android.view.animation.Interpolator;

import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.contextualeducation.GestureType;

import java.util.function.BiConsumer;

/**
 * Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
 */
public class NavBarToHomeTouchController implements TouchController,
        SingleAxisSwipeDetector.Listener {

    private static final Interpolator PULLBACK_INTERPOLATOR = DECELERATE_3;
    // The min amount of overview scrim we keep during the transition.
    private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f;

    private final Launcher mLauncher;
    private final BiConsumer<AnimatorSet, Long> mCancelSplitRunnable;
    private final SingleAxisSwipeDetector mSwipeDetector;
    private final float mPullbackDistance;

    private boolean mNoIntercept;
    private LauncherState mStartState;
    private LauncherState mEndState = NORMAL;
    private AnimatorPlaybackController mCurrentAnimation;

    /**
     * @param cancelSplitRunnable Called when split placeholder view needs to be cancelled.
     *                            Animation should be added to the provided AnimatorSet
     */
    public NavBarToHomeTouchController(Launcher launcher,
            BiConsumer<AnimatorSet, Long> cancelSplitRunnable) {
        mLauncher = launcher;
        mCancelSplitRunnable = cancelSplitRunnable;
        mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this,
                SingleAxisSwipeDetector.VERTICAL);
        mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance);
    }

    @Override
    public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            mStartState = mLauncher.getStateManager().getState();
            mNoIntercept = !canInterceptTouch(ev);
            if (mNoIntercept) {
                return false;
            }
            mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_POSITIVE,
                    false /* ignoreSlop */);
        }

        if (mNoIntercept) {
            return false;
        }

        onControllerTouchEvent(ev);
        return mSwipeDetector.isDraggingOrSettling();
    }

    private boolean canInterceptTouch(MotionEvent ev) {
        if (!isTrackpadMotionEvent(ev) && DisplayController.getNavigationMode(mLauncher)
                == THREE_BUTTONS) {
            return false;
        }
        boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
        if (!cameFromNavBar) {
            return false;
        }
        if (mStartState.isRecentsViewVisible || mStartState == ALL_APPS) {
            return true;
        }
        int typeToClose = TYPE_ALL & ~TYPE_ALL_APPS_EDU;
        if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, typeToClose) != null) {
            return true;
        }
        return false;
    }

    @Override
    public final boolean onControllerTouchEvent(MotionEvent ev) {
        return mSwipeDetector.onTouchEvent(ev);
    }

    private float getShiftRange() {
        return mLauncher.getDeviceProfile().heightPx;
    }

    @Override
    public void onDragStart(boolean start, float startDisplacement) {
        initCurrentAnimation();
    }

    private void initCurrentAnimation() {
        long accuracy = (long) (getShiftRange() * 2);
        final PendingAnimation builder = new PendingAnimation(accuracy);
        if (mStartState.isRecentsViewVisible) {
            RecentsView recentsView = mLauncher.getOverviewPanel();
            AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher,
                    builder);

            builder.addOnFrameCallback(recentsView::redrawLiveTile);

            AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_TASK_MENU);
        } else if (mStartState == ALL_APPS) {
            AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
            builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_TRANSLATION,
                    -mPullbackDistance, PULLBACK_INTERPOLATOR);
            builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_ALPHA,
                    0.5f, PULLBACK_INTERPOLATOR);
        }
        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
        if (topView != null) {
            topView.addHintCloseAnim(mPullbackDistance, PULLBACK_INTERPOLATOR, builder);
        }
        mCurrentAnimation = builder.createPlaybackController();
        mCurrentAnimation.getTarget().addListener(newSingleUseCancelListener(this::clearState));
    }

    private void clearState() {
        mCurrentAnimation = null;
        mSwipeDetector.finishedScrolling();
        mSwipeDetector.setDetectableScrollConditions(0, false);
    }

    @Override
    public boolean onDrag(float displacement) {
        // Only allow swipe up.
        displacement = Math.min(0, displacement);
        float progress = Utilities.getProgress(displacement, 0, getShiftRange());
        mCurrentAnimation.setPlayFraction(progress);
        return true;
    }

    @Override
    public void onDragEnd(float velocity) {
        boolean fling = mSwipeDetector.isFling(velocity);
        float progress = mCurrentAnimation.getProgressFraction();
        float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(progress);
        boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
                || (velocity < 0 && fling);
        if (success) {
            RecentsView recentsView = mLauncher.getOverviewPanel();
            recentsView.switchToScreenshot(null,
                    () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
            if (mStartState.isRecentsViewVisible) {
                Runnable onReachedHome = () -> {
                    StateManager.StateListener<LauncherState> listener =
                            new StateManager.StateListener<>() {
                                @Override
                                public void onStateTransitionComplete(LauncherState finalState) {
                                    mLauncher.onStateTransitionCompletedAfterSwipeToHome(
                                            finalState);
                                    mLauncher.getStateManager().removeStateListener(this);
                                }
                            };
                    mLauncher.getStateManager().addStateListener(listener);
                    onSwipeInteractionCompleted(mEndState);
                };
                new OverviewToHomeAnim(mLauncher, onReachedHome, mCancelSplitRunnable)
                        .animateWithVelocity(velocity);
            } else {
                mLauncher.getStateManager().goToState(mEndState, true,
                        forSuccessCallback(() -> onSwipeInteractionCompleted(mEndState)));
            }
            if (mStartState != mEndState) {
                logHomeGesture();
                SystemUiProxy.INSTANCE.get(mLauncher).updateContextualEduStats(
                        mSwipeDetector.isTrackpadGesture(), GestureType.HOME);
            }
            AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher);
            if (topOpenView != null) {
                AbstractFloatingView.closeAllOpenViews(mLauncher);
                // TODO: add to WW log
            }
            TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
        } else {
            // Quickly return to the state we came from (we didn't move far).
            ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
            anim.setFloatValues(progress, 0);
            anim.addListener(forSuccessCallback(() -> onSwipeInteractionCompleted(mStartState)));
            anim.setDuration(80).start();
        }
    }

    private void onSwipeInteractionCompleted(LauncherState targetState) {
        clearState();
        mLauncher.getStateManager().goToState(targetState, false /* animated */);
        AccessibilityManagerCompat.sendStateEventToTest(mLauncher, targetState.ordinal);
    }

    private void logHomeGesture() {
        mLauncher.getStatsLogManager().logger()
                .withSrcState(mStartState.statsLogOrdinal)
                .withDstState(mEndState.statsLogOrdinal)
                .log(LAUNCHER_HOME_GESTURE);
    }
}
