#lang racket/base
(require ffi/unsafe
         "atomic.rkt")

(provide allocator deallocator retainer 
         (rename-out [deallocator releaser]))

(define allocated (make-late-weak-hasheq))

(define (deallocate v)
  ;; Called as a finalizer, we we assume that the
  ;; enclosing thread will not be interrupted.
  (let ([ds (hash-ref allocated v #f)])
    (when ds
      (hash-remove! allocated v)
      (for ([d (in-list ds)])
        (d v)))))

(define ((allocator d) proc)
  (rename
   (lambda args
     (dynamic-wind
         start-atomic
         (lambda ()
           (let ([v (apply proc args)])
             (hash-set! allocated v (list d))
             (register-finalizer v deallocate)
             v))
         end-atomic))
   proc))

(define ((deallocator [get-arg car]) proc)
  (rename
   (lambda args
     (dynamic-wind
         start-atomic
         (lambda ()
           (apply proc args)
           (let ([v (get-arg args)])
             (let ([ds (hash-ref allocated v #f)])
               (when ds
                 (if (null? (cdr ds))
                     (hash-remove! allocated v)
                     (hash-set! allocated v (cdr ds)))))))
         end-atomic))
   proc))

(define ((retainer d [get-arg car]) proc)
  (rename
   (lambda args
     (dynamic-wind
         start-atomic
         (lambda ()
           (apply proc args)
           (let ([v (get-arg args)])
             (let ([ds (hash-ref allocated v null)])
               (hash-set! allocated v (cons d ds)))))
         end-atomic))
   proc))

(define (rename new orig)
  (and orig
       (let ([n (object-name orig)]
             [new (procedure-reduce-arity
                   new
                   (procedure-arity orig))])
         (if n
             (procedure-rename new n)
             new))))
