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

import static com.android.settingslib.drawer.CategoryKey.CATEGORY_DEVICE;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;

import static java.lang.String.CASE_INSENSITIVE_ORDER;

import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;

import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;

import com.android.car.settings.R;
import com.android.car.settings.activityembedding.ActivityEmbeddingRulesController;
import com.android.car.ui.preference.CarUiPreference;
import com.android.settingslib.drawer.TileUtils;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Loads Activity with TileUtils.EXTRA_SETTINGS_ACTION.
 */
// TODO: investigate using SettingsLib Tiles.
public class ExtraSettingsLoader {
    static final String META_DATA_IS_TOP_LEVEL_EXTRA_SETTINGS = "injectedTopLevelPreference";
    private static final Logger LOG = new Logger(ExtraSettingsLoader.class);
    private static final String META_DATA_PREFERENCE_CATEGORY = "com.android.settings.category";
    private final Context mContext;
    private final Set<String> mTopLevelCategories;
    private final boolean mIsTopLevelSummariesEnabled;
    private Map<Preference, Bundle> mPreferenceBundleMap;
    private PackageManager mPm;

    public ExtraSettingsLoader(Context context) {
        mContext = context;
        mPm = context.getPackageManager();
        mPreferenceBundleMap = new LinkedHashMap<>();
        mTopLevelCategories = Set.of(mContext.getResources().getStringArray(
                R.array.config_top_level_injection_categories));
        mIsTopLevelSummariesEnabled = mContext.getResources().getBoolean(
                R.bool.config_top_level_injection_enable_summaries);
    }

    @VisibleForTesting
    void setPackageManager(PackageManager pm) {
        mPm = pm;
    }

