// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.webapps;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;

import org.chromium.base.ContextUtils;
import org.chromium.base.ResettersForTesting;
import org.chromium.base.StrictModeContext;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.browser.ActivityUtils;
import org.chromium.chrome.browser.browserservices.intents.BitmapHelper;
import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.browserservices.intents.WebappInfo;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.components.embedder_support.util.Origin;
import org.chromium.components.webapk.lib.client.WebApkValidator;
import org.chromium.components.webapps.WebApkDetailsForDefaultOfflinePage;
import org.chromium.content_public.browser.WebContents;
import org.chromium.webapk.lib.common.WebApkConstants;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Provides access to more detail about webapks.
 */
public class WebApkDataProvider {
    // Contains the details to return for an offline app when testing.
    private static WebappInfo sWebappInfoForTesting;

    // Keeps track of the data needed for the custom offline page data.
    private static class OfflineData {
        private @NonNull String mName;
        private @NonNull String mIcon;
    }

    public static void setWebappInfoForTesting(WebappInfo webappInfo) {
        sWebappInfoForTesting = webappInfo;
        ResettersForTesting.register(() -> sWebappInfoForTesting = null);
    }

    /**
     * Converts a color value to a hex string.
     * @param color The color to convert.
     * @return The RGB values of the color, as string presented in hex (prefixed with #).
     *         For example: '#FF0000' (for red).
     */
    private static String colorToHexString(@ColorInt long color) {
        return String.format("#%02X%02X%02X", (((color) >> 16) & 0xFF), (((color) >> 8) & 0xFF),
                (((color) >> 0) & 0xFF));
    }

    private static WebappInfo getPartialWebappInfo(String url) {
        if (sWebappInfoForTesting != null) return sWebappInfoForTesting;

        Context appContext = ContextUtils.getApplicationContext();
        String packageName = WebApkValidator.queryFirstWebApkPackage(appContext, url);
        return WebappInfo.create(WebApkIntentDataProviderFactory.create(new Intent(), packageName,
                "", WebApkConstants.ShortcutSource.UNKNOWN, false /* forceNavigation */,
                false /* isSplashProvidedByWebApk */, null /* shareData */,
                null /* shareDataActivityClassName */));
    }

    private static OfflineData getOfflinePageInfoForPwa(String url) {
        WebappInfo webAppInfo = getPartialWebappInfo(url);
        if (webAppInfo == null) return null;

        OfflineData result = new OfflineData();
        result.mName = webAppInfo.shortName();
        // Encoding the image is marked as a slow method, but this call is intentional,
        // we're encoding only a single small icon (the app icon) and the code path is
        // triggered only when the device is offline. We therefore shouldn't bother
        // jumping through hoops to make it fast.
        try (StrictModeContext ignored = StrictModeContext.allowSlowCalls()) {
            result.mIcon = webAppInfo.icon().encoded();
        }

        return result;
    }

    private static OfflineData getOfflinePageInfoForTwa(CustomTabActivity customTabActivity) {
        BrowserServicesIntentDataProvider dataProvider = customTabActivity.getIntentDataProvider();
        if (dataProvider == null) return null;

        String clientPackageName = dataProvider.getClientPackageName();
        if (clientPackageName == null) return null;

        OfflineData result = new OfflineData();
        PackageManager packageManager = ContextUtils.getApplicationContext().getPackageManager();
        try {
            result.mName = packageManager
                                   .getApplicationLabel(packageManager.getApplicationInfo(
                                           clientPackageName, PackageManager.GET_META_DATA))
                                   .toString();
            Drawable d = packageManager.getApplicationIcon(clientPackageName);
            Bitmap bitmap = Bitmap.createBitmap(
                    d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            d.draw(canvas);
            try (StrictModeContext ignored = StrictModeContext.allowSlowCalls()) {
                result.mIcon = BitmapHelper.encodeBitmapAsString(bitmap);
            }
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }

        return result;
    }

    private static boolean isWithinScope(String url, CustomTabActivity customTabActivity) {
        BrowserServicesIntentDataProvider dataProvider = customTabActivity.getIntentDataProvider();
        Set<Origin> origins = new HashSet<>();
        origins.add(Origin.create(dataProvider.getUrlToLoad()));

        List<String> additionalOrigins = dataProvider.getTrustedWebActivityAdditionalOrigins();
        if (additionalOrigins != null) {
            for (String origin : additionalOrigins) {
                origins.add(Origin.create(origin));
            }
        }

        return origins.contains(Origin.create(url));
    }

    @CalledByNative
    public static String[] getOfflinePageInfo(int[] fields, String url, WebContents webContents) {
        Activity activity = ActivityUtils.getActivityFromWebContents(webContents);

        OfflineData offlineData = null;
        if (activity instanceof CustomTabActivity) {
            CustomTabActivity customTabActivity = (CustomTabActivity) activity;
            if (isWithinScope(url, customTabActivity)) {
                offlineData = getOfflinePageInfoForTwa(customTabActivity);
            }
        } else {
            offlineData = getOfflinePageInfoForPwa(url);
        }

        if (offlineData == null) return null;

        List<String> fieldValues = new ArrayList<String>();
        for (int field : fields) {
            switch (field) {
                case WebApkDetailsForDefaultOfflinePage.SHORT_NAME:
                    fieldValues.add(offlineData.mName);
                    break;
                case WebApkDetailsForDefaultOfflinePage.ICON:
                    fieldValues.add("data:image/png;base64," + offlineData.mIcon);
                    break;
                default:
                    fieldValues.add("No such field: " + field);
                    break;
            }
        }
        return fieldValues.toArray(new String[0]);
    }
}
