(use goo)
(use goo/cols/rep)
(use goo/cols/seq)
(use goo/cols/col)
(use goo/math)
(use time/lock)
(use time/event)

(dc <pipe> (<flat> <seq!>))
  (dp pipe-data (<pipe> => <rep>))
  (dp pipe-get-idx (<pipe> => <int>) 0)
  (dp pipe-put-idx (<pipe> => <int>) 0)
  (dp pipe-lock (<pipe> => <lock>))
  (dp pipe-available (<pipe> => <event>))

(dm fab (c|(t= <pipe>) s|<int> => <pipe>)
  (def dat (rep-fab (power-of-two-ceil s) #f))
  (new <pipe> pipe-data dat
       pipe-lock (new <lock>) pipe-available (new <event>)))

(df @len (c|<pipe> => <int>) 
  (mod (- (pipe-put-idx c) (pipe-get-idx c)) (rep-len (pipe-data c)))))

(dm len (c|<pipe> => <int>) (with-lock (pipe-lock c) (@len c)))

(dm grow-pipe! (c|<pipe>)
  (def odat (pipe-data c))
  (def olen (rep-len odat))
  (def ndat (rep-fab (max 2 (power-of-two-ceil (+ (rep-len odat) 1))) #f))
  (rep loop ((i (pipe-get-idx c)) (j 0))
    (unless (= i (pipe-put-idx c))
      (set (rep-elt ndat j) (rep-elt odat i))
      (loop (mod (+ i 1) olen) (+ j 1))))
  (set (pipe-data c) ndat)
  (set (pipe-put-idx c) (@len c))
  (set (pipe-get-idx c) 0))

(dm add! (c|<pipe> x => <pipe>)
  (with-lock (pipe-lock c)
    (def len (@len c))         
    (when (>= (+ len 1) (rep-len (pipe-data c)))
      (grow-pipe! c))
    (when (= len 0)
      (event-signal (pipe-available c) (pipe-lock c)))
    (set (rep-elt (pipe-data c) (pipe-put-idx c)) x)
    (modincf (pipe-put-idx c) (rep-len (pipe-data c)))
    c))

(dm enq! (c|<pipe> x => <pipe>)
  (add! c x))

(dm deq! (c|<pipe> => <any>)
  (with-lock (pipe-lock c)
    (when (= (@len c) 0)
      (event-wait (pipe-available c) (pipe-lock c)))
    (def res (rep-elt (pipe-data c) (pipe-get-idx c)))
    (modincf (pipe-get-idx c) (rep-len (pipe-data c)))
    res))

(dm zap! (c|<pipe> => <pipe>) 
  (with-lock (pipe-lock c)
    (set (pipe-get-idx c) 0)
    (set (pipe-put-idx c) 0)
    c))

(df pipe-idx (x|<pipe> i|<int> => <int>)
  (with-lock (pipe-lock x)
    (mod (+ i (pipe-get-idx x)) (rep-len (pipe-data x)))))

(dm low-elt (x|<pipe> i|<int> => <any>)
  (rep-elt (pipe-data x) (pipe-idx x i)))

(dm low-elt-setter (z x|<pipe> i|<int>)
  (set (rep-elt (pipe-data x) (pipe-idx x i)) z))

(dm elt-or (c|<pipe> i|<int> default => <any>)
  (if (range-check? c i)
      (low-elt c i)
      default))

(export <pipe> enq! deq!)
