(define (numeric? c)
  (member? c "0123456789"))

(define o (function (f g) (function (x) (f (g x)))))

(define first head)

(define second (o first tail))

(define third (o second tail))

(define (forall* vars P)
  (match vars
    ([] P)
    ((list-of x rest) (forall* rest (forall x P)))))

(define (quant* Q var-list P)
 (match var-list 
   ([] P)
   ((list-of v more-vars) (Q v (quant* Q more-vars P)))))

(define (forall* vl P) (quant* forall vl P))

(define (exists* vl P) (quant* exists vl P))

(define (uspec* P terms)
  (dmatch terms
    ([] (dcheck ((holds? P) (!claim P)) (else (!claim true))))
    ((list-of t more-terms) (!uspec* (!uspec P t) more-terms))))

(define (zip L1 L2)
  (letrec ((f (function (L1 L2 res)
		(match [L1 L2]
		  ([(list-of x1 rest1) (list-of x2 rest2)] (f rest1 rest2 (add [x1 x2] res)))
                  (_ (rev res))))))
    (f L1 L2 [])))

(define (wl l) 
  (check ((null? l) (print "None")) (else (map-proc write l))))

(define (make-and props)
  (dmatch props
    ([P] (!claim P))
    ((list-of P rest) (!both P (!make-and rest)))))

(define (urep P terms)
  (match [P terms]
    ([(forall x Q) (list-of t more)] (urep (replace-var x t Q) more))
    (_ P)))

(define (body-of P)
  (match P
    (((some-quant Q) _ body) body)))

(define (qvar-of P)
  (match P
    (((some-quant Q) x _) x)))

(define (get-all-vars arg)
  (match arg
    ((some-term t) (vars t))
    ((some-prop P) (vars P))
    ((some-list args) (foldr join [] (map get-all-vars args)))))

(define (get-vars arg) (remove-duplicates (get-all-vars arg)))

(define (conjoin props)
  (match props
    ([P] P)
    ((list-of P rest) (and P (conjoin rest)))))

(primitive-method (equality s t)
  (check ((equal? s t) (= s t))))

(define (println s)
  (print (join "\n" s "\n")))


;(define (mread)
;  (let ((i (read))) (begin (read) i)))

(define (mread) ())


(define (show str v)
  (begin (println str) (write v)))


(define (show-read str v) (begin (print (join "\n" str "\n")) (write v) (read)))

(define (subterms t)
  (add t (fold join (map subterms (children t)) [])))

(define (constants&vars t)
  (match t 
    ((some-var v) [v])
    (((some-symbol f) (some-list args)) (check ((null? args) [f])
					       (else (fold join (map constants&vars args) []))))))


(define (from-terms P f)
  (match P
    ((some-atom A) (f A))
    ((not body) (from-terms body f))
    (((some-prop-con pc) P1 P2) (join (from-terms P1 f) (from-terms P2 f)))
    (((some-quant q) _ body) (from-terms body f))))

(define (from-terms* props f)
  (fold join (map (function (P) (from-terms P f)) props) []))

(define (prop-constants-and-vars props)
  (from-terms* props constants&vars))

(define (prop-subterms P)
  (from-terms P subterms))

(define (prop-subterms* props)
  (remove-duplicates (from-terms* props subterms)))

(define (non-var-term t)
  (match t
    (((some-symbol f) (some-list args)) true)
    (_ false)))
    
(define (prop-constants-and-free-vars props)
  (let ((fvars (fold join (map free-vars props) [])))
   (remove-duplicates (filter (from-terms* props constants&vars) 
			      (function (t) (|| (member? t fvars) (non-var-term t)))))))
				

(define (plus* L)
  (fold plus L 0))

(define (term-size t)
  (match t
    (((some-symbol f) (some-list args)) (plus* (add 1 (map term-size args))))
    (_ 1)))

(define (term-less? s t)
  (less? (term-size s) (term-size t)))

(define (no-free-vars? P)
  (null? (free-vars P)))

(define (interesting-prop-subterms P)
  (check ((no-free-vars? P) (prop-constants-and-free-vars [P]))
	 (else (prop-subterms P))))

(define (interesting-prop-subterms* props)
  (remove-duplicates (fold join (map interesting-prop-subterms props) [])))


