/*
 * 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.systemui.biometrics;

import android.content.Context;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

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

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;

public class AuthBiometricFaceView extends AuthBiometricView {

    private static final String TAG = "BiometricPrompt/AuthBiometricFaceView";

    // Delay before dismissing after being authenticated/confirmed.
    private static final int HIDE_DELAY_MS = 500;

    protected static class IconController extends Animatable2.AnimationCallback {
        protected Context mContext;
        protected ImageView mIconView;
        protected TextView mTextView;
        protected Handler mHandler;
        protected boolean mLastPulseLightToDark; // false = dark to light, true = light to dark
        protected @BiometricState int mState;
        protected boolean mDeactivated;

        protected IconController(Context context, ImageView iconView, TextView textView) {
            mContext = context;
            mIconView = iconView;
            mTextView = textView;
            mHandler = new Handler(Looper.getMainLooper());
            showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
        }

        protected void animateOnce(int iconRes) {
            animateIcon(iconRes, false);
        }

        protected void showStaticDrawable(int iconRes) {
            mIconView.setImageDrawable(mContext.getDrawable(iconRes));
        }

        protected void animateIcon(int iconRes, boolean repeat) {
            Log.d(TAG, "animateIcon, state: " + mState + ", deactivated: " + mDeactivated);
            if (mDeactivated) {
                return;
            }

            final AnimatedVectorDrawable icon =
                    (AnimatedVectorDrawable) mContext.getDrawable(iconRes);
            mIconView.setImageDrawable(icon);
            icon.forceAnimationOnUI();
            if (repeat) {
                icon.registerAnimationCallback(this);
            }
            icon.start();
        }

        protected void startPulsing() {
            mLastPulseLightToDark = false;
            animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true);
        }

        protected void pulseInNextDirection() {
            int iconRes = mLastPulseLightToDark ? R.drawable.face_dialog_pulse_dark_to_light
                    : R.drawable.face_dialog_pulse_light_to_dark;
            animateIcon(iconRes, true /* repeat */);
            mLastPulseLightToDark = !mLastPulseLightToDark;
        }

        @Override
        public void onAnimationEnd(Drawable drawable) {
            super.onAnimationEnd(drawable);
            Log.d(TAG, "onAnimationEnd, mState: " + mState + ", deactivated: " + mDeactivated);
            if (mDeactivated) {
                return;
            }

            if (mState == STATE_AUTHENTICATING || mState == STATE_HELP) {
                pulseInNextDirection();
            }
        }

        protected void deactivate() {
            mDeactivated = true;
        }

        protected void updateState(int lastState, int newState) {
            if (mDeactivated) {
                Log.w(TAG, "Ignoring updateState when deactivated: " + newState);
                return;
            }

            final boolean lastStateIsErrorIcon =
                    lastState == STATE_ERROR || lastState == STATE_HELP;

            if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
                showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
                mIconView.setContentDescription(mContext.getString(
                        R.string.biometric_dialog_face_icon_description_authenticating));
            } else if (newState == STATE_AUTHENTICATING) {
                startPulsing();
                mIconView.setContentDescription(mContext.getString(
                        R.string.biometric_dialog_face_icon_description_authenticating));
            } else if (lastState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
                animateOnce(R.drawable.face_dialog_dark_to_checkmark);
                mIconView.setContentDescription(mContext.getString(
                        R.string.biometric_dialog_face_icon_description_confirmed));
            } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
                animateOnce(R.drawable.face_dialog_error_to_idle);
                mIconView.setContentDescription(mContext.getString(
                        R.string.biometric_dialog_face_icon_description_idle));
            } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
                animateOnce(R.drawable.face_dialog_dark_to_checkmark);
                mIconView.setContentDescription(mContext.getString(
                        R.string.biometric_dialog_face_icon_description_authenticated));
            } else if (newState == STATE_ERROR && lastState != STATE_ERROR) {
                animateOnce(R.drawable.face_dialog_dark_to_error);
            } else if (lastState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
                animateOnce(R.drawable.face_dialog_dark_to_checkmark);
                mIconView.setContentDescription(mContext.getString(
                        R.string.biometric_dialog_face_icon_description_authenticated));
            } else if (newState == STATE_PENDING_CONFIRMATION) {
                animateOnce(R.drawable.face_dialog_wink_from_dark);
                mIconView.setContentDescription(mContext.getString(
                        R.string.biometric_dialog_face_icon_description_authenticated));
            } else if (newState == STATE_IDLE) {
                showStaticDrawable(R.drawable.face_dialog_idle_static);
                mIconView.setContentDescription(mContext.getString(
                        R.string.biometric_dialog_face_icon_description_idle));
            } else {
                Log.w(TAG, "Unhandled state: " + newState);
            }
            mState = newState;
        }
    }

    @Nullable @VisibleForTesting IconController mFaceIconController;
    @NonNull private final OnAttachStateChangeListener mOnAttachStateChangeListener =
            new OnAttachStateChangeListener() {
        @Override
        public void onViewAttachedToWindow(View v) {

        }

        @Override
        public void onViewDetachedFromWindow(View v) {
            mFaceIconController.deactivate();
        }
    };

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

    public AuthBiometricFaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @VisibleForTesting
    AuthBiometricFaceView(Context context, AttributeSet attrs, Injector injector) {
        super(context, attrs, injector);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mFaceIconController = new IconController(mContext, mIconView, mIndicatorView);

        addOnAttachStateChangeListener(mOnAttachStateChangeListener);
    }

    @Override
    protected int getDelayAfterAuthenticatedDurationMs() {
        return HIDE_DELAY_MS;
    }

    @Override
    protected int getStateForAfterError() {
        return STATE_IDLE;
    }

    @Override
    protected void handleResetAfterError() {
        resetErrorView();
    }

    @Override
    protected void handleResetAfterHelp() {
        resetErrorView();
    }

    @Override
    protected boolean supportsSmallDialog() {
        return true;
    }

    @Override
    protected boolean supportsManualRetry() {
        return true;
    }

    @Override
    public void updateState(@BiometricState int newState) {
        mFaceIconController.updateState(mState, newState);

        if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
                (newState == STATE_AUTHENTICATING && getSize() == AuthDialog.SIZE_MEDIUM)) {
            resetErrorView();
        }

        // Do this last since the state variable gets updated.
        super.updateState(newState);
    }

    @Override
    public void onAuthenticationFailed(@Modality int modality, @Nullable String failureReason) {
        if (getSize() == AuthDialog.SIZE_MEDIUM) {
            if (supportsManualRetry()) {
                mTryAgainButton.setVisibility(View.VISIBLE);
                mConfirmButton.setVisibility(View.GONE);
            }
        }

        // Do this last since we want to know if the button is being animated (in the case of
        // small -> medium dialog)
        super.onAuthenticationFailed(modality, failureReason);
    }

    private void resetErrorView() {
        mIndicatorView.setTextColor(mTextColorHint);
        mIndicatorView.setVisibility(View.INVISIBLE);
    }
}
