/*
 * Copyright (C) 2019 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.permissioncontroller.role.ui.specialappaccess;

import android.app.Application;
import android.content.pm.ApplicationInfo;
import android.os.Process;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;

import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData;
import com.android.permissioncontroller.role.ui.RoleLiveData;
import com.android.permissioncontroller.role.ui.RoleSortFunction;
import com.android.permissioncontroller.role.utils.UserUtils;
import com.android.role.controller.model.Role;

import java.util.List;

/**
 * {@link ViewModel} for a special app access.
 */
public class SpecialAppAccessViewModel extends AndroidViewModel {

    private static final String LOG_TAG = SpecialAppAccessViewModel.class.getSimpleName();

    @NonNull
    private final Role mRole;

    @NonNull
    private final LiveData<List<Pair<ApplicationInfo, Boolean>>> mRoleLiveData;

    @NonNull
    private final ArrayMap<String, ManageRoleHolderStateLiveData> mManageRoleHolderStateLiveDatas =
            new ArrayMap<>();

    public SpecialAppAccessViewModel(@NonNull Role role, @NonNull Application application) {
        super(application);

        mRole = role;

        UserHandle user = Process.myUserHandle();
        RoleLiveData roleLiveData = new RoleLiveData(role, user, application);
        UserHandle workProfile = UserUtils.getWorkProfile(application);
        RoleSortFunction sortFunction = new RoleSortFunction(application);
        if (workProfile == null) {
            mRoleLiveData = Transformations.map(roleLiveData, sortFunction);
        } else {
            RoleLiveData workRoleLiveData = new RoleLiveData(role, workProfile, application);
            mRoleLiveData = Transformations.map(new MergeRoleLiveData(roleLiveData,
                    workRoleLiveData), sortFunction);
        }
    }

    @NonNull
    public LiveData<List<Pair<ApplicationInfo, Boolean>>> getRoleLiveData() {
        return mRoleLiveData;
    }

    /**
     * Observe all the {@link ManageRoleHolderStateLiveData} instances.
     *
     * @param owner the {@link LifecycleOwner} which controls the observer
     * @param observer the observer that will receive the events
     */
    public void observeManageRoleHolderState(@NonNull LifecycleOwner owner,
            @NonNull ManageRoleHolderStateObserver observer) {
        int manageRoleHolderStateLiveDatasSize = mManageRoleHolderStateLiveDatas.size();
        for (int i = 0; i < manageRoleHolderStateLiveDatasSize; i++) {
            ManageRoleHolderStateLiveData liveData = mManageRoleHolderStateLiveDatas.valueAt(i);

            liveData.observe(owner, state -> observer.onManageRoleHolderStateChanged(liveData,
                    state));
        }
    }

    /**
     * Get or create a {@link ManageRoleHolderStateLiveData} instance for the specified key.
     *
     * @param key the key for the {@link ManageRoleHolderStateLiveData}
     * @param owner the {@link LifecycleOwner} which controls the observer
     * @param observer the observer that will receive the events
     *
     * @return the {@link ManageRoleHolderStateLiveData}
     */
    @NonNull
    private ManageRoleHolderStateLiveData getManageRoleHolderStateLiveData(@NonNull String key,
            @NonNull LifecycleOwner owner, @NonNull ManageRoleHolderStateObserver observer) {
        ManageRoleHolderStateLiveData liveData = mManageRoleHolderStateLiveDatas.get(key);
        if (liveData == null) {
            liveData = new ManageRoleHolderStateLiveData();
            ManageRoleHolderStateLiveData finalLiveData = liveData;
            liveData.observe(owner, state -> observer.onManageRoleHolderStateChanged(finalLiveData,
                    state));
            mManageRoleHolderStateLiveDatas.put(key, liveData);
        }
        return liveData;
    }

    /**
     * Set whether an application has an special app access.
     *
     * @param packageName the package name of the application
     * @param allow whether the application should have the access
     * @param user the user of the application
     * @param key the key for the {@link ManageRoleHolderStateLiveData}
     * @param owner the {@link LifecycleOwner} which controls the observer
     * @param observer the observer that will receive the events
     */
    public void setSpecialAppAccessAsUser(@NonNull String packageName, boolean allow,
            @NonNull UserHandle user, @NonNull String key, @NonNull LifecycleOwner owner,
            @NonNull ManageRoleHolderStateObserver observer) {
        ManageRoleHolderStateLiveData liveData = getManageRoleHolderStateLiveData(key, owner,
                observer);
        if (liveData.getValue() != ManageRoleHolderStateLiveData.STATE_IDLE) {
            Log.i(LOG_TAG, "Trying to set special app access while another request is on-going");
            return;
        }
        liveData.setRoleHolderAsUser(mRole.getName(), packageName, allow, 0, user,
                getApplication());
    }

    /**
     * Observer for multiple {@link ManageRoleHolderStateLiveData} instances.
     */
    public interface ManageRoleHolderStateObserver {

        /**
         * Callback when any {@link ManageRoleHolderStateLiveData} changed.
         *
         * @param liveData the {@link ManageRoleHolderStateLiveData} that changed
         * @param state the state after the change
         */
        void onManageRoleHolderStateChanged(@NonNull ManageRoleHolderStateLiveData liveData,
                int state);
    }

    /**
     * {@link ViewModelProvider.Factory} for {@link SpecialAppAccessViewModel}.
     */
    public static class Factory implements ViewModelProvider.Factory {

        @NonNull
        private Role mRole;

        @NonNull
        private Application mApplication;

        public Factory(@NonNull Role role, @NonNull Application application) {
            mRole = role;
            mApplication = application;
        }

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection unchecked
            return (T) new SpecialAppAccessViewModel(mRole, mApplication);
        }
    }
}
