#lang racket/base

(require racket/bool racket/contract)

(provide poly/c parametric/c opaque/c memory/c)

(define-syntax-rule (poly/c [x ...] c)
  (make-polymorphic-contract 'poly/c
                             memory/c
                             '(x ...)
                             (lambda (x ...) c)))

(define-syntax-rule (parametric/c [x ...] c)
  (make-polymorphic-contract 'parametric/c
                             opaque/c
                             '(x ...)
                             (lambda (x ...) c)))

(define-struct polymorphic-contract [title barrier vars body]
  #:property prop:contract
  (build-contract-property
   #:name
   (lambda (c)
     (list (polymorphic-contract-title c)
           (polymorphic-contract-vars c)
           '...))
   #:projection
   (lambda (c)
     (lambda (b)

       (define (wrap p)
         ;; values in polymorphic types come in from negative position,
         ;; relative to the poly/c contract
         (define negative? (blame-swapped? b))
         (define barrier/c (polymorphic-contract-barrier c))
         (define instances
           (for/list ([var (in-list (polymorphic-contract-vars c))])
             (barrier/c negative? var)))
         (define protector
           (apply (polymorphic-contract-body c) instances))
         (((contract-projection protector) b) p))

       (lambda (p)
         (unless (procedure? p)
           (raise-blame-error b p "expected a procedure; got: ~e" p))
         (make-keyword-procedure
          (lambda (keys vals . args) (keyword-apply (wrap p) keys vals args))
          (case-lambda
            [() ((wrap p))]
            [(a) ((wrap p) a)]
            [(a b) ((wrap p) a b)]
            [(a b c) ((wrap p) a b c)]
            [(a b c d) ((wrap p) a b c d)]
            [(a b c d e) ((wrap p) a b c d e)]
            [(a b c d e f) ((wrap p) a b c d e f)]
            [(a b c d e f g) ((wrap p) a b c d e f g)]
            [(a b c d e f g h) ((wrap p) a b c d e f g h)]
            [args (apply (wrap p) args)])))))))

(define (memory/c positive? name)
  (define memory (make-weak-hasheq))
  (define (make x) (hash-set! memory x #t) x)
  (define (pred x) (hash-has-key? memory x))
  (define (get x) x)
  (make-barrier-contract name positive? make pred get))

(define (opaque/c positive? name)
  (define-values [ type make pred getter setter ]
    (make-struct-type name #f 1 0))
  (define (get x) (getter x 0))
  (make-barrier-contract name positive? make pred get))

(define-struct barrier-contract [name positive? make pred get]
  #:property prop:contract
  (build-contract-property
   #:name (lambda (c) (barrier-contract-name c))
   #:projection
   (lambda (c)
     (lambda (b)
       (if (boolean=? (blame-original? b) (barrier-contract-positive? c))
         (lambda (x)
           ((barrier-contract-make c) x))
         (lambda (x)
           (if ((barrier-contract-pred c) x)
             ((barrier-contract-get c) x)
             (raise-blame-error b x "expected a(n) ~a; got: ~e"
                                (barrier-contract-name c) x))))))))
