/*
 * Copyright (C) 2018 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.car.settings.accounts;

import android.car.drivingstate.CarUxRestrictions;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Handler;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.collection.ArrayMap;
import androidx.preference.PreferenceGroup;

import com.android.car.settings.common.ActivityResultCallback;
import com.android.car.settings.common.FragmentController;
import com.android.car.settings.common.PreferenceController;
import com.android.car.ui.preference.CarUiPreference;
import com.android.settingslib.accounts.AuthenticatorHelper;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Controller for showing the user the list of accounts they can add.
 *
 * <p>Largely derived from {@link com.android.settings.accounts.ChooseAccountActivity}
 */
public class ChooseAccountPreferenceController extends
        PreferenceController<PreferenceGroup> implements ActivityResultCallback {
    @VisibleForTesting
    static final int ADD_ACCOUNT_REQUEST_CODE = 100;

    private AccountTypesHelper mAccountTypesHelper;
    private ArrayMap<String, AuthenticatorDescriptionPreference> mPreferences = new ArrayMap<>();
    private boolean mHasPendingBack = false;

    public ChooseAccountPreferenceController(Context context, String preferenceKey,
            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
        super(context, preferenceKey, fragmentController, uxRestrictions);
        mAccountTypesHelper = new AccountTypesHelper(context);
        mAccountTypesHelper.setOnChangeListener(this::forceUpdateAccountsCategory);
    }

    /** Sets the authorities that the user has. */
    public void setAuthorities(List<String> authorities) {
        mAccountTypesHelper.setAuthorities(authorities);
    }

    /** Sets the filter for accounts that should be shown. */
    public void setAccountTypesFilter(Set<String> accountTypesFilter) {
        mAccountTypesHelper.setAccountTypesFilter(accountTypesFilter);
    }

    /** Sets the filter for accounts that should NOT be shown. */
    protected void setAccountTypesExclusionFilter(Set<String> accountTypesExclusionFilterFilter) {
        mAccountTypesHelper.setAccountTypesExclusionFilter(accountTypesExclusionFilterFilter);
    }

    @Override
    protected Class<PreferenceGroup> getPreferenceType() {
        return PreferenceGroup.class;
    }

    @Override
    protected void updateState(PreferenceGroup preferenceGroup) {
        mAccountTypesHelper.forceUpdate();
    }

    /**
     * Registers the account update callback.
     */
    @Override
    protected void onStartInternal() {
        mAccountTypesHelper.listenToAccountUpdates();

        if (mHasPendingBack) {
            mHasPendingBack = false;

            // Post the fragment navigation because FragmentManager may still be executing
            // transactions during onStart.
            new Handler().post(() -> getFragmentController().goBack());
        }
    }

    /**
     * Unregisters the account update callback.
     */
    @Override
    protected void onStopInternal() {
        mAccountTypesHelper.stopListeningToAccountUpdates();
    }

    /** Forces a refresh of the authenticator description preferences. */
    private void forceUpdateAccountsCategory() {
        Set<String> preferencesToRemove = new HashSet<>(mPreferences.keySet());
        List<AuthenticatorDescriptionPreference> preferences =
                getAuthenticatorDescriptionPreferences(preferencesToRemove);
        // Add all preferences that aren't already shown
        for (int i = 0; i < preferences.size(); i++) {
            AuthenticatorDescriptionPreference preference = preferences.get(i);
            preference.setOrder(i);
            String key = preference.getKey();
            getPreference().addPreference(preference);
            mPreferences.put(key, preference);
        }

        // Remove all preferences that should no longer be shown
        for (String key : preferencesToRemove) {
            getPreference().removePreference(mPreferences.get(key));
            mPreferences.remove(key);
        }
    }

    /**
     * Returns a list of preferences corresponding to the account types the user can add.
     *
     * <p> Derived from
     * {@link com.android.settings.accounts.ChooseAccountActivity#onAuthDescriptionsUpdated}
     *
     * @param preferencesToRemove the current preferences shown; will contain the preferences that
     *                            need to be removed from the screen after method execution
     */
    private List<AuthenticatorDescriptionPreference> getAuthenticatorDescriptionPreferences(
            Set<String> preferencesToRemove) {
        ArrayList<AuthenticatorDescriptionPreference> authenticatorDescriptionPreferences =
                new ArrayList<>();
        Set<String> authorizedAccountTypes = mAccountTypesHelper.getAuthorizedAccountTypes();
        // Create list of account providers to show on page.
        for (String accountType : authorizedAccountTypes) {
            CharSequence label = mAccountTypesHelper.getLabelForType(accountType);
            Drawable icon = mAccountTypesHelper.getDrawableForType(accountType);

            // Add a preference for the provider to the list and remove it from preferencesToRemove.
            AuthenticatorDescriptionPreference preference = mPreferences.getOrDefault(accountType,
                    new AuthenticatorDescriptionPreference(getContext(), accountType, label, icon));
            preference.setOnPreferenceClickListener(
                    pref -> {
                        Intent intent = AddAccountActivity.createAddAccountActivityIntent(
                                getContext(), preference.getAccountType());
                        getFragmentController().startActivityForResult(intent,
                                ADD_ACCOUNT_REQUEST_CODE, /* callback= */ this);
                        return true;
                    });
            authenticatorDescriptionPreferences.add(preference);
            preferencesToRemove.remove(accountType);
        }

        Collections.sort(authenticatorDescriptionPreferences);

        return authenticatorDescriptionPreferences;
    }

    /** Used for testing to trigger an account update. */
    @VisibleForTesting
    AccountTypesHelper getAccountTypesHelper() {
        return mAccountTypesHelper;
    }

    @VisibleForTesting
    void setAuthenticatorHelper(AuthenticatorHelper helper) {
        mAccountTypesHelper.setAuthenticatorHelper(helper);
    }

    @Override
    public void processActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (requestCode == ADD_ACCOUNT_REQUEST_CODE) {
            if (isStarted()) {
                getFragmentController().goBack();
            } else {
                mHasPendingBack = true;
            }
        }
    }

    /** Handles adding accounts. */
    interface AddAccountListener {
        /** Handles adding an account. */
        void addAccount(String accountType);
    }

    private static class AuthenticatorDescriptionPreference extends CarUiPreference {
        private final String mType;

        AuthenticatorDescriptionPreference(Context context, String accountType, CharSequence label,
                Drawable icon) {
            super(context);
            mType = accountType;

            setKey(accountType);
            setTitle(label);
            setIcon(icon);
            setShowChevron(false);
        }

        private String getAccountType() {
            return mType;
        }
    }
}
