/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 2006-2016. All Rights Reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * %CopyrightEnd%
 */

#ifndef ERL_TIME_H__
#define ERL_TIME_H__

/* timer wheel size NEED to be a power of 2 */
#ifdef SMALL_MEMORY
#define ERTS_TIW_SIZE (1 << 13)
#else
#define ERTS_TIW_SIZE (1 << 16)
#endif

#if defined(DEBUG) || 0
#define ERTS_TIME_ASSERT(B) ERTS_ASSERT(B)
#else
#define ERTS_TIME_ASSERT(B) ((void) 1)
#endif

typedef enum {
    ERTS_NO_TIME_WARP_MODE,
    ERTS_SINGLE_TIME_WARP_MODE,
    ERTS_MULTI_TIME_WARP_MODE
} ErtsTimeWarpMode;

typedef struct ErtsTimerWheel_ ErtsTimerWheel;
typedef ErtsMonotonicTime * ErtsNextTimeoutRef;

extern SysTimeval erts_first_emu_time;


void erts_monitor_time_offset(Eterm id, Eterm ref);
int erts_demonitor_time_offset(Eterm ref);

int erts_init_time_sup(int, ErtsTimeWarpMode);
void erts_late_init_time_sup(void);

ErtsNextTimeoutRef erts_get_next_timeout_reference(ErtsTimerWheel *);
void erts_init_time(int time_correction, ErtsTimeWarpMode time_warp_mode);
void erts_bump_timers(ErtsTimerWheel *, ErtsMonotonicTime);
Uint erts_timer_wheel_memory_size(void);

#ifdef DEBUG
void erts_p_slpq(void);
#endif

/* time_sup */

#if (defined(HAVE_GETHRVTIME) || defined(HAVE_CLOCK_GETTIME_CPU_TIME))
#  ifndef HAVE_ERTS_NOW_CPU
#    define HAVE_ERTS_NOW_CPU
#    ifdef HAVE_GETHRVTIME
#      define erts_start_now_cpu() sys_start_hrvtime()
#      define erts_stop_now_cpu()  sys_stop_hrvtime()
#    endif
#  endif
void erts_get_now_cpu(Uint* megasec, Uint* sec, Uint* microsec);
#endif

typedef UWord erts_approx_time_t;
erts_approx_time_t erts_get_approx_time(void);

int erts_has_time_correction(void);
int erts_check_time_adj_support(int time_correction,
				ErtsTimeWarpMode time_warp_mode);

ErtsTimeWarpMode erts_time_warp_mode(void);

typedef enum {
    ERTS_TIME_OFFSET_PRELIMINARY,
    ERTS_TIME_OFFSET_FINAL,
    ERTS_TIME_OFFSET_VOLATILE
} ErtsTimeOffsetState;

ErtsTimeOffsetState erts_time_offset_state(void); 
ErtsTimeOffsetState erts_finalize_time_offset(void); 
struct process;
Eterm erts_get_monotonic_start_time(struct process *c_p);
Eterm erts_get_monotonic_end_time(struct process *c_p);
Eterm erts_monotonic_time_source(struct process*c_p);
Eterm erts_system_time_source(struct process*c_p);

#ifdef SYS_CLOCK_RESOLUTION
#define ERTS_CLKTCK_RESOLUTION ((ErtsMonotonicTime) (SYS_CLOCK_RESOLUTION*1000))
#else
#define ERTS_CLKTCK_RESOLUTION (erts_time_sup__.r.o.clktck_resolution)
#endif

#define ERTS_TIMER_WHEEL_MSEC (ERTS_TIW_SIZE/(ERTS_CLKTCK_RESOLUTION/1000))

struct erts_time_sup_read_only__ {
    ErtsMonotonicTime monotonic_time_unit;
#if !ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT
    ErtsMonotonicTime start;
    struct {
	ErtsMonotonicTime native;
	ErtsMonotonicTime nsec;
	ErtsMonotonicTime usec;
	ErtsMonotonicTime msec;
	ErtsMonotonicTime sec;
    } start_offset;
#endif
#ifndef SYS_CLOCK_RESOLUTION
    ErtsMonotonicTime clktck_resolution;
#endif
};

