/*
 * Copyright (C) 2011 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.bluetooth;

import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.text.Spannable;
import android.text.style.TextAppearanceSpan;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.LinkifyUtils;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.SummaryLoader;
import com.android.settings.location.ScanningSettings;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.search.SearchIndexableRaw;
import com.android.settings.widget.GearPreference;
import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener;
import com.android.settings.widget.SwitchBar;
import com.android.settings.widget.SwitchBarController;
import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.FooterPreference;

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

/**
 * BluetoothSettings is the Settings screen for Bluetooth configuration and
 * connection management.
 */
public class BluetoothSettings extends DeviceListPreferenceFragment implements Indexable {
    private static final String TAG = "BluetoothSettings";
    private static final int PAIRED_DEVICE_ORDER = 1;
    private static final int PAIRING_PREF_ORDER = 2;

    @VisibleForTesting
    static final String KEY_PAIRED_DEVICES = "paired_devices";
    @VisibleForTesting
    static final String KEY_FOOTER_PREF = "footer_preference";
    private static final String KEY_RENAME_DEVICES = "bt_rename_device";

    @VisibleForTesting
    PreferenceGroup mPairedDevicesCategory;
    @VisibleForTesting
    FooterPreference mFooterPreference;
    private Preference mPairingPreference;
    private BluetoothEnabler mBluetoothEnabler;
    private AlwaysDiscoverable mAlwaysDiscoverable;

    private SwitchBar mSwitchBar;

    private BluetoothDeviceNamePreferenceController mDeviceNamePrefController;
    @VisibleForTesting
    BluetoothPairingPreferenceController mPairingPrefController;

    // For Search
    @VisibleForTesting
    static final String DATA_KEY_REFERENCE = "main_toggle_bluetooth";

    public BluetoothSettings() {
        super(DISALLOW_CONFIG_BLUETOOTH);
    }

