;-*- Mode:     Lisp -*-
;;;; Author:   Paul Dietz
;;;; Created:  Sun Jun  8 06:56:15 2003
;;;; Contains: Aux. functions and macros used for randomization

(in-package :cl-test)

(declaim (special +standard-chars+ *cl-symbols-vector*))

(defvar *maximum-random-int-bits*
  (max 36 (1+ (integer-length most-positive-fixnum))))

(defun random-from-seq (seq)
  "Generate a random member of a sequence."
  (let ((len (length seq)))
    (assert (> len 0))
    (elt seq (random len))))

(defmacro random-case (&body cases)
  (let ((len (length cases)))
    (assert (> len 0))
    `(case (random ,len)
       ,@(loop for i from 0 for e in cases collect `(,i ,e))
       (t (error "Can't happen?! (in random-case)~%")))))

(defmacro rcase (&body cases)
  "Usage: (RCASE (<weight> <form>+)+), where <weight> is a positive real
   indicating the relative probability of executing the associated implicit
   progn."
  (assert cases)
  (let* ((weights (mapcar #'car cases))
	 (cumulative-weights (let ((sum 0))
			       (loop for w in weights collect (incf sum w))))
	 (total (car (last cumulative-weights)))
	 (r (gensym)))
    (assert (every #'plusp weights))
    (when (typep total 'ratio) (setf total (coerce total 'double-float)))
    `(let ((,r (random ,total)))
       (cond
	,@(loop for case in (butlast cases)
		for cw in cumulative-weights
		collect `((< ,r ,cw) ,@(cdr case)))
	(t ,@(cdar (last cases)))))))

(defmacro rselect (cumulative-frequency-array &rest cases)
  (let ((len (length cases))
	(a (gensym "A"))
	(max (gensym "MAX"))
	(r (gensym "R"))
	(p (gensym "P"))
	(done (gensym "DONE")))
    (assert (> len 0))
    `(let ((,a ,cumulative-frequency-array))
       (assert (eql ,len (length ,a)))
       (let* ((,max (aref ,a ,(1- len)))
	      (,r (random ,max)))
	 (block ,done
	  ,@(loop for i from 0
		  for c in cases
		  collect 
		  `(let ((,p (aref ,a ,i)))
		     (when (< ,r ,p) (return-from ,done ,c))))
	  (error "Should not happen!"))))))

(defun make-random-integer-range (&optional var)
  "Generate a list (LO HI) of integers, LO <= HI.  This is used
   for generating integer types."
  (declare (ignore var))
  (rcase
   (1 (flet ((%r () (let ((r (ash 1 (1+ (random *maximum-random-int-bits*)))))
		      (- (random r) (floor (/ r 2))))))
	(let ((x (%r))
	      (y (%r)))
	  (list (min x y) (max x y)))))
   (1 (let* ((b (ash 1 (1+ (random *maximum-random-int-bits*))))
	     (b2 (floor (/ b 2))))
	(let ((x (- (random b) b2))
	      (y (- (random b) b2)))
	  (list (min x y) (max x y)))))))

(defun random-nonnegative-real ()
  (if (coin 3)
      (random-case
       (/ (random 10000) (1+ (random 1000)))
       (/ (random 1000000) (1+ (random 100000)))
       (/ (random 100000000) (1+ (random 10000000)))
       (/ (random 1000000000000) (1+ (random 10000000))))
    (random (random-case
	     1000
	     100000
	     10000000
	     1000000000
	     (expt 2.0s0 (random 15))
	     (expt 2.0f0 (random 32))
	     (expt 2.0d0 (random 32))
	     (expt 2.0l0 (random 32))))))

(defun make-random-integer ()
  (let ((r (ash 1 (1+ (random *maximum-random-int-bits*)))))
    (rcase
     (6 (- (random r) (floor (/ r 2))))
     (1 (- r (random (min 10 r))))
     (1 (+ (floor (/ r 2)) (random (min 10 r)))))))

