;;;; "random.scm" Pseudo-Random number generator for scheme.
;;; Copyright (C) 1991, 1993, 1998, 1999, 2002 Aubrey Jaffer
;
;Permission to copy this software, to modify it, to redistribute it,
;to distribute modified versions, and to use it for any purpose is
;granted, subject to the following restrictions and understandings.
;
;1.  Any copy made of this software must include this copyright notice
;in full.
;
;2.  I have made no warrantee or representation that the operation of
;this software will be error-free, and I am under no obligation to
;provide any services, by way of maintenance, update, or otherwise.
;
;3.  In conjunction with products arising from the use of this
;material, there shall be no use of my name in any advertising,
;promotional, or sales literature without prior written consent in
;each case.

(require 'array)
(require 'logical)

;;; random:chunk returns an integer in the range of 0 to 255.
(define (random:chunk sta)
  (cond ((positive? (array-ref sta 258))
	 (array-set! sta 0 258)
	 (slib:error "random state called reentrantly")))
  (array-set! sta 1 258)
  (let* ((idx (logand #xff (+ 1 (array-ref sta 256))))
	 (xtm (array-ref sta idx))
	 (idy (logand #xff (+ (array-ref sta 257) xtm))))
    (array-set! sta idx 256)
    (array-set! sta idy 257)
    (let ((ytm (array-ref sta idy)))
      (array-set! sta xtm idy)
      (array-set! sta ytm idx)
      (let ((ans (array-ref sta (logand #xff (+ ytm xtm)))))
	(array-set! sta 0 258)
	ans))))


;;@args n
;;@args n state
;;Accepts a positive integer or real @1 and returns a number of the
;;same type between zero (inclusive) and @1 (exclusive).  The values
;;returned by @0 are uniformly distributed from 0 to @1.
;;
;;The optional argument @var{state} must be of the type returned by
;;@code{(seed->random-state)} or @code{(make-random-state)}.  It defaults
;;to the value of the variable @code{*random-state*}.  This object is used
;;to maintain the state of the pseudo-random-number generator and is
;;altered as a side effect of calls to @code{random}.
(define (random modu . args)
  (let ((state (if (null? args) *random-state* (car args))))
    (if (exact? modu)
	(letrec ((bitlen (integer-length (+ -1 modu)))
		 (rnd (lambda ()
			(do ((bln bitlen (+ -8 bln))
			     (rbs 0 (+ (ash rbs 8) (random:chunk state))))
			    ((<= bln 7)
			     (if (positive? bln)
				 (set! rbs (logxor (ash rbs bln)
						   (random:chunk state))))
			     (if (< rbs modu) rbs (rnd)))))))
	  (rnd))
	(* (random:uniform1 state) modu))))

(define random:random random)
;;;random:uniform is in randinex.scm.  It is needed only if inexact is
;;;supported.


;;@defvar *random-state*
;;Holds a data structure that encodes the internal state of the
;;random-number generator that @code{random} uses by default.  The nature
;;of this data structure is implementation-dependent.  It may be printed
;;out and successfully read back in, but may or may not function correctly
;;as a random-number state object in another implementation.
;;@end defvar


;;@args state
;;Returns a new copy of argument @1.
;;
;;@args
;;Returns a new copy of @code{*random-state*}.
(define (copy-random-state . sta)
  (string-copy (if (null? sta) *random-state* (car sta))))


;;@body
;;Returns a new object of type suitable for use as the value of the
;;variable @code{*random-state*} or as a second argument to @code{random}.
;;The number or string @1 is used to initialize the state.  If
;;@0 is called twice with arguments which are
;;@code{equal?}, then the returned data structures will be @code{equal?}.
;;Calling @0 with unequal arguments will nearly
;;always return unequal states.
(define (seed->random-state seed)
  (define sta (create-array (Au8 0) (+ 3 256)))
  (if (number? seed) (set! seed (number->string seed)))
					; initialize state
  (do ((idx #xff (+ -1 idx)))
      ((negative? idx))
    (array-set! sta idx idx))
					; merge seed into state
  (do ((i 0 (+ 1 i))
       (j 0 (modulo (+ 1 j) seed-len))
       (seed-len (string-length seed))
       (k 0))
      ((>= i 256))
    (let ((swp (array-ref sta i)))
      (set! k (logand #xff (+ k (char->integer (string-ref seed j)) swp)))
      (array-set! sta (array-ref sta k) i)
      (array-set! sta swp k)))
  sta)


;;@args
;;@args obj
;;Returns a new object of type suitable for use as the value of the
;;variable @code{*random-state*} or as a second argument to @code{random}.
;;If the optional argument @var{obj} is given, it should be a printable
;;Scheme object; the first 50 characters of its printed representation
;;will be used as the seed.  Otherwise the value of @code{*random-state*}
;;is used as the seed.
(define (make-random-state . args)
  (let ((seed (if (null? args) *random-state* (car args))))
    (cond ((string? seed))
	  ((number? seed) (set! seed (number->string seed)))
	  (else (let ()
		  (require 'object->string)
		  (set! seed (object->limited-string seed 50)))))
    (seed->random-state seed)))

(define *random-state*
  (make-random-state "http://swissnet.ai.mit.edu/~jaffer/SLIB.html"))

(provide 'random)			;to prevent loops
(if (provided? 'inexact) (require 'random-inexact))
