"""
Handle small, millisecond precision, timestamps.

`timemark' marks a time in future - certain number of milliseconds ahead of
the current time.
"""
from datetime import datetime
import struct

# Max latency which can be recorded. Can be limited by cli.
RANGE = 60

OFFSET=0.0
NTP_SERVER=''
# asking interval, seconds
NTP_INTERVAL=10
# difference where averaging is reset
NTP_MAXDIFF=0.008
# double-queries
NTP_DQ=False

def get_timemark(relative_ts, latency_s):
    """
    Create a 1-ms resolution timemark equal to relative_ts + latency_ms.

    Args:
      relative_ts: UTC timestamp to which the mark will relate
      latency_ms: number of miliseconds in future
    Returns:
      16-bit binary marking the relative_ts + latency_ms time.
    """
    #base = int(relative // RANGE * RANGE)
    #mark = int((relative - (relative  // RANGE) * RANGE ) * 1000)
    future_ts = relative_ts + latency_s
    stamp = int((future_ts % RANGE * 1000))
    mark = struct.pack('>H', stamp)
    return future_ts, mark


def to_absolute_timestamp(relative_ts, mark):
    """
    Interpret a timemark as a full timestamp relative to `relative_ts`

    Args:
      relative_ts: UTC timestamp, close, but not exactly the same as the
                   one used when creating the mark.

      mark: 16-bit binary

    Returns:
      timestamp relative to relative_ts.
    """
    mark = struct.unpack('>H', mark)[0]
    base = relative_ts // RANGE * RANGE
    recovered = base + mark / 1000.0
    if recovered < relative_ts:
        # We ended up in the past, assume next interval
        recovered += RANGE
    return recovered


def now():
    "Current UTC timestamp"
    return datetime.utcnow().timestamp()+OFFSET


NTPclient=0;
def ntp_init(server,interval,maxdiff,doublequery):
    global NTPclient
    global NTP_SERVER
    global NTP_INTERVAL
    global NTP_MAXDIFF
    global NTP_DQ
    import ntplib
    NTPclient = ntplib.NTPClient()
    NTP_SERVER=server
    NTP_INTERVAL=interval
    NTP_MAXDIFF=maxdiff/1000
    NTP_DQ=doublequery
    print("NTP:server="+NTP_SERVER+", interval="+str(NTP_INTERVAL)+" sec, max diff="+str(maxdiff)+" ms, doublequery="+str(NTP_DQ))


from collections import deque
ntpdq=deque(maxlen=10)
ntptimer=0

def ntpdq_getavg():
    return sum(ntpdq)/len(ntpdq)


def ntp_getoffset():
    global OFFSET,ntpdq
    if NTP_SERVER == '': return
    try:
      # double for waking up the server or whatever - brings down roundtrip brutally
      if NTP_DQ:
        res1=NTPclient.request(NTP_SERVER, version=3)
        res2=NTPclient.request(NTP_SERVER, version=3)
        diff1=res1.offset-OFFSET
        diff2=res2.offset-OFFSET
        if res1.delay<res2.delay:
          res=res1
          print("first delay smaller than second: {:.3f}/{:.3f}".format(res1.delay*1000,res2.delay*1000))
        else:
          res=res2
      else:
        res=NTPclient.request(NTP_SERVER, version=3)
    except:
      print("NTP: query failed")
      return
    diff=res.offset-OFFSET
    if abs(diff) > NTP_MAXDIFF:
      print('NTP: OVERLY LARGE NTP DIFFERENCE - ntp daemon acted?, smoothing buffer is reset')
      ntpdq.clear()
    ntpdq.append(res.offset)
    ooffs=OFFSET
    OFFSET=ntpdq_getavg()
    if NTP_DQ:
      print('NTP: OFFSET set to {:.4f},   diff {:+5.3f}/{:+5.3f} ms,  diff-avg {:+5.3f} ms,  delay {:.3f}/{:.3f} ms'.format(OFFSET,diff1*1000,diff2*1000,(OFFSET-ooffs)*1000,res1.delay*1000,res2.delay*1000))
    else:
      print('NTP: OFFSET set to {:.4f},   diff {:+5.3f} ms,  diff-avg {:+5.3f} ms,  delay {:.3f} ms'.format(OFFSET,diff*1000,(OFFSET-ooffs)*1000,res.delay*1000))


import threading

def ntp_runtimer():
    if NTP_SERVER == '': return
    global ntptimer
    ntp_getoffset()
    ntptimer=threading.Timer(NTP_INTERVAL,ntp_runtimer)
    ntptimer.start()

def ntp_stoptimer():
    if NTP_SERVER == '': return
    ntptimer.cancel()

