// Copyright 2016 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.precache;

import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

import org.chromium.base.ContextUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.metrics.RecordHistogram;

import java.util.Arrays;

/**
 * Enumerates the various failure reasons and events of interest for precaching. When the library is
 * loaded, the events are logged as UMA metrics. Otherwise the events are persisted in shared
 * preferences until the library loads in future. When the library is not loaded, only single
 * occurrence of an event is recorded, duplicate occurrence of the same event is not persisted.
 */
public class PrecacheUMA {
    /**
     * The events should not be renumbered or reused since these are used in a histogram.
     * This must remain in sync with Precache.Events in tools/metrics/histograms/histograms.xml.
     */
    public static class Event {
        /**
         * Indicates that the precache scheduled task has started. The task can be periodic or
         * one-off completion task.
         */
        public static final int PRECACHE_TASK_STARTED_PERIODIC = 0;
        public static final int PRECACHE_TASK_STARTED_ONEOFF = 1;
        // Duplicate GCM task was started while precache was running.
        public static final int PRECACHE_TASK_STARTED_DUPLICATE = 2;

        /**
         * The native library failed to load, during the run of a precache scheduled task.
         */
        public static final int PRECACHE_TASK_LOAD_LIBRARY_FAIL = 3;

        /**
         * Various failure reasons due to which precache task was cancelled.
         */
        public static final int PRECACHE_CANCEL_NO_UNMETERED_NETWORK = 4;
        public static final int PRECACHE_CANCEL_NO_POWER = 5;
        public static final int PRECACHE_CANCEL_DISABLED_PREF = 6;
        public static final int DISABLED_IN_PRECACHE_PREF = 7;
        public static final int SYNC_SERVICE_TIMEOUT = 8;
        public static final int PRECACHE_SESSION_TIMEOUT = 9;

        /**
         * Precache session started.
         */
        public static final int PRECACHE_SESSION_STARTED = 10;

        /**
         * Precache task was scheduled. The task can be periodic or one-off completion task. The
         * result of scheduling can be success or failure. The periodic task can be scheduled due to
         * Chrome upgrade or at startup.
         */
        public static final int PERIODIC_TASK_SCHEDULE_STARTUP = 11;
        public static final int PERIODIC_TASK_SCHEDULE_STARTUP_FAIL = 12;
        public static final int PERIODIC_TASK_SCHEDULE_UPGRADE = 13;
        public static final int PERIODIC_TASK_SCHEDULE_UPGRADE_FAIL = 14;
        public static final int ONEOFF_TASK_SCHEDULE = 15;
        public static final int ONEOFF_TASK_SCHEDULE_FAIL = 16;

        /**
         * Precache session completed successfully or unsuccessfully.
         */
        public static final int PRECACHE_SESSION_COMPLETE = 17;
        public static final int PRECACHE_SESSION_INCOMPLETE = 18;

        /**
         * Limit of the events.
         */
        public static final int EVENT_START = 0;
        public static final int EVENT_END = 19;

        @VisibleForTesting
        static int getBitPosition(int event) {
            assert (event >= EVENT_START) && (event < EVENT_END);
            return event;
        }

        @VisibleForTesting
        static long getBitMask(int event) {
            assert (event >= EVENT_START) && (event < EVENT_END);
            return 1L << event;
        }

        @VisibleForTesting
        static int[] getEventsFromBitMask(long bitmask) {
            int[] events = new int[EVENT_END];
            int filledEvents = 0;
            for (int event = EVENT_START; event < EVENT_END; ++event) {
                if ((getBitMask(event) & bitmask) != 0L) {
                    events[filledEvents] = event;
                    ++filledEvents;
                }
            }
            return Arrays.copyOf(events, filledEvents);
        }

        @VisibleForTesting
        static long addEventToBitMask(long bitmask, int event) {
            return bitmask | getBitMask(event);
        }
    }

    static final String PREF_PERSISTENCE_METRICS = "precache.persistent_metrics";
    static final String EVENTS_HISTOGRAM = "Precache.Events";

    /**
     * Record the precache event. The event is persisted in shared preferences if the native library
     * is not loaded. If library is loaded, the event will be recorded as UMA metric, and any prior
     * persisted events are recorded to UMA as well.
     * @param event the precache event.
     */
    public static void record(int event) {
        SharedPreferences sharedPreferences = ContextUtils.getAppSharedPreferences();
        long persistent_metric = sharedPreferences.getLong(PREF_PERSISTENCE_METRICS, 0);
        Editor preferencesEditor = sharedPreferences.edit();

        if (LibraryLoader.isInitialized()) {
            RecordHistogram.recordEnumeratedHistogram(
                    EVENTS_HISTOGRAM, Event.getBitPosition(event), Event.EVENT_END);
            for (int e : Event.getEventsFromBitMask(persistent_metric)) {
                RecordHistogram.recordEnumeratedHistogram(
                        EVENTS_HISTOGRAM, Event.getBitPosition(e), Event.EVENT_END);
            }
            preferencesEditor.remove(PREF_PERSISTENCE_METRICS);
        } else {
            // Save the metric in preferences.
            persistent_metric = Event.addEventToBitMask(persistent_metric, event);
            preferencesEditor.putLong(PREF_PERSISTENCE_METRICS, persistent_metric);
        }
        preferencesEditor.apply();
    }
}