/*
 * 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.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_APPEAR;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
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.animation.ValueAnimator;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
import android.util.MathUtils;
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.app.animation.Interpolators;
import com.android.settingslib.animation.DisappearAnimationUtils;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;

/**
 * Displays a PIN pad for unlocking.
 */
public class KeyguardPINView extends KeyguardPinBasedInputView {

    ValueAnimator mAppearAnimator = ValueAnimator.ofFloat(0f, 1f);
    private final DisappearAnimationUtils mDisappearAnimationUtils;
    private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
    @Nullable private MotionLayout mContainerMotionLayout;
    // TODO (b/293252410) - usage of mContainerConstraintLayout should be removed
    //  when the flag is enabled/removed
    @Nullable private ConstraintLayout mContainerConstraintLayout;
    private int mDisappearYTranslation;
    private View[][] mViews;
    private int mYTrans;
    private int mYTransOffset;
    private View mBouncerMessageArea;
    private boolean mAlreadyUsingSplitBouncer = false;
    private boolean mIsSmallLockScreenLandscapeEnabled = false;
    @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
    public static final long ANIMATION_DURATION = 650;

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

    public KeyguardPINView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mDisappearAnimationUtils = new DisappearAnimationUtils(context,
                125, 0.6f /* translationScale */,
                0.45f /* delayScale */, AnimationUtils.loadInterpolator(
                        mContext, android.R.interpolator.fast_out_linear_in));
        mDisappearAnimationUtilsLocked = new DisappearAnimationUtils(context,
                (long) (125 * KeyguardPatternView.DISAPPEAR_MULTIPLIER_LOCKED),
                0.6f /* translationScale */,
                0.45f /* delayScale */, AnimationUtils.loadInterpolator(
                       mContext, android.R.interpolator.fast_out_linear_in));
        mDisappearYTranslation = getResources().getDimensionPixelSize(
                R.dimen.disappear_y_translation);
        mYTrans = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
        mYTransOffset = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry_offset);
    }

    /** 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.pin_container);
        } else {
            mContainerConstraintLayout = findViewById(R.id.pin_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();
    }

    @Override
    protected void resetState() {
    }

    @Override
    protected int getPasswordTextViewId() {
        return R.id.pinEntry;
    }

    private void updateMargins() {
        // Re-apply everything to the keys...
        int bottomMargin = mContext.getResources().getDimensionPixelSize(
                R.dimen.num_pad_entry_row_margin_bottom);
        int rightMargin = mContext.getResources().getDimensionPixelSize(
                R.dimen.num_pad_key_margin_end);
        String ratio = mContext.getResources().getString(R.string.num_pad_key_ratio);

        // mView contains all Views that make up the PIN pad; row0 = the entry test field, then
        // rows 1-4 contain the buttons. Iterate over all views that make up the buttons in the pad,
        // and re-set all the margins.
        for (int row = 1; row < 5; row++) {
            for (int column = 0; column < 3; column++) {
                View key = mViews[row][column];

                ConstraintLayout.LayoutParams lp =
                        (ConstraintLayout.LayoutParams) key.getLayoutParams();

                lp.dimensionRatio = ratio;

                // Don't set any margins on the last row of buttons.
                if (row != 4) {
                    lp.bottomMargin = bottomMargin;
                }

                // Don't set margins on the rightmost buttons.
                if (column != 2) {
                    lp.rightMargin = rightMargin;
                }

                key.setLayoutParams(lp);
            }
        }

        if (mIsSmallLockScreenLandscapeEnabled) {
            updateHalfFoldedConstraints();
        } else {
            updateHalfFoldedGuideline();
        }
    }

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

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

        int expectedMotionLayoutState = shouldCollapsePin
                ? 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.pin_pad_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.keyguard_security_width));
        }
    }

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

        mBouncerMessageArea = findViewById(R.id.bouncer_message_area);
        mViews = new View[][]{
                new View[]{
                        findViewById(R.id.row0), null, null
                },
                new View[]{
                        findViewById(R.id.key1), findViewById(R.id.key2),
                        findViewById(R.id.key3)
                },
                new View[]{
                        findViewById(R.id.key4), findViewById(R.id.key5),
                        findViewById(R.id.key6)
                },
                new View[]{
                        findViewById(R.id.key7), findViewById(R.id.key8),
                        findViewById(R.id.key9)
                },
                new View[]{
                        findViewById(R.id.delete_button), findViewById(R.id.key0),
                        findViewById(R.id.key_enter)
                },
                new View[]{
                        null, mEcaView, null
                }};
    }

    @Override
    public int getWrongPasswordStringId() {
        return R.string.kg_wrong_pin;
    }

    @Override
    public void startAppearAnimation() {
        setAlpha(1f);
        setTranslationY(0);
        if (mAppearAnimator.isRunning()) {
            mAppearAnimator.cancel();
        }
        mAppearAnimator.setDuration(ANIMATION_DURATION);
        mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction()));
        mAppearAnimator.addListener(getAnimationListener(CUJ_LOCKSCREEN_PIN_APPEAR));
        mAppearAnimator.start();
    }

    public boolean startDisappearAnimation(boolean needsSlowUnlockTransition,
            final Runnable finishRunnable) {
        if (mAppearAnimator.isRunning()) {
            mAppearAnimator.cancel();
        }

        setTranslationY(0);
        DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
                        ? mDisappearAnimationUtilsLocked
                        : mDisappearAnimationUtils;
        disappearAnimationUtils.createAnimation(
                this, 0, 200, mDisappearYTranslation, false,
                mDisappearAnimationUtils.getInterpolator(), () -> {
                    if (finishRunnable != null) {
                        finishRunnable.run();
                    }
                },
                getAnimationListener(CUJ_LOCKSCREEN_PIN_DISAPPEAR));
        return true;
    }

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

    /** Animate subviews according to expansion or time. */
    private void animate(float progress) {
        Interpolator standardDecelerate = Interpolators.STANDARD_DECELERATE;
        Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE;
        float standardProgress = standardDecelerate.getInterpolation(progress);

        mBouncerMessageArea.setTranslationY(
                mYTrans - mYTrans * standardProgress);
        mBouncerMessageArea.setAlpha(standardProgress);

        for (int i = 0; i < mViews.length; i++) {
            View[] row = mViews[i];
            for (View view : row) {
                if (view == null) {
                    continue;
                }

                float scaledProgress = legacyDecelerate.getInterpolation(MathUtils.constrain(
                        (progress - 0.075f * i) / (1f - 0.075f * mViews.length),
                        0f,
                        1f
                ));
                view.setAlpha(scaledProgress);
                int yDistance = mYTrans + mYTransOffset * i;
                view.setTranslationY(
                        yDistance - (yDistance * standardProgress));
                if (view instanceof NumPadAnimationListener) {
                    ((NumPadAnimationListener) view).setProgress(scaledProgress);
                }
            }
        }
    }
}
