/*
 * Copyright (C) 2017 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.settings.password;

import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;

import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.PasswordMetrics;
import android.content.Context;
import android.os.UserHandle;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;

import java.util.ArrayList;
import java.util.List;

/**
 * A controller for ChooseLockGeneric, and other similar classes which shows a list of possible
 * screen locks for the user to choose from.
 */
public class ChooseLockGenericController {

    private final Context mContext;
    private final int mUserId;
    @PasswordComplexity private final int mRequestedMinComplexity;
    private ManagedLockPasswordProvider mManagedPasswordProvider;
    private DevicePolicyManager mDpm;
    private final LockPatternUtils mLockPatternUtils;

    public ChooseLockGenericController(Context context, int userId) {
        this(
                context,
                userId,
                PASSWORD_COMPLEXITY_NONE,
                new LockPatternUtils(context));
    }

    /**
     * @param requestedMinComplexity specifies the min password complexity to be taken into account
     *                               when determining the available screen lock types
     */
    public ChooseLockGenericController(Context context, int userId,
            @PasswordComplexity int requestedMinComplexity, LockPatternUtils lockPatternUtils) {
        this(
                context,
                userId,
                requestedMinComplexity,
                context.getSystemService(DevicePolicyManager.class),
                ManagedLockPasswordProvider.get(context, userId),
                lockPatternUtils);
    }

    @VisibleForTesting
    ChooseLockGenericController(
            Context context,
            int userId,
            @PasswordComplexity int requestedMinComplexity,
            DevicePolicyManager dpm,
            ManagedLockPasswordProvider managedLockPasswordProvider,
            LockPatternUtils lockPatternUtils) {
        mContext = context;
        mUserId = userId;
        mRequestedMinComplexity = requestedMinComplexity;
        mManagedPasswordProvider = managedLockPasswordProvider;
        mDpm = dpm;
        mLockPatternUtils = lockPatternUtils;
    }

    /**
     * Returns the highest quality among the specified {@code quality}, the quality required by
     * {@link DevicePolicyManager#getPasswordQuality}, and the quality required by min password
     * complexity.
     */
    public int upgradeQuality(int quality) {
        // Compare specified quality and dpm quality
        int dpmUpgradedQuality = Math.max(quality, mDpm.getPasswordQuality(null, mUserId));
        return Math.max(dpmUpgradedQuality,
                PasswordMetrics.complexityLevelToMinQuality(mRequestedMinComplexity));
    }

    /**
     * Whether the given screen lock type should be visible in the given context.
     */
    public boolean isScreenLockVisible(ScreenLockType type) {
        final boolean managedProfile = mUserId != UserHandle.myUserId();
        switch (type) {
            case NONE:
                return !mContext.getResources().getBoolean(R.bool.config_hide_none_security_option)
                    && !managedProfile; // Profiles should use unified challenge instead.
            case SWIPE:
                return !mContext.getResources().getBoolean(R.bool.config_hide_swipe_security_option)
                    && !managedProfile; // Swipe doesn't make sense for profiles.
            case MANAGED:
                return mManagedPasswordProvider.isManagedPasswordChoosable();
            case PIN:
            case PATTERN:
            case PASSWORD:
                // Hide the secure lock screen options if the device doesn't support the secure lock
                // screen feature.
                return mLockPatternUtils.hasSecureLockScreen();
        }
        return true;
    }

    /**
     * Whether screen lock with {@code type} should be enabled.
     *
     * @param type The screen lock type.
     * @param quality The minimum required quality. This can either be requirement by device policy
     *                manager or because some flow only makes sense with secure lock screens.
     */
    public boolean isScreenLockEnabled(ScreenLockType type, int quality) {
        return type.maxQuality >= quality;
    }

    /**
     * Whether screen lock with {@code type} is disabled by device policy admin.
     *
     * @param type The screen lock type.
     * @param adminEnforcedQuality The minimum quality that the admin enforces.
     */
    public boolean isScreenLockDisabledByAdmin(ScreenLockType type, int adminEnforcedQuality) {
        boolean disabledByAdmin = type.maxQuality < adminEnforcedQuality;
        if (type == ScreenLockType.MANAGED) {
            disabledByAdmin = disabledByAdmin
                    || !mManagedPasswordProvider.isManagedPasswordChoosable();
        }
        return disabledByAdmin;
    }

    /**
     * User friendly title for the given screen lock type.
     */
    public CharSequence getTitle(ScreenLockType type) {
        switch (type) {
            case NONE:
                return mContext.getText(R.string.unlock_set_unlock_off_title);
            case SWIPE:
                return mContext.getText(R.string.unlock_set_unlock_none_title);
            case PATTERN:
                return mContext.getText(R.string.unlock_set_unlock_pattern_title);
            case PIN:
                return mContext.getText(R.string.unlock_set_unlock_pin_title);
            case PASSWORD:
                return mContext.getText(R.string.unlock_set_unlock_password_title);
            case MANAGED:
                return mManagedPasswordProvider.getPickerOptionTitle(false);
        }
        return null;
    }

    /**
     * Gets a list of screen locks that should be visible for the given quality. The returned list
     * is ordered in the natural order of the enum (the order those enums were defined).
     *
     * @param quality The minimum quality required in the context of the current flow. This should
     *                be one of the constants defined in
     *                {@code DevicePolicyManager#PASSWORD_QUALITY_*}.
     * @param includeDisabled Whether to include screen locks disabled by {@code quality}
     *                        requirements in the returned list.
     */
    @NonNull
    public List<ScreenLockType> getVisibleScreenLockTypes(int quality, boolean includeDisabled) {
        int upgradedQuality = upgradeQuality(quality);
        List<ScreenLockType> locks = new ArrayList<>();
        // EnumSet's iterator guarantees the natural order of the enums
        for (ScreenLockType lock : ScreenLockType.values()) {
            if (isScreenLockVisible(lock)) {
                if (includeDisabled || isScreenLockEnabled(lock, upgradedQuality)) {
                    locks.add(lock);
                }
            }
        }
        return locks;
    }
}
