(module more-useful-code mzscheme
  (require (lib "list.ss")
           (lib "pretty.ss")
	   (lib "etc.ss"))
  
  (require-for-syntax (lib "list.ss"))
  
  (provide assert
           cons-to-end
	   assoc-get
	   debug
           debug-x
           make-to-string
	   make-debug
	   to-string
	   member-eq?	   
	   string->char
	   last
	   member-str?
	   quicksort-vector!
	   struct->list/deep
           make-for-each
           begin0/rtn
           with-handlers/finally
           pretty-print-syntax
           with-semaphore

	   make-hash
           hash?
	   hash-get
	   hash-put!
	   hash-remove!
	   hash-map
	   hash-for-each
           hash-size/slow
	   hash-mem?
           hash-fold
           hash-filter!
           hash-keys
           hash-values
           hash-pairs
           hash-add-all!
           hash-get-or-define!

	   (all-from (lib "list.ss"))
	   (all-from (lib "etc.ss")))
  
  (define-struct (exn:assert exn) ())

  (define-syntax (assert stx)
    (syntax-case stx ()
      [(src-assert bool) #'(src-assert bool "")]
      [(src-assert bool msg ...)
       (with-syntax ([src-text (datum->syntax-object
                                (syntax src-assert)
                                (format "~a:~a:~a: assertion failed: "
                                        (syntax-source (syntax bool))
                                        (syntax-line (syntax bool))
                                        (syntax-column (syntax bool))))])
         #'(unless bool
             (raise (make-exn:assert (apply string-append
                                            (cons src-text
                                                  (map (lambda (item) 
                                                         (string-append (to-string item) " "))
                                                       (list msg ...))))
                                     (current-continuation-marks)))))]))

  (define-syntax (begin0/rtn stx)
    (syntax-case stx ()
      [(begin0/rtn body bodies ...)
       (with-syntax ([rtn (datum->syntax-object (syntax begin0/rtn) 'rtn)])
         (syntax (let ([rtn body]) bodies ... rtn)))]))

  (define-syntax with-handlers/finally
    (syntax-rules ()
      [(_ (handler ...) body finally)
       (let ([finally-fn (lambda () finally)])
         (begin0
             (with-handlers
                 (handler ...
                          [(lambda (exn) #t)
                           (lambda (exn) (finally-fn) (raise exn))])
               body)
           (finally-fn)))]))

  (define (make-for-each . iterator-fns)
    (lambda (obj fn)
      (cond ((list? obj) (for-each fn obj))
            ((vector? obj) (let loop ((x 0))
                             (if (< x (vector-length obj))
                                 (begin (fn (vector-ref obj x)) (loop (+ x 1))))))
            ((hash-table? obj) (hash-for-each obj (lambda (key val) (fn key))))
            (true (let loop ((cur iterator-fns))
                    (if (empty? cur) 
                        (if (struct? obj) (error "for-each: no iterator for struct `" (struct-name obj) "' value:" obj)
                            (error "for-each: no iterator for value:" obj))
                        (or ((first cur) obj fn)
                            (loop (rest cur)))))))))

  
  (define (quicksort-vector! v less-than)
    (let ([count (vector-length v)])
      (let loop ([min 0][max count])
	(if (< min (sub1 max))
	    (let ([pval (vector-ref v min)])
	      (let pivot-loop ([pivot min]
			       [pos (add1 min)])
		(if (< pos max)
		    (let ([cval (vector-ref v pos)])
		      (if (less-than cval pval)
			  (begin
			    (vector-set! v pos (vector-ref v pivot))
			    (vector-set! v pivot cval)
			    (pivot-loop (add1 pivot) (add1 pos)))
			  (pivot-loop pivot (add1 pos))))
		    (if (= min pivot)
			(loop (add1 pivot) max)
			(begin
			  (loop min pivot)
			  (loop pivot max)))))))))
    v)
  
  
  
  (define (member-str? s ls)
    (cond
     ((empty? ls) false)
     ((string=? s (first ls)) true)
     (else (member-str? s (rest ls)))))
  
  (define (last ls)
    (cond
     ((empty? ls) (error "took a last but it was emptry"))
     ((empty? (rest ls)) (first ls))
     (else (last (rest ls)))))

  (define (string->char s)
    (first (string->list s)))
  
  (define (member-eq? x ls)
    (not (empty? (filter (lambda (y) (eq? x y)) ls))))
  
  (define (to-string arg . extra-printers)
    (let ([on-stack-ids (make-hash)]
          [used-ids (make-hash)]
          [free-id 0])
      (let loop ((arg arg))
        (if (hash-mem? on-stack-ids arg)
            (begin
              (hash-put! used-ids arg true)
              (format "#~a#" (hash-get on-stack-ids arg)))
            (let ([my-id free-id])
              (hash-put! on-stack-ids arg my-id)
              (set! free-id (add1 free-id))
              (let ([result
                     (or
                      (let printer-loop ([printers extra-printers])
                        (if (empty? printers) 
                            false
                            (or (if (procedure-arity-includes? (car printers) 2)
                                    ((car printers) arg (lambda (arg) (apply to-string (cons arg extra-printers))))
                                    ((car printers) arg))
                                (printer-loop (cdr printers)))))
                      (cond
                       [(not arg) "#f"]
                       [(void? arg) "#<void>"]
                       [(eq? arg #t) "#t"]
                       [(char? arg) (list->string (list arg))]
                       [(string? arg) (format "\"~a\"" arg)]
                       [(symbol? arg) (symbol->string arg)]
                       [(number? arg) (number->string arg)]
                       [(vector? arg) (string-append "#" (loop (vector->list arg)))]
                       [(box? arg) (string-append "#&" (loop (unbox arg)))]
                       [(empty? arg) "empty"]
                       [(list? arg) 
                        (apply
                         string-append
                         `("(" ,@(cons (loop (first arg))
                                       (map (lambda (item) (string-append " " (loop item))) (rest arg)))
                           ")"))]
                       [(cons? arg) (format "(~a . ~a)"
                                            (loop (first arg))
                                            (loop (rest arg)))]
       
                       [(hash-table? arg)
                        (apply
                         string-append
                         `("[hash:"
                           ,@(map (lambda (item) (string-append " " (loop item))) (hash-pairs arg))
                           "]"))]
       
                       [(syntax? arg)
                        (format "[syntax: ~a:~a]" (syntax-line arg) (syntax-column arg))]

                       [(struct? arg)
                        (let ([as-list (vector->list (struct->vector arg))])
                          (apply
                           string-append
                           `("[" ,@(cons (loop (first as-list))
                                         (map (lambda (item) (string-append " " (loop item)))
                                              (rest as-list))) "]")))]

                       [else
                        (format "~a" arg)]))])
                (hash-remove! on-stack-ids arg)
                (if (hash-mem? used-ids arg)
                    (format "#~a=~a" my-id result)
                    result)))))))
  
  (define-syntax (debug-x stx)
    (syntax-case stx ()
      [(_ rest ... expr)
       #`(let ([result expr])
           (printf "~a:~a ~a: ~a ~a~n"
                   #,(syntax-line stx)
                   #,(syntax-column stx)
                   '#,(if (syntax->list #'expr)
                          (syntax-e (first (syntax->list #'expr)))
                          (syntax-e #'expr))
                   result
                   (list rest ...))
           result)]))
  
  ;; make-debug: usage example: (define debug-f (make-debug (make-to-string `([,is-type? ,type-to-string]))))
  ;; The printers have to take two arguments: the item to converts and the to-string function for subitems
  (define (make-debug to-string-fn)
    (lambda args
      (for-each (lambda (x) 
                  (display (if (string? x) x (to-string-fn x)))
                  (display " "))
                args)
      (newline)))

  (define debug (make-debug to-string))
  
  (define (make-to-string predicate-printer-pairs)
    (let ([printers (map (lambda (pair) (lambda (arg printer) 
                                     (cond [(not ((first pair) arg)) false]
                                           [(procedure-arity-includes? (second pair) 2)
                                            ((second pair) arg printer)]
                                           [else ((second pair) arg)])))
                         predicate-printer-pairs)])
      (case-lambda 
        [(arg) (apply to-string arg printers)]
        [(arg extra-printers) (apply to-string (append (list arg) printers extra-printers))])))
  
  (define (assoc-get label ls)
    (cond
     ((empty? ls) (error (string-append "failed to find " (to-string label))))
     ((eq? label (first (first ls)))
      (first ls))
     (else (assoc-get label (rest ls)))))
  
  (define (cons-to-end a ls)
    (cond
     ((empty? ls) (cons a ls))
     (else (cons (first ls)
		 (cons-to-end a (rest ls))))))
  
  (define (struct->list/deep item)
    (cond [(struct? item) (map struct->list/deep (vector->list (struct->vector item)))]
	  [(list? item)   (map struct->list/deep item)]
          [(vector? item) (list->vector (map struct->list/deep (vector->list item)))]
	  [else item]))

  (define (struct-name s) (vector-ref (struct->vector s) 0))

  (define (pretty-print-syntax width stx)
    (pretty-print-columns width)
    (pretty-print (syntax-object->datum stx)))

  (define (with-semaphore sem proc)
    (semaphore-wait sem)
    (let ([result (proc)])
      (semaphore-post sem)
      result))

  (define make-hash make-hash-table)
  (define hash? hash-table?)
  (define hash-get hash-table-get)
  (define hash-put! hash-table-put!)
  (define hash-remove! hash-table-remove!)
  (define hash-map hash-table-map)
  (define hash-for-each hash-table-for-each)
  (define (hash-empty? hash)(let/ec k (hash-for-each hash (lambda (k v) (k false))) true))
  (define (hash-size/slow hash) (hash-fold hash 0 (lambda (key val acc) (+ acc 1))))
  (define (hash-mem? hash item) (let/ec k (begin (hash-get hash item (lambda () (k false))) true)))
  (define (hash-fold hash init fn)
    (hash-for-each hash (lambda (key val) (set! init (fn key val init)))) init)
  (define (hash-filter! hash predicate)
    (hash-for-each 
     hash (lambda (key val) (if (not (predicate key val))
                           (hash-remove! hash key)))))
  (define (hash-keys hash) 
    (hash-fold hash empty (lambda (key val acc) (cons key acc))))
  (define (hash-values hash) 
    (hash-fold hash empty (lambda (key val acc) (cons val acc))))
  (define (hash-pairs hash)
    (hash-fold hash empty (lambda (key val acc) (cons (list key val) acc))))
  (define (hash-add-all! to-hash from-hash) ;; // memcpy-style argument order
    (hash-for-each from-hash
                   (lambda (key val) (hash-put! to-hash key val))))

  (define (hash-get-or-define! hash key val-fn)
    (if (not (hash-mem? hash key)) 
        (begin (let ((v (val-fn)))
                 (hash-put! hash key v)
                 v))
        (hash-get hash key))))
