;;; -*- Mode:LISP; Package:LISP-INTERNALS; Base:10; Readtable:CL -*-

;;; Following COMMON-LISP functions are defined in this file:
;	  CLRHASH
;	  GETHASH
;	  HASH-TABLE-P
;	  MAKE-HASH-TABLE
;	  MAPHASH
;	  REMHASH
;         SXHASH

	  
;;; HASH TABLE STRUCTURE 
;;;     $$DTP-HASH-TABLE-HEADER (with total number of Q's used by hash table)
;;;     test-function (0-2 for eq, eql, equal respectively)
;;;     rehash-size   (fixnum amount to grow by)
;;;     rehash-threshold (fixnum count of rehash threshold)
;;;     current-used   (fixnum number of currently used slots)
;;;     data-ptr       (locative to first element of simple vector holding the data)

;;;  Simple vector, 3 times size of hash table
;;;     sxhash-key ...... 
;;;     original-key ....
;;;     data ............
;;;
;;; Pointed to by $$DTP-HASH-TABLE
;;;

;-------------------------------------------------------------------------------
; Useful constants for hash table code

(defconstant hash-table-header-size 7)
(defconstant hash-table-max-size    #.(- (lisp:truncate (lisp:expt 2 23.) 3) hash-table-header-size 1))

;-------------------------------------------------------------------------------
; Hash table accessors

(defsubst %hash-table-size (ht)
  (cons:contents-offset ht 1))

(defsubst %hash-table-size-set (ht data)
  (cons:store-contents-offset ht 1 data))

(defsetf %hash-table-size %hash-table-size-set)

(defsubst %hash-table-use-count (ht)
  (cons:contents-offset ht 2))

(defsubst %hash-table-use-count-set (ht data)
  (cons:store-contents-offset ht 2 data))

(defsetf %hash-table-use-count %hash-table-use-count-set)

(defsubst %hash-table-test (ht)
  (cons:contents-offset ht 3))

(defsubst %hash-table-test-set (ht data)
  (cons:store-contents-offset ht 3 data))

(defsetf %hash-table-test %hash-table-test-set)

(defsubst %hash-table-rehash-size (ht)
  (cons:contents-offset ht 4))

(defsubst %hash-table-rehash-size-set (ht data)
  (cons:store-contents-offset ht 4 data))

(defsetf %hash-table-rehash-size %hash-table-rehash-size-set)

(defsubst %hash-table-rehash-threshold (ht)
  (cons:contents-offset ht 5))

(defsubst %hash-table-rehash-threshold-set (ht data)
  (cons:store-contents-offset ht 5 data))

(defsetf %hash-table-rehash-threshold %hash-table-rehash-threshold-set)

(defsubst %hash-table-data-ptr (ht)
  (cons:contents-offset ht 6))

(defsubst %hash-table-data-ptr-set (ht data)
  (cons:store-contents-offset ht 6 data))

(defsetf %hash-table-data-ptr %hash-table-data-ptr-set)


;-------------------------------------------------------------------------------

(defun MAKE-HASH-TABLE (&key
			(test 'eql)
			(size 23.)
			(rehash-size nil rehash-size-p)
			(rehash-threshold nil rehash-threshold-p))
  (when (or (not (vinc:fixnump size)) (<= size 0) (>= size hash-table-max-size))
    (error "Bad size to make-hash-table" size))
  (setq test (cond
	       ((eq test 'eq)      0)
	       ((eq test 'eql)     1)
	       ((eq test 'equal)   2)
	       ((eq test #'eq)     0)
	       ((eq test #'eql)    1)
	       ((eq test #'equal)  2)
	       (t (error "Bad test to make-hash-table" test))))
  (if rehash-size-p
       (unless (or (and (vinc:fixnump rehash-size) (> rehash-size 0))
		   (and (floatp rehash-size) (> rehash-size 1)))
	 (error "Bad rehash-size to make-hash-table" rehash-size))
    (setq rehash-size size))
  (if rehash-threshold-p
      (cond ((and (vinc:fixnump rehash-threshold) (> rehash-threshold 0) (< rehash-threshold size)) t)
	    ((and (floatp rehash-threshold) (> rehash-threshold 0) (< rehash-threshold 1))
	     (setq rehash-threshold (truncate (* rehash-threshold size))))
	    (t
	     (error "Bad rehash-threshold to mask-hash-table" rehash-threshold)))
    (setq rehash-threshold (ash (+ size size size) -2))) ;default threshold = 3/4
  (let* ((ht (cons:allocate-structure
	       hash-table-header-size
	       0
	       vinc:$$dtp-hash-table
	       (hw:dpb-unboxed vinc:$$dtp-hash-table-header vinc:%%data-type hash-table-header-size))))
    (setf (%hash-table-size ht)             size)
    (setf (%hash-table-use-count ht)        0)
    (setf (%hash-table-test ht)             test)
    (setf (%hash-table-rehash-size ht)      rehash-size)
    (setf (%hash-table-rehash-threshold ht) rehash-threshold)
    (setf (%hash-table-data-ptr ht)         (hw:24+ 1 (cons:make-pointer vinc:$$dtp-locative
									 (array:make-vector (+ size size size)))))
    ht))

;-------------------------------------------------------------------------------
    
(defsubst HASH-TABLE-P (object)
  (vinc:type-test object vinc:$$dtp-hash-table))

;-------------------------------------------------------------------------------

(defsubst %sxhash-raw-32 (raw-32 key)
  (hw:32+ raw-32
	  (hw:32-rotate-up
	    (hw:dpb-xor raw-32 (byte 16. -16.) key)
	    1)))

(defsubst %sxhash-nil (key)
  (let ((magic-number (hw:unboxed-constant #X87654321)))
    (%sxhash-raw-32 magic-number key)))

(defsubst %sxhash-fixnum (fixnum key)
  (%sxhash-raw-32 fixnum key))

(defun %sxhash-string (string key)
  (let ((len (length string)))
    (dotimes (i len key)
      (setq key (%sxhash-raw-32 (svref string i) key)))))

(defun %sxhash-bignum (bignum key)
  (let ((len (hw:ldb (array:%vm-read32 bignum 0) vinc:%%fixnum-field 0)))
      (dotimes (i len key)
	(setq key (%sxhash-raw-32 (array:%vm-read32 bignum (1+ i)) key)))))

(defun %sxhash-single-float (float key)
  (%sxhash-raw-32 (array:%vm-read32 float 1) key))

(defsubst %sxhash-double-float (float key)
  (%sxhash-raw-32 (array:%vm-read32 float 1) (%sxhash-raw-32 (array:%vm-read32 float 2) key)))

(defun %sxhash-array (array key)
  (cond ((vectorp array)
	 (let ((len (length array)))
	   (dotimes (i len)
	     (setq key (%sxhash-1 (svref array i) key)))))
	(t (%sxhash-raw-32 (array:%vm-read32 array -1) key))))

(defsubst %sxhash-structure (struct key)
  (%sxhash-raw-32 (array:%vm-read32 struct 0) (%sxhash-1 (cons:contents-offset struct 1) key)))

(defun %sxhash-1 (object key)
  (dispatch vinc:%%data-type object
     (vinc:$$dtp-nil            (%sxhash-nil key))
     (vinc:$$dtp-symbol 	(%sxhash-string (symbol-name object) key))
     (vinc:$$dtp-fixnum		(%sxhash-fixnum object key))
     (vinc:$$dtp-bignum 	(%sxhash-bignum object key))
     (vinc:$$dtp-rational	(%sxhash-1 (nm:numerator object) (%sxhash-1 (nm:denominator object) key)))
     (vinc:$$dtp-complex	(%sxhash-1 (nm:realpart  object) (%sxhash-1 (nm:imagpart    object) key)))
     (vinc:$$dtp-short-float	(%sxhash-raw-32 object key))
     (vinc:$$dtp-single-float	(%sxhash-single-float object key))
     (vinc:$$dtp-double-float	(%sxhash-double-float object key))
     (vinc:$$dtp-cons		(%sxhash-1 (car object) (%sxhash-1 (cdr object) key)))
     (vinc:$$dtp-compiled-function (%sxhash-1 (k2:%compiled-function-name object) key)) ;this doesn't work- fix it someday
     (vinc:$$dtp-array		(%sxhash-array object key))
     (vinc:$$dtp-structure	(%sxhash-structure object key))
     (vinc:$$dtp-character      (%sxhash-raw-32 object key))
     (t (error "I can't SXHASH that yet!" object))))

(defun SXHASH (object)
  (hw:ldb (%sxhash-1 object (hw:unboxed-constant #x89abcdef))
	  (byte 23. 0)
	  0))

;-------------------------------------------------------------------------------

(defun %lochash (key hash-table)
  (if (not (hash-table-p hash-table))
      (error "Not a hash-table to %lochash" hash-table)
    (let* ((size (%hash-table-size hash-table))
	   (sxkey (sxhash key))
	   (msxkey (rem (sxhash key) size))
	   (ptr (hw:24+ msxkey (%hash-table-data-ptr hash-table))))
      (values
	ptr
	sxkey
	size))))

(defun %inc-ptr (ptr hash-table)
  (setq ptr (hw:24+ 1 ptr))
  (let* ((size       (%hash-table-size hash-table))
	 (sxkey-base (%hash-table-data-ptr hash-table)))
    (if (hw:32< ptr (hw:24+ size sxkey-base))
	ptr
      sxkey-base)))

(defun %cmphash (key hkey cmp)
  (dispatch (byte 2 0) cmp
     (0 (eq key hkey))
     (1 (eql key hkey))
     (2 (equal key hkey))
     (3 (error "Bad compare code to %cmphash" cmp))))
;-------------------------------------------------------------------------------

(defun GETHASH (key hash-table &optional default)
  (multiple-value-bind (ptr sxkey size) (%lochash key hash-table)
    (let ((test (%hash-table-test hash-table)))
      (do () (())
	(let ((hsxkey (cons:contents-offset ptr 0)))
	  (cond
	    ((and (eql sxkey hsxkey)
		  (%cmphash key (cons:contents-offset ptr size) test))
	     (return-from gethash (values (cons:contents-offset ptr (ash size 1)) t)))
	    ((null hsxkey)
	     (return-from gethash (values default nil)))
	    (t
	     (setq ptr (%inc-ptr ptr hash-table)))))))))

(defun %sethash (key hash-table data)
  (multiple-value-bind (ptr sxkey size) (%lochash key hash-table)
    (let* ((test  (%hash-table-test hash-table)))
      (do () (())
	(let ((hsxkey (cons:contents-offset ptr 0)))
	  (cond
	    ((and (eql sxkey hsxkey)
		  (%cmphash key (cons:contents-offset ptr size) test))
	     (cons:store-contents-offset ptr size key)
	     (cons:store-contents-offset ptr (ash size 1) data)
	     (return-from %sethash (values data t)))
	    ((null hsxkey)
	     (let ((count (1+ (%hash-table-use-count hash-table)))
		   (limit (%hash-table-rehash-threshold hash-table)))
	       (if (>= count limit)
		   (return-from %sethash (%sethash key (%rehash hash-table) data))
		 (progn
		   (setf (%hash-table-use-count hash-table) count)
		   (cons:store-contents-offset ptr 0 sxkey)
		   (cons:store-contents-offset ptr size key)
		   (cons:store-contents-offset ptr (ash size 1) data)
		   (return-from %sethash (values data nil))))))
	    (t
	     (setq ptr (%inc-ptr ptr hash-table)))))))))

(defsetf gethash  (key hash-table &optional default) (value)
  `(%sethash ,key ,hash-table ,value))

;-------------------------------------------------------------------------------

(defun REMHASH (key hash-table)
  (multiple-value-bind (ptr sxkey size) (%lochash key hash-table)
    (let* ((test  (%hash-table-test hash-table)))
      (do () (())
	(let ((hsxkey (cons:contents-offset ptr 0)))
	  (cond
	    ((and (eql sxkey hsxkey)
		  (%cmphash key (cons:contents-offset ptr size) test))
	     (decf (%hash-table-use-count hash-table))
	     (cons:store-contents-offset ptr 0 nil)
	     (cons:store-contents-offset ptr size nil)
	     (cons:store-contents-offset ptr (ash size 1) nil)
	     (return-from remhash t))
	    ((null hsxkey)
	     (return-from remhash nil))
	    (t
	     (setq ptr (%inc-ptr ptr hash-table)))))))))

;-------------------------------------------------------------------------------

(defun MAPHASH (function hash-table)
  (if (not (hash-table-p hash-table))
      (error "Not a hash-table to maphash" hash-table)
    (let* ((size (%hash-table-size hash-table))
	   (ptr  (%hash-table-data-ptr hash-table)))
      (dotimes (i size)
	(let ((hsxkey  (cons:contents-offset ptr 0)))
	  (when hsxkey
	    (funcall function
		     (cons:contents-offset ptr size)
		     (cons:contents-offset ptr (ash size 1)))))
	(setq ptr (hw:24+ 1 ptr)))))
  nil)

;-------------------------------------------------------------------------------

(defun CLRHASH (function hash-table)
  (if (not (hash-table-p hash-table))
      (error "Not a hash-table to maphash" hash-table)
    (let* ((size (%hash-table-size hash-table))
	   (ptr  (%hash-table-data-ptr hash-table)))
      (dotimes (i size)
	(cons:store-contents-offset ptr 0 nil)
	(cons:store-contents-offset ptr size nil)
	(cons:store-contents-offset ptr (ash size 1) nil)
	(setq ptr (hw:24+ 1 ptr)))))
  nil)
		      
;-------------------------------------------------------------------------------

(defun HASH-TABLE-COUNT (hash-table)
  (if (not (hash-table-p hash-table))
      (error "Not a hash-table to hash-table-count" hash-table)
    (%hash-table-use-count hash-table)))

;-------------------------------------------------------------------------------

(defun %rehash (old-hash-table)
  (let* ((size             (%hash-table-size old-hash-table))
	 (test             (dispatch (byte 2 0) (%hash-table-test old-hash-table)
				     (0 'eq)
				     (1 'eql)
				     (2 'equal)))
	 (rehash-size      (%hash-table-rehash-size old-hash-table))
	 (rehash-threshold (%hash-table-rehash-threshold old-hash-table))
	 (percent-grow     (if (vinc:fixnump rehash-size)
			       (/ (+ rehash-size size) size)
			     (rehash-size)))
	 (new-hash-table (make-hash-table
			   :test 	     test
			   :size 	     (truncate (* percent-grow size))
			   :rehash-size      (truncate (* percent-grow rehash-size)))
			   :rehash-threshold (if (vinc:fixnump rehash-size)
						 (truncate (* percent-grow rehash-threshold))
					       rehash-threshold))
	 (ptr (%hash-table-data-ptr old-hash-table)))
    (dotimes (i size)
      (when (cons:contents-offset ptr 0)
	(let ((hkey  (cons:contents-offset ptr size))
	      (hdata (cons:contents-offset ptr (ash size 1))))
	  (setf (gethash hkey new-hash-table) hdata)))
      (setq ptr (hw:24+ 1 ptr)))
    (setf (%hash-table-size old-hash-table)             (%hash-table-size new-hash-table))
    (setf (%hash-table-test old-hash-table)             (%hash-table-test new-hash-table))
    (setf (%hash-table-rehash-size old-hash-table)      (%hash-table-rehash-size new-hash-table))
    (setf (%hash-table-rehash-threshold old-hash-table) (%hash-table-rehash-threshold new-hash-table))
    (setf (%hash-table-data-ptr old-hash-table)         (%hash-table-data-ptr new-hash-table))
    old-hash-table))