    @Override
    public int getMetricsCategory() {
        return MetricsEvent.BLUETOOTH;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        final SettingsActivity activity = (SettingsActivity) getActivity();
        mSwitchBar = activity.getSwitchBar();

        mBluetoothEnabler = new BluetoothEnabler(activity, new SwitchBarController(mSwitchBar),
                mMetricsFeatureProvider, Utils.getLocalBtManager(activity),
                MetricsEvent.ACTION_BLUETOOTH_TOGGLE);
        mBluetoothEnabler.setupSwitchController();
        if (mLocalAdapter != null) {
            mAlwaysDiscoverable = new AlwaysDiscoverable(getContext(), mLocalAdapter);
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();

        mBluetoothEnabler.teardownSwitchController();
    }

    @Override
    void initPreferencesFromPreferenceScreen() {
        mPairingPreference = mPairingPrefController.createBluetoothPairingPreference(
                PAIRING_PREF_ORDER);
        mFooterPreference = (FooterPreference) findPreference(KEY_FOOTER_PREF);
        mPairedDevicesCategory = (PreferenceGroup) findPreference(KEY_PAIRED_DEVICES);
    }

    @Override
    public void onStart() {
        // resume BluetoothEnabler before calling super.onStart() so we don't get
        // any onDeviceAdded() callbacks before setting up view in updateContent()
        if (mBluetoothEnabler != null) {
            mBluetoothEnabler.resume(getActivity());
        }
        super.onStart();
        // Always show paired devices regardless whether user-friendly name exists
        mShowDevicesWithoutNames = true;
        if (isUiRestricted()) {
            getPreferenceScreen().removeAll();
            if (!isUiRestrictedByOnlyAdmin()) {
                getEmptyTextView().setText(R.string.bluetooth_empty_list_user_restricted);
            }
            return;
        }

        if (mLocalAdapter != null) {
            updateContent(mLocalAdapter.getBluetoothState());
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        mPairedDevicesCategory.removeAll();
        if (mBluetoothEnabler != null) {
            mBluetoothEnabler.pause();
        }

        // Make the device only visible to connected devices.
        if (mAlwaysDiscoverable != null) {
            mAlwaysDiscoverable.stop();
        }

        if (isUiRestricted()) {
            return;
        }
    }

    @Override
    public String getDeviceListKey() {
        return KEY_PAIRED_DEVICES;
    }

    private void updateContent(int bluetoothState) {
        int messageId = 0;

        switch (bluetoothState) {
            case BluetoothAdapter.STATE_ON:
                displayEmptyMessage(false);
                mDevicePreferenceMap.clear();

                if (isUiRestricted()) {
                    messageId = R.string.bluetooth_empty_list_user_restricted;
                    break;
                }

                addDeviceCategory(mPairedDevicesCategory,
                        R.string.bluetooth_preference_paired_devices,
                        BluetoothDeviceFilter.BONDED_DEVICE_FILTER, true);
                mPairedDevicesCategory.addPreference(mPairingPreference);
                updateFooterPreference(mFooterPreference);

                if (mAlwaysDiscoverable != null) {
                    mAlwaysDiscoverable.start();
                }
                return; // not break

            case BluetoothAdapter.STATE_TURNING_OFF:
                messageId = R.string.bluetooth_turning_off;
                mLocalAdapter.stopScanning();
                break;

            case BluetoothAdapter.STATE_OFF:
                setOffMessage();
                if (isUiRestricted()) {
                    messageId = R.string.bluetooth_empty_list_user_restricted;
                }
                break;

            case BluetoothAdapter.STATE_TURNING_ON:
                messageId = R.string.bluetooth_turning_on;
                break;
        }

        displayEmptyMessage(true);
        if (messageId != 0) {
            getEmptyTextView().setText(messageId);
        }
    }

    private void setOffMessage() {
        final TextView emptyView = getEmptyTextView();
        if (emptyView == null) {
            return;
        }
        final CharSequence briefText = getText(R.string.bluetooth_empty_list_bluetooth_off);

        final ContentResolver resolver = getActivity().getContentResolver();
        final boolean bleScanningMode = Settings.Global.getInt(
                resolver, Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1;

        if (!bleScanningMode) {
            // Show only the brief text if the scanning mode has been turned off.
            emptyView.setText(briefText, TextView.BufferType.SPANNABLE);
        } else {
            final StringBuilder contentBuilder = new StringBuilder();
            contentBuilder.append(briefText);
            contentBuilder.append("\n\n");
            contentBuilder.append(getText(R.string.ble_scan_notify_text));
            LinkifyUtils.linkify(emptyView, contentBuilder, new LinkifyUtils.OnClickListener() {
                @Override
                public void onClick() {
                    new SubSettingLauncher(getActivity())
                            .setSourceMetricsCategory(getMetricsCategory())
                            .setDestination(ScanningSettings.class.getName())
                            .setTitle(R.string.location_scanning_screen_title)
                            .launch();
                }
            });
        }
        setTextSpan(emptyView.getText(), briefText);
    }

    @VisibleForTesting
    void displayEmptyMessage(boolean display) {
        final Activity activity = getActivity();
        activity.findViewById(android.R.id.list_container).setVisibility(
                display ? View.INVISIBLE : View.VISIBLE);
        activity.findViewById(android.R.id.empty).setVisibility(
                display ? View.VISIBLE : View.GONE);
    }

    @Override
    public void onBluetoothStateChanged(int bluetoothState) {
        super.onBluetoothStateChanged(bluetoothState);
        updateContent(bluetoothState);
    }

    @Override
    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
        updateContent(mLocalAdapter.getBluetoothState());
    }

    @VisibleForTesting
    void setTextSpan(CharSequence text, CharSequence briefText) {
        if (text instanceof Spannable) {
            Spannable boldSpan = (Spannable) text;
            boldSpan.setSpan(
                    new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0,
                    briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }

    @VisibleForTesting
    void setLocalBluetoothAdapter(LocalBluetoothAdapter localAdapter) {
        mLocalAdapter = localAdapter;
    }

    private final GearPreference.OnGearClickListener mDeviceProfilesListener = pref -> {
        // User clicked on advanced options icon for a device in the list
        if (!(pref instanceof BluetoothDevicePreference)) {
            Log.w(TAG, "onClick() called for other View: " + pref);
            return;
        }
        final CachedBluetoothDevice device =
                ((BluetoothDevicePreference) pref).getBluetoothDevice();
        if (device == null) {
            Log.w(TAG, "No BT device attached with this pref: " + pref);
            return;
        }
        final Bundle args = new Bundle();
        Context context = getActivity();
        boolean useDetailPage = FeatureFactory.getFactory(context).getBluetoothFeatureProvider(
                context).isDeviceDetailPageEnabled();
        if (!useDetailPage) {
            // Old version - uses a dialog.
            args.putString(DeviceProfilesSettings.ARG_DEVICE_ADDRESS,
                    device.getDevice().getAddress());
            final DeviceProfilesSettings profileSettings = new DeviceProfilesSettings();
            profileSettings.setArguments(args);
            profileSettings.show(getFragmentManager(),
                    DeviceProfilesSettings.class.getSimpleName());
        } else {
            // New version - uses a separate screen.
            args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS,
                    device.getDevice().getAddress());
            new SubSettingLauncher(context)
                    .setDestination(BluetoothDeviceDetailsFragment.class.getName())
                    .setArguments(args)
                    .setTitle(R.string.device_details_title)
                    .setSourceMetricsCategory(getMetricsCategory())
                    .launch();
        }
    };

    /**
     * Add a listener, which enables the advanced settings icon.
     *
     * @param preference the newly added preference
     */
    @Override
    void initDevicePreference(BluetoothDevicePreference preference) {
        preference.setOrder(PAIRED_DEVICE_ORDER);
        CachedBluetoothDevice cachedDevice = preference.getCachedDevice();
        if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
            // Only paired device have an associated advanced settings screen
            preference.setOnGearClickListener(mDeviceProfilesListener);
        }
    }

    @Override
    public int getHelpResource() {
        return R.string.help_url_bluetooth;
    }

    @Override
    protected String getLogTag() {
        return TAG;
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.bluetooth_settings;
    }

    @Override
    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
        final List<AbstractPreferenceController> controllers = new ArrayList<>();
        final Lifecycle lifecycle = getLifecycle();
        mDeviceNamePrefController = new BluetoothDeviceNamePreferenceController(context, lifecycle);
        mPairingPrefController = new BluetoothPairingPreferenceController(context, this);
        controllers.add(mDeviceNamePrefController);
        controllers.add(mPairingPrefController);
        controllers.add(new BluetoothFilesPreferenceController(context));
        controllers.add(
                new BluetoothDeviceRenamePreferenceController(context, KEY_RENAME_DEVICES, this,
                        lifecycle));

        return controllers;
    }

    @VisibleForTesting
    static class SummaryProvider implements SummaryLoader.SummaryProvider, OnSummaryChangeListener {

        private final LocalBluetoothManager mBluetoothManager;
        private final Context mContext;
        private final SummaryLoader mSummaryLoader;

        @VisibleForTesting
        BluetoothSummaryUpdater mSummaryUpdater;

        public SummaryProvider(Context context, SummaryLoader summaryLoader,
                LocalBluetoothManager bluetoothManager) {
            mBluetoothManager = bluetoothManager;
            mContext = context;
            mSummaryLoader = summaryLoader;
            mSummaryUpdater = new BluetoothSummaryUpdater(mContext, this, mBluetoothManager);
        }

        @Override
        public void setListening(boolean listening) {
            mSummaryUpdater.register(listening);
        }

        @Override
        public void onSummaryChanged(String summary) {
            if (mSummaryLoader != null) {
                mSummaryLoader.setSummary(this, summary);
            }
        }
    }

    public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
            = new SummaryLoader.SummaryProviderFactory() {
        @Override
        public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
                SummaryLoader summaryLoader) {

            return new SummaryProvider(activity, summaryLoader, Utils.getLocalBtManager(activity));
        }
    };

    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
            new BaseSearchIndexProvider() {
                @Override
                public List<SearchIndexableRaw> getRawDataToIndex(Context context,
                        boolean enabled) {

                    final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();

                    final Resources res = context.getResources();

                    // Add fragment title
                    SearchIndexableRaw data = new SearchIndexableRaw(context);
                    data.title = res.getString(R.string.bluetooth_settings);
                    data.screenTitle = res.getString(R.string.bluetooth_settings);
                    data.key = DATA_KEY_REFERENCE;
                    result.add(data);

                    // Removed paired bluetooth device indexing. See BluetoothSettingsObsolete.java.
                    return result;
                }

                @Override
                public List<String> getNonIndexableKeys(Context context) {
                    List<String> keys = super.getNonIndexableKeys(context);
                    if (!FeatureFactory.getFactory(context).getBluetoothFeatureProvider(
                            context).isPairingPageEnabled()) {
                        keys.add(DATA_KEY_REFERENCE);
                    }
                    return keys;
                }
            };
}
