namespace eval osctime {

#
# This handles RFC 2030 time spec, as per OSC
# If you want really accurate times, from a named server,
# then you should be running ntpd on your machine(s).
# If you want really, really accurate times, run a tertiary
# time server that your local machines can use.
# http://www.pool.ntp.org/use.html
#
#
# The following is probably good enough
# (Note: clock clicks is using -millisecond accuracy
#
variable debug 0
variable HasBeenSetup 0
variable lapseTime 3 ; #3 seconds

#
# We will need to know how many seconds to add
# to the standard epoch to convert to 1900 epoch
#

variable unixEpochOffset 2208988800

#
# We will need a conversion routine to convert
# internal clicks to seconds since epoch
#
variable clickrate
variable zeroClick
variable zeroSeconds

	proc setup {} {
		variable debug
		variable lapseTime
		variable clickrate
		variable zeroClick
		variable zeroSeconds
		variable HasBeenSetup

		#
		# first find the clickrate, ie how many clicks per second
		set milliSecs [expr {$lapseTime * 1000}]

		set click1 [clock clicks -milliseconds]
		after $milliSecs
		set click2 [clock clicks -milliseconds]

		# number of clicks per second
		set clickrate [expr {int(floor( ($click2 - $click1)/$lapseTime ))}]


		#
		# Now sync the arbitrary clicktime, with 'now'
		# Run around in a loop until the clock just changes
		#

		set zeroSeconds [expr 2 + [clock seconds]]

		set j 1
		for {set i 0} {$j == 1} {incr i} {
			set thenClick [expr wide([format "0x%x" [clock clicks -milliseconds]])]
			after 10
			set nowClick [expr wide([format "0x%x" [clock clicks -milliseconds]])]
			set nowSecZero [clock seconds]
			if { $nowSecZero == $zeroSeconds} {
				set j 0

				# Lots of conversions to stay positive


				set a [expr wide([format "0x%x" $thenClick])]
				set b [expr wide([format "0x%x" $nowClick])]
				set c [expr {($b - $a)/2}]
				set d [expr {$a + $c}]
				set zeroClick [format "0x%x" $d]

			}
		}
		if {$debug == 1} {
			puts "Clickrate: $clickrate ticks per second"
			puts [format "Map (%u) %u (%u) clicks to %u seconds" $a $zeroClick $b $zeroSeconds]
		}
		
		set HasBeenSetup 1
	}

	proc instant {} {
		return [format "0x%x%x" 0 1]
	}
	proc nowToOscTime {} {
		return [futureToOscTime 0 0 1]
	}

	#
	# How far into the future is the given oscTime ?
	#
	proc oscTimeToDelay {oscTime accuracy} {
		#
		# accuracy : 1000 return offset in millsecs
		#
		# Assume we will not get better than microsecond accuracy
		#
		set secDelay [expr [oscTimeToEpoch $oscTime] - [clock seconds]]
		set secDelayMicro [expr {1000000.0*$secDelay}]

		set fracNow [oscTimeToFracMicro [nowToOscTime]]
		set fracThen [oscTimeToFracMicro $oscTime]
		set fracDelta [expr {$fracThen - $fracNow}]
		set secDelayMicro [expr {$secDelayMicro+$fracDelta}]

		return [expr {int(round($accuracy*($secDelayMicro/1000000.0) ))}]
	}

	proc oscTimeToFrac {oscTime} {
		set ufrac [oscTimeToFracHex $oscTime]
		return [expr {double((1.0*$ufrac)/0xffffffff)}]
	}
	proc oscTimeToFracMilli {oscTime} {
		set ufrac [oscTimeToFracHex $oscTime]
		return [expr {int(round(double((1000.0*$ufrac)/0xffffffff)))}]
	}
	proc oscTimeToFracMicro {oscTime} {
		set ufrac [oscTimeToFracHex $oscTime]
		return [expr {int(floor(double((1000000.0*$ufrac)/0xffffffff)))}]
	}
	proc oscTimeToFracNano {oscTime} {
		set ufrac [oscTimeToFracHex $oscTime]
		return [expr {int(floor(double((1000000000.0*$ufrac)/0xffffffff)))}]
	}
	proc oscTimeToFracPico {oscTime} {
		set ufrac [oscTimeToFracHex $oscTime]
		return [expr {wide(floor(double((1000000000000.0*$ufrac)/0xffffffff)))}]
	}

