#lang racket
(require csc151)
(require rackunit)
(require racket/undefined)

;; CSC-151 (Fall 2021)
;; Lab: Data Abstraction
;; Authors: YOUR NAMES HERE
;; Date: THE DATE HERE
;; Acknowledgements:
;;   ACKNOWLEDGEMENTS HERE

; +-------------------------+----------------------------------------
; | Documentation for names |
; +-------------------------+

;;; (name prefix given middle family suffix) -> name?
;;;   prefix : string? or #f
;;;   given : string?
;;;   middle : string? or #f
;;;   family : string? or #f
;;;   suffix : string? or #f

;;; (name? val) -> boolean?
;;;   val : any?
;;; Determine if val is a name.

;;; (name-prefix name) -> string? or #f
;;;   name : name?
;;; Get the prefix of a name.  Returns #f if the name has no prefix.

;;; (name-given name) -> string?
;;;   name : name?
;;; Get the given name from a name.

;;; (name-middle name) -> string? or #f
;;;   name : name?
;;; Get the middle name from a name.  Returns #f if the name does
;;; not contain a middle name.

;;; (name-family name) -> string? or #f
;;;   name : name?
;;; Get the family name from a name.  Returns #f if the name lacks
;;; a family name.

;;; (name-suffix name) -> string? or #f
;;;   name : name?
;;; Get the suffix from a name.  Returns #f if the name lacks a 
;;; suffix.

; +------------------------------+-----------------------------------
; | Names implemented with lists |
; +------------------------------+

(define name
  (lambda (prefix given middle family suffix)
    (cond
      [(and (not (string? prefix)) prefix)
       (error "name: Invalid prefix:" prefix)]
      [(not (string? given))
       (error "name: Invalid given name:" given)]
      [(and (not (string? middle)) middle)
       (error "name: Invalid middle:" middle)]
      [(and (not (string? family)) family)
       (error "name: Invalid family:" family)]
      [(and (not (string? suffix)) suffix)
       (error "name: Invalid suffix:" suffix)]
      [else
       (list prefix given middle family suffix)])))

(define name?
  (let ([string-or-false?
         (lambda (val)
           (or (string? val) (false? val)))])
    (lambda (val)
      (and (list? val)
           (= 5 (length val))
           (string-or-false? (name-prefix val))
           (string? (name-given val))
           (string-or-false? (name-middle val))
           (string-or-false? (name-family val))
           (string-or-false? (name-suffix val))))))

(define name-prefix (section list-ref <> 0))
(define name-given (section list-ref <> 1))
(define name-middle (section list-ref <> 2))
(define name-family (section list-ref <> 3))
(define name-suffix (section list-ref <> 4))


; +--------------------------------+---------------------------------
; | Names implemented with vectors |
; +--------------------------------+

#|
(define name
  (lambda (prefix given middle family suffix)
    (cond
      [(and (not (string? prefix)) prefix)
       (error "name: Invalid prefix:" prefix)]
      [(not (string? given))
       (error "name: Invalid given name:" given)]
      [(and (not (string? middle)) middle)
       (error "name: Invalid middle:" middle)]
      [(and (not (string? family)) family)
       (error "name: Invalid family:" family)]
      [(and (not (string? suffix)) suffix)
       (error "name: Invalid suffix:" suffix)]
      [else
       (vector prefix given middle family suffix)])))

(define name?
  (let ([string-or-false?
         (lambda (val)
           (or (string? val) (false? val)))])
    (lambda (val)
      (and (vector? val)
           (= 5 (vector-length val))
           (string-or-false? (name-prefix val))
           (string? (name-given val))
           (string-or-false? (name-middle val))
           (string-or-false? (name-family val))
           (string-or-false? (name-suffix val))))))

(define name-prefix (section vector-ref <> 0))
(define name-given (section vector-ref <> 1))
(define name-middle (section vector-ref <> 2))
(define name-family (section vector-ref <> 3))
(define name-suffix (section vector-ref <> 4))
|#

; +-------------------------------+----------------------------------
; | Names implemented with hashes |
; +-------------------------------+

