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

import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;

import androidx.annotation.WorkerThread;

import com.android.internal.util.ArrayUtils;
import com.android.systemui.DejankUtils;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.leak.LeakDetector;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.inject.Inject;


/**
 * @deprecated Don't use this class to listen to Secure Settings. Use {@code SecureSettings} instead
 * or {@code SettingsObserver} to be able to specify the handler.
 * This class will interact with SecureSettings using the main looper.
 */
@Deprecated
@SysUISingleton
public class TunerServiceImpl extends TunerService {

    private static final String TAG = "TunerService";
    private static final String TUNER_VERSION = "sysui_tuner_version";

    private static final int CURRENT_TUNER_VERSION = 4;

    // Things that use the tunable infrastructure but are now real user settings and
    // shouldn't be reset with tuner settings.
    private static final String[] RESET_EXCEPTION_LIST = new String[] {
            QSHost.TILES_SETTING,
            Settings.Secure.DOZE_ALWAYS_ON,
            Settings.Secure.MEDIA_CONTROLS_RESUME,
            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION
    };

    private final Observer mObserver = new Observer();
    // Map of Uris we listen on to their settings keys.
    private final ArrayMap<Uri, String> mListeningUris = new ArrayMap<>();
    // Map of settings keys to the listener.
    private final ConcurrentHashMap<String, Set<Tunable>> mTunableLookup =
            new ConcurrentHashMap<>();
    // Set of all tunables, used for leak detection.
    private final HashSet<Tunable> mTunables = LeakDetector.ENABLED ? new HashSet<>() : null;
    private final Context mContext;
    private final LeakDetector mLeakDetector;
    private final DemoModeController mDemoModeController;

    private ContentResolver mContentResolver;
    private int mCurrentUser;
    private UserTracker.Callback mCurrentUserTracker;
    private UserTracker mUserTracker;
    private final ComponentName mTunerComponent;

    /**
     */
    @Inject
    public TunerServiceImpl(
            Context context,
            @Main Handler mainHandler,
            LeakDetector leakDetector,
            DemoModeController demoModeController,
            UserTracker userTracker) {
        super(context);
        mContext = context;
        mContentResolver = mContext.getContentResolver();
        mLeakDetector = leakDetector;
        mDemoModeController = demoModeController;
        mUserTracker = userTracker;
        mTunerComponent = new ComponentName(mContext, TunerActivity.class);

        for (UserInfo user : UserManager.get(mContext).getUsers()) {
            mCurrentUser = user.getUserHandle().getIdentifier();
            if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) {
                upgradeTuner(getValue(TUNER_VERSION, 0), CURRENT_TUNER_VERSION, mainHandler);
            }
        }

