#lang racket/base

(require "contract.rkt")
(provide spawn/name
         receive
         (contract-out*
          [! (tid? any/c . -> . void)]
          [self (-> tid?)]))


(require racket/bool
         racket/match
         "match.rkt"
         #;"sema-mailbox.rkt"
         "mailbox.rkt")

(define-struct tid (lid) #:prefab)
(define (create-tid thr) (make-tid thr))

; We need a mapping from Racket's tids to our tids (just for `self')
; and a mapping from symbols to mailboxes (for local threads).

(define tids (make-weak-hash))
(define mailboxes (make-hash))

(define (do-receive timeout timeout-thunk matcher)
  (define mb (hash-ref mailboxes (tid-lid (self))))
  (define val-thunk (mailbox-receive mb timeout timeout-thunk matcher))
  (val-thunk))

(define-syntax receive
  (syntax-rules (after)
    [(_ (after timeout to-expr ...) (pat expr ...) ...)
     (do-receive 
      timeout 
      (lambda () to-expr ...)
      (match-lambda
        (pat (lambda () expr ...)) ...
        [_ match-fail]))]
    [(_ clause ...) (receive (after false (void)) clause ...)]))

; must ensure name not already taken
(define (spawn/name-help thunk name)
  (if (hash-ref mailboxes name (lambda () #f))
      #f
      (let ([new-tid (create-tid name)]
            [parent-tid (self)])
        (thread
         (lambda ()
           (hash-set! tids (current-thread) new-tid)
           (hash-set! mailboxes name (new-mailbox))
           (! parent-tid new-tid)
           (thunk)))
        (receive [(? (lambda (m) (equal? m new-tid))) new-tid]))))

(define next-thread
  (let ([last-thread 1]
        [lock (make-semaphore 1)])
    (lambda ()
      (call-with-semaphore
       lock
       (lambda ()
         (begin0
           last-thread
           (set! last-thread (add1 last-thread))))))))
(define (next-thread-name)
  (string->symbol
   (string-append "thread" (number->string (next-thread)))))

(define-syntax spawn/name
  (syntax-rules ()
    [(_ name expr ...)
     (spawn/name-help
      (lambda () expr ...)
      name)]))

(define (! tid msg)
  (define mb (hash-ref mailboxes (tid-lid tid) (lambda () false)))
  (when mb
    (send-msg mb msg)))

(define (send-msg mbox msg)
  (mailbox-send! mbox msg))

(define (self)
  (hash-ref! 
   tids (current-thread)
   ; allows thread not created by spawn to receive messages
   (lambda ()
     (define name (next-thread-name))
     (define new-tid (create-tid name))
     (hash-set! mailboxes name (new-mailbox))
     new-tid)))
