/*
 * 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.phone;

import android.content.Context;
import android.os.PersistableBundle;
import android.provider.Settings;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;

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

import com.android.internal.telephony.util.ArrayUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

class ShortcutViewUtils {
    private static final String LOG_TAG = "ShortcutViewUtils";

    // Emergency services which will be promoted on the shortcut view.
    static final int[] PROMOTED_CATEGORIES = {
            EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
            EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE,
            EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
    };

    static final int PROMOTED_CATEGORIES_BITMASK;

    static {
        int bitmask = 0;
        for (int category : PROMOTED_CATEGORIES) {
            bitmask |= category;
        }
        PROMOTED_CATEGORIES_BITMASK = bitmask;
    }

    static class Config {
        private final boolean mCanEnableShortcutView;
        private PhoneInfo mPhoneInfo = null;

        Config(@NonNull Context context, PersistableBundle carrierConfig, int entryType) {
            mCanEnableShortcutView = canEnableShortcutView(carrierConfig, entryType);
            refresh(context);
        }

        void refresh(@NonNull Context context) {
            if (mCanEnableShortcutView && !isAirplaneModeOn(context)) {
                mPhoneInfo = ShortcutViewUtils.pickPreferredPhone(context);
            } else {
                mPhoneInfo = null;
            }
        }

        boolean isEnabled() {
            return mPhoneInfo != null;
        }

        PhoneInfo getPhoneInfo() {
            return mPhoneInfo;
        }

        String getCountryIso() {
            if (mPhoneInfo == null) {
                return null;
            }
            return mPhoneInfo.getCountryIso();
        }

        boolean hasPromotedEmergencyNumber(String number) {
            if (mPhoneInfo == null) {
                return false;
            }
            return mPhoneInfo.hasPromotedEmergencyNumber(number);
        }

        private boolean canEnableShortcutView(PersistableBundle carrierConfig, int entryType) {
            if (entryType != EmergencyDialer.ENTRY_TYPE_POWER_MENU) {
                Log.d(LOG_TAG, "Disables shortcut view since it's not launched from power menu");
                return false;
            }
            if (carrierConfig == null || !carrierConfig.getBoolean(
                    CarrierConfigManager.KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL)) {
                Log.d(LOG_TAG, "Disables shortcut view by carrier requirement");
                return false;
            }
            return true;
        }

        private boolean isAirplaneModeOn(@NonNull Context context) {
            return Settings.Global.getInt(context.getContentResolver(),
                    Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
        }
    }

    // Info and emergency call capability of every phone.
    static class PhoneInfo {
        private final PhoneAccountHandle mHandle;
        private final boolean mCanPlaceEmergencyCall;
        private final int mSubId;
        private final String mCountryIso;
        private final List<EmergencyNumber> mPromotedEmergencyNumbers;

        private PhoneInfo(int subId, String countryIso,
                List<EmergencyNumber> promotedEmergencyNumbers) {
            this(null, true, subId, countryIso, promotedEmergencyNumbers);
        }

        private PhoneInfo(PhoneAccountHandle handle, boolean canPlaceEmergencyCall, int subId,
                String countryIso, List<EmergencyNumber> promotedEmergencyNumbers) {
            mHandle = handle;
            mCanPlaceEmergencyCall = canPlaceEmergencyCall;
            mSubId = subId;
            mCountryIso = countryIso;
            mPromotedEmergencyNumbers = promotedEmergencyNumbers;
        }

        public PhoneAccountHandle getPhoneAccountHandle() {
            return mHandle;
        }

        public boolean canPlaceEmergencyCall() {
            return mCanPlaceEmergencyCall;
        }

        public int getSubId() {
            return mSubId;
        }

        public String getCountryIso() {
            return mCountryIso;
        }

        public List<EmergencyNumber> getPromotedEmergencyNumbers() {
            return mPromotedEmergencyNumbers;
        }

        public boolean isSufficientForEmergencyCall(@NonNull Context context) {
            // Checking mCountryIso because the emergency number list is not reliable to be
            // suggested to users if the device didn't camp to any network. In this case, users
            // can still try to dial emergency numbers with dial pad.
            return mCanPlaceEmergencyCall && mPromotedEmergencyNumbers != null
                    && isSupportedCountry(context, mCountryIso);
        }

        public boolean hasPromotedEmergencyNumber(String number) {
            for (EmergencyNumber emergencyNumber : mPromotedEmergencyNumbers) {
                if (emergencyNumber.getNumber().equalsIgnoreCase(number)) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("{");
            if (mHandle != null) {
                sb.append("handle=").append(mHandle.getId()).append(", ");
            }
            sb.append("subId=").append(mSubId)
                    .append(", canPlaceEmergencyCall=").append(mCanPlaceEmergencyCall)
                    .append(", networkCountryIso=").append(mCountryIso);
            if (mPromotedEmergencyNumbers != null) {
                sb.append(", emergencyNumbers=");
                for (EmergencyNumber emergencyNumber : mPromotedEmergencyNumbers) {
                    sb.append(emergencyNumber.getNumber()).append(":")
                            .append(emergencyNumber).append(",");
                }
            }
            sb.append("}");
            return sb.toString();
        }
    }

    /**
     * Picks a preferred phone (SIM slot) which is sufficient for emergency call and can provide
     * promoted emergency numbers.
     *
     * A promoted emergency number should be dialed out over the preferred phone. Other emergency
     * numbers should be still dialable over the system default phone.
     *
     * @return A preferred phone and its promoted emergency number, or null if no phone/promoted
     * emergency numbers available.
     */
    @Nullable
    static PhoneInfo pickPreferredPhone(@NonNull Context context) {
        TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
        if (telephonyManager.getPhoneCount() <= 0) {
            Log.w(LOG_TAG, "No phone available!");
            return null;
        }

        Map<Integer, List<EmergencyNumber>> promotedLists =
                getPromotedEmergencyNumberLists(telephonyManager);
        if (promotedLists == null || promotedLists.isEmpty()) {
            return null;
        }

        // For a multi-phone device, tries the default phone account.
        TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
        PhoneAccountHandle defaultHandle = telecomManager.getDefaultOutgoingPhoneAccount(
                PhoneAccount.SCHEME_TEL);
        if (defaultHandle != null) {
            PhoneInfo phone = loadPhoneInfo(context, defaultHandle, telephonyManager,
                    telecomManager, promotedLists);
            if (phone.isSufficientForEmergencyCall(context)) {
                return phone;
            }
            Log.w(LOG_TAG, "Default PhoneAccount is insufficient for emergency call: "
                    + phone.toString());
        } else {
            Log.w(LOG_TAG, "Missing default PhoneAccount! Is this really a phone device?");
        }

        // Looks for any one phone which supports emergency call.
        List<PhoneAccountHandle> allHandles = telecomManager.getCallCapablePhoneAccounts();
        if (allHandles != null && !allHandles.isEmpty()) {
            for (PhoneAccountHandle handle : allHandles) {
                PhoneInfo phone = loadPhoneInfo(context, handle, telephonyManager, telecomManager,
                        promotedLists);
                if (phone.isSufficientForEmergencyCall(context)) {
                    return phone;
                } else {
                    if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
                        Log.d(LOG_TAG, "PhoneAccount " + phone.toString()
                                + " is insufficient for emergency call.");
                    }
                }
            }
        }

        Log.w(LOG_TAG, "No PhoneAccount available for emergency call!");
        return null;
    }

    private static boolean isSupportedCountry(@NonNull Context context, String countryIso) {
        if (TextUtils.isEmpty(countryIso)) {
            return false;
        }

        String[] countrysToEnableShortcutView = context.getResources().getStringArray(
                R.array.config_countries_to_enable_shortcut_view);
        for (String supportedCountry : countrysToEnableShortcutView) {
            if (countryIso.equalsIgnoreCase(supportedCountry)) {
                return true;
            }
        }
        return false;
    }

    private static PhoneInfo loadPhoneInfo(
            @NonNull Context context,
            @NonNull PhoneAccountHandle handle,
            @NonNull TelephonyManager telephonyManager,
            @NonNull TelecomManager telecomManager,
            Map<Integer, List<EmergencyNumber>> promotedLists) {
        boolean canPlaceEmergencyCall = false;
        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
        String countryIso = null;
        List<EmergencyNumber> emergencyNumberList = null;

        PhoneAccount phoneAccount = telecomManager.getPhoneAccount(handle);
        if (phoneAccount != null) {
            canPlaceEmergencyCall = phoneAccount.hasCapabilities(
                    PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS);
            subId = telephonyManager.getSubIdForPhoneAccount(phoneAccount);
        }

        TelephonyManager subTelephonyManager = telephonyManager.createForSubscriptionId(subId);
        if (subTelephonyManager != null) {
            countryIso = subTelephonyManager.getNetworkCountryIso();
        }

        if (promotedLists != null) {
            emergencyNumberList = removeCarrierSpecificPrefixes(context, subId,
                    promotedLists.get(subId));
        }

        return new PhoneInfo(handle, canPlaceEmergencyCall, subId, countryIso, emergencyNumberList);
    }

    @Nullable
    private static String[] getCarrierSpecificPrefixes(@NonNull Context context, int subId) {
        CarrierConfigManager configMgr = context.getSystemService(CarrierConfigManager.class);
        if (configMgr == null) {
            return null;
        }
        PersistableBundle b = configMgr.getConfigForSubId(subId);
        return b == null ? null : b.getStringArray(
                CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY);
    }

    // Removes carrier specific emergency number prefixes (if there is any) from every emergency
    // number and create a new list without duplications. Returns the original list if there is no
    // prefixes.
    @NonNull
    private static List<EmergencyNumber> removeCarrierSpecificPrefixes(
            @NonNull Context context,
            int subId,
            @NonNull List<EmergencyNumber> emergencyNumberList) {
        String[] prefixes = getCarrierSpecificPrefixes(context, subId);
        if (ArrayUtils.isEmpty(prefixes)) {
            return emergencyNumberList;
        }

        List<EmergencyNumber> newList = new ArrayList<>(emergencyNumberList.size());
        for (EmergencyNumber emergencyNumber : emergencyNumberList) {
            // If no prefix was removed from emergencyNumber, add it to the newList directly.
            EmergencyNumber newNumber = emergencyNumber;
            String number = emergencyNumber.getNumber();
            for (String prefix : prefixes) {
                // If emergencyNumber starts with this prefix, remove this prefix to retrieve the
                // actual emergency number.
                // However, if emergencyNumber is exactly the same with this prefix, it could be
                // either a real emergency number, or composed with another prefix. It shouldn't be
                // processed with this prefix whatever.
                if (!TextUtils.isEmpty(prefix) && number.startsWith(prefix)
                        && !number.equals(prefix)) {
                    newNumber = new EmergencyNumber(
                            number.substring(prefix.length()),
                            emergencyNumber.getCountryIso(),
                            emergencyNumber.getMnc(),
                            emergencyNumber.getEmergencyServiceCategoryBitmask(),
                            emergencyNumber.getEmergencyUrns(),
                            emergencyNumber.getEmergencyNumberSourceBitmask(),
                            emergencyNumber.getEmergencyCallRouting());
                    // There should not be more than one prefix attached to a number.
                    break;
                }
            }
            if (!newList.contains(newNumber)) {
                newList.add(newNumber);
            }
        }
        return newList;
    }

    @NonNull
    private static Map<Integer, List<EmergencyNumber>> getPromotedEmergencyNumberLists(
            @NonNull TelephonyManager telephonyManager) {
        Map<Integer, List<EmergencyNumber>> allLists =
                telephonyManager.getEmergencyNumberList();
        if (allLists == null || allLists.isEmpty()) {
            Log.w(LOG_TAG, "Unable to retrieve emergency number lists!");
            return new ArrayMap<>();
        }

        boolean isDebugLoggable = Log.isLoggable(LOG_TAG, Log.DEBUG);
        Map<Integer, List<EmergencyNumber>> promotedEmergencyNumberLists = new ArrayMap<>();
        for (Map.Entry<Integer, List<EmergencyNumber>> entry : allLists.entrySet()) {
            if (entry.getKey() == null || entry.getValue() == null) {
                continue;
            }
            List<EmergencyNumber> emergencyNumberList = entry.getValue();
            if (isDebugLoggable) {
                Log.d(LOG_TAG, "Emergency numbers of " + entry.getKey());
            }

            // The list of promoted emergency numbers which will be visible on shortcut view.
            List<EmergencyNumber> promotedList = new ArrayList<>();
            // A temporary list for non-prioritized emergency numbers.
            List<EmergencyNumber> tempList = new ArrayList<>();

            for (EmergencyNumber emergencyNumber : emergencyNumberList) {
                boolean isPromotedCategory = (emergencyNumber.getEmergencyServiceCategoryBitmask()
                        & PROMOTED_CATEGORIES_BITMASK) != 0;

                // Emergency numbers in DATABASE are prioritized for shortcut view since they were
                // well-categorized.
                boolean isFromPrioritizedSource =
                        (emergencyNumber.getEmergencyNumberSourceBitmask()
                                & EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE) != 0;
                if (isDebugLoggable) {
                    Log.d(LOG_TAG, "  " + emergencyNumber
                            + (isPromotedCategory ? "M" : "")
                            + (isFromPrioritizedSource ? "P" : ""));
                }

                if (isPromotedCategory) {
                    if (isFromPrioritizedSource) {
                        promotedList.add(emergencyNumber);
                    } else {
                        tempList.add(emergencyNumber);
                    }
                }
            }
            // Puts numbers in temp list after prioritized numbers.
            promotedList.addAll(tempList);

            if (!promotedList.isEmpty()) {
                promotedEmergencyNumberLists.put(entry.getKey(), promotedList);
            }
        }

        if (promotedEmergencyNumberLists.isEmpty()) {
            Log.w(LOG_TAG, "No promoted emergency number found!");
        }
        return promotedEmergencyNumberLists;
    }
}