	proc oscTimeToFracHex {oscTime} {
		binary scan [binary format W $oscTime] H8H8 a b
		append utime 0x $a
		append ufrac 0x $b
		return $ufrac
	}
	proc oscTimeToEpoch {oscTime} {
		variable unixEpochOffset

		#set oscTime [expr {wide($oscTime + 0)}]
		binary scan [binary format W $oscTime] H8H8 a b
		append utime 0x $a
		append ufrac 0x $b
		
		return [expr {$utime - $unixEpochOffset}]
	}

	proc fractionalTimeToOsc {givenTime} {
		set secs [expr {wide(floor($givenTime))}]
		set secOffset [expr $secs - [clock seconds]]
		set denom 1000000
		set numar [expr {int(floor($denom*($givenTime - $secs)))}]
		return [futureToOscTime $secOffset $numar $denom]
	}

	proc futureToOscTime {plusSeconds plusFracNumerator plusFracDenominator} {
		variable debug
		variable clickrate
		variable zeroClick
		variable zeroSeconds
		variable unixEpochOffset

		set click [expr wide([format "0x%x" [clock clicks -milliseconds]])]
		set secs [clock seconds]

		#
		# How many clicks have elapsed since our arbitrary sync ?
		#
		set nowClicks [expr {$click - $zeroClick}]
		if {$debug == 1} {
			puts [format "clicktime=%u deltaclick=%u" $click $nowClicks]
		}
		#
		# How many seconds have elapsed since our arbitrary sync ?
		#
		set nowSecs [expr {wide($plusSeconds + floor($nowClicks / $clickrate))}]

		#
		# How many fractions of a second have passed?
		#
		set carry 0
		set remainderSec [expr {$nowClicks % $clickrate}]
		set fracSec [expr {double($remainderSec) / double($clickrate)}]
		set fracSec32 [expr {wide($fracSec*0xffffffff)}]
		if {$plusFracNumerator > 0 && $plusFracDenominator > 0} {
			set fracSecAdd [expr {($plusFracNumerator*1.0)/double($plusFracDenominator)}]
			if {$debug == 1} {
				puts [format "Adding %d/%d of a second (%f) onto %f"\
					$plusFracNumerator $plusFracDenominator $fracSecAdd $fracSec]
			}
			set fracSec [expr {$fracSec + $fracSecAdd}]
			set carry [expr {int(floor($fracSec))}]
			if {$carry > 0} {
				set $fracSec [expr {$fracSec - $carry}]
			}
			set fracSec32 [expr {wide($fracSec*0xffffffff)}]
			if {$debug == 1} {
				puts [format "Carry=%d , new fracSec=%f (%u/%u)" $carry $fracSec $fracSec32 0xffffffff ]
			}
		}
		set tempHex [binary format "W" $fracSec32]
		binary scan $tempHex H16 fracFixed 
		set fracFixed [string range $fracFixed 8 end]
		if {$debug == 1} {
			puts [format "fracSec=%f fracFixed=%s" $fracSec $fracFixed ]
		}


		set unixEpochSeconds [expr {$zeroSeconds+$nowSecs+$carry}]

		if {$debug == 1} {
			puts [format "clocktme=%u , clicktime=%u %s" $secs $unixEpochSeconds $fracFixed]
			puts [format "clocktme=%u , clicktime=%x.%s" $secs [expr {$unixEpochSeconds + $unixEpochOffset}] $fracFixed]
		}

		return [format "0x%8x%s" [expr {$unixEpochSeconds + $unixEpochOffset}] $fracFixed]
	}

}
