/*
 * 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 static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
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.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
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.util.ViewController;

import java.util.ArrayList;

import javax.inject.Inject;

/**
 * Manages the user switcher on the Keyguard.
 */
@KeyguardUserSwitcherScope
public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {

    private static final String TAG = "KeyguardUserSwitcherController";
    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 final UserSwitcherController mUserSwitcherController;
    private final ScreenLifecycle mScreenLifecycle;
    private final KeyguardUserAdapter mAdapter;
    private final KeyguardStateController mKeyguardStateController;
    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
    protected final SysuiStatusBarStateController mStatusBarStateController;
    private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
    private ObjectAnimator mBgAnimator;
    private final KeyguardUserSwitcherScrim mBackground;

    // Child views of KeyguardUserSwitcherView
    private KeyguardUserSwitcherListView mListView;

    // State info for the user switcher
    private boolean mUserSwitcherOpen;
    private int mCurrentUserId = UserHandle.USER_NULL;
    private int mBarState;
    private float mDarkAmount;

    private final KeyguardUpdateMonitorCallback mInfoCallback =
            new KeyguardUpdateMonitorCallback() {
                @Override
                public void onKeyguardVisibilityChanged(boolean showing) {
                    if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing));
                    // Any time the keyguard is hidden, try to close the user switcher menu to
                    // restore keyguard to the default state
                    if (!showing) {
                        closeSwitcherIfOpenAndNotSimple(false);
                    }
                }

                @Override
                public void onUserSwitching(int userId) {
                    closeSwitcherIfOpenAndNotSimple(false);
                }
            };

    private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
        @Override
        public void onScreenTurnedOff() {
            if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
            closeSwitcherIfOpenAndNotSimple(false);
        }
    };

    private final StatusBarStateController.StateListener mStatusBarStateListener =
            new StatusBarStateController.StateListener() {
                @Override
                public void onStateChanged(int newState) {
                    if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState));

                    boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
                    boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
                    int oldState = mBarState;
                    mBarState = newState;

                    if (mStatusBarStateController.goingToFullShade()
                            || mKeyguardStateController.isKeyguardFadingAway()) {
                        closeSwitcherIfOpenAndNotSimple(true);
                    }

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

                @Override
                public void onDozeAmountChanged(float linearAmount, float amount) {
                    if (DEBUG) {
                        Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f",
                                linearAmount, amount));
                    }
                    setDarkAmount(amount);
                }
            };

    @Inject
    public KeyguardUserSwitcherController(
            KeyguardUserSwitcherView keyguardUserSwitcherView,
            Context context,
            @Main Resources resources,
            LayoutInflater layoutInflater,
            ScreenLifecycle screenLifecycle,
            UserSwitcherController userSwitcherController,
            KeyguardStateController keyguardStateController,
            SysuiStatusBarStateController statusBarStateController,
            KeyguardUpdateMonitor keyguardUpdateMonitor,
            DozeParameters dozeParameters) {
        super(keyguardUserSwitcherView);
        if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController");
        mContext = context;
        mScreenLifecycle = screenLifecycle;
        mUserSwitcherController = userSwitcherController;
        mKeyguardStateController = keyguardStateController;
        mStatusBarStateController = statusBarStateController;
        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
        mAdapter = new KeyguardUserAdapter(mContext, resources, layoutInflater,
                mUserSwitcherController, this);
        mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
                keyguardStateController, dozeParameters);
        mBackground = new KeyguardUserSwitcherScrim(context);
    }

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

        if (DEBUG) Log.d(TAG, "onInit");

        mListView = mView.findViewById(R.id.keyguard_user_switcher_list);

        mView.setOnTouchListener((v, event) -> {
            if (!isListAnimating()) {
                // Hide switcher if it didn't handle the touch event (and block the event from
                // going through).
                return closeSwitcherIfOpenAndNotSimple(true);
            }
            return false;
        });
    }

    @Override
    protected void onViewAttached() {
        if (DEBUG) Log.d(TAG, "onViewAttached");
        mAdapter.registerDataSetObserver(mDataSetObserver);
        mAdapter.notifyDataSetChanged();
        mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
        mStatusBarStateController.addCallback(mStatusBarStateListener);
        mScreenLifecycle.addObserver(mScreenObserver);
        if (isSimpleUserSwitcher()) {
            // Don't use the background for the simple user switcher
            setUserSwitcherOpened(true /* open */, true /* animate */);
        } else {
            mView.addOnLayoutChangeListener(mBackground);
            mView.setBackground(mBackground);
            mBackground.setAlpha(0);
        }
    }

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

        // Detaching the view will always close the switcher
        closeSwitcherIfOpenAndNotSimple(false);

        mAdapter.unregisterDataSetObserver(mDataSetObserver);
        mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
        mStatusBarStateController.removeCallback(mStatusBarStateListener);
        mScreenLifecycle.removeObserver(mScreenObserver);
        mView.removeOnLayoutChangeListener(mBackground);
        mView.setBackground(null);
        mBackground.setAlpha(0);
    }

    /**
     * See:
     *
     * <ul>
     *   <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li>
     *    <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li>
     * </ul>
     *
     * @return true if the user switcher should be open by default on the lock screen.
     * @see android.os.UserManager#isUserSwitcherEnabled()
     */
    public boolean isSimpleUserSwitcher() {
        return mUserSwitcherController.isSimpleUserSwitcher();
    }

    /**
     * @param animate if the transition should be animated
     * @return true if the switcher state changed
     */
    public boolean closeSwitcherIfOpenAndNotSimple(boolean animate) {
        if (isUserSwitcherOpen() && !isSimpleUserSwitcher()) {
            setUserSwitcherOpened(false /* open */, animate);
            return true;
        }
        return false;
    }

    public final DataSetObserver mDataSetObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            refreshUserList();
        }
    };

    void refreshUserList() {
        final int childCount = mListView.getChildCount();
        final int adapterCount = mAdapter.getCount();
        final int count = Math.max(childCount, adapterCount);

        if (DEBUG) {
            Log.d(TAG, String.format("refreshUserList childCount=%d adapterCount=%d", childCount,
                    adapterCount));
        }

        boolean foundCurrentUser = false;
        for (int i = 0; i < count; i++) {
            if (i < adapterCount) {
                View oldView = null;
                if (i < childCount) {
                    oldView = mListView.getChildAt(i);
                }
                KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView)
                        mAdapter.getView(i, oldView, mListView);
                UserSwitcherController.UserRecord userTag =
                        (UserSwitcherController.UserRecord) newView.getTag();
                if (userTag.isCurrent) {
                    if (i != 0) {
                        Log.w(TAG, "Current user is not the first view in the list");
                    }
                    foundCurrentUser = true;
                    mCurrentUserId = userTag.info.id;
                    // Current user is always visible
                    newView.updateVisibilities(true /* showItem */,
                            mUserSwitcherOpen /* showTextName */, false /* animate */);
                } else {
                    // Views for non-current users are always expanded (e.g. they should the name
                    // next to the user icon). However, they could be hidden entirely if the list
                    // is closed.
                    newView.updateVisibilities(mUserSwitcherOpen /* showItem */,
                            true /* showTextName */, false /* animate */);
                }
                newView.setDarkAmount(mDarkAmount);
                if (oldView == null) {
                    // We ran out of existing views. Add it at the end.
                    mListView.addView(newView);
                } else if (oldView != newView) {
                    // We couldn't rebind the view. Replace it.
                    mListView.replaceView(newView, i);
                }
            } else {
                mListView.removeLastView();
            }
        }
        if (!foundCurrentUser) {
            Log.w(TAG, "Current user is not listed");
            mCurrentUserId = UserHandle.USER_NULL;
        }
    }

    /**
     * Set the visibility of the keyguard user switcher view based on some new state.
     */
    public void setKeyguardUserSwitcherVisibility(
            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(mListView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES,
                animate);
        PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
                ANIMATION_PROPERTIES, animate);

        Rect r = new Rect();
        mListView.getDrawingRect(r);
        mView.offsetDescendantRectToMyCoords(mListView, r);
        mBackground.setGradientCenter(
                (int) (mListView.getTranslationX() + r.left + r.width() / 2),
                (int) (mListView.getTranslationY() + r.top + r.height() / 2));
    }

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

    /**
     * Set the amount (ratio) that the device has transitioned to doze.
     *
     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
     */
    private void setDarkAmount(float darkAmount) {
        boolean isFullyDozed = darkAmount == 1;
        if (darkAmount == mDarkAmount) {
            return;
        }
        mDarkAmount = darkAmount;
        mListView.setDarkAmount(darkAmount);
        if (isFullyDozed) {
            closeSwitcherIfOpenAndNotSimple(false);
        }
    }

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

    /**
     * NOTE: switcher state is updated before animations finish.
     *
     * @param animate true to animate transition. The user switcher state (i.e.
     *                {@link #isUserSwitcherOpen()}) is updated before animation is finished.
     */
    private void setUserSwitcherOpened(boolean open, boolean animate) {
        if (DEBUG) {
            Log.d(TAG,
                    String.format("setUserSwitcherOpened: %b -> %b (animate=%b)",
                            mUserSwitcherOpen, open, animate));
        }
        mUserSwitcherOpen = open;
        updateVisibilities(animate);
    }

    private void updateVisibilities(boolean animate) {
        if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate));
        if (mBgAnimator != null) {
            mBgAnimator.cancel();
        }

        if (mUserSwitcherOpen) {
            mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
            mBgAnimator.setDuration(400);
            mBgAnimator.setInterpolator(Interpolators.ALPHA_IN);
            mBgAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    mBgAnimator = null;
                }
            });
            mBgAnimator.start();
        } else {
            mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 255, 0);
            mBgAnimator.setDuration(400);
            mBgAnimator.setInterpolator(Interpolators.ALPHA_OUT);
            mBgAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    mBgAnimator = null;
                }
            });
            mBgAnimator.start();
        }
        mListView.updateVisibilities(mUserSwitcherOpen, animate);
    }

    private boolean isUserSwitcherOpen() {
        return mUserSwitcherOpen;
    }

    static class KeyguardUserAdapter extends
            UserSwitcherController.BaseUserAdapter implements View.OnClickListener {

        private final Context mContext;
        private final Resources mResources;
        private final LayoutInflater mLayoutInflater;
        private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
        private View mCurrentUserView;
        // List of users where the first entry is always the current user
        private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();

        KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater,
                UserSwitcherController controller,
                KeyguardUserSwitcherController keyguardUserSwitcherController) {
            super(controller);
            mContext = context;
            mResources = resources;
            mLayoutInflater = layoutInflater;
            mKeyguardUserSwitcherController = keyguardUserSwitcherController;
        }

        @Override
        public void notifyDataSetChanged() {
            // At this point, value of isSimpleUserSwitcher() may have changed in addition to the
            // data set
            refreshUserOrder();
            super.notifyDataSetChanged();
        }

        void refreshUserOrder() {
            ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
            mUsersOrdered = new ArrayList<>(users.size());
            for (int i = 0; i < users.size(); i++) {
                UserSwitcherController.UserRecord record = users.get(i);
                if (record.isCurrent) {
                    mUsersOrdered.add(0, record);
                } else {
                    mUsersOrdered.add(record);
                }
            }
        }

        @Override
        protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
            return mUsersOrdered;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            UserSwitcherController.UserRecord item = getItem(position);
            return createUserDetailItemView(convertView, parent, item);
        }

        KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
            if (!(convertView instanceof KeyguardUserDetailItemView)
                    || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
                convertView = mLayoutInflater.inflate(
                        R.layout.keyguard_user_switcher_item, parent, false);
            }
            return (KeyguardUserDetailItemView) convertView;
        }

        KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
                UserSwitcherController.UserRecord item) {
            KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
            v.setOnClickListener(this);

            String name = getName(mContext, item);
            if (item.picture == null) {
                v.bind(name, getDrawable(item).mutate(), item.resolveId());
            } else {
                int avatarSize =
                        (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
                Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
                drawable.setColorFilter(
                        item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
                v.bind(name, drawable, item.info.id);
            }
            v.setActivated(item.isCurrent);
            v.setDisabledByAdmin(item.isDisabledByAdmin);
            v.setEnabled(item.isSwitchToEnabled);
            v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);

            if (item.isCurrent) {
                mCurrentUserView = v;
            }
            v.setTag(item);
            return v;
        }

        private Drawable getDrawable(UserSwitcherController.UserRecord item) {
            Drawable drawable;
            if (item.isCurrent && item.isGuest) {
                drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
            } else {
                drawable = getIconDrawable(mContext, item);
            }

            int iconColorRes;
            if (item.isSwitchToEnabled) {
                iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
            } else {
                iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
            }
            drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));

            Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar);
            drawable = new LayerDrawable(new Drawable[]{bg, drawable});
            return drawable;
        }

        @Override
        public void onClick(View v) {
            UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();

            if (mKeyguardUserSwitcherController.isListAnimating()) {
                return;
            }

            if (mKeyguardUserSwitcherController.isUserSwitcherOpen()) {
                if (!user.isCurrent || user.isGuest) {
                    onUserListItemClicked(user);
                } else {
                    mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(
                            true /* animate */);
                }
            } else {
                // If switcher is closed, tapping anywhere in the view will open it
                mKeyguardUserSwitcherController.setUserSwitcherOpened(
                        true /* open */, true /* animate */);
            }
        }
    }
}
