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

import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;

import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;

import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;

import androidx.constraintlayout.motion.widget.MotionLayout;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;

import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternView;
import com.android.settingslib.animation.AppearAnimationCreator;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
import com.android.systemui.Flags;
import com.android.systemui.bouncer.shared.constants.PatternBouncerConstants.ColorId;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;

public class KeyguardPatternView extends KeyguardInputView
        implements AppearAnimationCreator<LockPatternView.CellState> {

    private static final String TAG = "SecurityPatternView";
    private static final boolean DEBUG = KeyguardConstants.DEBUG;


    // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
    private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;

    // How much we scale up the duration of the disappear animation when the current user is locked
    public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f;

    // Extra padding, in pixels, that should eat touch events.
    private static final int PATTERNS_TOUCH_AREA_EXTENSION = 40;

    private final AppearAnimationUtils mAppearAnimationUtils;
    private final DisappearAnimationUtils mDisappearAnimationUtils;
    private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
    private final int[] mTmpPosition = new int[2];
    private final Rect mTempRect = new Rect();
    private final Rect mLockPatternScreenBounds = new Rect();

    private LockPatternView mLockPatternView;

    /**
     * Keeps track of the last time we poked the wake lock during dispatching of the touch event.
     * Initialized to something guaranteed to make us poke the wakelock when the user starts
     * drawing the pattern.
     * @see #dispatchTouchEvent(android.view.MotionEvent)
     */
    private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;

    BouncerKeyguardMessageArea mSecurityMessageDisplay;
    private View mEcaView;
    @Nullable private MotionLayout mContainerMotionLayout;
    // TODO (b/293252410) - usage of mContainerConstraintLayout should be removed
    //  when the flag is enabled/removed
    @Nullable private ConstraintLayout mContainerConstraintLayout;
    private boolean mAlreadyUsingSplitBouncer = false;
    private boolean mIsSmallLockScreenLandscapeEnabled = false;
    @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;

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

    public KeyguardPatternView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mAppearAnimationUtils = new AppearAnimationUtils(context,
                AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */,
                2.0f /* delayScale */, AnimationUtils.loadInterpolator(
                        mContext, android.R.interpolator.linear_out_slow_in));
        mDisappearAnimationUtils = new DisappearAnimationUtils(context,
                125, 1.2f /* translationScale */,
                0.6f /* delayScale */, AnimationUtils.loadInterpolator(
                        mContext, android.R.interpolator.fast_out_linear_in));
        mDisappearAnimationUtilsLocked = new DisappearAnimationUtils(context,
                (long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */,
                0.6f /* delayScale */, AnimationUtils.loadInterpolator(
                mContext, android.R.interpolator.fast_out_linear_in));
    }

    /**
     * Use motion layout (new bouncer implementation) if LOCKSCREEN_ENABLE_LANDSCAPE flag is
     * enabled, instead of constraint layout (old bouncer implementation)
     */
    public void setIsLockScreenLandscapeEnabled(boolean isLockScreenLandscapeEnabled) {
        mIsSmallLockScreenLandscapeEnabled = isLockScreenLandscapeEnabled;
        findContainerLayout();
    }

    private void findContainerLayout() {
        if (mIsSmallLockScreenLandscapeEnabled) {
            mContainerMotionLayout = findViewById(R.id.pattern_container);
        } else {
            mContainerConstraintLayout = findViewById(R.id.pattern_container);
        }
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        updateMargins();
    }

    void onDevicePostureChanged(@DevicePostureInt int posture) {
        if (mLastDevicePosture == posture) return;
        mLastDevicePosture = posture;

        if (mIsSmallLockScreenLandscapeEnabled) {
            boolean useSplitBouncerAfterFold =
                    mLastDevicePosture == DEVICE_POSTURE_CLOSED
                    && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE
                    && getResources().getBoolean(R.bool.update_bouncer_constraints);

            if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
                updateConstraints(useSplitBouncerAfterFold);
            }
        }

        updateMargins();
    }

    private void updateMargins() {
        if (mIsSmallLockScreenLandscapeEnabled) {
            updateHalfFoldedConstraints();
        } else {
            updateHalfFoldedGuideline();
        }
    }

    private void updateHalfFoldedConstraints() {
        // Update the constraints based on the device posture...
        if (mAlreadyUsingSplitBouncer) return;

        boolean shouldCollapsePattern =
                mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
                        && mContext.getResources().getConfiguration().orientation
                        == ORIENTATION_PORTRAIT;

        int expectedMotionLayoutState = shouldCollapsePattern
                ? R.id.half_folded_single_constraints
                : R.id.single_constraints;

        transitionToMotionLayoutState(expectedMotionLayoutState);
    }

    // TODO (b/293252410) - this method can be removed when the flag is enabled/removed
    private void updateHalfFoldedGuideline() {
        // Update the guideline based on the device posture...
        float halfOpenPercentage =
                mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);

        ConstraintSet cs = new ConstraintSet();
        cs.clone(mContainerConstraintLayout);
        cs.setGuidelinePercent(R.id.pattern_top_guideline,
                mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
        cs.applyTo(mContainerConstraintLayout);
    }

    private void transitionToMotionLayoutState(int state) {
        if (mContainerMotionLayout.getCurrentState() != state) {
            mContainerMotionLayout.transitionToState(state);
        }
    }

    /**
     * Updates the keyguard view's constraints (single or split constraints).
     * Split constraints are only used for small landscape screens.
     * Only called when flag LANDSCAPE_ENABLE_LOCKSCREEN is enabled.
     */
    @Override
    protected void updateConstraints(boolean useSplitBouncer) {
        if (!mIsSmallLockScreenLandscapeEnabled) return;

        mAlreadyUsingSplitBouncer = useSplitBouncer;

        if (useSplitBouncer) {
            mContainerMotionLayout.jumpToState(R.id.split_constraints);
            mContainerMotionLayout.setMaxWidth(Integer.MAX_VALUE);
        } else {
            boolean useHalfFoldedConstraints =
                    mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
                            && mContext.getResources().getConfiguration().orientation
                            == ORIENTATION_PORTRAIT;

            if (useHalfFoldedConstraints) {
                mContainerMotionLayout.jumpToState(R.id.half_folded_single_constraints);
            } else {
                mContainerMotionLayout.jumpToState(R.id.single_constraints);
            }
            mContainerMotionLayout.setMaxWidth(getResources()
                    .getDimensionPixelSize(R.dimen.biometric_auth_pattern_view_max_size));
        }
    }

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

        mLockPatternView = findViewById(R.id.lockPatternView);
        if (Flags.bouncerUiRevamp2()) {
            mLockPatternView.setDotColors(mContext.getColor(ColorId.dotColor), mContext.getColor(
                    ColorId.activatedDotColor));
            mLockPatternView.setColors(mContext.getColor(ColorId.pathColor), 0, 0);
            mLockPatternView.setDotSizes(
                    getResources().getDimensionPixelSize(R.dimen.keyguard_pattern_dot_size),
                    getResources().getDimensionPixelSize(
                            R.dimen.keyguard_pattern_activated_dot_size));
            mLockPatternView.setPathWidth(
                    getResources().getDimensionPixelSize(R.dimen.keyguard_pattern_stroke_width));
        }

        mEcaView = findViewById(R.id.keyguard_selector_fade_container);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mSecurityMessageDisplay = findViewById(R.id.bouncer_message_area);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean result = super.onTouchEvent(ev);
        // as long as the user is entering a pattern (i.e sending a touch event that was handled
        // by this screen), keep poking the wake lock so that the screen will stay on.
        final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime;
        if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
            mLastPokeTime = SystemClock.elapsedRealtime();
        }
        mTempRect.set(0, 0, 0, 0);
        offsetRectIntoDescendantCoords(mLockPatternView, mTempRect);
        ev.offsetLocation(mTempRect.left, mTempRect.top);
        result = mLockPatternView.dispatchTouchEvent(ev) || result;
        ev.offsetLocation(-mTempRect.left, -mTempRect.top);
        return result;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        mLockPatternView.getLocationOnScreen(mTmpPosition);
        mLockPatternScreenBounds.set(mTmpPosition[0] - PATTERNS_TOUCH_AREA_EXTENSION,
                mTmpPosition[1] - PATTERNS_TOUCH_AREA_EXTENSION,
                mTmpPosition[0] + mLockPatternView.getWidth() + PATTERNS_TOUCH_AREA_EXTENSION,
                mTmpPosition[1] + mLockPatternView.getHeight() + PATTERNS_TOUCH_AREA_EXTENSION);
    }

    @Override
    boolean disallowInterceptTouch(MotionEvent event) {
        return !mLockPatternView.isEmpty()
                || mLockPatternScreenBounds.contains((int) event.getRawX(), (int) event.getRawY());
    }

    public void startAppearAnimation() {
        enableClipping(false);
        setAlpha(0f);
        setTranslationY(mAppearAnimationUtils.getStartTranslation());
        AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */,
                0, mAppearAnimationUtils.getInterpolator(),
                getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_APPEAR));
        mLockPatternView.post(() -> {
            setAlpha(1f);
            mAppearAnimationUtils.startAnimation2d(
                    mLockPatternView.getCellStates(),
                    () -> {
                        enableClipping(true);
                        mLockPatternView.invalidate();
                    },
                    KeyguardPatternView.this);
        });
        if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
            mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
                    AppearAnimationUtils.DEFAULT_APPEAR_DURATION,
                    mAppearAnimationUtils.getStartTranslation(),
                    true /* appearing */,
                    mAppearAnimationUtils.getInterpolator(),
                    null /* finishRunnable */);
        }
    }

    public boolean startDisappearAnimation(boolean needsSlowUnlockTransition,
            final Runnable finishRunnable) {
        float durationMultiplier = needsSlowUnlockTransition ? DISAPPEAR_MULTIPLIER_LOCKED : 1f;
        mLockPatternView.clearPattern();
        enableClipping(false);
        setTranslationY(0);
        AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */,
                (long) (300 * durationMultiplier),
                -mDisappearAnimationUtils.getStartTranslation(),
                mDisappearAnimationUtils.getInterpolator(),
                getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR));

        DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
                ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils;
        disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
                () -> {
                    enableClipping(true);
                    if (finishRunnable != null) {
                        finishRunnable.run();
                    }
                }, KeyguardPatternView.this);
        if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
            mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
                    (long) (200 * durationMultiplier),
                    -mDisappearAnimationUtils.getStartTranslation() * 3,
                    false /* appearing */,
                    mDisappearAnimationUtils.getInterpolator(),
                    null /* finishRunnable */);
        }
        return true;
    }

    private void enableClipping(boolean enable) {
        if (mContainerConstraintLayout != null) {
            setClipChildren(enable);
            mContainerConstraintLayout.setClipToPadding(enable);
            mContainerConstraintLayout.setClipChildren(enable);
        }
        if (mContainerMotionLayout != null) {
            setClipChildren(enable);
            mContainerMotionLayout.setClipToPadding(enable);
            mContainerMotionLayout.setClipChildren(enable);
        }
    }

    @Override
    public void createAnimation(final LockPatternView.CellState animatedCell, long delay,
            long duration, float translationY, final boolean appearing,
            Interpolator interpolator,
            final Runnable finishListener) {
        mLockPatternView.startCellStateAnimation(animatedCell,
                1f, appearing ? 1f : 0f, /* alpha */
                appearing ? translationY : 0f, appearing ? 0f : translationY, /* translation */
                appearing ? 0f : 1f, 1f /* scale */,
                delay, duration, interpolator, finishListener);
        if (finishListener != null) {
            // Also animate the Emergency call
            mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY,
                    appearing, interpolator, null);
        }
    }

    @Override
    public boolean hasOverlappingRendering() {
        return false;
    }

    @Override
    public CharSequence getTitle() {
        return getResources().getString(
                com.android.internal.R.string.keyguard_accessibility_pattern_unlock);
    }
}
