#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include "readwrite.h"
#include "exit.h"
#include "select.h"
#include "scan.h"
#include "fmt.h"
#include "str.h"
#include "fifo.h"
#include "open.h"
#include "error.h"
#include "auto_home.h"
#include "timing.h"

#if !defined(HASRDTSC) && !defined(HASARMCNT) && !defined(HASGETHRTIME)

#error  "Error! Need an unadjusted hardware clock."

#endif

struct point {
  timing lowlevel;
  timing_basic ostime;
  double adj; /* real - ostime, if flagknown; else 0 */
  int flagknown;
} ;

void now(struct point * p)
{
  timing_now(&p->lowlevel);
  timing_basic_now(&p->ostime);
  p->adj = 0;
  p->flagknown = 0;
}

double nano(unsigned char buf[16])
{
  unsigned long u;
  double result;

  /* XXX: ignoring buf[0...3] */

  u = buf[12];
  u <<= 8; u += buf[13];
  u <<= 8; u += buf[14];
  u <<= 8; u += buf[15];
  result = u * 0.000000001;

  u = buf[8];
  u <<= 8; u += buf[9];
  u <<= 8; u += buf[10];
  u <<= 8; u += buf[11];
  result += u;

  u = buf[4];
  u <<= 8; u += buf[5];
  u <<= 8; u += buf[6];
  u <<= 8; u += buf[7];
  if (u < 2147483648UL)
    result += 1000000000.0 * u;
  else
    result += 0.0 - 1000000000.0 * (4294967295UL + 1 - u);

  return result;
}

static struct point first;
static struct point current;
static unsigned char buf[16];

static double deriv = 0; /* 0 for unknown */

void savederiv(void)
{
  int fd;
  double z;
  unsigned long u;

  if (deriv <= 0) return;
  if (deriv > 200000000) return; /* 5Hz ticks? be serious */

  fd = open_trunc("etc/clockspeed/atto.tmp");
  if (fd == -1) return;

  buf[0] = 0;
  buf[1] = 0;
  buf[2] = 0;
  buf[3] = 0;
  buf[4] = 0;
  buf[5] = 0;
  buf[6] = 0;
  buf[7] = 0;

  z = deriv;
  u = z;
  if (u > z) --u;
  if (u > 999999999) u = 999999999;
  z -= u;
  buf[11] = u; u >>= 8;
  buf[10] = u; u >>= 8;
  buf[9] = u; u >>= 8;
  buf[8] = u;

  z *= 1000000000.0;
  u = z;
  if (u > z) --u;
  if (u > 999999999) u = 999999999;
  z -= u;
  buf[15] = u; u >>= 8;
  buf[14] = u; u >>= 8;
  buf[13] = u; u >>= 8;
  buf[12] = u;

  if (write(fd,buf,sizeof buf) < sizeof buf) { close(fd); return; }
  if (fsync(fd) == -1) { close(fd); return; }
  if (close(fd) == -1) return; /* NFS stupidity */

  rename("etc/atto.tmp","etc/atto"); /* if it fails, bummer */
}

int main(void)
{
  struct timeval tvselect;
  fd_set rfds;
  int r;
  double deltareal;
  double deltalowlevel;
  struct timeval tvchange;

  close(0);

  if (chdir(auto_home) == -1) return (1);
  umask(033);

  if (open_read("etc/atto") == 0) {
    r = read(0,buf,sizeof buf);
    if (r == sizeof buf)
      deriv = nano(buf);
    close(0);
  }

  if (fifo_make("adjust",0600) == -1) if (errno != error_exist) return (1);
  if (open_read("adjust") != 0) return (1);
  if (open_write("adjust") == -1) return (1);

  now(&first);

  for (;;) {
    FD_ZERO(&rfds);
    FD_SET(0,&rfds);

    tvselect.tv_sec = 3;
    tvselect.tv_usec = 0;

    if (select(1,&rfds,(fd_set *) 0,(fd_set *) 0,&tvselect) == 1) {
      r = read(0,buf,sizeof buf);
      if (r <= 0) return (1); /* not possible */

      /* XXX: ignoring partial packets */
      if (r == sizeof buf) {
        now(&current);
        current.adj = nano(buf);
        current.flagknown = 1;

        if (!first.flagknown) first = current;

        deltalowlevel = timing_diff(&current.lowlevel,&first.lowlevel);
        deltareal = timing_basic_diff(&current.ostime,&first.ostime);
        deltareal += current.adj - first.adj;
        if (deltareal > 10.0) {
          deriv = deltareal / deltalowlevel;
          savederiv();
        }
      }
    }

    if (deriv) {
      now(&current);

      deltalowlevel = timing_diff(&current.lowlevel,&first.lowlevel);
      deltareal = deltalowlevel * deriv;
      deltareal -= timing_basic_diff(&current.ostime,&first.ostime);
      deltareal += first.adj;

      deltareal *= 0.001;
      if (deltareal > 99999999.0) deltareal = 99999999.0;
      if (deltareal < -99999999.0) deltareal = -99999999.0;

      tvchange.tv_sec = 0;
      tvchange.tv_usec = deltareal;
      while (tvchange.tv_usec < 0) {
        tvchange.tv_sec -= 1;
        tvchange.tv_usec += 1000000;
      }
      while (tvchange.tv_usec > 999999) {
        tvchange.tv_sec += 1;
        tvchange.tv_usec -= 1000000;
      }

      adjtime(&tvchange,(struct timeval *) 0); /* if it fails, bummer */
    }
  }
}
