/*
 * Copyright (C) 2022 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.internal.app;

import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus;

import android.app.LocaleConfig;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageManager;
import android.os.LocaleList;
import android.util.Log;

import java.util.HashSet;
import java.util.Locale;
import java.util.stream.Collectors;

/**
 * A class used to access an application's supporting locales.
 */
public class AppLocaleStore {
    private static final String TAG = AppLocaleStore.class.getSimpleName();

    public static AppLocaleResult getAppSupportedLocales(
            Context context, String packageName) {
        LocaleConfig localeConfig = null;
        AppLocaleResult.LocaleStatus localeStatus = LocaleStatus.UNKNOWN_FAILURE;
        HashSet<Locale> appSupportedLocales = new HashSet<>();
        HashSet<Locale> assetLocale = getAssetLocales(context, packageName);

        try {
            localeConfig = new LocaleConfig(context.createPackageContext(packageName, 0));
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Can not found the package name : " + packageName + " / " + e);
        }

        if (localeConfig != null) {
            if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) {
                LocaleList packageLocaleList = localeConfig.getSupportedLocales();
                boolean shouldFilterNotMatchingLocale = !hasInstallerInfo(context, packageName) &&
                        isSystemApp(context, packageName);

                Log.d(TAG, "filterNonMatchingLocale. " +
                        ", shouldFilterNotMatchingLocale: " + shouldFilterNotMatchingLocale +
                        ", assetLocale size: " + assetLocale.size() +
                        ", packageLocaleList size: " + packageLocaleList.size());

                for (int i = 0; i < packageLocaleList.size(); i++) {
                    appSupportedLocales.add(packageLocaleList.get(i));
                }
                if (shouldFilterNotMatchingLocale) {
                    appSupportedLocales = filterNotMatchingLocale(appSupportedLocales, assetLocale);
                }

                if (appSupportedLocales.size() > 0) {
                    localeStatus = LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG;
                } else {
                    localeStatus = LocaleStatus.NO_SUPPORTED_LANGUAGE_IN_APP;
                }
            } else if (localeConfig.getStatus() == LocaleConfig.STATUS_NOT_SPECIFIED) {
                if (assetLocale.size() > 0) {
                    localeStatus = LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
                    appSupportedLocales = assetLocale;
                } else {
                    localeStatus = LocaleStatus.ASSET_LOCALE_IS_EMPTY;
                }
            }
        }
        Log.d(TAG, "getAppSupportedLocales(). package: " + packageName
                + ", status: " + localeStatus
                + ", appSupportedLocales:" + appSupportedLocales.size());
        return new AppLocaleResult(localeStatus, appSupportedLocales);
    }

    private static HashSet<Locale> getAssetLocales(Context context, String packageName) {
        HashSet<Locale> result = new HashSet<>();
        try {
            PackageManager packageManager = context.getPackageManager();
            String[] locales = packageManager.getResourcesForApplication(
                    packageManager.getPackageInfo(packageName, PackageManager.MATCH_ALL)
                            .applicationInfo).getAssets().getNonSystemLocales();
            if (locales == null) {
                Log.i(TAG, "[" + packageName + "] locales are null.");
            } else if (locales.length <= 0) {
                Log.i(TAG, "[" + packageName + "] locales length is 0.");
            } else {
                for (String language : locales) {
                    result.add(Locale.forLanguageTag(language));
                }
            }
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Can not found the package name : " + packageName + " / " + e);
        }
        return result;
    }

    private static HashSet<Locale> filterNotMatchingLocale(
            HashSet<Locale> appSupportedLocales, HashSet<Locale> assetLocale) {
        return appSupportedLocales.stream()
                .filter(locale -> matchLanguageInSet(locale, assetLocale))
                .collect(Collectors.toCollection(HashSet::new));
    }

    private static boolean matchLanguageInSet(Locale locale, HashSet<Locale> localesSet) {
        if (localesSet.contains(locale)) {
            return true;
        }
        for (Locale l: localesSet) {
            if(LocaleList.matchesLanguageAndScript(l, locale)) {
                return true;
            }
        }
        return false;
    }

    private static boolean hasInstallerInfo(Context context, String packageName) {
        InstallSourceInfo installSourceInfo;
        try {
            installSourceInfo = context.getPackageManager().getInstallSourceInfo(packageName);
            return installSourceInfo != null;
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Installer info not found for: " + packageName);
        }
        return false;
    }

    private static boolean isSystemApp(Context context, String packageName) {
        ApplicationInfo applicationInfo;
        try {
            applicationInfo = context.getPackageManager()
                    .getApplicationInfoAsUser(packageName, /* flags= */ 0, context.getUserId());
            return applicationInfo.isSystemApp();
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Application info not found for: " + packageName);
        }
        return false;
    }

    /**
     * A class used to store an application's supporting locales.
     */
    public static class AppLocaleResult {
        public enum LocaleStatus {
            UNKNOWN_FAILURE,
            NO_SUPPORTED_LANGUAGE_IN_APP,
            ASSET_LOCALE_IS_EMPTY,
            GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG,
            GET_SUPPORTED_LANGUAGE_FROM_ASSET,
        }

        LocaleStatus mLocaleStatus;
        public HashSet<Locale> mAppSupportedLocales;

        public AppLocaleResult(LocaleStatus localeStatus, HashSet<Locale> appSupportedLocales) {
            this.mLocaleStatus = localeStatus;
            this.mAppSupportedLocales = appSupportedLocales;
        }
    }
}
