#lang typed-scheme
#;(require mzlib/etc)
#;(require "prims.rkt")
(require mzlib/match
         (for-syntax scheme/base))

(define-type-alias number Number)
(define-type-alias boolean Boolean)
(define-type-alias symbol Symbol)
(define-type-alias top Any)
(define-type-alias list-of Listof)

(define-type-alias atom (Un Number Symbol Boolean))

(define: atom? : (Any -> Boolean : atom) (lambda: ([v : Any]) (if (number? v) #t (if (symbol? v) #t (boolean? v)))))

(define-syntax (cond* stx)
  (syntax-case stx (else)
    [(_ [pred expr id rhs] . rest)
     (quasisyntax/loc stx
         (let ([id expr])
           (if (pred id)
               rhs
               #,(syntax/loc #'rest (cond . rest)))))]
    [(_ [else . rest]) #'(begin . rest)]
    [(_ [p . rhs] . rest)
     #'(if p (begin . rhs)
           (cond* . rest))]))

(define-type-alias lat (list-of atom))

(define: (lat? [l : (list-of top)]) : boolean
  (cond [(null? l) #t]
	[(atom? (car l)) (lat? (cdr l))]
	[else #f]))

(define: (member? [a : atom] [l : lat]) : boolean
  (cond
   [(null? l) #f]
   [else (or (equal? a (car l))
	     (member? a (cdr l)))]))

(define: (rember [a : symbol] [l : (list-of symbol)]) : (list-of symbol)
  (cond
   [(null? l) l]
   [(eq? (car l) a) (cdr l)]
   [else (cons (car l) (rember a (cdr l)))]))

(define: (multisubst [new : symbol] [old : symbol] [lat : (list-of symbol)]) : (list-of symbol)
  (cond
   [(null? lat) lat]
   [(eq? (car lat) old) (cons new (multisubst new old (cdr lat)))]
   [else (cons (car lat) (multisubst new old (cdr lat)))]))

(define: (tup+ [t1 : (list-of number)] [t2 : (list-of number)]) : (list-of number)
  (cond
   [(null? t1) t2]
   [(null? t2) t1]
   [else (cons (+ (car t1) (car t2))
	       (tup+ (cdr t1) (cdr t2)))]))

(define: (len [l : (list-of top)]) : number
  (cond
   [(null? l) 0]
   [else (add1 (len (cdr l)))]))

(define: (pick [n : number] [lat : (list-of symbol)]) : symbol
  (cond
   [(zero? (sub1 n)) (car lat)]
   [else (pick (sub1 n) (cdr lat))]))

(define: (no-nums [lat : (list-of atom)]) : (list-of atom)
  (cond
   [(null? lat) lat]
   [(number? (car lat)) (no-nums (cdr lat))]
   [else (cons (car lat) (no-nums (cdr lat)))]))

(define: (one? [n : number]) : boolean
  (= n 1))

(define: (rempick [n : number] [lat : (list-of atom)]) : (list-of atom)
  (cond
   [(one? n) (cdr lat)]
   [else (cons (car lat)
	       (rempick (sub1 n) (cdr lat)))]))

(define: (foo2 [x : top]) : boolean
  (if (number? x) (= x x) #f))

;; doesn't work because of and! - bug in type system
(define: (eqan? [a1 : top] [a2 : top]) : boolean
  (cond [(and (number? a1) (number? a2)) (= a1 a2)]
	[else (eq? a1 a2)]))

(define: (occur [a : atom] [lat : (list-of atom)]) : number
  (cond [(null? lat) 0]
	[(eq? (car lat) a) (add1 (occur a (cdr lat)))]
	[else (occur a (cdr lat))]))

(define-type-alias SExp (mu x (U atom (Listof x))))


;; (atom? (car l)) doesn't do anything - bug in type system
#;(define: (rember* [a : atom] [l : (list-of SExp)]) : (list-of SExp)
(cond
 [(null? l) l]
 [(atom? (car l))
  (cond [(eq? (car l) a) (rember* a (cdr l))]
	[else (cons (car l) (rember* a (cdr l)))])]
 [else (cons (rember* a (car l)) (rember* a (cdr l)))]))

(define: (rember* [a : atom] [l : (list-of SExp)]) : (list-of SExp)
  (cond
   [(null? l) l]
   [else
    (let ([c (car l)])
      (cond
        [(atom? c)
         (cond [(eq? c a) (rember* a (cdr l))]
               [else (cons c (rember* a (cdr l)))])]
        [else (cons (rember* a c) (rember* a (cdr l)))]))]))

(define: (insertR* [new : atom] [old : atom] [l : (list-of SExp)]) : (list-of SExp)
  (cond
   [(null? l) l]
   [else
    (let ([c (car l)])
      (cond
        [(atom? c)
         (cond
           [(eq? c old)
            (cons old (cons new (insertR* new old (cdr l))))]
           [else
            (cons c
                  (insertR* new old (cdr l)))])]
        [else (cons (insertR* new old c)
                    (insertR* new old (cdr l)))]))]))

(define: (occur* [a : atom] [l : (list-of SExp)]) : number
  (cond*
   [(null? l) 0]
   [atom? (car l) c
	  (cond [(eq? c a) (add1 (occur* a (cdr l)))]
		[else (occur* a (cdr l))])]
   [else (+ (occur* a c)
	    (occur* a (cdr l)))]))

(define: (member* [a : atom] [l : (list-of SExp)]) : boolean
  (cond*
   [(null? l) #f]
   [atom? (car l) c
	  (or (eq? a c) (member* a (cdr l)))]
   [else (or (member* a c) (member* a (cdr l)))]))

(define: (^ [n : number] [m : number]) : number
  (if (= m 0) 1 (* n (^ n (sub1 m)))))

(define: (1st-sub-exp [ae : (list-of SExp)]) : SExp
  (car ae))

(define: (2nd-sub-exp [ae : (list-of SExp)]) : SExp
  (car (cdr (cdr ae))))

(define: (operator [ae : (list-of SExp)]) : SExp
  (car (cdr ae)))

(define-type-alias num-exp (Rec N (U Number (List N (U '+ '* '^) N))))

(define: (value [nexp : num-exp]) : number
  (cond
   [(atom? nexp) nexp]
   [(eq? (car (cdr nexp)) '+)
    (+ (value (car nexp))
       (value (car (cdr (cdr nexp)))))]
   [(eq? (car (cdr nexp)) '*)
    (* (value (car nexp))
       (value (car (cdr (cdr nexp)))))]
   [else
    (^ (value (car nexp))
       (value (car (cdr (cdr nexp)))))]
   ))

#;(define-type aexp (Un atom (list-of aexp)))

(define: (set? [l : (list-of atom)]) : boolean
  (cond
   [(null? l) #t]
   [(member? (car l) (cdr l)) #f]
   [else (set? (cdr l))]))

(define: (multirember [a : atom] [l : (list-of atom)]) : (list-of atom)
  (cond
   [(null? l) l]
   [(equal? a (car l)) (multirember a (cdr l))]
   [else (cons (car l) (multirember a (cdr l)))]))

(define: (makeset [l : lat]) : lat
  (cond
   [(null? l) l]
   [else (cons (car l)
	       (makeset (multirember (car l) (cdr l))))]))

(define: (subset? [set1 : lat] [set2 : lat]) : boolean
  (cond
   [(null? set1) #t]
   [(member? (car set1) set2)
    (subset? (cdr set1) set2)]
   [else #f]))

(define: (subset2? [set1 : (list-of atom)] [set2 : (list-of atom)]) : boolean
  (cond
   [(null? set1) #t]
   [else (and (member? (car set1) set2)
	      (subset? (cdr set1) set2))]))

(define: (intersect? [set1 : (list-of atom)] [set2 : (list-of atom)]) : boolean
  (cond
   [(null? set1) #t]
   [else (or (member? (car set1) set2)
	     (intersect? (cdr set1) set2))]))

(define: (eqset? [set1 : (list-of atom)] [set2 : (list-of atom)]) : boolean
  (and (subset? set1 set2) (subset? set2 set1)))

(define: (intersect [set1 : (list-of atom)] [set2 : (list-of atom)]) : (list-of atom)
  (cond
   [(null? set1) set1]
   [(member? (car set1) set2)
    (cons (car set1) (intersect (cdr set1) set2))]
   [else (intersect (cdr set1) set2)])
  )

(define: (union [set1 : (list-of atom)] [set2 : (list-of atom)]) : (list-of atom)
  (cond
   [(null? set1) set2]
   [(member? (car set1) set2)
    (union (cdr set1) set2)]
   [else (cons (car set1) (intersect (cdr set1) set2))])
  )

(define: (rember-f [test? : (atom atom -> boolean)] [a : atom] [l : (list-of atom)]) : (list-of atom)
  (cond
   [(null? l) l]
   [(test? (car l) a) (cdr l)]
   [else (cons (car l) (rember-f test? a (cdr l)))]))

(define: (rember-f-curry [test? : (atom atom -> boolean)]) : (atom lat -> lat)
  (lambda: ([a : atom] [l : (list-of atom)])
	   (cond
	    [(null? l) l]
	    [(test? (car l) a) (cdr l)]
	    [else (cons (car l) ((rember-f-curry test?) a (cdr l)))])))

(define: eq?-c : (atom -> (atom -> boolean))
  (lambda: ([a : atom])
	   (lambda: ([x : atom])
		    (eq? x a))))

(define: (insertR-f [test? : (atom atom -> boolean)] [new : atom] [old : atom] [l : (list-of atom)]) : (list-of atom)
  (cond
   [(null? l) l]
   [(test? (car l) old)
    (cons old (cons new (cdr l)))]
   [else (cons (car l)
	       (insertR-f test? new old (cdr l)))]))

(define: (seqL [new : atom] [old : atom] [l : (list-of atom)]) : (list-of atom)
  (cons new (cons old l)))


(define: (seqR [new : atom] [old : atom] [l : (list-of atom)]) : (list-of atom)
  (cons old (cons new l)))

(define: (insertR-g [seq : (atom atom lat -> lat)]
		    [test? : (atom atom -> boolean)]
		    [new : atom] [old : atom] [l : (list-of atom)])
  : (list-of atom)
  (cond
   [(null? l) l]
   [(test? (car l) old)
    (seq new old (cdr l))]
   [else (cons (car l)
	       (insertR-g seq test? new old (cdr l)))]))

(define: (insertR-g-curry [seq : (atom atom (list-of atom) -> (list-of atom))])
  : ((atom atom -> boolean) atom atom (list-of atom) -> (list-of atom))
  (lambda: ([test? : (atom atom -> boolean)]
	    [new : atom] [old : atom] [l : (list-of atom)])
	   (cond
	    [(null? l) l]
	    [(test? (car l) old)
	     (seq new old (cdr l))]
	    [else (cons (car l)
			((insertR-g-curry seq) test? new old (cdr l)))])))

(define: (seqS [new : atom] [old : atom] [l : lat]) : lat
  (cons new l))

#;(define: subst : ((atom atom -> boolean) atom atom (list-of atom) -> (list-of atom))
(insertR-g-curry seqS))

(define: (atom->function [x : atom]) : (number number -> number)
  (case x
    [(+) +]
    [(*) *]
    [else ^]))

;; doesn't work - is operator really a number? (bug in type system)
;; also infinite loops checking num-exp <: SExp
#;(define: (value-new [nexp : num-exp]) : number
(cond
 [(number? nexp) nexp]
 [else ((atom->function (operator nexp))
	(value-new (1st-sub-exp nexp))
	(value-new (2nd-sub-exp nexp)))]))

(define: (multiremberT [test? : (atom -> boolean)] [l : (list-of atom)]) : (list-of atom)
  (cond
   [(null? l) l]
   [(test? (car l)) (multiremberT test? (cdr l))]
   [else (cons (car l) (multiremberT test? (cdr l)))]))

(define: (build [a : SExp] [b : SExp]) : (list-of SExp)
  (cons a (cons b '())))

(define: (first [pair : (list-of SExp)]) : SExp
  (car pair))

(define: (second [pair : (list-of SExp)]) : SExp
  (car (cdr pair)))

;; need to specify more about the list in the type here  - type system bug
#;(define: (shift [pair : (list-of SExp)]) : (list-of SExp)
(build (first (first pair))
       (build (second (first pair))
	      (second pair))))

;; changed to test for exact-integer? before even? check.
(define: (collatz [n : number]) : number
  (cond [(one? n) 1]
	[(and (exact-integer? n) (even? n)) (collatz (/ n 2))]
	[else (collatz (add1 (* 3 n)))]))


(define: (ack [n : number] [m : number]) : number
  (cond
   [(zero? n) (add1 m)]
   [(zero? m) (ack (sub1 n) 1)]
   [else (ack (sub1 n)
	      (ack n (sub1 m)))]))


					;(define-type-alias entry (list-of (list-of atom)))

(define: empty-atom : (list-of (list-of atom)) '())

;; FIXME
(define: mymap : (All (a b) ((a -> b) (list-of a) -> (list-of b)))
  (plambda: (a b) ([f : (a -> b)] [l : (list-of a)])
	    (cond [(null? l) '()]
		  [else (cons (f (car l))
			      (mymap f (cdr l)))])))

(mymap add1 (cons 1 (cons 2 (cons 3 '()))))

(define-type-alias entry (list-of lat))

(define-type-alias table (list-of entry))


(define: (new-entry  [keys : (list-of atom)]
		     [vals : (list-of atom)]) : entry
		     (cons keys (cons vals empty-atom)))

(define: (numbered? [aexp : num-exp]) : boolean
  (cond
   [(number? aexp) #t]
   [(atom? aexp) #f]
   [else (and (numbered? (car aexp))
	      (numbered? (car (cdr (cdr aexp)))))]))

(define: (lookup-in-entry-help [name : atom] [names : (list-of atom)] [values : (list-of atom)] [entry-f : (atom -> atom)]) : atom
  (cond [(null? names) (entry-f name)]
	[(eq? (car names) name) (car values)]
	[else (lookup-in-entry-help name (cdr names) (cdr values) entry-f)]))

(define: (lookup-in-entry [name : atom] [e : entry] [fail : (atom -> atom)]) : atom
  (lookup-in-entry-help name (car e) (car (cdr e)) fail))

(define: extend-table : (entry table -> table) #{cons @ entry Any})

(define: (lookup-in-table [name : atom] [t : table] [fail : (atom -> atom)]) : atom
  (cond
   [(null? t) (fail name)]
   [else (lookup-in-entry
	  name
	  (car t)
	  (lambda: ([name : atom])
		   (lookup-in-table name (cdr t) fail)))]))

(define-type-alias action (atom table -> SExp))

(define: (*const [e : SExp] [t : table]) : SExp
  (cond
   [(number? e) e]
   [(eq? e #t) #t]
   [(eq? e #f) #f]
   [else (build 'primitive e)]))

(define: (initial-table [name : atom]) : atom
  (error 'fail))

(define: (*identifier [e : atom] [tbl : table]) : SExp
  (lookup-in-table e tbl initial-table))

(define: (atom->action [e : atom]) : action
  (cond
   [(number? e) *const]
   #;[(string? e) (error "shouldn't get strings")] ;; FIXME - had to change the code
   [else
    (case e
      [(#t #f cons car cdr null? eq? atom? zero? add1 sub1 number?) *const]
      [else *identifier])]))

(define: (*quote [a : atom] [t : table]) : SExp (error 'fail))
(define: (*lambda [a : atom] [t : table]) : SExp (error 'fail))
(define: (*cond [a : atom] [t : table]) : SExp (error 'fail))
(define: (*application [a : atom] [t : table]) : SExp (error 'fail))

(define: (list->action [e : (list-of SExp)]) : action
  (cond*
   [atom? (car e) it
	  (case it
	    [(quote) *quote]
	    [(lambda) *lambda]
	    [(cond) *cond]
	    [else *application])]
   [else *application]))


(define: (expression->action [e : SExp]) : action
  (cond
   [(atom? e) (atom->action e)]
   [else (list->action e)]))

#;(define: (meaning [e : SExp] [t : table]) : SExp
((expression->action e) e t))

#;(define: (value [e : SExp]) : SExp
(meaning e '()))

#;(provide (all-defined))



