/*
 * This file is part of LSPosed.
 *
 * LSPosed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LSPosed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LSPosed.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2021 LSPosed Contributors
 */

package org.lsposed.lspd.service;

import static org.lsposed.lspd.service.ServiceManager.TAG;

import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Build;
import android.os.IBinder;
import android.os.IUserManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

import org.lsposed.lspd.util.Utils;

import java.util.LinkedList;
import java.util.List;

public class UserService {
    private static IUserManager um = null;
    private static IBinder binder = null;
    private static final IBinder.DeathRecipient recipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.w(TAG, "um is dead");
            binder.unlinkToDeath(this, 0);
            binder = null;
            um = null;
        }
    };

    static boolean isAlive() {
        var um = getUserManager();
        return um != null && um.asBinder().isBinderAlive();
    }

    public static IUserManager getUserManager() {
        if (binder == null || um == null) {
            binder = ServiceManager.getService(Context.USER_SERVICE);
            if (binder == null) return null;
            try {
                binder.linkToDeath(recipient, 0);
            } catch (RemoteException e) {
                Log.e(TAG, Log.getStackTraceString(e));
            }
            um = IUserManager.Stub.asInterface(binder);
        }
        return um;
    }

    public static List<UserInfo> getUsers() throws RemoteException {
        IUserManager um = getUserManager();
        List<UserInfo> users = new LinkedList<>();
        if (um == null) return users;
        try {
            users = um.getUsers(true);
        } catch (NoSuchMethodError e) {
            users = um.getUsers(true, true, true);
        }
        if (Utils.isLENOVO) { // lenovo hides user [900, 910) for app cloning
            var gotUsers = new boolean[10];
            for (var user : users) {
                var residual = user.id - 900;
                if (residual >= 0 && residual < 10) gotUsers[residual] = true;
            }
            for (int i = 900; i <= 909; i++) {
                var user = um.getUserInfo(i);
                if (user != null && !gotUsers[i - 900]) {
                    users.add(user);
                }
            }
        }
        return users;
    }

    public static UserInfo getUserInfo(int userId) throws RemoteException {
        IUserManager um = getUserManager();
        if (um == null) return null;
        return um.getUserInfo(userId);
    }

    public static String getUserName(int userId) {
        try {
            var userInfo = getUserInfo(userId);
            if (userInfo != null) return userInfo.name;
        } catch (RemoteException ignored) {
        }
        return String.valueOf(userId);
    }

    public static int getProfileParent(int userId) throws RemoteException {
        IUserManager um = getUserManager();
        if (um == null) return -1;
        var userInfo = um.getProfileParent(userId);
        if (userInfo == null) return userId;
        else return userInfo.id;
    }

    public static boolean isUserUnlocked(int userId) throws RemoteException {
        IUserManager um = getUserManager();
        if (um == null) return false;
        return um.isUserUnlocked(userId);
    }
}