(define (map-method M L K)
  (dletrec ((loop (method (L res)
		    (dmatch L
		      ([] (!K (rev res)))
		      ((list-of x rest) (dlet ((th (!M x)))
			  	          (!loop rest (add th res))))))))
     (!loop L [])))

(define (do-props props M K)
  (dletrec ((loop (method (props res)
                    (dmatch props
	  	      ([] (!K (rev res)))
		      ((list-of (some-prop P) rest) (dlet ((th (!M P)))
			  			      (!loop rest (add th res))))))))
     (!loop props [])))

(define (make-conjunction props)
  (dmatch props
    ([P] (!claim P))
    ((list-of P more-props) (!both P (!make-conjunction more-props)))))

(define (unify-props P1 P2)
    (letrec ((f (function (P1 P2)
		   (match [P1 P2]
    		     ([(some-atom A1) (some-atom A2)] (unify A1 A2))
		     ([(not Q1) (not Q2)] (f Q1 Q2))
		     ([((some-prop-con pc) Q1 Q2) (pc Q3 Q4)] 
			  (let ((sub1 (f Q1 Q3)))
                            (compose-subs (f (sub1 Q2) (sub1 Q4)) sub1)))
		     ([((some-quant Q) x B1) (Q y B2)] 
			  (let ((v (fresh-var)))
			    (f (replace-var x v B1) (replace-var y v B2))))
                     (_ false)))))
       (f (rename P1) P2)))

(define up unify-props)

(define (unify* v1 v2)
  (match [v1 v2]
    ([(some-term t1) (some-term t2)] (unify t1 t2))
    ([(some-prop P1) (some-prop P2)] (unify-props P1 P2))))

(define (get-conjuncts P)
  (match P
    ((and P1 P2) (join (get-conjuncts P1) (get-conjuncts P2)))
    (true [])
    (P [P])))

(primitive-method (cases P1 P2)
  (match [P1 P2]
    ([(if P Q) (if (not P) Q)] (check ((& (holds? P1) (holds? P2)) Q)))))

(define (hold? props)
  (match props
    ([] true)
    ((list-of P more) (& (holds? P) (hold? more)))))

(define (fire premise terms M)
  (dmatch premise
    ((forall (some-list vars) (if P Q)) (!mp (!uspec* premise terms) P))
    ((forall (some-list vars) (iff P Q)) 
       (dlet ((th (!M (!uspec* premise terms))))
         (dmatch th
           ((if P1 P2) (!mp th P1)))))))


(define (antecedent P)
  (match P
    ((if P1 P2) P1)))

(define (consequent P)
  (match P
    ((if P1 P2) P2)))

(define (prove-conjuncts-of P)
  (dmatch P
    ((and P1 P2) (!both P1 (!prove-conjuncts-of P2)))
    (_ (!claim P))))

(define (prove-antecedent P)
  (dmatch P
    ((if P1 P2) (!prove-conjuncts-of P1))))


(define (fire premise terms)
  (dmatch premise
    ((forall (some-list vars) (if P _)) (dlet ((th (!uspec* premise terms)))
                                          (!mp th (!prove-antecedent th))))
    ((forall (some-list vars) (iff _ _)) 
       (dlet ((th (!uspec* premise terms)))
         (dtry (dlet ((P (!left-iff th))) 
                 (!mp P (!prove-antecedent P)))
               (dlet ((P (!right-iff th)))
                 (!mp P (!prove-antecedent P))))))))


(define (~ t)
  (match t 
    (true false)
    (false true)))

(define (os p) (print (head (make-otter-prop-string [p]))))

(define pf prove-from)

(define (gp s) (match (and s s) ((and p p) p)))

;; Proves x = y from the assumption [x] in {[y]}

(define (eq-tup-parts premise)
  (dmatch premise 
    ((in (cons x unit) (singl (cons y unit)))
       (dlet ((P1 ((= (tup [x]) (tup [y])) BY (!fire singleton-axiom [(tup [x]) (tup [y])]))))
         (!left-and (!mp (!uspec* (!constructor-injectivity cons) [x unit y unit]) P1))))))

(define (eq-tup-parts1 premise)
  (dmatch premise 
    ((in (cons x unit) (singl (cons y unit)))
       (!pf (= x y) [premise (!constructor-injectivity cons) singleton-axiom]))))

