#lang typed/racket #:optimize

(provide head queue)


(define-type (Promiseof A) (Boxof (U (→ (Listof A)) (Listof A))))

;; Physicists Queue
;; Maintains invariant lenr <= lenf
;; pref is empty only if lenf = 0
(struct: (A) Queue ([preF  : (Listof A)]
                    [front : (Promiseof A)]
                    [lenf  : Integer]
                    [rear  : (Listof A)]
                    [lenr  : Integer]))

(define-syntax-rule (force promise)
  (let ([p (unbox promise)])
    (if (procedure? p)
        (let ([result (p)])
          (set-box! promise result)
          result)
        ; (error 'force "not a promise ~s" promise)
        p)))

;; Maintains "preF" invariant (preF in not not null when front is not null)
(: check-pref-inv : 
   (All (A) ((Listof A) (Promiseof A) Integer (Listof A) Integer ->
                        (Queue A))))
(define (check-pref-inv pref front lenf rear lenr)
  (if (null? pref)
      ((inst Queue A) (force front) front lenf rear lenr)
      (Queue pref front lenf rear lenr)))


;; Maintains lenr <= lenf invariant
(: check-len-inv : 
   (All (A) ((Listof A) (Promiseof A) Integer (Listof A) Integer -> (Queue A))))
(define (check-len-inv pref front lenf rear lenr)
  (if (>= lenf lenr)
      (check-pref-inv pref front lenf rear lenr)
      (let*: ([newpref : (Listof A) (force front)]
              [newf : (Promiseof A)
               (box (lambda () (append newpref (reverse rear))))])
        ((inst check-pref-inv A) newpref newf 
                                 (+ lenf lenr) null 0))))

;; Maintains queue invariants
(: internal-queue : 
   (All (A) ((Listof A) (Promiseof A) Integer (Listof A) Integer -> (Queue A))))
(define (internal-queue pref front lenf rear lenr)
  (check-len-inv pref front lenf rear lenr))

;; Enqueues an item into the list
(: enqueue : (All (A) (A (Queue A) -> (Queue A))))
(define (enqueue item que)
  (internal-queue (Queue-preF que)
                  (Queue-front que)
                  (Queue-lenf que)
                  (cons item (Queue-rear que))
                  (add1 (Queue-lenr que))))


;; Returns the first element in the queue if non empty. Else raises an error
(: head : (All (A) ((Queue A) -> A)))
(define (head que)
  (if (zero? (Queue-lenf que))
      (error 'head "given queue is empty")
      (car (Queue-preF que))))


;; Queue constructor function
(: queue : (All (A) (A * -> (Queue A))))
(define (queue . items)
  (foldl (inst enqueue A)
         ((inst Queue A) '() (box (lambda: () '())) 0 '() 0) items))
