#lang scheme/base

(require (for-syntax scheme/base))

(provide mmap
         mfor-each
         mlist
         mlist?
         mlength
         mappend
         mappend!
         mreverse
         mreverse!
         mlist-tail
         mlist-ref
         mmemq
         mmemv
         mmember
         massq
         massv
         massoc
         mlist->list
         list->mlist
         mlistof)

(define mmap
  (case-lambda
   [(f l) (let loop ([l l])
            (cond
             [(null? l) null]
             [else (mcons (f (mcar l)) (loop (mcdr l)))]))]
   [(f l1 l2) (let loop ([l1 l1][l2 l2])
                (cond
                 [(null? l1) null]
                 [else (mcons (f (mcar l1) (mcar l2))
                              (loop (mcdr l1) (mcdr l2)))]))]
   [(f l . ls) (let loop ([l l][ls ls])
                 (cond
                  [(null? l) null]
                  [else (mcons (apply f (mcar l) (map mcar ls))
                               (loop (mcdr l) (map mcdr ls)))]))]))

(define mfor-each
  (case-lambda
   [(f l) (let loop ([l l])
            (cond
             [(null? l) (void)]
             [else (f (mcar l))
                   (loop (mcdr l))]))]
   [(f l1 l2) (let loop ([l1 l1][l2 l2])
                (cond
                 [(null? l1) (void)]
                 [else (f (mcar l1) (mcar l2))
                       (loop (mcdr l1) (mcdr l2))]))]
   [(f l . ls) (let loop ([l l][ls ls])
                 (cond
                  [(null? l) (void)]
                  [else (apply f (mcar l) (map mcar ls))
                        (loop (mcdr l) (map mcdr ls))]))]))

(define (list->mlist l)
  (cond
   [(null? l) null]
   [else (mcons (car l) (list->mlist (cdr l)))]))

(define (mlist->list l)
  (cond
   [(null? l) null]
   [else (cons (mcar l) (mlist->list (mcdr l)))]))

(define-syntax mlist
  (make-set!-transformer
   (lambda (stx)
     (syntax-case stx (set!)
       [(set! id . _) (raise-syntax-error #f
                                          "cannot mutate imported variable"
                                          stx
                                          #'id)]
       [(_ a) #'(mcons a null)]
       [(_ a b) #'(mcons a (mcons b null))]
       [(_ a b c) #'(mcons a (mcons b (mcons c null)))]
       [(_ arg ...) #'(-mlist arg ...)]
       [_ #'-mlist]))))

(define -mlist
  (let ([mlist
         (case-lambda
          [() null]
          [(a) (mcons a null)]
          [(a b) (mcons a (mcons b null))]
          [(a b c) (mcons a (mcons b (mcons c null)))]
          [(a b c d) (mcons a (mcons b (mcons c (mcons d null))))]
          [l (list->mlist l)])])
    mlist))

(define (mlist? l)
  (cond
   [(null? l) #t]
   [(mpair? l)
    (let loop ([turtle l][hare (mcdr l)])
      (cond
       [(null? hare) #t]
       [(eq? hare turtle) #f]
       [(mpair? hare)
        (let ([hare (mcdr hare)])
          (cond
           [(null? hare) #t]
           [(eq? hare turtle) #f]
           [(mpair? hare)
            (loop (mcdr turtle) (mcdr hare))]
           [else #f]))]
       [else #f]))]
   [else #f]))
  
(define (mlength l)
  (let loop ([l l][len 0])
    (cond
     [(null? l) len]
     [else (loop (mcdr l) (add1 len))])))

(define mappend
  (case-lambda
   [() null]
   [(a) a]
   [(a b) (let loop ([a a])
            (if (null? a)
                b
                (mcons (mcar a) (loop (mcdr a)))))]
   [(a . l) (mappend a (apply mappend l))]))

;; mappend! : like append, but mutate each list to refer to the next.
;; modeled loosely on the v372 behavior

(define mappend!
  (case-lambda
    [() null]
    [(a) a]
    [(a b) (if (null? a)
               b
               (let loop ([atail a])
                 (cond [(null? (mcdr atail)) (set-mcdr! atail b) a]
                       [else (loop (mcdr atail))])))]
    [(a . l) (mappend! a (apply mappend! l))]))

(define (mreverse l)
  (let loop ([l l][a null])
    (cond
     [(null? l) a]
     [else (loop (mcdr l) (mcons (mcar l) a))])))

(define (mreverse! l)
  (let loop ([l l][prev null])
    (cond
     [(null? l) prev]
     [else (let ([next (mcdr l)])
             (set-mcdr! l prev)
             (loop next l))])))

(define (mlist-tail l n)
  (cond
   [(zero? n) l]
   [else (mlist-tail (mcdr l) (sub1 n))]))

(define (mlist-ref l n)
  (cond
   [(zero? n) (mcar l)]
   [else (mlist-ref (mcdr l) (sub1 n))]))

(define (do-member =? v l)
  (let loop ([l l])
    (cond
     [(null? l) #f]
     [(=? v (mcar l)) l]
     [else (loop (mcdr l))])))

(define (mmemq v l)
  (do-member eq? v l))

(define (mmemv v l)
  (do-member eqv? v l))

(define (mmember v l)
  (do-member equal? v l))


(define (do-assoc =? v l)
  (let loop ([l l])
    (cond
     [(null? l) #f]
     [(=? v (mcar (mcar l))) (mcar l)]
     [else (loop (mcdr l))])))

(define (massq v l)
  (do-assoc eq? v l))

(define (massv v l)
  (do-assoc eqv? v l))

(define (massoc v l)
  (do-assoc equal? v l))

(define ((mlistof p?) l)
  (let loop ([l l])
    (cond
     [(null? l) #t]
     [(not (mpair? l)) #f]
     [(p? (mcar l)) (loop (mcdr l))]
     [else #f])))