(define (all-props) (fetch-all (function (P) true)))

(load-file "num.ath")

(define (map f lst)
  (match lst
    ([] [])
    ((list-of x rest) (add (f x) (map f rest)))))

(define (map-list f lst)
  (match lst
    ([] [])
    ((list-of x rest) (add (f x) (map-list f rest)))))

(define (map-proc f l)
  (match l
    ([] ())
    ((list-of x rest) (begin (f x) (map-proc f rest)))))

(define (fold f lst id)
  (match lst
    ([] [])
    ([x] (f x id))
    ((list-of x (list-of y more)) 
       (fold f (add (f x y) more) id))))

(define (foldr f e l)
  (match l
    ([] e)
    ((list-of x rest) (f x (foldr f e rest)))))

(define (length lst)
  (match lst
    ([] 0)
    ((list-of _ rest) (plus 1 (length rest)))))

(define (remove x L)
  (match L
    ((split L1 (list-of (val-of x) L2)) 
       (remove x (join L1 L2)))
    (_ L)))

(define (remove-duplicates lst)
  (match lst
    ((split L1 (list-of x (split L2 (list-of x more)))) 
       (remove-duplicates (join L1 [x] L2 more)))
    (_ lst)))

(define rd remove-duplicates)

(define (member? x l) 
  (match l
    ((split l1 (list-of (val-of x) l2)) true)
    (_ false)))

(define (for-each list pred)
  (match list
    ([] true)
    ((list-of x rest) (& (pred x) (for-each rest pred)))))

(define (subset? L1 L2)
  (for-each L1 (function (x) (member? x L2))))

(define (for-some list pred)
  (match list
    ([] false)
    ((list-of x rest) (|| (pred x) (for-some rest pred)))))

(define (filter L f)
  (match L
    ([] [])
    ((list-of x rest) (check ((f x) (add x (filter rest f)))
                             (else (filter rest f))))))

(define (list-diff L1 L2)
  (filter L1 (function (x) (~ (member? x L2)))))

(define (nth i l)
  (match [i l]
    ([1 (list-of x _)] x)
    (_ (nth (minus i 1) (tail l)))))

(define (nth-tail l n)
  (match n
    (0 l)
    (_ (nth-tail (tail l) (minus n 1)))))

(define (prefix? s1 s2)
  (match [s1 s2]
    ([[] _] true)
    ([(list-of x rest1) (list-of x rest2)] (prefix? rest1 rest2))
    (_ false)))

(define (find-min-rest l fun)
  (letrec ((f (function (rem rest min i)
		(match rem
		  ([] [min rest])
		  ((list-of x more) (check 
			              ((greater? i 0) (check ((less? (fun x) (fun min)) (f more (add min rest) x (plus i 1)))
  	 					             (else (f more (add x rest) min (plus i 1)))))
                                      (else (f more [] x (plus i 1)))))))))
    (check ((null? l) ()) (else (f l [] () 0)))))

(define (take l n)
  (letrec ((f (function (l n res)
		(match [l n]
		  ([_ 0] (rev res))
		  ([[] _] (rev res))
		  (_ (f (tail l) (minus n 1) (add (head l) res)))))))
    (f l n [])))

(define 
  (even-positions L)
    (match L
      ((list-of _ rest) (odd-positions rest))
      (_ []))
  (odd-positions L)
    (match L
      ((list-of x rest) (add x (even-positions rest)))
      (_ [])))


(define (merge L1 L2 less?)
  (match [L1 L2]
    ([(list-of x rest1) (list-of y rest2)] 
	(check ((less? x y) (add x (merge rest1 L2 less?)))
               (else (add y (merge rest2 L1 less?)))))
    ([[] _] L2)
    (_ L1)))

(define (merge-sort L less?)
  (match L
    ((list-of _ (list-of _ rest)) (merge (merge-sort (odd-positions L) less?)
				         (merge-sort (even-positions L) less?) less?))
    (_ L)))


(define (skip-until list pred)
  (match list
    ([] [])
    ((list-of x rest) (check ((pred x) list)
			     (else (skip-until rest pred))))))

(define (intersect l1 l2)
  (filter l1 (function (x) (member? x l2))))
