#include "jemalloc/internal/jemalloc_preamble.h"
#include "jemalloc/internal/jemalloc_internal_includes.h"

#include "jemalloc/internal/nstime.h"

#include "jemalloc/internal/assert.h"

#define BILLION UINT64_C(1000000000)
#define MILLION UINT64_C(1000000)

static void
nstime_set_initialized(nstime_t *time) {
#ifdef JEMALLOC_DEBUG
	time->magic = NSTIME_MAGIC;
#endif
}

static void
nstime_assert_initialized(const nstime_t *time) {
#ifdef JEMALLOC_DEBUG
	/*
	 * Some parts (e.g. stats) rely on memset to zero initialize.  Treat
	 * these as valid initialization.
	 */
	assert(
	    time->magic == NSTIME_MAGIC || (time->magic == 0 && time->ns == 0));
#endif
}

static void
nstime_pair_assert_initialized(const nstime_t *t1, const nstime_t *t2) {
	nstime_assert_initialized(t1);
	nstime_assert_initialized(t2);
}

static void
nstime_initialize_operand(nstime_t *time) {
	/*
	 * Operations like nstime_add may have the initial operand being zero
	 * initialized (covered by the assert below).  Full-initialize needed
	 * before changing it to non-zero.
	 */
	nstime_assert_initialized(time);
	nstime_set_initialized(time);
}

void
nstime_init(nstime_t *time, uint64_t ns) {
	nstime_set_initialized(time);
	time->ns = ns;
}

void
nstime_init2(nstime_t *time, uint64_t sec, uint64_t nsec) {
	nstime_set_initialized(time);
	time->ns = sec * BILLION + nsec;
}

uint64_t
nstime_ns(const nstime_t *time) {
	nstime_assert_initialized(time);
	return time->ns;
}

uint64_t
nstime_ms(const nstime_t *time) {
	nstime_assert_initialized(time);
	return time->ns / MILLION;
}

uint64_t
nstime_sec(const nstime_t *time) {
	nstime_assert_initialized(time);
	return time->ns / BILLION;
}

uint64_t
nstime_nsec(const nstime_t *time) {
	nstime_assert_initialized(time);
	return time->ns % BILLION;
}

void
nstime_copy(nstime_t *time, const nstime_t *source) {
	/* Source is required to be initialized. */
	nstime_assert_initialized(source);
	*time = *source;
	nstime_assert_initialized(time);
}

int
nstime_compare(const nstime_t *a, const nstime_t *b) {
	nstime_pair_assert_initialized(a, b);
	return (a->ns > b->ns) - (a->ns < b->ns);
}

void
nstime_add(nstime_t *time, const nstime_t *addend) {
	nstime_pair_assert_initialized(time, addend);
	assert(UINT64_MAX - time->ns >= addend->ns);

	nstime_initialize_operand(time);
	time->ns += addend->ns;
}

void
nstime_iadd(nstime_t *time, uint64_t addend) {
	nstime_assert_initialized(time);
	assert(UINT64_MAX - time->ns >= addend);

	nstime_initialize_operand(time);
	time->ns += addend;
}

void
nstime_subtract(nstime_t *time, const nstime_t *subtrahend) {
	nstime_pair_assert_initialized(time, subtrahend);
	assert(nstime_compare(time, subtrahend) >= 0);

	/* No initialize operand -- subtraction must be initialized. */
	time->ns -= subtrahend->ns;
}

void
nstime_isubtract(nstime_t *time, uint64_t subtrahend) {
	nstime_assert_initialized(time);
	assert(time->ns >= subtrahend);

	/* No initialize operand -- subtraction must be initialized. */
	time->ns -= subtrahend;
}

void
nstime_imultiply(nstime_t *time, uint64_t multiplier) {
	nstime_assert_initialized(time);
	assert(
	    (((time->ns | multiplier) & (UINT64_MAX << (sizeof(uint64_t) << 2)))
	        == 0)
	    || ((time->ns * multiplier) / multiplier == time->ns));

	nstime_initialize_operand(time);
	time->ns *= multiplier;
}

void
nstime_idivide(nstime_t *time, uint64_t divisor) {
	nstime_assert_initialized(time);
	assert(divisor != 0);

	nstime_initialize_operand(time);
	time->ns /= divisor;
}

uint64_t
nstime_divide(const nstime_t *time, const nstime_t *divisor) {
	nstime_pair_assert_initialized(time, divisor);
	assert(divisor->ns != 0);

	/* No initialize operand -- *time itself remains unchanged. */
	return time->ns / divisor->ns;
}