    /**
     * Returns a map of {@link Preference} and {@link Bundle} representing settings injected from
     * system apps and their metadata. The given intent must specify the action to use for
     * resolving activities and a category with the key "com.android.settings.category" and one of
     * the values in {@link com.android.settingslib.drawer.CategoryKey}.
     *
     * {@link com.android.settingslib.drawer.TileUtils#EXTRA_SETTINGS_ACTION} is automatically added
     * for backwards compatibility. Please make sure to use
     * {@link com.android.settingslib.drawer.TileUtils#IA_SETTINGS_ACTION} instead.
     *
     * @param intent intent specifying the extra settings category to load
     */
    public Map<Preference, Bundle> loadPreferences(Intent intent) {
        intent.setAction(TileUtils.IA_SETTINGS_ACTION);
        List<ResolveInfo> results = mPm.queryIntentActivitiesAsUser(intent,
                PackageManager.GET_META_DATA, ActivityManager.getCurrentUser());

        intent.setAction(TileUtils.EXTRA_SETTINGS_ACTION);
        List<ResolveInfo> extra_settings_results = mPm.queryIntentActivitiesAsUser(intent,
                PackageManager.GET_META_DATA, ActivityManager.getCurrentUser());
        for (ResolveInfo extra_settings_resolveInfo : extra_settings_results) {
            // queryIntentActivitiesAsUser returns shallow copies so we can't use .equals()
            addResolveInfoIfUnique(results, extra_settings_resolveInfo);
        }

        String extraCategory = intent.getStringExtra(META_DATA_PREFERENCE_CATEGORY);

        // Filter to only include valid results and then sort the results
        // Filter criteria: must be a system application and must have metaData
        // Sort criteria: sort results based on [order, package within order]
        results = results.stream()
                .filter(r -> r.system && r.activityInfo != null && r.activityInfo.metaData != null)
                .sorted((r1, r2) -> {
                    // First sort by order
                    int orderCompare = r2.activityInfo.metaData.getInt(META_DATA_KEY_ORDER)
                            - r1.activityInfo.metaData.getInt(META_DATA_KEY_ORDER);
                    if (orderCompare != 0) {
                        return orderCompare;
                    }

                    // Then sort by package name
                    String package1 = r1.activityInfo.packageName;
                    String package2 = r2.activityInfo.packageName;
                    return CASE_INSENSITIVE_ORDER.compare(package1, package2);
                })
                .collect(Collectors.toList());

        for (ResolveInfo resolved : results) {
            String key = null;
            String title = null;
            String summary = null;
            String category = null;
            ActivityInfo activityInfo = resolved.activityInfo;
            Bundle metaData = activityInfo.metaData;
            try {
                Resources res = mPm.getResourcesForApplication(activityInfo.packageName);
                if (metaData.containsKey(META_DATA_PREFERENCE_KEYHINT)) {
                    key = extractMetaDataString(metaData, META_DATA_PREFERENCE_KEYHINT, res);
                }
                if (!metaData.containsKey(META_DATA_PREFERENCE_TITLE_URI)) {
                    title = extractMetaDataString(metaData, META_DATA_PREFERENCE_TITLE, res);
                    if (TextUtils.isEmpty(title)) {
                        LOG.d("no title.");
                        title = activityInfo.loadLabel(mPm).toString();
                    }
                }
                if (!metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
                    summary = extractMetaDataString(metaData, META_DATA_PREFERENCE_SUMMARY, res);
                    if (TextUtils.isEmpty(summary)) {
                        LOG.d("no description.");
                    }
                }
                category = extractMetaDataString(metaData, META_DATA_PREFERENCE_CATEGORY, res);
                if (TextUtils.isEmpty(category)) {
                    LOG.d("no category.");
                }
            } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
                LOG.d("Couldn't find info", e);
            }
            Intent extraSettingIntent =
                    new Intent().setClassName(activityInfo.packageName, activityInfo.name);
            if (category == null) {
                // If category is not specified or not supported, default to device.
                category = CATEGORY_DEVICE;
            }
            boolean isTopLevel = mTopLevelCategories.contains(category);
            metaData.putBoolean(META_DATA_IS_TOP_LEVEL_EXTRA_SETTINGS, isTopLevel);

            Drawable icon = ExtraSettingsUtil.createIcon(mContext, metaData,
                    activityInfo.packageName);

            if (!TextUtils.equals(extraCategory, category)) {
                continue;
            }
            CarUiPreference preference;
            if (isTopLevel) {
                preference = new TopLevelPreference(mContext);
                if (!mIsTopLevelSummariesEnabled) {
                    // remove summary data
                    summary = null;
                    metaData.remove(META_DATA_PREFERENCE_SUMMARY_URI);
                }
                key = key != null ? key : getTopLevelPreferenceKey(title);
                registerDualPaneSplitRule(extraSettingIntent, activityInfo.launchMode);
            } else {
                preference = new CarUiPreference(mContext);
            }
            preference.setTitle(title);
            preference.setSummary(summary);
            if (key != null) {
                preference.setKey(key);
            }
            if (icon != null) {
                preference.setIcon(icon);
            }
            preference.setIntent(extraSettingIntent);
            mPreferenceBundleMap.put(preference, metaData);
        }
        return mPreferenceBundleMap;
    }

    private String getTopLevelPreferenceKey(CharSequence preferenceTitle) {
        return new String("top_level_extra_settings_preference_" + preferenceTitle + "_entry")
                .replace(" ", "_")
                .toLowerCase(Locale.ROOT);
    }

    /**
     * Extracts the value in the metadata specified by the key.
     * If it is resource, resolve the string and return. Otherwise, return the string itself.
     */
    private String extractMetaDataString(Bundle metaData, String key, Resources res) {
        if (metaData.containsKey(key)) {
            if (metaData.get(key) instanceof Integer) {
                return res.getString(metaData.getInt(key));
            }
            return metaData.getString(key);
        }
        return null;
    }

    /** Adds new ResolveInfo to list if it is unique. */
    private void addResolveInfoIfUnique(List<ResolveInfo> originalList,
            ResolveInfo newResolveInfo) {
        ComponentName componentName = newResolveInfo.activityInfo.getComponentName();
        boolean alreadyContains = originalList.stream().anyMatch(resolveInfo ->
                componentName.equals(resolveInfo.activityInfo.getComponentName()));

        if (!alreadyContains) {
            originalList.add(newResolveInfo);
        }
    }

    /**
     * Registers {@link ActivityEmbeddingRulesController#registerHomepageDualPaneSplitRule} for all
     * top level extra settings preference as these activities should all finish along with the
     * {@link CarSettingActivities.HomepageActivity} on back press.
     */
    private void registerDualPaneSplitRule(Intent extraSettingIntent, int launchMode) {
        // Single instance activities may create new tasks if they already exist in the back of
        // current task stack. Settings clearTop to true will bring those activities behind Settings
        // rather than to the foreground.
        boolean clearTop = (launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE)
                && (launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK);
        ActivityEmbeddingRulesController.registerHomepageDualPaneSplitRule(/* context= */ mContext,
                /* secondaryComponent= */ extraSettingIntent.getComponent(),
                /* secondaryIntentAction= */ extraSettingIntent.getAction(),
                /* clearTop= */ clearTop);
    }
}
