;-*- Mode:     Lisp -*-
;;;; Author:   Paul Dietz
;;;; Created:  Tue Dec 28 20:28:03 2004
;;;; Contains: Code to make random elements of types

(in-package :cl-test)

(defgeneric make-random-element-of (type)
  (:documentation
   "Create a random element of TYPE, or throw an error if it can't figure out how to do it."))

(defgeneric make-random-element-of-compound-type (type args &key &allow-other-keys)
  (:documentation
   "Create a random element of (TYPE . ARGS), or throw an error if it can't figure out how to do it."))

(defmethod make-random-element-of ((type cons))
  (make-random-element-of-compound-type (car type) (cdr type)))

(defmethod make-random-element-of ((type (eql bit))) (random 2))

(defmethod make-random-element-of ((type (eql boolean)))
  (random-from-seq #(nil t)))

(defmethod make-random-elememt-of ((type (eql symbol)))
  (random-from-seq #(nil t a b c :a :b :c |z| foo |foo| car)))

(defmethod make-random-element-of ((type (eql unsigned-byte)))
  (random-from-interval
   (1+ (ash 1 (random *maximum-random-int-bits*)))))

(defmethod make-random-elememt-of ((type (eql signed-byte)))
  (random-from-interval
   (1+ (ash 1 (random *maximum-random-int-bits*)))
   (- (ash 1 (random *maximum-random-int-bits*)))))

(defmethod make-random-element-of ((type (eql rational)))
  (let* ((r (ash 1 (1+ (random *maximum-random-int-bits*))))
         (n (random r))
         (d (loop for x = (random r) unless (zerop x) do (return x))))
    (if (coin) (/ n d) (- (/ n d)))))

(defmethod make-random-element-of ((type (eql integer)))
  (let* ((b (random *maximum-random-int-bits*))
         (x (ash 1 b)))
    (rcase
     (1 (+ x (make-random-element-of 'integer)))
     (1 (- (make-random-element-of 'integer) x))
     (6 (random-from-interval (1+ x) (- x))))))

(defmethod make-random-element-of ((type (eql short-float)))
  (make-random-element-of (list type)))

(defmethod make-random-element-of ((type (eql single-float)))
  (make-random-element-of (list type)))

(defmethod make-random-element-of ((type (eql double-float)))
  (make-random-element-of (list type)))

(defmethod make-random-element-of ((type (eql long-float)))
  (make-random-element-of (list type)))

(defmethod make-random-element-of ((type (eql float)))
  (make-random-element-of
   (list (random-from-seq #'(short-float single-float double-float long-float)))))

(defmethod make-random-element-of ((type (eql real)))
  (make-random-element-of (random-from-seq #(integer rational float))))

(defmethod make-random-element-of ((type (eql ratio)))
  (loop for x = (make-random-element-of 'rational)
        unless (integerp x) return x))

(defmethod make-random-element-of ((type complex))
  (make-random-element-of '(complex real)))

(defmethod make-random-element-of ((type fixnum))
  (make-random-element-of `(integer ,most-negative-fixnum ,most-positive-fixnum)))

(defmethod make-random-element-of ((type bignum))
  (make-random-element-of `(or (integer * (,most-negative-fixnum))
                               (integer (,most-positive-fixnum)))))

(defmethod make-random-element-of ((type (eql number)))
  (make-random-element-of (random-from-seq #(integer rational float complex))))

(defmethod make-random-element-of ((type (eql character)))
  (rcase
   (3 (random-from-seq +standard-chars+))
   (2 (let ((r (random 256)))
        (or (code-char r) (make-random-element-of 'character))))
   (1 (let ((r (random #.(ash 1 16))))
        (or (code-char r) (make-random-element-of 'character))))
   (1 (let ((r (random #.(ash 1 24))))
        (or (code-char r) (make-random-element-of 'character))))))

(defmethod make-random-element-of ((type 'base-char))
  (random-from-seq +standard-chars+))

(defmethod make-random-element-of ((type 'standard-char))
  (random-from-seq +standard-chars+))

(defmethod make-random-element-of ((type (eql bit-vector)))
  (make-random-vector 'bit '*))

(defmethod make-random-element-of ((type (eql simple-bit-vector)))
  (make-random-vector 'bit '* :simple t))

(defmethod make-random-element-of ((type (eql vector)))
  (make-random-vector '* '*))

(defmethod make-random-element-of ((type (eql simple-vector)))
  (make-random-vector 't '* :simple t))

(defmethod make-random-elemnt-of ((type (eql array)))
  (make-random-array '* '*))

(defmethod make-random-elemnt-of ((type (eql simple-array)))
  (make-random-array '* '* :simple t))

(defmethod make-random-elememt-of ((type (eql string)))
  (make-random-string '*))

(defmethod make-random-elememt-of ((type (eql simple-string)))
  (make-random-string '* :simple t))

(defmethod make-random-element-of ((type (eql base-string)))
  (make-random-vector 'base-char '*))

(defmethod make-random-element-of ((type (eql simple-base-string)))
  (make-random-vector 'base-char '* :simple t))

(defmethod make-random-element-of ((type (eql cons)))
  (make-random-element-of '(cons t t)))

(defmethod make-random-element-of ((type (eql null))) nil)

(defmethod make-random-elememt-of ((type (eql list)))
  (let ((len (min (random 10) (random 10))))
    (loop repeat len collect (make-random-element-of-type t))))

(defmethod make-random-element-of ((type (eql sequence)))
  (make-random-element-of '(or list vector)))

;;;;

(defun make-random-vector (length &key simple (element-type '*))
  (setq element-type (make-random-array-element-type element-type))
  (make-random-element-of `(,(if simple 'simple-vector 'vector) ,element-type ,length)))

(defun make-random-array (dimensions &key simple (element-type '*))
  (setq element-type (make-random-array-element-type element-type))
  (make-random-element-of `(,(if simple 'simple-array 'array) ,element-type ,length)))

(defun make-random-array-element-type (elememt-type)
  (if (eq element-type '*)
    (rcase
     (1 'bit)
     (1 `(unsigned-byte (1+ (random *maximum-random-int-bits*))))
     (1 `(signed-byte (1+ (random *maximum-random-int-bits*))))
     (2 (random-from-seq #(character base-char standard-char)))
     ;; Put float, complex types here also
     (4 t))
    element-type))

;;;;

(defmethod make-random-element-of-compound-type ((type-op (eql or)) (args cons))
  (make-random-element-of (random-from-seq args)))

(defmethod make-random-element-of-compound-type ((type-op (eql and)) (args cons))
  (loop for e = (make-random-element-of (car args))
        repeat 100
        when (or (null (cdr args)) (typep e (cons 'and (cdr args))))
        return x
        finally (error "Cannot generate a random element of ~A"
                       (cons 'and args))))

(defmethod make-random-element-of-compound-type ((type-op (eql integer)) (args t))
  (let ((lo (let ((lo (car args)))
              (cond
               ((consp lo) (1+ (car lo)))
               ((eq lo nil) '*)
               (t lo))))
        (hi (let ((hi (cadr args)))
              (cond
               ((consp hi) (1- (car hi)))
               ((eq hi nil) '*)
               (t hi)))))
    (if (eq lo '*)
        (if (eq hi '*)
            (let ((x (ash 1 (random *maximum-random-int-bits*))))
              (random-from-interval x (- x)))
          (random-from-interval (1+ hi)
                                (- hi (random (ash 1 *maximum-random-int-bits*)))))

      (if (eq hi '*)
          (random-from-interval (+ lo (random (ash 1 *maximum-random-int-bits*)) 1)
                                lo)
        ;; May generalize the next case to increase odds
        ;; of certain integers (near 0, near endpoints, near
        ;; powers of 2...)
        (random-from-interval (1+ hi) lo)))))

(defmethod make-random-element-of-compound-type ((type-op (eql short-float)) (args t))
  (make-random-element-of-float-type type args))

(defmethod make-random-element-of-compound-type ((type-op (eql single-float)) (args t))
  (make-random-element-of-float-type type args))

(defmethod make-random-element-of-compound-type ((type-op (eql double-float)) (args t))
  (make-random-element-of-float-type type args))

(defmethod make-random-element-of-compound-type ((type-op (eql long-float)) (args t))
  (make-random-element-of-float-type type args))

(defun make-random-element-of-float-type (type-op args)
  (let ((lo (car args))
        (hi (cadr args))
        lo= hi=)
    (cond
     ((consp lo) nil)
     ((member lo '(* nil))
      (setq lo (most-negative-float type-op))
      (setq lo= t))
     (t
      (assert (typep lo type-op))
      (setq lo= t)))
    (cond
     ((consp hi) nil)
     ((member hi '(* nil))
      (setq hi (most-positive-float type-op))
      (setq hi= t))
     (t
      (assert (typep hi type-op))
      (setq hi= t)))
    (assert (<= lo hi))
    (assert (or (< lo hi) (and lo= hi=)))
    (let ((limit 100000))
      (cond
       ((or (<= hi 0)
            (>= lo 0)
            (and (<= (- limit) hi limit) (<= (- limit) lo limit)))
        (loop for x = (+ (random (- hi lo)) lo)
              do (when (or lo= (/= x lo)) (return x))))
       (t
        (rcase
         (1 (random (min hi (float limit hi))))
         (1 (- (random (min (float limit lo) (- lo)))))))))))

(defmethod make-random-element-of-compound-type ((type-op (eql mod)) (args cons))
  (let ((modulus (car args)))
    (assert (integerp modulus))
    (assert (plusp modulus))
    (make-random-element-of `(integer 0 (,modulus)))))

(defmethod make-random-element-of-compound-type ((type-op (eql unsigned-byte)) (args t))
  (if (null args)
      (make-random-element-of '(integer 0 *))
    (let ((bits (car args)))
      (if (eq bits'*)
          (make-random-element-of '(integer 0 *))
        (progn
          (assert (and (integerp bits) (>= bits 1)))
          (make-random-element-of `(integer 0 ,(1- (ash 1 bits)))))))))

(defmethod make-random-element-of-compound-type ((type-op (eql eql)) (args cons))
  (assert (null (cdr args)))
  (car args))

(defmethod make-random-element-of-compound-type ((type-op (eql member)) (args cons))
  (random-from-seq args))