typedef struct {
    union {
	struct erts_time_sup_read_only__ o;
	char align__[(((sizeof(struct erts_time_sup_read_only__) - 1)
		       / ASSUMED_CACHE_LINE_SIZE) + 1)
		     * ASSUMED_CACHE_LINE_SIZE];
    } r;
} ErtsTimeSupData;

extern ErtsTimeSupData erts_time_sup__;

ErtsMonotonicTime erts_napi_monotonic_time(int time_unit);
ErtsMonotonicTime erts_napi_time_offset(int time_unit);
ErtsMonotonicTime erts_napi_convert_time_unit(ErtsMonotonicTime val, int from, int to);

ERTS_GLB_INLINE Uint64
erts_time_unit_conversion(Uint64 value,
			  Uint32 from_time_unit,
			  Uint32 to_time_unit);

ErtsSysPerfCounter erts_perf_counter_unit(void);

#if ERTS_GLB_INLINE_INCL_FUNC_DEF

ERTS_GLB_INLINE Uint64
erts_time_unit_conversion(Uint64 value,
			  Uint32 from_time_unit,
			  Uint32 to_time_unit)
{
    Uint64 high, low, result;
    if (value <= ~((Uint64) 0)/to_time_unit)
	return (value*to_time_unit)/from_time_unit;

    low = value & ((Uint64) 0xffffffff);
    high = (value >> 32) & ((Uint64) 0xffffffff);

    low *= to_time_unit;
    high *= to_time_unit;

    high += (low >> 32) & ((Uint64) 0xffffffff);
    low &= ((Uint64) 0xffffffff);

    result = high % from_time_unit;
    high /= from_time_unit;
    high <<= 32;

    result <<= 32;
    result += low;
    result /= from_time_unit;
    result += high;

    return result;
}

#endif /* ERTS_GLB_INLINE_INCL_FUNC_DEF */

/*
 * Range of monotonic time internally
 */

#define ERTS_MONOTONIC_BEGIN						\
    ERTS_MONOTONIC_TIME_UNIT
#define ERTS_MONOTONIC_END						\
    ((ERTS_MONOTONIC_TIME_MAX / ERTS_MONOTONIC_TIME_UNIT)		\
     * ERTS_MONOTONIC_TIME_UNIT)

#if ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT

/*
 * If the monotonic time unit is a compile time constant,
 * it is assumed (and need) to be a power of 10.
 */

#if ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT < 1000*1000
#  error Compile time time unit needs to be at least 1000000
#endif

#if ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT == 1000*1000*1000
/* Nano-second time unit */

#define ERTS_MONOTONIC_TO_SEC__(NSEC) ((NSEC) / (1000*1000*1000))
#define ERTS_MONOTONIC_TO_MSEC__(NSEC) ((NSEC) / (1000*1000))
#define ERTS_MONOTONIC_TO_USEC__(NSEC) ((NSEC) / 1000)
#define ERTS_MONOTONIC_TO_NSEC__(NSEC) (NSEC)

#define ERTS_SEC_TO_MONOTONIC__(SEC) (((ErtsMonotonicTime) (SEC))*(1000*1000*1000))
#define ERTS_MSEC_TO_MONOTONIC__(MSEC) (((ErtsMonotonicTime) (MSEC))*(1000*1000))
#define ERTS_USEC_TO_MONOTONIC__(USEC) (((ErtsMonotonicTime) (USEC))*1000)
#define ERTS_NSEC_TO_MONOTONIC__(NSEC) ((ErtsMonotonicTime) (NSEC))

#elif ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT == 1000*1000
/* Micro-second time unit */

#define ERTS_MONOTONIC_TO_SEC__(USEC) ((USEC) / (1000*1000))
#define ERTS_MONOTONIC_TO_MSEC__(USEC) ((USEC) / 1000)
#define ERTS_MONOTONIC_TO_USEC__(USEC) (USEC)
#define ERTS_MONOTONIC_TO_NSEC__(USEC) ((USEC)*1000)

#define ERTS_SEC_TO_MONOTONIC__(SEC) (((ErtsMonotonicTime) (SEC))*(1000*1000))
#define ERTS_MSEC_TO_MONOTONIC__(MSEC) (((ErtsMonotonicTime) (MSEC))*1000)
#define ERTS_USEC_TO_MONOTONIC__(USEC) ((ErtsMonotonicTime) (USEC))
#define ERTS_NSEC_TO_MONOTONIC__(NSEC) (((ErtsMonotonicTime) (NSEC))/1000)