        mCurrentUser = mUserTracker.getUserId();
        mCurrentUserTracker = new UserTracker.Callback() {
            @Override
            public void onUserChanged(int newUser, Context userContext) {
                mCurrentUser = newUser;
                reloadAll();
                reregisterAll();
            }
        };
        mUserTracker.addCallback(mCurrentUserTracker,
                new HandlerExecutor(mainHandler));
    }

    @Override
    public void destroy() {
        mUserTracker.removeCallback(mCurrentUserTracker);
    }

    private void upgradeTuner(int oldVersion, int newVersion, Handler mainHandler) {
        if (oldVersion < 1) {
            String hideListStr = getValue(StatusBarIconController.ICON_HIDE_LIST);
            if (hideListStr != null) {
                ArraySet<String> iconHideList =
                        StatusBarIconController.getIconHideList(mContext, hideListStr);

                iconHideList.add("rotate");
                iconHideList.add("headset");

                Settings.Secure.putStringForUser(mContentResolver,
                        StatusBarIconController.ICON_HIDE_LIST,
                        TextUtils.join(",", iconHideList), mCurrentUser);
            }
        }
        if (oldVersion < 2) {
            setTunerEnabled(false);
        }
        // 3 Removed because of a revert.
        if (oldVersion < 4) {
            // Delay this so that we can wait for everything to be registered first.
            final int user = mCurrentUser;
            mainHandler.postDelayed(
                    () -> clearAllFromUser(user), 5000);
        }
        setValue(TUNER_VERSION, newVersion);
    }

    @Override
    public String getValue(String setting) {
        return Settings.Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
    }

    @Override
    public void setValue(String setting, String value) {
         Settings.Secure.putStringForUser(mContentResolver, setting, value, mCurrentUser);
    }

    @Override
    public int getValue(String setting, int def) {
        return Settings.Secure.getIntForUser(mContentResolver, setting, def, mCurrentUser);
    }

    @Override
    public String getValue(String setting, String def) {
        String ret = Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
        if (ret == null) return def;
        return ret;
    }

    @Override
    public void setValue(String setting, int value) {
         Settings.Secure.putIntForUser(mContentResolver, setting, value, mCurrentUser);
    }

    @Override
    public void addTunable(Tunable tunable, String... keys) {
        for (String key : keys) {
            addTunable(tunable, key);
        }
    }

    private void addTunable(Tunable tunable, String key) {
        if (!mTunableLookup.containsKey(key)) {
            mTunableLookup.put(key, new ArraySet<Tunable>());
        }
        mTunableLookup.get(key).add(tunable);
        if (LeakDetector.ENABLED) {
            mTunables.add(tunable);
            mLeakDetector.trackCollection(mTunables, "TunerService.mTunables");
        }
        Uri uri = Settings.Secure.getUriFor(key);
        if (!mListeningUris.containsKey(uri)) {
            mListeningUris.put(uri, key);
            mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
        }
        // Send the first state.
        String value = DejankUtils.whitelistIpcs(() -> Settings.Secure
                .getStringForUser(mContentResolver, key, mCurrentUser));
        tunable.onTuningChanged(key, value);
    }

    @Override
    public void removeTunable(Tunable tunable) {
        for (Set<Tunable> list : mTunableLookup.values()) {
            list.remove(tunable);
        }
        if (LeakDetector.ENABLED) {
            mTunables.remove(tunable);
        }
    }

    protected void reregisterAll() {
        if (mListeningUris.size() == 0) {
            return;
        }
        mContentResolver.unregisterContentObserver(mObserver);
        for (Uri uri : mListeningUris.keySet()) {
            mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
        }
    }

    private void reloadSetting(Uri uri) {
        String key = mListeningUris.get(uri);
        Set<Tunable> tunables = mTunableLookup.get(key);
        if (tunables == null) {
            return;
        }
        String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
        for (Tunable tunable : tunables) {
            tunable.onTuningChanged(key, value);
        }
    }

    private void reloadAll() {
        for (String key : mTunableLookup.keySet()) {
            String value = Settings.Secure.getStringForUser(mContentResolver, key,
                    mCurrentUser);
            for (Tunable tunable : mTunableLookup.get(key)) {
                tunable.onTuningChanged(key, value);
            }
        }
    }

    @Override
    public void clearAll() {
        clearAllFromUser(mCurrentUser);
    }

    public void clearAllFromUser(int user) {
        // Turn off demo mode
        mDemoModeController.requestFinishDemoMode();
        mDemoModeController.requestSetDemoModeAllowed(false);

        // A couple special cases.
        for (String key : mTunableLookup.keySet()) {
            if (ArrayUtils.contains(RESET_EXCEPTION_LIST, key)) {
                continue;
            }
            Settings.Secure.putStringForUser(mContentResolver, key, null, user);
        }
    }


    @Override
    public void setTunerEnabled(boolean enabled) {
        mUserTracker.getUserContext().getPackageManager().setComponentEnabledSetting(
                mTunerComponent,
                enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP
        );
    }

    @Override
    @WorkerThread
    public boolean isTunerEnabled() {
        return mUserTracker.getUserContext().getPackageManager().getComponentEnabledSetting(
                mTunerComponent) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
    }

    @Override
    public void showResetRequest(Runnable onDisabled) {
        SystemUIDialog dialog = new SystemUIDialog(mContext);
        dialog.setShowForAllUsers(true);
        dialog.setMessage(R.string.remove_from_settings_prompt);
        dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mContext.getString(R.string.cancel),
                (DialogInterface.OnClickListener) null);
        dialog.setButton(DialogInterface.BUTTON_POSITIVE,
                mContext.getString(R.string.qs_customize_remove), (d, which) -> {
                    // Tell the tuner (in main SysUI process) to clear all its settings.
                    mContext.sendBroadcast(new Intent(TunerService.ACTION_CLEAR));
                    // Disable access to tuner.
                    setTunerEnabled(false);
                    // Make them sit through the warning dialog again.
                    Secure.putInt(mContext.getContentResolver(),
                            TunerFragment.SETTING_SEEN_TUNER_WARNING, 0);
                    if (onDisabled != null) {
                        onDisabled.run();
                    }
                });
        dialog.show();
    }

    private class Observer extends ContentObserver {
        public Observer() {
            super(new Handler(Looper.getMainLooper()));
        }

        @Override
        public void onChange(boolean selfChange, java.util.Collection<Uri> uris,
                int flags, int userId) {
            if (userId == mUserTracker.getUserId()) {
                for (Uri u : uris) {
                    reloadSetting(u);
                }
            }
        }

    }
}