(module btree mzscheme
  (require "sig.ss"
           (lib "unitsig.ss"))
  
  (provide btree@)
  
;; Implements a red-black tree with relative indexing along right
;; splines. This allows the usual O(log(n)) operations, plus a
;; O(log(n)) shift operation.
  
;; (This is the same data structure as used for lines by MrEd's text%
;; class, but that one is implemented in C++.)
  (define btree@
    (unit/sig relative-btree^
      (import)
      (rename (create-btree make-btree))
      
      (define-struct btree (root))
      
      (define-struct node (pos data parent left right color))
      
      (define (adjust-offsets n new-child)
        (when new-child
          (set-node-pos! new-child (- (node-pos new-child)
                                      (node-pos n)))))
      
      (define (deadjust-offsets n old-child)
        (when old-child
          (set-node-pos! old-child (+ (node-pos old-child)
                                      (node-pos n)))))
      
      (define (rotate-left n btree)
        (let ([old-right (node-right n)])
          (deadjust-offsets n old-right)
          
          (let ([r (node-left old-right)])
            (set-node-right! n r)
            (when r
              (set-node-parent! r n)))
          
          (let ([p (node-parent n)])
            (set-node-parent! old-right p)
            (cond
              [(not p) (set-btree-root! btree old-right)]
              [(eq? n (node-left p)) (set-node-left! p old-right)]
              [else                 (set-node-right! p old-right)]))
          
          (set-node-left! old-right n)
          (set-node-parent! n old-right)))
      
      (define (rotate-right n btree)
        (let ([old-left (node-left n)])
          (adjust-offsets old-left n)
          
          (let ([l (node-right old-left)])
            (set-node-left! n l)
            (when l
              (set-node-parent! l n)))
          
          (let ([p (node-parent n)])
            (set-node-parent! old-left p)
            (cond
              [(not p) (set-btree-root! btree old-left)]
              [(eq? n (node-left p)) (set-node-left! p old-left)]
              [else                 (set-node-right! p old-left)]))
          
          (set-node-right! old-left n)
          (set-node-parent! n old-left)))
      
      
      (define (insert before? n btree pos data)
        (let ([new (make-node pos data #f #f #f 'black)])
          (if (not (btree-root btree))
              (set-btree-root! btree new)
              
              (begin
                
                (set-node-color! new 'red)
                
                ; Insert into tree
                (if before?
                    
                    (if (not (node-left n))
                        (begin
                          (set-node-left! n new)
                          (set-node-parent! new n))
                        
                        (let loop ([node (node-left n)])
                          (if (node-right node)
                              (loop (node-right node))
                              (begin
                                (set-node-right! node new)
                                (set-node-parent! new node)))))
                    
                    (if (not (node-right n))
                        (begin
                          (set-node-right! n new)
                          (set-node-parent! new n))
                        
                        (let loop ([node (node-right n)])
                          (if (node-left node)
                              (loop (node-left node))
                              (begin
                                (set-node-left! node new)
                                (set-node-parent! new node))))))
                
                ; Make value in new node relative to right-hand parents
                (let loop ([node new])
                  (let ([p (node-parent node)])
                    (when p
                      (when (eq? node (node-right p))
                        (adjust-offsets p new))
                      (loop p))))
                
                ; Balance tree
                (let loop ([node new])
                  (let ([p (node-parent node)])
                    (when (and (not (eq? node (btree-root btree)))
                               (eq? 'red (node-color p)))
                      (let* ([recolor-k
                              (lambda (y)
                                (set-node-color! p 'black)
                                (set-node-color! y 'black)
                                (let ([pp (node-parent p)])
                                  (set-node-color! pp 'red)
                                  (loop pp)))]
                             [rotate-k
                              (lambda (rotate node)
                                (let ([p (node-parent node)])
                                  (set-node-color! p 'black)
                                  (let ([pp (node-parent p)])
                                    (set-node-color! pp 'red)
                                    (rotate pp btree)
                                    (loop pp))))]
                             [k
                              (lambda (node-y long-rotate always-rotate)
                                (let ([y (node-y (node-parent p))])
                                  (if (and y (eq? 'red (node-color y)))
                                      (recolor-k y)
                                      (let ([k (lambda (node)
                                                 (rotate-k always-rotate node))])
                                        (if (eq? node (node-y p))
                                            (begin
                                              (long-rotate p btree)
                                              (k p))
                                            (k node))))))])
                        (if (eq? p (node-left (node-parent p)))
                            (k node-right rotate-left rotate-right)
                            (k node-left rotate-right rotate-left))))))
                
                (set-node-color! (btree-root btree) 'black)))))
      
      (define (find-following-node btree pos)
        (let ([root (btree-root btree)])
          (let loop ([n root]
                     [so-far root]
                     [so-far-pos (and root (node-pos root))]
                     [v 0])
            (if (not n) 
                (values so-far so-far-pos)
                (let ([npos (+ (node-pos n) v)])
                  (cond
                    [(<= pos npos)
                     (loop (node-left n) n npos v)]
                    [(or (not so-far-pos)
                         (> npos so-far-pos))
                     (loop (node-right n) n npos npos)]
                    [else
                     (loop (node-right n) so-far so-far-pos npos)]))))))
      
      (define (create-btree)
        (make-btree #f))
      
      (define (btree-get btree pos)
        (let-values ([(n npos) (find-following-node btree pos)])
          (and n
               (= npos pos)
               (node-data n))))
      
      (define (btree-put! btree pos data)
        (let-values ([(n npos) (find-following-node btree pos)])
          (if (and n (= npos pos))
              (set-node-data! n data)
              (insert (and n (< pos npos))
                      n btree pos data))))
      
      (define (btree-shift! btree start delta)
        (let loop ([n (btree-root btree)]
                   [v 0])
          (when n
            (let ([npos (node-pos n)])
              (cond
                [(< start (+ v npos))
                 (set-node-pos! n (+ npos delta))
                 (loop (node-left n) v)]
                [else
                 (loop (node-right n) (+ v npos))])))))
      
      (define (btree-for-each btree f)
        (when (btree-root btree)
          (let loop ([n (btree-root btree)]
                     [v 0])
            (when (node-left n)
              (loop (node-left n) v))
            (f (+ v (node-pos n)) (node-data n))
            (when (node-right n)
              (loop (node-right n)
                    (+ v (node-pos n)))))))
      
      (define (btree-map btree f)
        (let ([start (cons #f null)])
          (let loop ([n (btree-root btree)]
                     [l start]
                     [v 0])
            (if (not n)
                l
                (let ([pre (loop (node-left n) l v)]
                      [here (cons (f (+ v (node-pos n))
                                     (node-data n))
                                  null)])
                  (set-cdr! pre here)
                  (loop (node-right n)
                        here
                        (+ v (node-pos n))))))
          (cdr start))))))
