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

import android.os.SystemClock;

import org.chromium.build.annotations.CheckDiscard;

/**
 * Utilities related to timestamps, including the ability to use fake time for tests via
 * FakeTimeTestRule.
 */
public class TimeUtils {
    /**
     * Interval timer using SystemClock.uptimeMillis() (excludes deep sleep).
     * See: https://developer.android.com/reference/android/os/SystemClock
     */
    @CheckDiscard("Class should get inlined by R8.")
    public static class UptimeMillisTimer {
        private final long mStart = uptimeMillis();

        public long getElapsedMillis() {
            return uptimeMillis() - mStart;
        }
    }

    /**
     * Interval timer using SystemClock.elapsedRealtime() (includes deep sleep).
     * See: https://developer.android.com/reference/android/os/SystemClock
     */
    @CheckDiscard("Class should get inlined by R8.")
    public static class ElapsedRealtimeMillisTimer {
        private final long mStart = elapsedRealtimeMillis();

        public long getElapsedMillis() {
            return elapsedRealtimeMillis() - mStart;
        }
    }

    /**
     * Interval timer using SystemClock.elapsedRealtimeNanos() (includes deep sleep).
     * See: https://developer.android.com/reference/android/os/SystemClock
     */
    @CheckDiscard("Class should get inlined by R8.")
    public static class ElapsedRealtimeNanosTimer {
        private final long mStart = elapsedRealtimeNanos();

        public long getElapsedNanos() {
            return elapsedRealtimeNanos() - mStart;
        }
    }

    /**
     * Interval timer using SystemClock.currentThreadTimeMillis() (excludes blocking time).
     * See: https://developer.android.com/reference/android/os/SystemClock
     */
    @CheckDiscard("Class should get inlined by R8.")
    public static class CurrentThreadTimeMillisTimer {
        private final long mStart = currentThreadTimeMillis();

        public long getElapsedMillis() {
            return currentThreadTimeMillis() - mStart;
        }
    }

    interface FakeClock {
        long uptimeMillis();
        long elapsedRealtimeNanos();
        long currentThreadTimeMillis();
        long currentTimeMillis();
    }

    private TimeUtils() {}

    // Use these in favor of TimeUnit.convert() in order to avoid the overhead of a
    // static-get / static-invoke.
    public static final long NANOSECONDS_PER_MICROSECOND = 1000;
    public static final long NANOSECONDS_PER_MILLISECOND = 1000000;
    public static final long SECONDS_PER_MINUTE = 60;
    public static final long SECONDS_PER_HOUR = SECONDS_PER_MINUTE * 60;
    public static final long SECONDS_PER_DAY = SECONDS_PER_HOUR * 24;

    // Used by FakeTimeTestRule. Visibility is restricted to ensure tests use the rule, which
    // restores the value to null in its clean-up logic.
    static FakeClock sFakeClock;

    /**
     * Wrapper for System.currentTimeMillis() (milliseconds since the epoch).
     * Can be faked in tests using FakeTimeTestRule.
     * See: https://developer.android.com/reference/android/os/SystemClock
     */
    @CheckDiscard("Should get inlined by R8.")
    public static long currentTimeMillis() {
        if (sFakeClock != null) {
            return sFakeClock.currentTimeMillis();
        }
        return System.currentTimeMillis();
    }

    /**
     * Wrapper for SystemClock.uptimeMillis() (uptime excluding deep sleep).
     * Can be faked in tests using FakeTimeTestRule.
     * See: https://developer.android.com/reference/android/os/SystemClock
     */
    @CheckDiscard("Should get inlined by R8.")
    public static long uptimeMillis() {
        if (sFakeClock != null) {
            return sFakeClock.uptimeMillis();
        }
        return SystemClock.uptimeMillis();
    }

    /**
     * Wrapper for SystemClock.elapsedRealtimeNanos() (uptime including deep sleep).
     * Can be faked in tests using FakeTimeTestRule.
     * See: https://developer.android.com/reference/android/os/SystemClock
     */
    @CheckDiscard("Should get inlined by R8.")
    public static long elapsedRealtimeNanos() {
        if (sFakeClock != null) {
            return sFakeClock.elapsedRealtimeNanos();
        }
        return SystemClock.elapsedRealtimeNanos();
    }

    /**
     * Wrapper for SystemClock.elapsedRealtimeMillis() (uptime including deep sleep).
     * Can be faked in tests using FakeTimeTestRule.
     * See: https://developer.android.com/reference/android/os/SystemClock
     */
    @CheckDiscard("Should get inlined by R8.")
    public static long elapsedRealtimeMillis() {
        if (sFakeClock != null) {
            return sFakeClock.elapsedRealtimeNanos() / NANOSECONDS_PER_MILLISECOND;
        }
        return SystemClock.elapsedRealtime();
    }

    /**
     * Wrapper for SystemClock.currentThreadTimeMillis() (excludes blocking time).
     * Can be faked in tests using FakeTimeTestRule.
     * See: https://developer.android.com/reference/android/os/SystemClock
     */
    @CheckDiscard("Should get inlined by R8.")
    public static long currentThreadTimeMillis() {
        if (sFakeClock != null) {
            return sFakeClock.currentThreadTimeMillis();
        }
        return SystemClock.currentThreadTimeMillis();
    }
}
