// 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.ntp;

import android.os.SystemClock;
import android.support.annotation.IntDef;

import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.rappor.RapporServiceBridge;
import org.chromium.chrome.browser.suggestions.SuggestionsEventReporterBridge;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.util.UrlUtilities;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.base.PageTransition;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.TimeUnit;

/**
 * Records UMA stats for which actions the user takes on the NTP in the
 * "NewTabPage.ActionAndroid2" histogram.
 */
public final class NewTabPageUma {
    private NewTabPageUma() {}

    // Possible actions taken by the user on the NTP. These values are also defined in
    // histograms.xml. WARNING: these values must stay in sync with histograms.xml.

    /** User performed a search using the omnibox. */
    private static final int ACTION_SEARCHED_USING_OMNIBOX = 0;

    /** User navigated to Google search homepage using the omnibox. */
    private static final int ACTION_NAVIGATED_TO_GOOGLE_HOMEPAGE = 1;

    /** User navigated to any other page using the omnibox. */
    private static final int ACTION_NAVIGATED_USING_OMNIBOX = 2;

    /** User opened a most visited tile. */
    public static final int ACTION_OPENED_MOST_VISITED_TILE = 3;

    /** User opened the recent tabs manager. */
    public static final int ACTION_OPENED_RECENT_TABS_MANAGER = 4;

    /** User opened the history manager. */
    public static final int ACTION_OPENED_HISTORY_MANAGER = 5;

    /** User opened the bookmarks manager. */
    public static final int ACTION_OPENED_BOOKMARKS_MANAGER = 6;

    /** User opened the downloads manager. */
    public static final int ACTION_OPENED_DOWNLOADS_MANAGER = 7;

    /** User navigated to the webpage for a snippet shown on the NTP. */
    public static final int ACTION_OPENED_SNIPPET = 8;

    /** User clicked on the "learn more" link in the footer. */
    public static final int ACTION_CLICKED_LEARN_MORE = 9;

    /** User clicked on the "Refresh" button in the "all dismissed" state. */
    public static final int ACTION_CLICKED_ALL_DISMISSED_REFRESH = 10;

    /** The number of possible actions. */
    private static final int NUM_ACTIONS = 11;

    /** User navigated to a page using the omnibox. */
    private static final int RAPPOR_ACTION_NAVIGATED_USING_OMNIBOX = 0;

    /** User navigated to a page using one of the suggested tiles. */
    public static final int RAPPOR_ACTION_VISITED_SUGGESTED_TILE = 1;

    /** Regular NTP impression (usually when a new tab is opened). */
    public static final int NTP_IMPRESSION_REGULAR = 0;

    /** Potential NTP impressions (instead of blank page if no tab is open). */
    public static final int NTP_IMPESSION_POTENTIAL_NOTAB = 1;

    /** The number of possible NTP impression types */
    private static final int NUM_NTP_IMPRESSION = 2;

    /**
     * Possible results when sizing the NewTabPageLayout.
     * Do not remove or change existing values other than NUM_NTP_LAYOUT_RESULTS.
     */
    @IntDef({NTP_LAYOUT_DOES_NOT_FIT, NTP_LAYOUT_FITS_WITHOUT_FIELD_TRIAL,
            NTP_LAYOUT_FITS_WITH_FIELD_TRIAL, NTP_LAYOUT_CONDENSED, NUM_NTP_LAYOUT_RESULTS})
    @Retention(RetentionPolicy.SOURCE)
    public @interface NTPLayoutResult {}

    /** The NewTabPageLayout does not fit above the fold and it is displayed as is. */
    public static final int NTP_LAYOUT_DOES_NOT_FIT = 0;

    /**
     * The NewTabPageLayout does not fit above the fold, but we added some extra space so that
     * Most Likely is cut off, indicating to the user they can scroll.
     */
    public static final int NTP_LAYOUT_DOES_NOT_FIT_PUSH_MOST_LIKELY = 1;