#else
#error Missing implementation for monotonic time unit
#endif

#define ERTS_MONOTONIC_TIME_UNIT \
    ((ErtsMonotonicTime) ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT)

/*
 * NOTE! ERTS_MONOTONIC_TIME_START_EXTERNAL *need* to be a multiple
 *       of ERTS_MONOTONIC_TIME_UNIT.
 */

#ifdef ARCH_32
/*
 * Want to use a big-num of arity 2 as long as possible (584 years
 * in the nano-second time unit case).
 */
#define ERTS_MONOTONIC_TIME_START_EXTERNAL		\
    (((((((ErtsMonotonicTime) 1) << 32)-1)	\
       / ERTS_MONOTONIC_TIME_UNIT)		\
      * ERTS_MONOTONIC_TIME_UNIT)		\
     + ERTS_MONOTONIC_TIME_UNIT)

#else /* ARCH_64 */

#if ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT <= 10*1000*1000

/*
 * Using micro second time unit or lower. Start at zero since
 * time will remain an immediate for a very long time anyway
 * (1827 years in the 10 micro second case)...
 */
#define ERTS_MONOTONIC_TIME_START_EXTERNAL ((ErtsMonotonicTime) 0)

#else /* ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT > 10*1000*1000 */

/*
 * Want to use an immediate as long as possible (36 years in the
 * nano-second time unit case).
*/
#define ERTS_MONOTONIC_TIME_START_EXTERNAL 		\
    ((((ErtsMonotonicTime) MIN_SMALL)		\
      / ERTS_MONOTONIC_TIME_UNIT)		\
     * ERTS_MONOTONIC_TIME_UNIT)

#endif /* ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT > 1000*1000 */

#endif /* ARCH_64 */

/*
 * Offsets from internal monotonic time to external monotonic time
 */

#define ERTS_MONOTONIC_OFFSET_NATIVE \
    (ERTS_MONOTONIC_TIME_START_EXTERNAL - ERTS_MONOTONIC_BEGIN)
#define ERTS_MONOTONIC_OFFSET_NSEC					\
    ERTS_MONOTONIC_TO_NSEC__(ERTS_MONOTONIC_OFFSET_NATIVE)
#define ERTS_MONOTONIC_OFFSET_USEC					\
    ERTS_MONOTONIC_TO_USEC__(ERTS_MONOTONIC_OFFSET_NATIVE)
#define ERTS_MONOTONIC_OFFSET_MSEC					\
    ERTS_MONOTONIC_TO_MSEC__(ERTS_MONOTONIC_OFFSET_NATIVE)
#define ERTS_MONOTONIC_OFFSET_SEC					\
    ERTS_MONOTONIC_TO_SEC__(ERTS_MONOTONIC_OFFSET_NATIVE)

#define ERTS_MONOTONIC_TO_CLKTCKS__(MON) \
    ((MON) / (ERTS_MONOTONIC_TIME_UNIT/ERTS_CLKTCK_RESOLUTION))
#define ERTS_CLKTCKS_TO_MONOTONIC__(TCKS) \
    ((TCKS) * (ERTS_MONOTONIC_TIME_UNIT/ERTS_CLKTCK_RESOLUTION))

#else /* !ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT */

/*
 * Initialized in erts_init_sys_time_sup()
 */
#define ERTS_MONOTONIC_TIME_UNIT (erts_time_sup__.r.o.monotonic_time_unit)

/*
 * Offsets from internal monotonic time to external monotonic time
 *
 * Initialized in erts_init_time_sup()...
 */
#define ERTS_MONOTONIC_TIME_START_EXTERNAL (erts_time_sup__.r.o.start)
#define ERTS_MONOTONIC_OFFSET_NATIVE (erts_time_sup__.r.o.start_offset.native)
#define ERTS_MONOTONIC_OFFSET_NSEC (erts_time_sup__.r.o.start_offset.nsec)
#define ERTS_MONOTONIC_OFFSET_USEC (erts_time_sup__.r.o.start_offset.usec)
#define ERTS_MONOTONIC_OFFSET_MSEC (erts_time_sup__.r.o.start_offset.msec)
#define ERTS_MONOTONIC_OFFSET_SEC (erts_time_sup__.r.o.start_offset.sec)

