(module sharing mzscheme

  (require (lib "")
           (lib "")
           (lib "")
           (lib "")
           (lib "" "scheme")
           (only (lib "" "srfi") partition)

  ;; A Sharing is (make-sharing order groups)
  ;; order : (IDMap Number) defines an ordering for shared identifiers;
  ;;  external identifiers are mapped to -1.
  ;; groups : (Listof (Listof Identifier)) defines the equivalence classes.
  (define-struct sharing (order groups))

   [sharing? (-> any/c boolean?)]
   [sharing-empty? (-> sharing? boolean?)]
   [empty-sharing (-> (listof identifier?) sharing?)]
    (-> (listof (listof identifier?)) boolean? sharing? sharing?)]
   [sharing-representative? (-> sharing? identifier? boolean?)]
   [sharing-representative (-> sharing? identifier? identifier?)]
   [sharing-remove (-> sharing? (listof identifier?) sharing?)]
   [sharing->renaming (-> sharing? idmap?)]
   [sharing-subset? (-> sharing? sharing? boolean?)]
   [sharing-union (-> sharing? sharing? sharing?)]
   [sharing-retag (-> (alistof identifier? identifier?) sharing? sharing?)]
   [sharing->sexp (-> sharing? (listof (listof symbol?)))])

  (define (sharing-empty? s)
    (null? (sharing-groups s)))

  (define (empty-sharing ids)
    (make-sharing (list->order ids) null))

  (define (list->order ids)
    (let* ([order (empty-idmap)]
           [count 0])
       (lambda (id)
         (set! count (+ count 1))
         (idmap-put-unique! order id count))

  (define (sharing-add-clauses cs before? s)
    (foldl (curry sharing-add-clause before?) s cs))

  (define (sharing-add-clause before? c s)
    (let* ([groups (sharing-groups s)]
           [order (sharing-order s)]
           [_ (for-each (curry order-add! order before?) c)]
           [clause (sort-clause order c)])
      (let*-values ([(hits rest)
                     (partition (curry clause-intersects? clause) groups)])
         (cons (merge-clauses order (cons clause hits)) rest)))))

  (define (clause-intersects? one two)
    (ormap (curry clause-contains? one) two))

  (define (clause-contains? clause id)
    (ormap (curry id=? id) clause))

  (define (sort-clause order clause)
    (let* ([id<? (order->less-than order)])
      (uniquify-clause id<? (sort clause id<?))))

  (define (uniquify-clause id<? clause)
     [(null? clause) clause]
     [(null? (cdr clause)) clause]
     [(id<? (car clause) (cadr clause))
      (cons (car clause) (uniquify-clause id<? (cdr clause)))]
     [(id=? (car clause) (cadr clause))
      (uniquify-clause id<? (cdr clause))]
      (syntax-error (car clause) "inconsistent constraint: ~s = ~s"
                    (syntax-e (car clause)) (syntax-e (cdr clause)))]))

  (define (merge-clauses order clauses)
    (sort-clause order (apply append clauses)))

  (define (order-add! order before? id)
    (unless (idmap-member? order id)
      (idmap-put! order id before?)))

  (define (order->less-than order)
    (lambda (one two)
      (index<? (order-index order one) (order-index order two))))

  (define index<?
     [(list (? number? a) (? number? b)) (< a b)]
     [(list _ #t) #f]
     [(list #f _) #f]
     [_ #t]))

  (define (order-index order id)
     order id
     (lambda ()
       (syntax-error id "not registered in sharing constraints"))))

  (define (sharing-representative s id)
    (let* ([clause (findf (lambda (clause) (clause-contains? clause id))
                          (sharing-groups s))])
      (if clause (car clause) id)))

  (define (sharing-representative? s id)
    (id=? id (sharing-representative s id)))

  (define (sharing-remove s ids)
    (make-sharing (sharing-order s)
                   (map (curry clause-remove ids) (sharing-groups s)))))

  (define (clause-remove ids clause)
    (filter (lambda (id) (not (clause-contains? ids id))) clause))

  (define (sharing->renaming s)
     (map (lambda (id)
            (cons id (sharing-representative s id)))
          (idmap-domain (sharing-order s)))))

  (define (sharing-subset? one two)
    (let* ([one (sharing->map-of-sets one)]
           [two (sharing->map-of-sets two)])
       (lambda (id)
          (idmap-get one id (lambda () (sharing-id-vanished! id)))
          (idmap-get two id (lambda () (list->idset (list id))))))
       (idmap-domain one))))

  (define (sharing-id-vanished! id)
    (syntax-error id "id ~s vanished from sharing constraints" (syntax-e id)))

  (define (sharing->map-of-sets s)
    (let* ([clauses (sharing-groups s)]
           [idmap (empty-idmap)])
       (lambda (clause)
         (let* ([idset (list->idset clause)])
            (lambda (id)
              (idmap-put-unique! idmap id idset))

  (define (sharing-union one two)
     (append (sharing-groups one) (sharing-groups two))
     (make-sharing (order-union (sharing-order one) (sharing-order two)) null)))

  (define (order-union one two)
    (let* ([new (empty-idmap)]
           [count 0]
           [count (order-union/count! new count one)]
           [count (order-union/count! new count two)])

  (define (order-union/count! dest base src)
    (let* ([count 0])
       (lambda (id index)
         (when (number? index)
           (set! count (max count (+ index 1))))
         (unless (idmap-member? dest id)
            dest id
            (if (number? index) (+ index base) index)))))
      (+ base count)))

  (define (sharing-retag alist s)
    (sharing-rename (tag-renaming alist s) s))

  (define (renaming->sexp idmap)
    (map (lambda (pair)
           (cons (syntax-e (car pair))
                 (syntax-e (cdr pair))))
         (idmap->alist idmap)))

  (define (sharing-rename idmap s)
     (map (curry clause-rename idmap) (sharing-groups s))
     (make-sharing (order-rename idmap (sharing-order s)) null)))

  (define (clause-rename idmap ids)
    (map (curry rename idmap) ids))

  (define (rename idmap id)
    (idmap-get idmap id (lambda () id)))

  (define (order-rename idmap old)
    (let* ([new (empty-idmap)])
       (lambda (id index)
         (let* ([name (rename idmap id)])
           (when (or (not (idmap-member? new name))
                     (index<? index (idmap-get new name)))
             (idmap-put! new name index)))))

  (define (tag-renaming alist s)
    (let* ([tags (alist->idmap alist)]
           [order (sharing-order s)]
           [renaming (empty-idmap)])
       (lambda (id index)
         (when (number? index)
           (idmap-put! renaming id (retag tags id)))))

  (define (sharing->sexp sharing)
    (map (curry map syntax-e) (sharing-groups sharing)))