;; Write a simple calculator

(define (calc-add num1 num2)
  (+ num1 num2))

(define (calc-mult num1 num2)
  (* num1 num2))

(define (calc-avg num1 num2)
  (/ (+ num1 num2) 2))

;; calculator0 : symbol number number -> number
;; runs indicated calculator command on given numbers
(define (calculator0 cmd num1 num2)
  (cond [(symbol=? cmd 'add) (calc-add num1 num2)]
        [(symbol=? cmd 'mult) (calc-mult num1 num2)]
        [(symbol=? cmd 'avg) (calc-avg num1 num2)]))

;; This works fine at the DrScheme prompt, but what if we want a
;; fancier interface to our calculator?  One that prompts for inputs,
;; or perhaps one that uses a web interface?  We need to separate our
;; calculator into two parts: one that gets the numbers (the
;; interface) and one that runs the calculator command on those
;; numbers

;;---------------------------------------------------------------

;; A first stab at an interface for getting two numbers:

;(printf "Enter two numbers (separated by whitespace)~n")
;(read)
;(read)

;; The above code does get two numbers, but it doesn't do anything
;; with them because it doesn't know _what_ to do with them.  In other 
;; words, the interface has information, but needs to give it to an
;; unknown function.  Thus, the interface should take the function as an
;; argument.  We don't want to specify the calculator in the interface
;; because then we can't reuse the interface in different programs.

;; get-two-nums1 : (num num -> num) -> num
;; requests two numbers, then passes them to given function
(define (get-two-nums1 func)
  (begin
    (printf "Enter two numbers (separated by whitespace)~n")
    (func (read) (read))))

;; Now, rewrite the calculator to use this interface

;; calculator1 : symbol -> number
;; uses the get-two-nums1 interface to get two numbers and send to calculator
(define (calculator1 cmd)
  (cond [(symbol=? cmd 'add) (get-two-nums1 calc-add)]
        [(symbol=? cmd 'mult) (get-two-nums1 calc-mult)]
        [(symbol=? cmd 'avg) (get-two-nums1 calc-avg)]))

;;---------------------------------------------------------------

;; What if we wanted a different interface though?  I'll use a
;; different textual one here for simplicity, but you could imagine
;; making a GUI interface, a web interface, or a voice-driven
;; interface as an alternative. 

;; get-two-nums2 : (num num -> num) -> num
;; requests two numbers, then passes them to given function
(define (get-two-nums2 func)
  (begin 
    (printf "Enter a number: ")
    (let ([num1 (read)])
      (begin
        (printf "Enter a number: ")
        (let ([num2 (read)])
          (func num1 num2))))))

;; Now, rewrite the calculator to use this interface

;; calculator2 : symbol -> number
;; uses the get-two-nums2 interface to get two numbers and send to calculator
(define (calculator2 cmd)
  (cond [(symbol=? cmd 'add) (get-two-nums2 calc-add)]
        [(symbol=? cmd 'mult) (get-two-nums2 calc-mult)]
        [(symbol=? cmd 'avg) (get-two-nums2 calc-avg)]))

;;---------------------------------------------------------------

;; Notice that calculator1 and calculator2 are the same minus the
;; interface that they use.  So, we can make the interface a parameter
;; to the calculator function

;; An interface is a function (num num -> num) -> num

;; calculator3 : symbol interface -> num
;; uses the given interface to get numbers to pass to the calculator
(define (calculator3 cmd an-interface)
  (cond [(symbol=? cmd 'add) (an-interface calc-add)]
        [(symbol=? cmd 'mult) (an-interface calc-mult)]
        [(symbol=? cmd 'avg) (an-interface calc-avg)]))

;;---------------------------------------------------------------

;; Having to specify the interface every time you use the calculator
;; is odd though.  Usually, when we write an application, we provide
;; it with one consistent interface.  We therefore want to customize
;; the interface at one point in time (when we release the software),
;; and take the commands at another time (when someone uses the
;; software).

;; We can separate these two times (release and use) in our code by
;; returning functions as values.  Our first function takes in the
;; interface--the second function takes the command:

;; A calculator is a function (symbol -> number)

;; make-calculator : interface -> calculator
;; creates a calcultor with the given interface
(define (make-calculator an-interface)
  (lambda (cmd)
    (cond [(symbol=? cmd 'add) (an-interface calc-add)]
          [(symbol=? cmd 'mult) (an-interface calc-mult)]
          [(symbol=? cmd 'avg) (an-interface calc-avg)])))
   
(define calc1 (make-calculator get-two-nums1))
(define calc2 (make-calculator get-two-nums2))