#define ERTS_CONV_FROM_MON_UNIT___(M, TO)				\
    ((ErtsMonotonicTime)						\
     erts_time_unit_conversion((Uint64) (M),				\
			       (Uint32) ERTS_MONOTONIC_TIME_UNIT,	\
			       (Uint32) (TO)))

#define ERTS_CONV_TO_MON_UNIT___(M, FROM)				\
    ((ErtsMonotonicTime)						\
     erts_time_unit_conversion((Uint64) (M),				\
			       (Uint32) (FROM),				\
			       (Uint32) ERTS_MONOTONIC_TIME_UNIT))	\

#define ERTS_MONOTONIC_TO_SEC__(M) \
    ERTS_CONV_FROM_MON_UNIT___((M), 1)
#define ERTS_MONOTONIC_TO_MSEC__(M) \
    ERTS_CONV_FROM_MON_UNIT___((M), 1000)
#define ERTS_MONOTONIC_TO_USEC__(M) \
    ERTS_CONV_FROM_MON_UNIT___((M), 1000*1000)
#define ERTS_MONOTONIC_TO_NSEC__(M) \
    ERTS_CONV_FROM_MON_UNIT___((M), 1000*1000*1000)

#define ERTS_SEC_TO_MONOTONIC__(SEC) \
    ERTS_CONV_TO_MON_UNIT___((SEC), 1)
#define ERTS_MSEC_TO_MONOTONIC__(MSEC) \
    ERTS_CONV_TO_MON_UNIT___((MSEC), 1000)
#define ERTS_USEC_TO_MONOTONIC__(USEC) \
    ERTS_CONV_TO_MON_UNIT___((USEC), 1000*1000)
#define ERTS_NSEC_TO_MONOTONIC__(NSEC) \
    ERTS_CONV_TO_MON_UNIT___((NSEC), 1000*1000*1000)

#define ERTS_MONOTONIC_TO_CLKTCKS__(MON) \
    ERTS_CONV_FROM_MON_UNIT___((MON), ERTS_CLKTCK_RESOLUTION)
#define ERTS_CLKTCKS_TO_MONOTONIC__(TCKS) \
    ERTS_CONV_TO_MON_UNIT___((TCKS), ERTS_CLKTCK_RESOLUTION)

#endif /* !ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT */

#define ERTS_MONOTONIC_TIME_END_EXTERNAL				\
    (ERTS_MONOTONIC_TIME_START_EXTERNAL < 0				\
     ? (ERTS_MONOTONIC_TIME_START_EXTERNAL				\
	+ (ERTS_MONOTONIC_END - ERTS_MONOTONIC_BEGIN))			\
     : (ERTS_MONOTONIC_END - ERTS_MONOTONIC_TIME_START_EXTERNAL))

#define ERTS_MSEC_TO_CLKTCKS__(MON) \
    ((MON) * (ERTS_CLKTCK_RESOLUTION/1000))
#define ERTS_CLKTCKS_TO_MSEC__(TCKS) \
    ((TCKS) / (ERTS_CLKTCK_RESOLUTION/1000))

#define ERTS_MONOTONIC_TO_SEC(X)	\
    (ERTS_TIME_ASSERT((X) >= 0),	\
     ERTS_MONOTONIC_TO_SEC__((X)))
#define ERTS_MONOTONIC_TO_MSEC(X)	\
    (ERTS_TIME_ASSERT((X) >= 0),	\
     ERTS_MONOTONIC_TO_MSEC__((X)))
#define ERTS_MONOTONIC_TO_USEC(X)	\
    (ERTS_TIME_ASSERT((X) >= 0),	\
     ERTS_MONOTONIC_TO_USEC__((X)))
#define ERTS_MONOTONIC_TO_NSEC(X)	\
    (ERTS_TIME_ASSERT((X) >= 0),	\
     ERTS_MONOTONIC_TO_NSEC__((X)))
#define ERTS_SEC_TO_MONOTONIC(X)	\
    (ERTS_TIME_ASSERT((X) >= 0),	\
     ERTS_SEC_TO_MONOTONIC__((X)))
#define ERTS_MSEC_TO_MONOTONIC(X)	\
    (ERTS_TIME_ASSERT((X) >= 0),	\
     ERTS_MSEC_TO_MONOTONIC__((X)))
