(use goo)
(dc <bank> (<any>))
 (dp name (<bank> => <str>))
 (dp customers (<bank> => <col>) (vec)) 
(dc <customer> (<any>))
 (dp accounts (<customer> => <col>) (vec))
 (dp first-name (<customer> => <str>))
 (dp last-name (<customer> => <str>))
(dc <account> (<any>))
 (dp balance (<account> => <int>) 0) 
(dc <checking-account> (<account>))
(dc <savings-account> (<account>))
(dv *minimum-balance* 100) (dv *minimum-balance-for-no-fee* 1000)
(dv *monthly-fee* 10)
(dv *monthly-interest* 0.02)
(dm credit (account|<account> amount|<int>)
    (opf (balance account) (+ _ amount))
      #t)
(dm debit (account|<account> amount|<int> => <log>)
  (opf (balance account) (- _ amount))
  #t)
(dm can-handle-debit? (account|<account> amount|<int> => <log>)
    (>= (balance account) amount))
(dm transfer (from-account|<account> to-account|<account> amount|<int> => <log>)
  (when (and (> amount 0)
             (can-handle-debit? from-account amount))
    (debit from-account amount)
    (credit to-account amount)))
(dm charge-monthly-fee (account|<account>)
  (when (< (balance account) *minimum-balance-for-no-fee*)
    (debit account *monthly-fee*)))
(dm earn-interest (account|<account>)
  (credit account (floor (* (balance account) *monthly-interest*))))
(dm earn-interest (account|<checking-account>)
    )
(dm to-str (account|<account> => <str>)
  (cat (if (isa? account <checking-account>)
           "Checking "
           "Savings ")
       "Account: "
       (to-str (balance account))))
(dm new-customer (first-name|<str> last-name|<str> => <customer>)
  (def customer (new <customer>))
      (set (first-name customer) first-name)
  (set (last-name customer) last-name)
        
    customer)
(dm find-customer (customer-list|<col> prop-getter match-val|<any> => (t? <customer>))
  (esc found-it
    (do (fun (current-customer)
          (when (= (prop-getter current-customer) match-val)
            (found-it current-customer)))
        customer-list)))
(dm to-str (customer|<customer> => <str>)
  (def cat-pair-with-newline (fun (str1 str2)
                               (cat str1 "\n" str2)))
  (def prepend-spaces (fun (str)
                        (cat "  " str)))
  (cat (last-name customer)
       ", "
       (first-name customer)
       "\n"
                                          (fold+ cat-pair-with-newline (map prepend-spaces (map to-str (accounts customer))))))
(dm new-bank (bank-name|<str> => <bank>)
  (def bank (new <bank>))
  (set (name bank) bank-name)
  bank)
(dm get-all-accounts (bank|<bank> => <col>)
  (def all-accounts-in-list-of-lists-form (map accounts (customers bank)))
  (def all-accounts-flat (fold+ cat all-accounts-in-list-of-lists-form))
  all-accounts-flat)
(dm charge-monthly-fees (bank|<bank>)
  (do charge-monthly-fee
      (get-all-accounts bank)))
(dm earn-monthly-interest (bank|<bank>)
  (do earn-interest
      (get-all-accounts bank)))
(dm add-customer (bank|<bank> customer|<customer>)
  (add! (customers bank) customer))
(dm new-customer-account (bank|<bank> customer|<customer> type|(t< <account>) => (t? <account>))
    (when (mem? (customers bank) customer)
    (def account (new type))
    (add! (accounts customer) account)
    account))
(dm to-str (bank|<bank>)
  (def cat-pair-with-newline (fun (str1 str2)
                               (cat str1 "\n" str2)))
  (cat (name bank)
       "\n"
       "-------------\n"
       (fold+ cat-pair-with-newline (map to-str (customers bank)))))
(df do-banking-stuff ()
    (def bank (new-bank "Charges'r'us"))
  
    (def alice (new-customer "Alice" "Hax0r"))
  (def ben   (new-customer "Ben"   "Bitdiddle"))
  (def beef  (new-customer "0x"    "deadbeef"))
  (add-customer bank alice)
  (add-customer bank ben)
  (add-customer bank beef)
    (def alice-savings  (new-customer-account bank alice <savings-account>))
  (def alice-checking (new-customer-account bank alice <checking-account>))
  
  (def ben-savings  (new-customer-account bank ben <savings-account>))
  (def ben-checking (new-customer-account bank ben <checking-account>))
  (def beef-savings  (new-customer-account bank beef <savings-account>))
  (def beef-checking (new-customer-account bank beef <checking-account>))
    (credit alice-savings 100)   (credit ben-savings 10000)   (credit beef-savings 20)   
    (transfer alice-savings alice-checking 40)
  (transfer ben-savings ben-checking 1000)
  (transfer beef-savings beef-checking 10)
    (charge-monthly-fees bank)
  (earn-monthly-interest bank)
    (msg out "%s\n" (to-str bank)))
(export
  do-banking-stuff)