/*
 * Copyright (C) 2021 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.server.location.settings;

import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;

import android.app.ActivityManager;
import android.content.Context;
import android.os.Environment;
import android.os.RemoteException;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.FgThread;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;

/**
 * Accessor for location user settings. Ensure there is only ever one instance as multiple instances
 * don't play nicely with each other.
 */
public class LocationSettings {

    /** Listens for changes to location user settings. */
    public interface LocationUserSettingsListener {
        /** Invoked when location user settings have changed for the given user. */
        void onLocationUserSettingsChanged(int userId, LocationUserSettings oldSettings,
                LocationUserSettings newSettings);
    }

    private static final String LOCATION_DIRNAME = "location";
    private static final String LOCATION_SETTINGS_FILENAME = "settings";

    final Context mContext;

    @GuardedBy("mUserSettings")
    private final SparseArray<LocationUserSettingsStore> mUserSettings;
    private final CopyOnWriteArrayList<LocationUserSettingsListener> mUserSettingsListeners;

    public LocationSettings(Context context) {
        mContext = context;
        mUserSettings = new SparseArray<>(1);
        mUserSettingsListeners = new CopyOnWriteArrayList<>();
    }

    /** Registers a listener for changes to location user settings. */
    public final void registerLocationUserSettingsListener(LocationUserSettingsListener listener) {
        mUserSettingsListeners.add(listener);
    }

    /** Unregisters a listener for changes to location user settings. */
    public final void unregisterLocationUserSettingsListener(
            LocationUserSettingsListener listener) {
        mUserSettingsListeners.remove(listener);
    }

    protected File getUserSettingsDir(int userId) {
        return Environment.getDataSystemDeDirectory(userId);
    }

    protected LocationUserSettingsStore createUserSettingsStore(int userId, File file) {
        return new LocationUserSettingsStore(userId, file);
    }

    private LocationUserSettingsStore getUserSettingsStore(int userId) {
        synchronized (mUserSettings) {
            LocationUserSettingsStore settingsStore = mUserSettings.get(userId);
            if (settingsStore == null) {
                File file = new File(new File(getUserSettingsDir(userId), LOCATION_DIRNAME),
                        LOCATION_SETTINGS_FILENAME);
                settingsStore = createUserSettingsStore(userId, file);
                mUserSettings.put(userId, settingsStore);
            }
            return settingsStore;
        }
    }

    /** Retrieves the current state of location user settings. */
    public final LocationUserSettings getUserSettings(int userId) {
        return getUserSettingsStore(userId).get();
    }

    /** Updates the current state of location user settings for the given user. */
    public final void updateUserSettings(int userId,
            Function<LocationUserSettings, LocationUserSettings> updater) {
        getUserSettingsStore(userId).update(updater);
    }

    /** Dumps info for debugging. */
    public final void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
        int[] userIds;
        try {
            userIds = ActivityManager.getService().getRunningUserIds();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }

        if (mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)) {
            ipw.print("ADAS Location Setting: ");
            ipw.increaseIndent();
            if (userIds.length > 1) {
                ipw.println();
                for (int userId : userIds) {
                    ipw.print("[u");
                    ipw.print(userId);
                    ipw.print("] ");
                    ipw.println(getUserSettings(userId).isAdasGnssLocationEnabled());
                }
            } else {
                ipw.println(getUserSettings(userIds[0]).isAdasGnssLocationEnabled());
            }
            ipw.decreaseIndent();
        }
    }

    @VisibleForTesting
    final void flushFiles() throws InterruptedException {
        synchronized (mUserSettings) {
            int size = mUserSettings.size();
            for (int i = 0; i < size; i++) {
                mUserSettings.valueAt(i).flushFile();
            }
        }
    }

    @VisibleForTesting
    final void deleteFiles() throws InterruptedException {
        synchronized (mUserSettings) {
            int size = mUserSettings.size();
            for (int i = 0; i < size; i++) {
                mUserSettings.valueAt(i).deleteFile();
            }
        }
    }

    protected final void fireListeners(int userId, LocationUserSettings oldSettings,
            LocationUserSettings newSettings) {
        for (LocationUserSettingsListener listener : mUserSettingsListeners) {
            listener.onLocationUserSettingsChanged(userId, oldSettings, newSettings);
        }
    }

    class LocationUserSettingsStore extends SettingsStore<LocationUserSettings> {

        protected final int mUserId;

        LocationUserSettingsStore(int userId, File file) {
            super(file);
            mUserId = userId;
        }

        @Override
        protected LocationUserSettings read(int version, DataInput in) throws IOException {
            return filterSettings(LocationUserSettings.read(mContext.getResources(), version, in));
        }

        @Override
        protected void write(DataOutput out, LocationUserSettings settings) throws IOException {
            settings.write(out);
        }

        @Override
        public void update(Function<LocationUserSettings, LocationUserSettings> updater) {
            super.update(settings -> filterSettings(updater.apply(settings)));
        }

        @Override
        protected void onChange(LocationUserSettings oldSettings,
                LocationUserSettings newSettings) {
            FgThread.getExecutor().execute(() -> fireListeners(mUserId, oldSettings, newSettings));
        }

        private LocationUserSettings filterSettings(LocationUserSettings settings) {
            if (settings.isAdasGnssLocationEnabled()
                    && !mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)) {
                // prevent non-automotive devices from ever enabling this
                settings = settings.withAdasGnssLocationEnabled(false);
            }
            return settings;
        }
    }
}
