;; interface.scm -- hash-table interface
;; Copyright (c) 2009-2017 Alex Shinn.  All rights reserved.
;; BSD-style license: http://synthcode.com/license.txt

;; the non-exported hash-table-cell is the heart of the implemenation

(define (make-hash-table . o)
  (let* ((eq-fn (if (pair? o) (car o) equal?))
         (hash-fn (if (and (pair? o) (pair? (cdr o)))
                      (car (cdr o))
                      (if (eq? eq? eq-fn) hash-by-identity hash))))
    (cond
     ((not (procedure? eq-fn))
      (error "make-hash-table: bad equivalence function" eq-fn))
     ((not (procedure? hash-fn))
      (error "make-hash-table: bad hash function" hash-fn))
     (else
      (%make-hash-table
       (make-vector 23 '())
       0
       (if (eq? hash-fn hash-by-identity) 1 (if (eq? hash-fn hash) 2 hash-fn))
       (if (eq? eq-fn eq?) 1 (if (eq? eq-fn equal?) 2 eq-fn)))))))

(define (hash-table-hash-function table)
  (let ((f (%hash-table-hash-function table)))
    (case f ((1) hash-by-identity) ((2) hash) (else f))))

(define (hash-table-equivalence-function table)
  (let ((f (%hash-table-equivalence-function table)))
    (case f ((1) eq?) ((2) equal?) (else f))))

(define-syntax assert-hash-table
  (syntax-rules ()
    ((assert-hash-table from obj)
     (if (not (hash-table? obj))
         (error (string-append from ": not a Hash-Table") obj)))))

(define (hash-table-ref table key . o)
  (assert-hash-table "hash-table-ref" table)
  (let ((cell (hash-table-cell table key #f)))
    (cond (cell (if (and (pair? o) (pair? (cdr o)))
                    ((cadr o) (cdr cell))
                    (cdr cell)))
          ((pair? o) ((car o)))
          (else (error "hash-table-ref: key not found" key)))))

(define (hash-table-ref/default table key default)
  (assert-hash-table "hash-table-ref/default" table)
  (let ((cell (hash-table-cell table key #f)))
    (if cell (cdr cell) default)))

(define (hash-table-set! table key value)
  (assert-hash-table "hash-table-set!" table)
  (let ((cell (hash-table-cell table key #t)))
    (set-cdr! cell value)))

(define (hash-table-exists? table key)
  (assert-hash-table "hash-table-exists?" table)
  (and (hash-table-cell table key #f) #t))

(define hash-table-update!
  (let ((not-found (cons 'not-found '())))
    (lambda (table key func . o)
      (assert-hash-table "hash-table-update!" table)
      (let ((cell (hash-table-cell table key not-found)))
        (set-cdr! cell (if (eq? not-found (cdr cell))
                           (if (pair? o)
                               (func ((car o)))
                               (error "hash-table-update!: key not found" key))
                           (func (if (and (pair? o) (pair? (cdr o)))
                                     ((cadr o) (cdr cell))
                                     (cdr cell)))))))))

(define hash-table-update!/default
  (let ((not-found (cons 'not-found '())))
    (lambda (table key func default)
      (assert-hash-table "hash-table-update!/default" table)
      (let ((cell (hash-table-cell table key not-found)))
        (set-cdr! cell (func (if (eq? not-found (cdr cell)) default (cdr cell))))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (hash-table-fold table kons knil)
  (assert-hash-table "hash-table-fold" table)
  (let ((vec (hash-table-buckets table)))
    (let lp1 ((i (- (vector-length vec) 1)) (acc knil))
      (if (< i 0)
          acc
          (let lp2 ((ls (vector-ref vec i)) (acc acc))
            (if (null? ls)
                (lp1 (- i 1) acc)
                (lp2 (cdr ls) (kons (car (car ls)) (cdr (car ls)) acc))))))))

(define (hash-table-walk table proc)
  (hash-table-fold table (lambda (k v a) (proc k v)) #f)
  (if #f #f))

(define (hash-table->alist table)
  (hash-table-fold table (lambda (k v a) (cons (cons k v) a)) '()))

(define (hash-table-keys table)
  (hash-table-fold table (lambda (k v a) (cons k a)) '()))

(define (hash-table-values table)
  (hash-table-fold table (lambda (k v a) (cons v a)) '()))

(define (alist->hash-table ls . o)
  (let ((res (apply make-hash-table o)))
    (for-each (lambda (x) (hash-table-set! res (car x) (cdr x))) ls)
    res))

(define (hash-table-merge! a b)
  (hash-table-walk b (lambda (k v)
                       (if (not (hash-table-exists? a k))
                           (hash-table-set! a k v))))
  a)

(define (hash-table-copy table)
  (assert-hash-table "hash-table-copy" table)
  (let ((res (make-hash-table (hash-table-equivalence-function table)
                              (hash-table-hash-function table))))
    (hash-table-merge! res table)
    res))
