/*
 * Copyright (C) 2023 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.statusbar.notification.row;

import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Trace;

import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.settings.SecureSettings;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;

import javax.inject.Inject;

/**
 * Centralized controller for listening to Secure Settings changes and informing in-process
 * listeners, on a background thread.
 */
@SysUISingleton
public class NotificationSettingsController implements Dumpable {

    private final static String TAG = "NotificationSettingsController";
    private final UserTracker mUserTracker;
    private final UserTracker.Callback mCurrentUserTrackerCallback;
    private final Handler mMainHandler;
    private final Handler mBackgroundHandler;
    private final ContentObserver mContentObserver;
    private final SecureSettings mSecureSettings;
    private final HashMap<Uri, ArrayList<Listener>> mListeners = new HashMap<>();

    @Inject
    public NotificationSettingsController(UserTracker userTracker,
            @Main Handler mainHandler,
            @Background Handler backgroundHandler,
            SecureSettings secureSettings,
            DumpManager dumpManager) {
        mUserTracker = userTracker;
        mMainHandler = mainHandler;
        mBackgroundHandler = backgroundHandler;
        mSecureSettings = secureSettings;
        mContentObserver = new ContentObserver(mBackgroundHandler) {
            @Override
            public void onChange(boolean selfChange, Uri uri) {
                Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".ContentObserver.onChange");
                super.onChange(selfChange, uri);
                synchronized (mListeners) {
                    if (mListeners.containsKey(uri)) {
                        int userId = mUserTracker.getUserId();
                        String value = getCurrentSettingValue(uri, userId);
                        for (Listener listener : mListeners.get(uri)) {
                            mMainHandler.post(() -> listener.onSettingChanged(uri, userId, value));
                        }
                    }
                }
                Trace.traceEnd(Trace.TRACE_TAG_APP);
            }
        };

        mCurrentUserTrackerCallback = new UserTracker.Callback() {
            @Override
            public void onUserChanged(int newUser, Context userContext) {
                Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".UserTracker.Callback.onUserChanged");

                synchronized (mListeners) {
                    if (mListeners.size() > 0) {
                        mSecureSettings.unregisterContentObserver(mContentObserver);
                        for (Uri uri : mListeners.keySet()) {
                            mSecureSettings.registerContentObserverForUser(
                                    uri, false, mContentObserver, newUser);
                        }
                    }
                }
                Trace.traceEnd(Trace.TRACE_TAG_APP);
            }
        };
        mUserTracker.addCallback(
                mCurrentUserTrackerCallback, new HandlerExecutor(mBackgroundHandler));

        dumpManager.registerNormalDumpable(TAG, this);
    }

    /**
     * Register a callback whenever the given secure settings changes.
     *
     * On registration, will trigger the listener on the main thread with the current value of
     * the setting.
     */
    @Main
    public void addCallback(@NonNull Uri uri, @NonNull Listener listener) {
        if (uri == null || listener == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".addCallback");
        synchronized (mListeners) {
            ArrayList<Listener> currentListeners = mListeners.get(uri);
            if (currentListeners == null) {
                currentListeners = new ArrayList<>();
            }
            if (!currentListeners.contains(listener)) {
                currentListeners.add(listener);
            }
            mListeners.put(uri, currentListeners);
            if (currentListeners.size() == 1) {
                mBackgroundHandler.post(() -> {
                    mSecureSettings.registerContentObserverForUser(
                            uri, false, mContentObserver, mUserTracker.getUserId());
                });
            }
        }
        mBackgroundHandler.post(() -> {
            int userId = mUserTracker.getUserId();
            String value = getCurrentSettingValue(uri, userId);
            mMainHandler.post(() -> listener.onSettingChanged(uri, userId, value));
        });
        Trace.traceEnd(Trace.TRACE_TAG_APP);
    }

    public void removeCallback(Uri uri, Listener listener) {
        Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".removeCallback");

        synchronized (mListeners) {
            ArrayList<Listener> currentListeners = mListeners.get(uri);

            if (currentListeners != null) {
                currentListeners.remove(listener);
            }
            if (currentListeners == null || currentListeners.size() == 0) {
                mListeners.remove(uri);
            }

            if (mListeners.size() == 0) {
                mBackgroundHandler.post(() -> {
                    mSecureSettings.unregisterContentObserver(mContentObserver);
                });
            }
        }
        Trace.traceEnd(Trace.TRACE_TAG_APP);
    }

    @Override
    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".dump");

        synchronized (mListeners) {
            pw.println("Settings Uri Listener List:");
            for (Uri uri : mListeners.keySet()) {
                pw.println("   Uri=" + uri);
                for (Listener listener : mListeners.get(uri)) {
                    pw.println("      Listener=" + listener.getClass().getName());
                }
            }
        }
        Trace.traceEnd(Trace.TRACE_TAG_APP);
    }

    private String getCurrentSettingValue(Uri uri, int userId) {
        final String setting = uri == null ? null : uri.getLastPathSegment();
        return mSecureSettings.getStringForUser(setting, userId);
    }

    /**
     * Listener invoked whenever settings are changed.
     */
    public interface Listener {
        @MainThread
        void onSettingChanged(@NonNull Uri setting, int userId, @Nullable String value);
    }
}