#! /bin/env tclsh

variable doc::flip {
	description {
		Assume that there is jitter inherent in the system, and use it as an
		entropy source by capturaing variations in time at the edge of a click.
	}
}
proc flip {} {
	variable repeat
	if {$repeat > 10} {
		set repeat [expr {entier($repeat - $repeat * .20)}]
	}

	set direction -1
	while 1 {
		set diffs {}
		set count 25
		set half [expr {entier($count / 2)}]
		for {set i 0} {$i < $count} {incr i} {
			set c1 [clock clicks]
			time {expr {cos($repeat)}} $repeat
			set c2 [clock clicks]
			set diff [expr {$c2 - $c1}]
			dict incr diffs $diff 
		}

		set mindiffs [expr {entier(sqrt($count))}]

		# Select and whiten
		if {[llength [dict keys $diffs]] >= $mindiffs && [::tcl::mathfunc::max {*}[dict values $diffs]] <= $half} {
			# The calculations took place at the edge of a click , reflecting
			# jitter , or at some other point when the system is unstable .
			break
		}
		if {$repeat == 0} {
			set direction 1
		}
		incr repeat $direction
	}
	expr {$diff % 2}
}

variable doc::randbytes {
	description {
		Produce random bytes .
	}
}
proc randbytes count {
	set res {}
	while {[string length $res] < $count} {
		set num 0
		set diff 0
		for {set i 0} {$i < 9} {incr i} {
			set num [expr {($num << 1) | [flip]}]
		}
		append res [binary format c [expr {$num & 255}]]
	}
	return $res
}

proc randbytes_udev {{bytes 256}} {
	set res {}
	if {[file exists /dev/urandom]} {
		set chan [open /dev/urandom rb]
		chan configure $chan -blocking 1
		append res [read $chan $bytes]
		close $chan
	} else {
		#todo: accomodate more methods
		return -code error [list {no entropy source}]
		package require platform 
	}
	return $res
}

proc rand ceiling {
	package require {ycl math rng}
	namespace import [yclprefix]::math::rng
	proc rand ceiling {
		set needed [expr {entier(ceil(log($ceiling)/log(2)/8))}]
		set data [rng take $needed]
		binary scan $data H* hex
		scan $hex %llx res
		return [expr {$res % $ceiling}]
	}
	tailcall rand $ceiling
}


variable doc::randprint_script {
	description  {
		Creates a script that produces random strings of
		printable characters.   By default, it produces a command that produces
		strings that, when interpreted as an integer in base-68 occupies
		approximately, but no more than 256 bits.
	}
	args {
		randscript {
			description {
				A script that evaluates to a random mumber between 0 and 67 ,
				inclusive .
			}

			count {
				The size of the string to produce, in characters
			}
		}
		
	}
}
proc randprint_script args {
	dict update args randscript randscript count count {}
	if {![info exists randscript]} {
		set randscript {expr {entier(rand() * 68)}}
	}
	if {![info exists count]} {
		set count 42
	}
	
	return "lindex [string repeat "\[lindex {# @ + - ^ = 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z} \[$randscript]]" $count]"
}

proc randprint_256 {} {
	package require {ycl math rng}
	namespace import [yclprefix]::math::rng
	proc randprint_256 {} [randprint_script randscript [namespace code {
		apply [list args {
			binary scan [rng take 8] H* data
			expr int(.[string reverse [expr 0x$data]] * 68)
		} [namespace current]]
	}]]
	tailcall randprint_256
}



variable repeat 1