#|
(define name
  (lambda (prefix given middle family suffix)
    (let ([n (make-hash)])
      (when prefix
        (hash-set! n 'prefix prefix))
      (hash-set! n 'given given)
      (when middle
        (hash-set! n 'middle middle))
      (when family
        (hash-set! n 'family family))
      (when suffix
        (hash-set! n 'suffix suffix))
      n)))

(define name?
  (let ([ok-key?
         (lambda (key hash)
           (string? (hash-ref hash key "")))])
    (lambda (val)
      (and (hash? val)
           (ok-key? 'prefix val)
           (string? (hash-ref val 'given))
           (ok-key? 'middle val)
           (ok-key? 'family val)
           (ok-key? 'suffix val)))))

(define name-prefix (section hash-ref <> 'prefix #f))
(define name-given (section hash-ref <> 'given))
(define name-middle (section hash-ref <> 'middle #f))
(define name-family (section hash-ref <> 'family #f))
(define name-suffix (section hash-ref <> 'suffix #f))
|#

; +---------------------------------------------+--------------------
; | Names implemented with bar-separated-values |
; +---------------------------------------------+

; You will finish this portion in the lab.

#|
(define name
  (let ([convert (lambda (str) (or str ""))])
    (lambda (prefix given middle family suffix)
      (string-append (convert prefix)
                     "|" given
                     "|" (convert middle)
                     "|" (convert family)
                     "|" (convert suffix)))))

(define name?
  (lambda (val)
    undefined))

(define name-prefix 
  (lambda (name)
    undefined))

(define name-given
  (lambda (name)
    undefined))

(define name-middle 
  (lambda (name)
    undefined))

(define name-family 
  (lambda (name)
    undefined))

(define name-suffix
  (lambda (name)
    undefined))
|#

; +--------------+---------------------------------------------------
; | Sample names |
; +--------------+

(define simon (name #f "Simon" #f #f #f))
(define ada (name "Countess" "Ada" "Augusta" "Byron" #f))
(define babbage (name #f "Charles" #f "Babbage" #f))
(define hopper (name #f "Grace" "Murray" "Hopper" #f))
(define qe2 (name "Queen" "Elizabeth" #f #f "II"))
(define clay (name #f "Roy" #f "Clay" "Sr"))

; +----------------+-------------------------------------------------
; | Tests of names |
; +----------------+

(test-true "Admiral Grace Murray Hopper" (name? hopper))
(test-true "Charles Babbage" (name? babbage))
(test-true "Countess Ada Augusta Byron" (name? ada))
(test-true "QEII" (name? qe2))
(test-true "Roy Clay Sr." (name? clay))
(test-true "Simon" (name? simon))

(test-equal? "ada/prefix" (name-prefix ada) "Countess")
(test-equal? "ada/given" (name-given ada) "Ada")
(test-equal? "ada/middle" (name-middle ada) "Augusta")
(test-equal? "ada/family" (name-family ada) "Byron")
(test-false "ada/suffix" (name-suffix ada))

(test-false "babbage/prefix" (name-prefix babbage))
(test-equal? "babbage/given" (name-given babbage) "Charles")
(test-false "babbage/middle" (name-middle babbage))
(test-equal? "babbage/family" (name-family babbage) "Babbage")
(test-false "babbage/suffix" (name-suffix babbage))

(test-false "clay/prefix" (name-prefix clay))
(test-equal? "clay/given" (name-given clay) "Roy")
(test-false "clay/middle" (name-middle clay))
(test-equal? "clay/family" (name-family clay) "Clay")
(test-equal? "clay/suffix" (name-suffix clay) "Sr")

(test-false "hopper/prefix" (name-prefix hopper))
(test-equal? "hopper/given" (name-given hopper) "Grace")
(test-equal? "hopper/middle" (name-middle hopper) "Murray")
(test-equal? "hopper/family" (name-family hopper) "Hopper")
(test-false "hopper/suffix" (name-prefix hopper))

(test-equal? "qe2/prefix" (name-prefix qe2) "Queen")
(test-equal? "qe2/given" (name-given qe2) "Elizabeth")
(test-false "qe2/middle" (name-middle qe2))
(test-false "qe2/family" (name-family qe2))
(test-equal? "qe2/suffix" (name-suffix qe2) "II")

(test-equal? "simon/given" (name-given simon) "Simon")
(test-false "simon/family" (name-family simon))
(test-false "simon/middle" (name-middle simon))
(test-false "simon/prefix" (name-prefix simon))
(test-false "simon/suffix" (name-suffix simon))

#| AB |#

; +-------------------------+----------------------------------------
; | Exercise 0: Preparation |
; +-------------------------+

#|
a. Begin the lab as we always do (or always should): Introduce
yourself to your partner, describe your strength and areas to
improve, discuss work styles, etc.

b. Quickly review the three implementations of names in the sections
above.

c. Compare your broad answers to the double-dagger problems.  If
you have questions, feel free to reach out to the course staff. 

Note: We'll be implementing your answers to those problems in this
lab, so you should feel comfortable having not-quite-perfect answers.
|#

#| A |#

; +-----------------------------------+------------------------------
; | Exercise 1: From names to strings |
; +-----------------------------------+

#|
a. The practice of presenting your given name before your family
name is not universal; other cultures consider it important to
start with your family name.

Document, write tests for, and implement a procedure, `(name->fg)`.
that takes a name as a parameter and returns a string consisting
of the family name (if there is a family name) and the given name,
separated by spaces.

    > (name->fg simon)
    "Simon"
    > (name->fg clay)
    "Clay Roy"
|#

;;; (name->fg name) -> 
;;;   name :
;;;
(define name->fg
  (lambda (name)
    undefined))

#|
(test-equal? "(name->fg simon)"
             (name->fg simon)
             "Simon")
(test-equal? "(name->fg clay)"
             (name->fg clay)
             "Clay Roy")
|#

#|
b. Try each implementation of names to ensure that your process
works correctly independently of how names are impelmented.  (You
can try each implementation by commenting out the first version 
and uncommenting another.)

You do not need to enter anything here; you may just have to fix
your code.
|#

#| B |#

; +----------------------------------------------+-------------------
; | Exercise 2: From names to strings, revisited |
; +----------------------------------------------+

#|
a. Document, write tests for, and implement a procedure,
`name->simple-string`, that takes a name as input and returns it
as an appropriate string.  Make sure to rely on the procedures
like `name-prefix` rather than the underyling implementation.

    > (name->simple-string qe2)
    "Queen Elizabeth II"
    > (name->simple-string simon)
    "Simon"
    > (name->simple-string babbage)
    "Charles Babbage"
|#

;;; (name->simple-string name) -> 
;;;   name : 
;;; 
(define name->simple-string
  (lambda (name)
    undefined))

#|
(test-equal? "QE2"
             (name->simple-string qe2)
             "Queen Elizabeth II")
|#

#|
b. Once again, make sure your procedure works for each of the three.
implementations.
|#

; +------------------------------+-----------------------------------
; | Exercise 3: Ordering strings |
; +------------------------------+

#|
You may recall from the reading that there are more things we
want to do with names than just convert them to strings using some
process.  For example, we might want to compare two names so that
we can put them in order.  Here's the start of a procedure to
do so.
|#

;;; (name<=? name1 name2) -> boolean?
;;;   name1 : name
;;;   name2 : name
;;; Determine whether name1 should come before name2 in traditional
;;; alphabetical ordering.
(define name<=?
  (lambda (name1 name2)
    (string<=? (name->sortable name1) (name->sortable name2))))

#|
a. As you can tell, this relies on `name->sortable`.  Guess what?  It's
your job to write it for this exercise.

    > (name->sortable babbage)
    "Babbage, Charles"
    > (name->sortable simon)
    "Simon"
    > (name->sortable qe2)
    "Queen Elizabeth II"
    > (name->sortable ada)
    "Byron, Ada Augusta"

For information on sort order, you refer to 

  <https://www.webpages.uidaho.edu/cte419/Offline-Modules/M6/ARMA-12_Filing_Rules.htm>
|#

;;; (name->sortable name) -> string?
;;;   name : name?
;;; Convert a name to a form appropriate for sorting/indexing.  Uses
;;; a variant of the ARMA filing rules borrowed from UIdaho.
(define name->sortable
  (lambda (name)
    ""))

#|
b. Using your new version of `name->sortable`, demonstrate what happens
when you sort our list of names.

   > (map name->simple-string 
          (sort (list hopper babbage ada qe2 clay simon) name<=?))

<TODO: Enter the results of this experiment.
|#

#| A |#

; +------------------------------------+-----------------------------
; | Exercise 4: Another implementation |
; +------------------------------------+

#|
In the self-check for the corresponding reading, you considered
representing names as "bar-separated values".  Finish that
implementation, which you can find started above, and then verify
that all of the procedures you've written still work as expected.
|#


#| B |#

; +---------------------------+--------------------------------------
; | Exercise 5: Pointed types |
; +---------------------------+

#|
Suppose we want to represent points in two-space, which have an
x coordinate and a y corrdinate.  
|#

#|
a. Decide on the list of procedures you think it would be useful
for someone to have.

Constructors 

* ...

Predicates

* ...

Extracting fields

* ...

Other useful procedures

...
|#

#|
b. Pick one of the following implementation styles and implement the
procedures you just described.

* A pair of the x and y coordinates.
* A string of the form "x,y".
* A complex number.
* A hash table.
|#

#|
c. Document, write tests for, and implement a procedure,
`(distance point1 point2)`, that finds the distance between
two points.

In case you've forgotten, the distance between two points is
the square root of the sum of the x offset and the y offset.

E.g., given the points p1=(x1,y1) and p2=(x2,y2), the distance
between p1 and p2 is (sqrt (+ (sqr (- x2 x1)) (sqr (- y2 y1)))).
|#

;;; (distance point1 point2) -> real?
;;;   point1 : point?
;;;   point2 : point?
;;; Find the distance beween point1 and point2.
(define distance
  (lambda (point1 point2)
    undefined))

#| AB |#

; +---------------------------+--------------------------------------
; | For those with extra time |
; +---------------------------+

#|
If you find that you have extra time, you might consider one or more
of the following procedures.
|#

; +--------------------------+---------------------------------------
; | Extra 1: Pointed testing |
; +--------------------------+

#|
Write some tests for your point procedures.
|#

; +------------------------------------+-----------------------------
; | Extra 2: Alternate implementations |
; +------------------------------------+

#|
Sketch how you might approach the other three implementations of
points.
|#

; +--------------------------+---------------------------------------
; | Extra 3: More alternates |
; +--------------------------+

#|
Try a second implementation of points.
|#