(defun make-random-rational ()
  (let* ((r (ash 1 (1+ (random *maximum-random-int-bits*))))
	 (n (random r)))
    (assert (>= r 2))
    (let ((d (loop for x = (random r) unless (zerop x) do (return x))))
      (if (coin) (/ n d) (- (/ n d))))))

(defun make-random-nonnegative-rational ()
  (let* ((r (ash 1 (1+ (random *maximum-random-int-bits*))))
	 (n (random r)))
    (assert (>= r 2))
    (let ((d (loop for x = (random r) unless (zerop x) do (return x))))
      (/ n d))))

(defun make-random-positive-rational ()
  (let* ((r (ash 1 (1+ (random *maximum-random-int-bits*))))
	 (n (1+ (random r))))
    (assert (>= r 2))
    (let ((d (loop for x = (random r) unless (zerop x) do (return x))))
      (/ n d))))

(defun make-random-bounded-rational (upper-limit lower-inclusive upper-inclusive)
  (assert (rationalp upper-limit))
  (assert (not (minusp upper-limit)))
  (cond
   ((= upper-limit 0) 0)
   ((<= upper-limit 1/1000000)
    (/ (make-random-bounded-rational (* 1000000 upper-limit) lower-inclusive upper-inclusive)
       1000000))
   ((>= upper-limit 1000000)
    (* (random 1000000)
       (make-random-bounded-rational (/ upper-limit 1000000) lower-inclusive upper-inclusive)))
   (t
    (assert (< 1/1000000 upper-limit 1000000))
    (let ((x 0))
      (loop do (setq x (* upper-limit (rational (random 1.0))))
	    while (or (and (not lower-inclusive) (zerop x))
		      (and (not upper-inclusive) (= x upper-limit)))
	    finally (return x))))))   

(defun make-random-float ()
  (rcase
   (1 (random most-positive-short-float))
   (1 (random most-positive-single-float))
   (1 (random most-positive-double-float))
   (1 (random most-positive-long-float))))

