// Copyright 2015 The Chromium Authors. All rights reserved.
// 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.datausage;

import android.content.Context;
import android.text.TextUtils;

import org.chromium.base.ApplicationState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.ContextUtils;
import org.chromium.base.PackageUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeClassQualifiedName;
import org.chromium.chrome.browser.ChromeApplication;

/**
 * This class provides a base class implementation of a data use observer that is external to
 * Chromium. This class should be accessed only on UI thread.
 */
@JNINamespace("chrome::android")
public class ExternalDataUseObserver {
    /**
     * Listens for application state changes and whenever Chromium state changes to running, checks
     * and notifies {@link #ExternalDataUseObserverBridge} if the control app gets installed or
     * uninstalled.
     */
    private class ControlAppManager implements ApplicationStatus.ApplicationStateListener {
        // Package name of the control app.
        private final String mControlAppPackageName;

        // True if the control app is installed.
        private boolean mInstalled;

        ControlAppManager(String controlAppPackageName) {
            mControlAppPackageName = controlAppPackageName;
            mInstalled = false;
            ApplicationStatus.registerApplicationStateListener(this);
            checkAndNotifyPackageInstallState();
            if (!mInstalled) {
                // Notify the state when the control app is not installed on startup.
                nativeOnControlAppInstallStateChange(mNativeExternalDataUseObserverBridge, false);
            }
        }

        @Override
        public void onApplicationStateChange(int newState) {
            if (newState == ApplicationState.HAS_RUNNING_ACTIVITIES) {
                checkAndNotifyPackageInstallState();
            }
        }

        /**
         * Checks if the control app is installed or uninstalled and notifies {@link
         * #ExternalDataUseObserverBridge} if there is change of installation state.
         */
        private void checkAndNotifyPackageInstallState() {
            // Check if native object is destroyed. This may happen at the time of Chromium
            // shutdown.
            if (mNativeExternalDataUseObserverBridge == 0) {
                return;
            }
            if (TextUtils.isEmpty(mControlAppPackageName)) {
                return;
            }
            boolean isControlAppInstalled =
                    PackageUtils.getPackageVersion(
                            ContextUtils.getApplicationContext(), mControlAppPackageName)
                    != -1;
            if (isControlAppInstalled != mInstalled) {
                mInstalled = isControlAppInstalled;
                nativeOnControlAppInstallStateChange(
                        mNativeExternalDataUseObserverBridge, mInstalled);
            }
        }
    }

    /**
     * Pointer to the native ExternalDataUseObserverBridge object.
     */
    private long mNativeExternalDataUseObserverBridge;

    /**
     * {@link #ControlAppManager} object that notifies when control app is installed.
     */
    private ControlAppManager mControlAppManager;

    @CalledByNative
    private static ExternalDataUseObserver create(Context context, long nativePtr) {
        return ((ChromeApplication) context).createExternalDataUseObserver(nativePtr);
    }

    /**
     * Creates an instance of {@link #ExternalDataUseObserver}.
     * @param nativePtr pointer to the native ExternalDataUseObserver object.
     */
    public ExternalDataUseObserver(long nativePtr) {
        mNativeExternalDataUseObserverBridge = nativePtr;
        assert mNativeExternalDataUseObserverBridge != 0;
    }

    /**
     * @return the default control app package name.
     */
    protected String getDefaultControlAppPackageName() {
        return "";
    }

    /**
     * @return the google variation id.
     * TODO(rajendrant): This function is unused, and should be removed.
     */
    protected int getGoogleVariationID() {
        return 0;
    }

    /**
     * Initializes the control app manager with package name of the control app.
     * @param controlAppPackageName package name of the control app. If this is empty the default
     * control app package name from {@link getDefaultControlAppPackageName} will be used.
     */
    @CalledByNative
    protected void initControlAppManager(String controlAppPackageName) {
        if (TextUtils.isEmpty(controlAppPackageName)) {
            controlAppPackageName = getDefaultControlAppPackageName();
        }
        mControlAppManager = new ControlAppManager(controlAppPackageName);
    }

