/*
 * Copyright (C) 2021 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.statusbar.policy;

import android.content.Context;
import android.content.res.Resources;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardVisibilityHelper;
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.R;
import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.user.UserSwitchDialogController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.UserAvatarView;
import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.util.ViewController;

import javax.inject.Inject;

/**
 * Manages the user switch on the Keyguard that is used for opening the QS user panel.
 */
@KeyguardUserSwitcherScope
public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> {

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

    private static final AnimationProperties ANIMATION_PROPERTIES =
            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);

    private final Context mContext;
    private Resources mResources;
    private final UserSwitcherController mUserSwitcherController;
    private BaseUserSwitcherAdapter mAdapter;
    private final KeyguardStateController mKeyguardStateController;
    private final FalsingManager mFalsingManager;
    protected final SysuiStatusBarStateController mStatusBarStateController;
    private final ConfigurationController mConfigurationController;
    private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
    private final UserSwitchDialogController mUserSwitchDialogController;
    private final UiEventLogger mUiEventLogger;
    @VisibleForTesting
    UserAvatarView mUserAvatarView;
    private View mUserAvatarViewWithBackground;
    UserRecord mCurrentUser;
    private boolean mIsKeyguardShowing;

    // State info for the user switch and keyguard
    private int mBarState;

    private final StatusBarStateController.StateListener mStatusBarStateListener =
            new StatusBarStateController.StateListener() {
                @Override
                public void onStateChanged(int newState) {
                    boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
                    boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
                    int oldState = mBarState;
                    mBarState = newState;

                    setKeyguardQsUserSwitchVisibility(
                            newState,
                            keyguardFadingAway,
                            goingToFullShade,
                            oldState);
                }
            };

    private ConfigurationController.ConfigurationListener mConfigurationListener =
            new ConfigurationController.ConfigurationListener() {

                @Override
                public void onUiModeChanged() {
                    // Force update when dark theme toggled. Otherwise, icon will not update
                    // until it is clicked
                    if (mIsKeyguardShowing) {
                        updateView();
                    }
                }
            };

    private final KeyguardStateController.Callback mKeyguardStateCallback =
            new KeyguardStateController.Callback() {
                @Override
                public void onUnlockedChanged() {
                    updateKeyguardShowing(false /* forceViewUpdate */);
                }

                @Override
                public void onKeyguardShowingChanged() {
                    updateKeyguardShowing(false /* forceViewUpdate */);
                }

                @Override
                public void onKeyguardFadingAwayChanged() {
                    updateKeyguardShowing(false /* forceViewUpdate */);
                }
            };

    @Inject
    public KeyguardQsUserSwitchController(
            FrameLayout view,
            Context context,
            @Main Resources resources,
            UserSwitcherController userSwitcherController,
            KeyguardStateController keyguardStateController,
            FalsingManager falsingManager,
            ConfigurationController configurationController,
            SysuiStatusBarStateController statusBarStateController,
            DozeParameters dozeParameters,
            ScreenOffAnimationController screenOffAnimationController,
            UserSwitchDialogController userSwitchDialogController,
            UiEventLogger uiEventLogger) {
        super(view);
        if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
        mContext = context;
        mResources = resources;
        mUserSwitcherController = userSwitcherController;
        mKeyguardStateController = keyguardStateController;
        mFalsingManager = falsingManager;
        mConfigurationController = configurationController;
        mStatusBarStateController = statusBarStateController;
        mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
                keyguardStateController, dozeParameters,
                screenOffAnimationController,  /* animateYPos= */ false, /* logBuffer= */ null);
        mUserSwitchDialogController = userSwitchDialogController;
        mUiEventLogger = uiEventLogger;
    }

    @Override
    protected void onInit() {
        super.onInit();
        if (DEBUG) Log.d(TAG, "onInit");
        mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar);
        mUserAvatarViewWithBackground = mView.findViewById(
                R.id.kg_multi_user_avatar_with_background);
        mAdapter = new BaseUserSwitcherAdapter(mUserSwitcherController) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                return null;
            }
        };

        mUserAvatarView.setOnClickListener(v -> {
            if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
                return;
            }
            if (isListAnimating()) {
                return;
            }

            // Tapping anywhere in the view will open the user switcher
            mUiEventLogger.log(
                    LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);

            mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(),
                    Expandable.fromView(mUserAvatarViewWithBackground));
        });

        mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                super.onInitializeAccessibilityNodeInfo(host, info);
                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
                        AccessibilityNodeInfo.ACTION_CLICK,
                        mContext.getString(
                                R.string.accessibility_quick_settings_choose_user_action)));
            }
        });
    }

    @Override
    protected void onViewAttached() {
        if (DEBUG) Log.d(TAG, "onViewAttached");
        mAdapter.registerDataSetObserver(mDataSetObserver);
        mDataSetObserver.onChanged();
        mStatusBarStateController.addCallback(mStatusBarStateListener);
        mConfigurationController.addCallback(mConfigurationListener);
        mKeyguardStateController.addCallback(mKeyguardStateCallback);
        // Force update when view attached in case configuration changed while the view was detached
        updateCurrentUser();
        updateKeyguardShowing(true /* forceViewUpdate */);
    }

    @Override
    protected void onViewDetached() {
        if (DEBUG) Log.d(TAG, "onViewDetached");

        mAdapter.unregisterDataSetObserver(mDataSetObserver);
        mStatusBarStateController.removeCallback(mStatusBarStateListener);
        mConfigurationController.removeCallback(mConfigurationListener);
        mKeyguardStateController.removeCallback(mKeyguardStateCallback);
    }

    public final DataSetObserver mDataSetObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            boolean userChanged = updateCurrentUser();
            if (userChanged || (mIsKeyguardShowing && mUserAvatarView.isEmpty())) {
                updateView();
            }
        }
    };

    private void clearAvatar() {
        if (DEBUG) Log.d(TAG, "clearAvatar");
        mUserAvatarView.setAvatar(null);
    }

    /**
     * @param forceViewUpdate whether view should be updated regardless of whether
     *                        keyguard-showing state changed
     */
    @VisibleForTesting
    void updateKeyguardShowing(boolean forceViewUpdate) {
        boolean wasKeyguardShowing = mIsKeyguardShowing;
        mIsKeyguardShowing = mKeyguardStateController.isShowing()
                || mKeyguardStateController.isKeyguardGoingAway();
        if (wasKeyguardShowing == mIsKeyguardShowing && !forceViewUpdate) {
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "updateKeyguardShowing:"
                    + " mIsKeyguardShowing=" + mIsKeyguardShowing
                    + " forceViewUpdate=" + forceViewUpdate);
        }
        if (mIsKeyguardShowing) {
            updateView();
        } else {
            clearAvatar();
        }
    }

    /**
     * @return true if the current user has changed
     */
    private boolean updateCurrentUser() {
        UserRecord previousUser = mCurrentUser;
        mCurrentUser = null;
        for (int i = 0; i < mAdapter.getCount(); i++) {
            UserRecord r = mAdapter.getItem(i);
            if (r.isCurrent) {
                mCurrentUser = r;
                return !mCurrentUser.equals(previousUser);
            }
        }
        return mCurrentUser == null && previousUser != null;
    }

    private String getContentDescription() {
        if (mCurrentUser != null && mCurrentUser.info != null
                && !TextUtils.isEmpty(mCurrentUser.info.name)) {
            // If we know the current user's name, have TalkBack to announce "Signed in as [user
            // name]" when the icon is selected
            return mContext.getString(
                    R.string.accessibility_quick_settings_user, mCurrentUser.info.name);
        } else {
            // As a fallback, have TalkBack announce "Switch user"
            return mContext.getString(R.string.accessibility_multi_user_switch_switcher);
        }
    }

    private void updateView() {
        if (DEBUG) Log.d(TAG, "updateView");
        mUserAvatarView.setContentDescription(getContentDescription());
        int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL;
        mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
    }

    Drawable getCurrentUserIcon() {
        Drawable drawable;
        if (mCurrentUser == null || mCurrentUser.picture == null) {
            if (mCurrentUser != null && mCurrentUser.isGuest) {
                drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
            } else {
                drawable = mContext.getDrawable(R.drawable.ic_avatar_user);
            }
            int iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
            drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
        } else {
            int avatarSize = (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
            drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize);
        }

        Drawable bg = mContext.getDrawable(com.android.settingslib.R.drawable.user_avatar_bg);
        drawable = new LayerDrawable(new Drawable[]{bg, drawable});
        return drawable;
    }

    /**
     * Get the height of the keyguard user switcher view when closed.
     */
    public int getUserIconHeight() {
        return mUserAvatarView.getHeight();
    }

    /**
     * Set the visibility of the user avatar view based on some new state.
     */
    public void setKeyguardQsUserSwitchVisibility(
            int statusBarState,
            boolean keyguardFadingAway,
            boolean goingToFullShade,
            int oldStatusBarState) {
        mKeyguardVisibilityHelper.setViewVisibility(
                statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
    }

    /**
     * Update position of the view with an optional animation
     */
    public void updatePosition(int x, int y, boolean animate) {
        PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate);
        PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
                ANIMATION_PROPERTIES, animate);
    }

    /**
     * Set keyguard user avatar view alpha.
     */
    public void setAlpha(float alpha) {
        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
            mView.setAlpha(alpha);
        }
    }

    private boolean isListAnimating() {
        return mKeyguardVisibilityHelper.isVisibilityAnimating();
    }
}