(defun make-random-symbol ()
  (rcase
   (3 (random-from-seq #(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)))
   (2 (random-from-seq *cl-symbols-vector*))
   (1 (gensym))))

(defun random-real ()
  (if (coin) (random-nonnegative-real)
    (- (random-nonnegative-real))))

(defun random-fixnum ()
  (+ (random (1+ (- most-positive-fixnum most-negative-fixnum)))
     most-negative-fixnum))

(defun random-thing (n)
  (if (<= n 1)
      (random-leaf)
    (rcase
     (1 (apply #'cons (mapcar #'random-thing (random-partition (1- n) 2))))
     (1 (apply #'vector (mapcar #'random-thing
				(random-partition (1- n) (max 10 (1- n))))))
     )))

(defparameter *use-random-byte* t)
(defparameter *random-readable* nil)

(defun make-random-string (size-spec &key simple)
  (let*
      ((size (if (eql size-spec '*) (random 30) size-spec))
       (use-random-byte nil)
       (etype 'character)
       (s (random-case
	   (progn
	     (setf use-random-byte *use-random-byte*)
	     (make-string size :element-type 'character))
	   (progn
	     (setf use-random-byte *use-random-byte*)
	     (make-array size :element-type 'character
			 :initial-element #\a))
	   (make-array size :element-type (setf etype (if *random-readable* 'character 'standard-char))
		       :adjustable (and (not simple) (not *random-readable*) (rcase (3 nil) (1 t)))
		       :fill-pointer (and (not simple) (not *random-readable*) (rcase (3 nil) (1 (random (1+ size)))))
		       :initial-element #\a)
	   (make-array size :element-type (setf etype (if *random-readable* 'character 'base-char))
		       :adjustable (and (not simple) (not *random-readable*) (rcase (3 nil) (1 t)))
		       :fill-pointer (and (not simple) (not *random-readable*) (rcase (3 nil) (1 (random (1+ size)))))
		       :initial-element #\a))))
    (if (coin)
	(dotimes (i size)
	  (setf (char s i) (elt #(#\a #\b #\A #\B) (random 4))))
      (dotimes (i size)
	(setf (char s i)
	      (or (and (eql etype 'character)
		       use-random-byte
		       (or (code-char (random (min char-code-limit (ash 1 16))))
			   (code-char (random 256))))
		  (elt "abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
		       (random 62))))))
    (when (and (not simple) (not *random-readable*) (coin 5))
      (let ((len (+ (random (1+ size)) size)))
	(setq s (make-random-string len))
	(setq etype (array-element-type s))
	(setq s (make-array size
			    :element-type etype
			    :displaced-to s
			    :displaced-index-offset (random (1+ (- len size)))))))
      
    s))

(defun random-leaf ()
  (rcase
   (1 (let ((k (ash 1 (1+ (random 40)))))
	(random-from-interval k (- k))))
   (1 (random-from-seq +standard-chars+))
   (1 (random-real))
   (1 (make-random-string (random 20)))
   (1 (gensym))
   (1 (make-symbol (make-random-string (random 20))))
   (1 (random-from-seq *cl-symbols-vector*))))

(defun random-from-interval (upper &optional (lower (- upper)))
  (+ (random (- upper lower)) lower))

(defun coin (&optional (n 2))
  "Flip an n-sided coin."
  (eql (random n) 0))

;;; Randomly permute a sequence
(defun random-permute (seq)
  (setq seq (copy-seq seq))
  (let ((len (length seq)))
    (loop for i from len downto 2
	  do (let ((r (random i)))
	       (rotatef (elt seq r) (elt seq (1- i))))))
  seq)

(defun binomial-distribution-test (n fn)
  (let* ((count (loop repeat n count (funcall fn)))
	 (sigma (/ (sqrt n) 2.0))
	 (bound (* sigma 6))
	 (expected (/ n 2.0)))
    (<= (- expected bound)
	count
	(+ expected bound))))

(defun random-partition* (n p)
  "Partition n into p numbers, each >= 0.  Return list of numbers."
  (assert (<= 1 p))
  (cond
   ((= p 1) (list n))
   ((= n 0) (make-list p :initial-element 0))
   (t (let* ((r (random p))
	     (n1 (random (1+ n))))
	(cond
	 ((= r 0)
	  (cons n1 (random-partition* (- n n1) (1- p))))
	 ((= r (1- p))
	  (append (random-partition* (- n n1) (1- p)) (list n1)))
	 (t
	  (let* ((n2 (random (1+ (- n n1))))
		 (n3 (- n n1 n2)))
	    (append (random-partition* n2 r)
		    (list n1)
		    (random-partition* n3 (- p 1 r))))))))))

(defun random-partition (n p)
  "Partition n into p numbers, each >= 1 (if possible.)"
  (cond
   ((<= n p)
    (make-list p :initial-element 1))
   (t (mapcar #'1+ (random-partition* (- n p) p)))))


;;; Random method combination
;;; Methods in this method combination take a single method qualifier,
;;; which is a positive integer.  Each method is invoked
;;; with probability proportional to its qualifier.
;;;
;;; Inside a method, a throw to the symbol FAIL causes
;;; the application to repeat.  This enables methods to abort
;;; and retry the random selection process.

(defun positive-integer-qualifier-p (qualifiers)
  (typep qualifiers '(cons (integer 1) null)))

(define-method-combination randomized nil ((method-list positive-integer-qualifier-p))
  (assert method-list)
  (let ((clauses (mapcar #'(lambda (method)
			     (let ((weight (car (method-qualifiers method))))
			       `(,weight (call-method ,method))))
			 method-list)))
  `(loop (catch 'fail (return (rcase ,@clauses))))))