    /** The NewTabPageLayout fits above the fold, the field trial is not enabled. */
    public static final int NTP_LAYOUT_FITS_NO_FIELD_TRIAL = 2;

    /**
     * The NewTabPageLayout fits above the fold, but cannot allow space for the field trial
     * experiment.
     */
    public static final int NTP_LAYOUT_FITS_WITHOUT_FIELD_TRIAL = 3;

    /** The NewTabPageLayout fits above the fold allowing space for the field trial experiment. */
    public static final int NTP_LAYOUT_FITS_WITH_FIELD_TRIAL = 4;

    /** The NewTabPageLayout is condensed to take up minimal space. */
    public static final int NTP_LAYOUT_CONDENSED = 5;

    /** The number of possible results for the NewTabPageLayout calculations. */
    public static final int NUM_NTP_LAYOUT_RESULTS = 6;

    /**
     * Possible results when updating content suggestions list in the UI. Keep in sync with the
     * ContentSuggestionsUIUpdateResult enum in histograms.xml. Do not remove or change existing
     * values other than NUM_UI_UPDATE_RESULTS.
     */
    @IntDef({UI_UPDATE_SUCCESS_APPENDED, UI_UPDATE_SUCCESS_REPLACED, UI_UPDATE_FAIL_ALL_SEEN,
            UI_UPDATE_FAIL_DISABLED})
    @Retention(RetentionPolicy.SOURCE)
    public @interface ContentSuggestionsUIUpdateResult {}

    /**
     * The content suggestions are successfully appended (because they are set for the first time
     * or explicitly marked to be appended).
     */
    public static final int UI_UPDATE_SUCCESS_APPENDED = 0;

    /**
     * Update successful, suggestions were replaced (some of them possibly seen, the exact number
     * reported in a separate histogram).
     */
    public static final int UI_UPDATE_SUCCESS_REPLACED = 1;

    /** Update failed, all previous content suggestions have been seen (and kept). */
    public static final int UI_UPDATE_FAIL_ALL_SEEN = 2;

    /** Update failed, because it is disabled by a variation parameter. */
    public static final int UI_UPDATE_FAIL_DISABLED = 3;

    private static final int NUM_UI_UPDATE_RESULTS = 4;

    /** The NTP was loaded in a cold startup. */
    private static final int LOAD_TYPE_COLD_START = 0;

    /** The NTP was loaded in a warm startup. */
    private static final int LOAD_TYPE_WARM_START = 1;

    /**
     * The NTP was loaded at some other time after activity creation and the user interacted with
     * the activity in the meantime.
     */
    private static final int LOAD_TYPE_OTHER = 2;

    /** The number of load types. */
    private static final int LOAD_TYPE_COUNT = 3;

    /**
     * Records an action taken by the user on the NTP.
     * @param action One of the ACTION_* values defined in this class.
     */
    public static void recordAction(int action) {
        assert action >= 0;
        assert action < NUM_ACTIONS;
        RecordHistogram.recordEnumeratedHistogram("NewTabPage.ActionAndroid2", action, NUM_ACTIONS);
    }

    /**
     * Record that the user has navigated away from the NTP using the omnibox.
     * @param destinationUrl The URL to which the user navigated.
     * @param transitionType The transition type of the navigation, from PageTransition.java.
     */
    public static void recordOmniboxNavigation(String destinationUrl, int transitionType) {
        if ((transitionType & PageTransition.CORE_MASK) == PageTransition.GENERATED) {
            recordAction(ACTION_SEARCHED_USING_OMNIBOX);
        } else {
            if (UrlUtilities.nativeIsGoogleHomePageUrl(destinationUrl)) {
                recordAction(ACTION_NAVIGATED_TO_GOOGLE_HOMEPAGE);
            } else {
                recordAction(ACTION_NAVIGATED_USING_OMNIBOX);
            }
            recordExplicitUserNavigation(destinationUrl, RAPPOR_ACTION_NAVIGATED_USING_OMNIBOX);
        }
    }

