#include "uulib/clock.h"
#include "uulib/chacha.h"

#ifdef USE_WIN32_NATIVE
/* #define getpid()       _getpid() */
#define ftruncate(a,b) _chsize(a,b)
/* typedef U32            mode_t; */
#endif
#undef open

static const char *statepath = NULL;

void uu_init_statepath(const char *path) {
  statepath = path;
}

IV uu_clock(U64 *clock_reg, U16 *ret_clock_seq) {
  static int            adjustment = 0;
  static struct timeval last = {0, 0};
  static int            state_fd = -2;
  static FILE           *state_f;
  static U16            clock_seq;
  struct timeval        tv;
  struct stat           statbuf;
  mode_t                save_umask;
  int                   len;
#ifndef _WIN32
  struct flock          fl;
#endif

  if (state_fd == -2) {
#ifdef HAVE_LSTAT
    if ((lstat(statepath, &statbuf) == 0)
      && ((statbuf.st_mode & S_IFMT) == S_IFLNK))
      state_fd = -1;
    else {
#endif
      save_umask = umask(0);
      state_fd = open(statepath, O_RDWR|O_CREAT, 0660);
      (void) umask(save_umask);
      if (state_fd >= 0) {
#ifdef HAVE_LSTAT
        state_f = NULL;
        if ((lstat(statepath, &statbuf) == 0)
          && ((statbuf.st_mode & S_IFMT) != S_IFLNK))
#endif
          state_f = fdopen(state_fd, "r+");
        if (!state_f) {
          close(state_fd);
          state_fd = -1;
        }
      }
#ifdef HAVE_LSTAT
    }
#endif
  }
#ifndef _WIN32
  fl.l_type = F_WRLCK;
  fl.l_whence = SEEK_SET;
  fl.l_start = 0;
  fl.l_len = 0;
  fl.l_pid = 0;
  if (state_fd >= 0) {
    rewind(state_f);
    while (fcntl(state_fd, F_SETLKW, &fl) < 0) {
      if ((errno == EAGAIN) || (errno == EINTR))
        continue;
      fclose(state_f);
      state_fd = -1;
      break;
    }
  }
#else
  if (state_fd >= 0)
    rewind(state_f);
#endif
  if (state_fd >= 0) {
    unsigned int cl;
    unsigned long tv1, tv2;
    int a;

    if (fscanf(state_f, "clock: %04x tv: %lu %lu adj: %d\n",
         &cl, &tv1, &tv2, &a) == 4) {
      clock_seq = cl & 0x3FFF;
      last.tv_sec = tv1;
      last.tv_usec = tv2;
      adjustment = a;
    }
  }

  if ((last.tv_sec == 0) && (last.tv_usec == 0)) {
    cc_rand16(&clock_seq);
    clock_seq &= 0x3FFF;
    gettimeofday(&last, 0);
    last.tv_sec--;
  }

  gettimeofday(&tv, 0);
  if ((tv.tv_sec < last.tv_sec) || ((tv.tv_sec == last.tv_sec) && (tv.tv_usec < last.tv_usec))) {
    clock_seq = (clock_seq+1) & 0x3FFF;
    adjustment = 0;
    last = tv;
  }
  else if ((tv.tv_sec == last.tv_sec) && (tv.tv_usec == last.tv_usec)) {
    if (adjustment >= MAX_ADJUSTMENT) {
      clock_seq = (clock_seq+1) & 0x3FFF;
      adjustment = 0;
    }
    else {
      adjustment++;
    }
  }
  else {
    adjustment = 0;
    last = tv;
  }

  *clock_reg = tv.tv_usec*10 + adjustment;
  *clock_reg += ((U64)tv.tv_sec)*10000000;
  *clock_reg += (((U64)0x01B21DD2) << 32) + 0x13814000;

  if (state_fd > 0) {
    rewind(state_f);
    len = fprintf(state_f,
            "clock: %04x tv: %016lu %08lu adj: %08d\n",
            clock_seq, (unsigned long)last.tv_sec,
            (unsigned long)last.tv_usec, adjustment);
    fflush(state_f);
    if (ftruncate(state_fd, len) < 0) {
      fprintf(state_f, "                   \n");
      fflush(state_f);
    }
    rewind(state_f);
#ifndef _WIN32
    fl.l_type = F_UNLCK;
    if (fcntl(state_fd, F_SETLK, &fl) < 0) {
      fclose(state_f);
      state_fd = -1;
    }
#endif
  }

  /* *clock_high = clock_reg >> 32; */
  /* *clock_low = (U32)clock_reg; */
  *ret_clock_seq = clock_seq;
  return 0;
}

/* ex:set ts=2 sw=2 itab=spaces: */