#define ERTS_USEC_TO_MONOTONIC(X)	\
    (ERTS_TIME_ASSERT((X) >= 0),	\
     ERTS_USEC_TO_MONOTONIC__((X)))
#define ERTS_NSEC_TO_MONOTONIC(X)	\
    (ERTS_TIME_ASSERT((X) >= 0),	\
     ERTS_NSEC_TO_MONOTONIC__((X)))

#define ERTS_MONOTONIC_TO_CLKTCKS(X) \
    (ERTS_TIME_ASSERT((X) >= 0),		\
     ERTS_MONOTONIC_TO_CLKTCKS__((X)))
#define ERTS_CLKTCKS_TO_MONOTONIC(X) \
    (ERTS_TIME_ASSERT((X) >= 0),		\
     ERTS_CLKTCKS_TO_MONOTONIC__((X)))

#define ERTS_MSEC_TO_CLKTCKS(X) \
    (ERTS_TIME_ASSERT((X) >= 0),		\
     ERTS_MSEC_TO_CLKTCKS__((X)))
#define ERTS_CLKTCKS_TO_MSEC(X) \
    (ERTS_TIME_ASSERT((X) >= 0),		\
     ERTS_CLKTCKS_TO_MSEC__((X)))

#endif /* ERL_TIME_H__ */

/* timer-wheel api */
#if defined(ERTS_WANT_TIMER_WHEEL_API) && !defined(ERTS_GOT_TIMER_WHEEL_API)
#define ERTS_GOT_TIMER_WHEEL_API

#include "erl_thr_progress.h"
#include "erl_process.h"

void erts_sched_init_time_sup(ErtsSchedulerData *esdp);


#define ERTS_TWHEEL_SLOT_AT_ONCE -1
#define ERTS_TWHEEL_SLOT_INACTIVE -2

/*
** Timer entry:
*/
typedef struct erl_timer {
    struct erl_timer* next;	/* next entry tiw slot or chain */
    struct erl_timer* prev;	/* prev entry tiw slot or chain */
    union {
	struct {
	    void (*timeout)(void*); /* called when timeout */
	    void (*cancel)(void*);  /* called when cancel (may be NULL) */
	    void* arg;              /* argument to timeout/cancel procs */
	} func;
	ErtsThrPrgrLaterOp cleanup;
    } u;
    ErtsMonotonicTime timeout_pos; /* Timeout in absolute clock ticks */
    int slot;
} ErtsTWheelTimer;

typedef void (*ErlTimeoutProc)(void*);
typedef void (*ErlCancelProc)(void*);

void erts_twheel_set_timer(ErtsTimerWheel *tiw,
			   ErtsTWheelTimer *p, ErlTimeoutProc timeout,
			   ErlCancelProc cancel, void *arg,
			   ErtsMonotonicTime timeout_pos);
void erts_twheel_cancel_timer(ErtsTimerWheel *tiw, ErtsTWheelTimer *p);
ErtsTimerWheel *erts_create_timer_wheel(ErtsSchedulerData *esdp);

ErtsMonotonicTime erts_check_next_timeout_time(ErtsSchedulerData *);

ERTS_GLB_INLINE void erts_twheel_init_timer(ErtsTWheelTimer *p);
ERTS_GLB_INLINE ErtsMonotonicTime erts_next_timeout_time(ErtsNextTimeoutRef);

#if ERTS_GLB_INLINE_INCL_FUNC_DEF

ERTS_GLB_INLINE void erts_twheel_init_timer(ErtsTWheelTimer *p)
{
    p->slot = ERTS_TWHEEL_SLOT_INACTIVE;
}

ERTS_GLB_INLINE ErtsMonotonicTime erts_next_timeout_time(ErtsNextTimeoutRef nxt_tmo_ref)
{
    return *((ErtsMonotonicTime *) nxt_tmo_ref);
}

#endif /* ERTS_GLB_INLINE_INCL_FUNC_DEF */

void
erts_twheel_debug_foreach(ErtsTimerWheel *tiw,
			  void (*tclbk)(void *),
			  void (*func)(void *,
				       ErtsMonotonicTime,
				       void *),
			  void *arg);

#endif /* timer wheel api */