    /**
     * Record the eTLD+1 for a website explicitly visited by the user, using Rappor.
     */
    public static void recordExplicitUserNavigation(String destinationUrl, int rapporMetric) {
        switch (rapporMetric) {
            case RAPPOR_ACTION_NAVIGATED_USING_OMNIBOX:
                RapporServiceBridge.sampleDomainAndRegistryFromURL(
                        "NTP.ExplicitUserAction.PageNavigation.OmniboxNonSearch", destinationUrl);
                return;
            case RAPPOR_ACTION_VISITED_SUGGESTED_TILE:
                RapporServiceBridge.sampleDomainAndRegistryFromURL(
                        "NTP.ExplicitUserAction.PageNavigation.NTPTileClick", destinationUrl);
                return;
            default:
                return;
        }
    }

    /**
     * Records how the NewTabPageLayout fits on the user's screen.
     * @param result result key, one of {@link NTPLayoutResult}'s values.
     */
    public static void recordNTPLayoutResult(@NTPLayoutResult int result) {
        RecordHistogram.recordEnumeratedHistogram(
                "NewTabPage.Layout", result, NUM_NTP_LAYOUT_RESULTS);
    }

    /**
     * Records how content suggestions have been updated in the UI.
     * @param result result key, one of {@link ContentSuggestionsUIUpdateResult}'s values.
     */
    public static void recordUIUpdateResult(
            @ContentSuggestionsUIUpdateResult int result) {
        RecordHistogram.recordEnumeratedHistogram(
                "NewTabPage.ContentSuggestions.UIUpdateResult2", result, NUM_UI_UPDATE_RESULTS);
    }

    /**
     * Record how many content suggestions have been seen by the user in the UI section before the
     * section was successfully updated.
     * @param numberOfSuggestionsSeen The number of content suggestions seen so far in the section.
     */
    public static void recordNumberOfSuggestionsSeenBeforeUIUpdateSuccess(
            int numberOfSuggestionsSeen) {
        assert numberOfSuggestionsSeen >= 0;
        RecordHistogram.recordCount100Histogram(
                "NewTabPage.ContentSuggestions.UIUpdateSuccessNumberOfSuggestionsSeen",
                numberOfSuggestionsSeen);
    }

    /**
     * Record a NTP impression (even potential ones to make informed product decisions).
     * @param impressionType Type of the impression from NewTabPageUma.java
     */
    public static void recordNTPImpression(int impressionType) {
        assert impressionType >= 0;
        assert impressionType < NUM_NTP_IMPRESSION;
        RecordHistogram.recordEnumeratedHistogram(
                "Android.NTP.Impression", impressionType, NUM_NTP_IMPRESSION);
    }

    /**
     * Records stats related to content suggestion visits, such as the time spent on the website, or
     * if the user comes back to the NTP.
     * @param tab Tab opened to load a content suggestion.
     * @param category The category of the content suggestion.
     */
    public static void monitorContentSuggestionVisit(Tab tab, int category) {
        tab.addObserver(new SnippetVisitRecorder(category));
    }

    /**
     * Records how often new tabs with a NewTabPage are created. This helps to determine how often
     * users navigate back to already opened NTPs.
     * @param tabModelSelector Model selector controlling the creation of new tabs.
     */
    public static void monitorNTPCreation(TabModelSelector tabModelSelector) {
        tabModelSelector.addObserver(new TabCreationRecorder());
    }

    /**
     * Records the type of load for the NTP, such as cold or warm start.
     */
    public static void recordLoadType(ChromeActivity activity) {
        if (activity.getLastUserInteractionTime() > 0) {
            RecordHistogram.recordEnumeratedHistogram(
                    "NewTabPage.LoadType", LOAD_TYPE_OTHER, LOAD_TYPE_COUNT);
            return;
        }

        if (activity.hadWarmStart()) {
            RecordHistogram.recordEnumeratedHistogram(
                    "NewTabPage.LoadType", LOAD_TYPE_WARM_START, LOAD_TYPE_COUNT);
            return;
        }

        RecordHistogram.recordEnumeratedHistogram(
                "NewTabPage.LoadType", LOAD_TYPE_COLD_START, LOAD_TYPE_COUNT);
    }