uint64_t
nstime_ns_between(const nstime_t *earlier, const nstime_t *later) {
	nstime_assert_initialized(earlier);
	nstime_assert_initialized(later);
	assert(nstime_compare(later, earlier) >= 0);
	return later->ns - earlier->ns;
}

uint64_t
nstime_ms_between(const nstime_t *earlier, const nstime_t *later) {
	return nstime_ns_between(earlier, later) / MILLION;
}

/* Returns time since *past in nanoseconds, w/o updating *past. */
uint64_t
nstime_ns_since(const nstime_t *past) {
	nstime_assert_initialized(past);

	nstime_t now;
	nstime_copy(&now, past);
	nstime_update(&now);
	return nstime_ns_between(past, &now);
}

/* Returns time since *past in milliseconds, w/o updating *past. */
uint64_t
nstime_ms_since(const nstime_t *past) {
	return nstime_ns_since(past) / MILLION;
}

#ifdef _WIN32
#	define NSTIME_MONOTONIC false
static void
nstime_get(nstime_t *time) {
	FILETIME ft;
	uint64_t ticks_100ns;

	GetSystemTimeAsFileTime(&ft);
	ticks_100ns = (((uint64_t)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;

	nstime_init(time, ticks_100ns * 100);
}
#elif defined(JEMALLOC_HAVE_CLOCK_MONOTONIC_COARSE)
#	define NSTIME_MONOTONIC true
static void
nstime_get(nstime_t *time) {
	struct timespec ts;

	clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
	nstime_init2(time, ts.tv_sec, ts.tv_nsec);
}
#elif defined(JEMALLOC_HAVE_CLOCK_MONOTONIC)
#	define NSTIME_MONOTONIC true
static void
nstime_get(nstime_t *time) {
	struct timespec ts;

	clock_gettime(CLOCK_MONOTONIC, &ts);
	nstime_init2(time, ts.tv_sec, ts.tv_nsec);
}
#elif defined(JEMALLOC_HAVE_CLOCK_GETTIME_NSEC_NP)
#	define NSTIME_MONOTONIC true
static void
nstime_get(nstime_t *time) {
	nstime_init(time, clock_gettime_nsec_np(CLOCK_UPTIME_RAW));
}
#elif defined(JEMALLOC_HAVE_MACH_ABSOLUTE_TIME)
#	define NSTIME_MONOTONIC true
static void
nstime_get(nstime_t *time) {
	static mach_timebase_info_data_t sTimebaseInfo;
	if (sTimebaseInfo.denom == 0) {
		(void)mach_timebase_info(&sTimebaseInfo);
	}
	nstime_init(time,
	    mach_absolute_time() * sTimebaseInfo.numer / sTimebaseInfo.denom);
}
#else
#	define NSTIME_MONOTONIC false
static void
nstime_get(nstime_t *time) {
	struct timeval tv;

	gettimeofday(&tv, NULL);
	nstime_init2(time, tv.tv_sec, tv.tv_usec * 1000);
}
#endif

static bool
nstime_monotonic_impl(void) {
	return NSTIME_MONOTONIC;
#undef NSTIME_MONOTONIC
}
nstime_monotonic_t *JET_MUTABLE nstime_monotonic = nstime_monotonic_impl;

prof_time_res_t opt_prof_time_res = prof_time_res_default;

const char *const prof_time_res_mode_names[] = {
    "default",
    "high",
};

static void
nstime_get_realtime(nstime_t *time) {
#if defined(JEMALLOC_HAVE_CLOCK_REALTIME) && !defined(_WIN32)
	struct timespec ts;

	clock_gettime(CLOCK_REALTIME, &ts);
	nstime_init2(time, ts.tv_sec, ts.tv_nsec);
#else
	unreachable();
#endif
}

static void
nstime_prof_update_impl(nstime_t *time) {
	nstime_t old_time;

	nstime_copy(&old_time, time);

	if (opt_prof_time_res == prof_time_res_high) {
		nstime_get_realtime(time);
	} else {
		nstime_get(time);
	}
}
nstime_prof_update_t *JET_MUTABLE nstime_prof_update = nstime_prof_update_impl;

static void
nstime_update_impl(nstime_t *time) {
	nstime_t old_time;

	nstime_copy(&old_time, time);
	nstime_get(time);

	/* Handle non-monotonic clocks. */
	if (unlikely(nstime_compare(&old_time, time) > 0)) {
		nstime_copy(time, &old_time);
	}
}
nstime_update_t *JET_MUTABLE nstime_update = nstime_update_impl;

void
nstime_init_update(nstime_t *time) {
	nstime_init_zero(time);
	nstime_update(time);
}

void
nstime_prof_init_update(nstime_t *time) {
	nstime_init_zero(time);
	nstime_prof_update(time);
}
