#pragma once

#include <stdint.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>

#define SBT_1S  ((sbintime_t)1 << 32)
#define SBT_1M  (SBT_1S * 60)
#define SBT_1MS (SBT_1S / 1000)
#define SBT_1US (SBT_1S / 1000000)
#define SBT_1NS (SBT_1S / 1000000000)
#define SBT_MAX 0x7fffffffffffffffLL

#define	FREQ2BT(freq, bt) \
{ \
  (bt)->sec = 0; \
  (bt)->frac = ((uint64_t)0x8000000000000000  / (freq)) << 1; \
}

#define BT2FREQ(bt) \
  (((uint64_t)0x8000000000000000 + ((bt)->frac >> 2)) / \
    ((bt)->frac >> 1))

struct bintime {
  uint64_t sec;
  uint64_t frac;
};

typedef int64_t sbintime_t;

static inline sbintime_t bttosbt(const struct bintime bt) {
  return (sbintime_t) ((bt.sec << 32) + (bt.frac >> 32));
}

static inline void bintime_mul(struct bintime *bt, unsigned int x) {
  uint64_t p1, p2;
  
  p1 = (bt->frac & 0xffffffffull) * x;
  p2 = (bt->frac >> 32) * x + (p1 >> 32);
  bt->sec *= x;
  bt->sec += (p2 >> 32);
  bt->frac = (p2 << 32) | (p1 & 0xffffffffull);
}

static inline void bintime_add(struct bintime *_bt, const struct bintime *_bt2)
{
  uint64_t _u;

  _u = _bt->frac;
  _bt->frac += _bt2->frac;
  if (_u > _bt->frac)
    _bt->sec++;
  _bt->sec += _bt2->sec;
}

static inline void bintime_sub(struct bintime *_bt, const struct bintime *_bt2)
{
  uint64_t _u;

  _u = _bt->frac;
  _bt->frac -= _bt2->frac;
  if (_u < _bt->frac)
    _bt->sec--;
  _bt->sec -= _bt2->sec;
}

#define bintime_cmp(a, b, cmp) \
  (((a)->sec == (b)->sec) ? \
    ((a)->frac cmp (b)->frac) : \
    ((a)->sec cmp (b)->sec))


void binuptime(struct bintime *bt);
void getmicrotime(struct timeval *tv);

static inline sbintime_t sbinuptime(void) {
  struct bintime _bt;
  
  binuptime(&_bt);
  return (bttosbt(_bt));
}

struct callout {
  pthread_cond_t wait;
  struct callout *prev;
  struct callout *next;
  uint64_t timeout;
  void *argument;
  void (*callout)(void *);
  int flags;
  int queued;
};

#define C_ABSOLUTE 0x0200 /* event time is absolute */
#define CALLOUT_ACTIVE 0x0002 /* callout is currently active */
#define CALLOUT_PENDING 0x0004 /* callout is waiting for timeout */
#define CALLOUT_MPSAFE 0x0008 /* callout handler is mp safe */
#define CALLOUT_RETURNUNLOCKED 0x0010 /* handler returns with mtx unlocked */
#define CALLOUT_COMPLETED 0x0020 /* callout thread finished */
#define CALLOUT_WAITING 0x0040 /* thread waiting for callout to finish */
//#define CALLOUT_QUEUED 0x0080

void callout_system_init(void);
void callout_init(struct callout *c, int mpsafe);
int callout_reset_sbt(struct callout *c, sbintime_t sbt,
  sbintime_t precision, void (*ftn)(void *), void *arg,
  int flags);

int callout_stop_safe(struct callout *c, int drain);

#define callout_active(c) ((c)->flags & CALLOUT_ACTIVE)
#define callout_deactivate(c) ((c)->flags &= ~CALLOUT_ACTIVE)
#define callout_pending(c)  ((c)->flags & CALLOUT_PENDING)
#define callout_completed(c)  ((c)->flags & CALLOUT_COMPLETED)
#define callout_drain(c) callout_stop_safe(c, 1)
#define callout_stop(c) callout_stop_safe(c, 0)