    /**
     * Records how much time elapsed from start until the search box became available to the user.
     */
    public static void recordSearchAvailableLoadTime(ChromeActivity activity) {
        // Log the time it took for the search box to be displayed at startup, based on the
        // timestamp on the intent for the activity. If the user has interacted with the
        // activity already, it's not a startup, and the timestamp on the activity would not be
        // relevant either.
        if (activity.getLastUserInteractionTime() != 0) return;
        long timeFromIntent = SystemClock.elapsedRealtime()
                - IntentHandler.getTimestampFromIntent(activity.getIntent());
        if (activity.hadWarmStart()) {
            RecordHistogram.recordMediumTimesHistogram(
                    "NewTabPage.SearchAvailableLoadTime2.WarmStart", timeFromIntent,
                    TimeUnit.MILLISECONDS);
        } else {
            RecordHistogram.recordMediumTimesHistogram(
                    "NewTabPage.SearchAvailableLoadTime2.ColdStart", timeFromIntent,
                    TimeUnit.MILLISECONDS);
        }
    }

    /**
     * Records the number of new NTPs opened in a new tab. Use through
     * {@link NewTabPageUma#monitorNTPCreation(TabModelSelector)}.
     */
    private static class TabCreationRecorder extends EmptyTabModelSelectorObserver {
        @Override
        public void onNewTabCreated(Tab tab) {
            if (!NewTabPage.isNTPUrl(tab.getUrl())) return;
            RecordUserAction.record("MobileNTPOpenedInNewTab");
        }
    }

    /**
     * Records stats related to content suggestion visits, such as the time spent on the website, or
     * if the user comes back to the NTP. Use through
     * {@link NewTabPageUma#monitorContentSuggestionVisit(Tab, int)}.
     */
    private static class SnippetVisitRecorder extends EmptyTabObserver {
        private final int mCategory;
        private final long mStartTimeMs = SystemClock.elapsedRealtime();

        private SnippetVisitRecorder(int category) {
            mCategory = category;
        }

        @Override
        public void onHidden(Tab tab) {
            endRecording(tab);
        }

        @Override
        public void onDestroyed(Tab tab) {
            endRecording(null);
        }

        @Override
        public void onUpdateUrl(Tab tab, String url) {
            // onLoadUrl below covers many exit conditions to stop recording but not all,
            // such as navigating back. We therefore stop recording if a URL change
            // indicates some non-Web page was visited.
            if (!url.startsWith(UrlConstants.CHROME_URL_PREFIX)
                    && !url.startsWith(UrlConstants.CHROME_NATIVE_URL_PREFIX)) {
                assert !NewTabPage.isNTPUrl(url);
                return;
            }
            if (NewTabPage.isNTPUrl(url) && !FeatureUtilities.isChromeHomeEnabled()) {
                RecordUserAction.record("MobileNTP.Snippets.VisitEndBackInNTP");
            }
            endRecording(tab);
        }

        @Override
        public void onLoadUrl(Tab tab, LoadUrlParams params, int loadType) {
            // End recording if a new URL gets loaded e.g. after entering a new query in
            // the omnibox. This doesn't cover the navigate-back case so we also need
            // onUpdateUrl.
            int transitionTypeMask = PageTransition.FROM_ADDRESS_BAR | PageTransition.HOME_PAGE
                    | PageTransition.CHAIN_START | PageTransition.CHAIN_END;

            if ((params.getTransitionType() & transitionTypeMask) != 0) endRecording(tab);
        }

        private void endRecording(Tab removeObserverFromTab) {
            if (removeObserverFromTab != null) removeObserverFromTab.removeObserver(this);
            RecordUserAction.record("MobileNTP.Snippets.VisitEnd");
            long visitTimeMs = SystemClock.elapsedRealtime() - mStartTimeMs;
            SuggestionsEventReporterBridge.onSuggestionTargetVisited(mCategory, visitTimeMs);
        }
    }
}