    /**
     * Notification that the native object has been destroyed.
     */
    @CalledByNative
    private void onDestroy() {
        mNativeExternalDataUseObserverBridge = 0;
    }

    /**
     * Fetches matching rules and returns them via {@link #fetchMatchingRulesDone}. subsequent
     * calls to this method While the fetch is underway, may cause the {@link
     * #fetchMatchingRulesDone} callback to be missed for the subsequent call.
     */
    @CalledByNative
    protected void fetchMatchingRules() {
        fetchMatchingRulesDone(null, null, null);
    }

    /*
     * {@link #fetchMatchingRulesDone}  reports the result of {@link #fetchMatchingRules} to
     * the native.
     * @param appPackageName package name of the app that should be matched.
     * @domainPathRegex regex in RE2 syntax that is used for matching URLs.
     * @param label opaque label that must be applied to the data use reports, and must uniquely
     * identify the matching rule. Each element in {@link #label} must have non-zero length.
     * The three vectors are should have equal length. All three vectors may be empty which
     * implies that no matching rules are active.
     */
    protected void fetchMatchingRulesDone(
            String[] appPackageName, String[] domainPathRegEx, String[] label) {
        // Check if native object is destroyed. This may happen at the time of Chromium shutdown.
        if (mNativeExternalDataUseObserverBridge == 0) {
            return;
        }
        nativeFetchMatchingRulesDone(
                mNativeExternalDataUseObserverBridge, appPackageName, domainPathRegEx, label);
    }

    /**
     * Asynchronously reports data use to the external observer.
     * @param label the label provided by {@link #ExternalDataUseObserver} for the matching rule.
     * @param tag “ChromeCustomTab” for Chrome custom tab, or "ChromeTab" for a default tab.
     * @param networkType type of the network on which the traffic was exchanged. This integer value
     * must map to NetworkChangeNotifier.ConnectionType.
     * @param mccMnc MCCMNC of the network on which the traffic was exchanged.
     * @param startTimeInMillis start time of the report in milliseconds since the Epoch (January
     * 1st 1970, 00:00:00.000).
     * @param endTimeInMillis end time of the report in milliseconds since the Epoch (January 1st
     * 1970, 00:00:00.000).
     * @param bytesDownloaded number of bytes downloaded by Chromium.
     * @param bytesUploaded number of bytes uploaded by Chromium.
     * The result of this request is returned asynchronously via
     * {@link #nativeOnReportDataUseDone}. A new report should preferably be submitted only after
     * the result of the previous report has been returned via {@link #nativeOnReportDataUseDone}.
     * Submitting another data use report while the previous is pending may cause the previous
     * report to be lost.
     */
    @CalledByNative
    protected void reportDataUse(String label, String tag, int networkType, String mccMnc,
            long startTimeInMillis, long endTimeInMillis, long bytesDownloaded,
            long bytesUploaded) {}

    /*
     * {@link #onReportDataUseDone}  reports the result of {@link #reportDataUse} to
     * the native.
     * @param success true if the data report was successfully submitted to the external observer.
     */
    protected void onReportDataUseDone(boolean success) {
        // Check if native object is destroyed.  This may happen at the time of Chromium shutdown.
        if (mNativeExternalDataUseObserverBridge == 0) {
            return;
        }
        nativeOnReportDataUseDone(mNativeExternalDataUseObserverBridge, success);
    }

    @NativeClassQualifiedName("ExternalDataUseObserverBridge")
    private native void nativeFetchMatchingRulesDone(long nativeExternalDataUseObserver,
            String[] appPackageName, String[] domainPathRegEx, String[] label);

    @NativeClassQualifiedName("ExternalDataUseObserverBridge")
    private native void nativeOnReportDataUseDone(
            long nativeExternalDataUseObserver, boolean success);

    @NativeClassQualifiedName("ExternalDataUseObserverBridge")
    private native void nativeOnControlAppInstallStateChange(
            long nativeExternalDataUseObserver, boolean isControlAppInstalled);
}