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

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

; +---------------+--------------------------------------------------
; | Provided Code |
; +---------------+

;;; (pad-with-zeros str n) -> string?
;;;   str : string?
;;;   n : integer?
;;; Puts 0's at the front of str so that the result is
;;; n characters long.
(define pad-with-zeros
  (lambda (str n)
    (string-append (make-string (- n (string-length str)) #\0) str)))

;;; (date-kernel year month day) -> date-kernel
;;;   year : integer?
;;;   month : integer?
;;;   day : integer?
;;; Create a date-kernel.  Does not check parameters.
(struct date-kernel (year month day))

;;; (date year month day) -> date-kernel
;;;   year : integer?
;;;   month : integer?
;;;   day : integer?
;;; Create a date-kernel.  Checks parameters for validity.
(define date
  (lambda (year month day)
    (cond
      [(not (integer? year))
       (error "date: invalid year" year)]
      [(or (not (integer? month))
           (< month 1)
           (> month 12))
       (error "date: invalid month" month)]
      [(or (not (integer? day))
           (< day 1)
           (> day 31))]
      [else
       (date-kernel year month day)])))

;;; (date? val) -> boolean
;;;   val : any/c
;;; Determine if val is a date.
(define date?
  (lambda (val)
    (and (date-kernel? val)
         (integer? (date-kernel-year val))
         (integer? (date-kernel-month val))
         (integer? (date-kernel-day val))
         (<= 1 (date-kernel-month val) 12)
         (<= 1 (date-kernel-day val) 31))))

;;; (date-year d) -> integer?
;;;   d : date?
;;; Extract the year from d
(define date-year date-kernel-year)

;;; (date-month d) -> integer?
;;;   d : date?
;;; Extract the month from d
(define date-month date-kernel-month)

;;; (date-day d) -> integer?
;;;   d : date?
;;; Extract the day from d
(define date-day date-kernel-day)

;;; (date->string d) -> string?
;;;   d : date?
;;; Convert d to a string in YYYY-MM-DD format.
(define date->string
  (lambda (d)
    (string-append (pad-with-zeros (number->string (date-year d)) 4)
                   "-"
                   (pad-with-zeros (number->string (date-month d)) 2)
                   "-"
                   (pad-with-zeros (number->string (date-day d)) 2))))

;;; (string->date str) -> date?
;;;   str : string?
;;; Convert str to a date.
;;; str should have the form YYYY-MM-DD [unverified]
(define string->date
  (lambda (str)
    (let ([parts (string-split str "-")])
      (date (string->number (list-ref parts 0))
            (string->number (list-ref parts 1))
            (string->number (list-ref parts 2))))))

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

#|
a. If we did not review the self checks at the start of class, review
the self checks with your partner.

b. Review the procedures provided above.
|#

#| A |#

; +-------------------------------+----------------------------------
; | Exercise 1: Representing Time |
; +-------------------------------+

#|
As long as we're representing dates, we should probably take the
time to represent times.  Create a structured type, `clock`, with
three fields: hours, minutes, and seconds.  You need not add the
husk to clocks (at least not yet).

Note: We call this `clock`, rather than `time`, because `time` is
a built-in Racket procedure.
|#

; +--------------------------------------------------+---------------
; | Exercise 2: From times to strings and back again |
; +--------------------------------------------------+

#|
a. Write a procedure, `(clock->string atime)`, that takes a clock as
a parameter and returns the clock time as a string of the form
`HH:MM:SS`.
|#

(define clock->string
  (lambda (atime)
    undefined))

#|
b. Write a procedure `(string->clock str)`, that takes a string 
of the form `HH:MM:SS` as a parameter and returns a clock structure.
|#

(define string->clock
  (lambda (str)
    undefined))

#| B |#

; +---------------------------------+--------------------------------
; | Exercise 3: Representing chirps |
; +---------------------------------+

#|
*Chirp* is a new Internet startup that lets you sent notes to your
friends, which they call "chirps".  (Creativity is not their strong
suit.)  Create a structured type, `chirp-kernel`, with the following
fields.

* `id`, a symbol we'll use to identify the chirp.
* `author`, a string that identifies the author of the chirp.
* `contents`, a string that contains the body of the chirp.
* `tags`, a list of strings
* `date`, a date that represents when the chirp was chirped.
* `time`, a clock that represents the time the chirp was chirped.
|#

; +--------------------------+---------------------------------------
; | Exercise 4: A chirp husk |
; +--------------------------+

#|
Create a procedure, `(chirp author contents tags date time)`, that
takes five parameters, verifies their types, and then creates a
chirp kernel using those four parameters.  What about the identifier?
You should generate that automatically with `(gensym "chirp")`.
(`gensym` is a procedure that generates a unique symbol.)
|#

(define chirp
  (lambda (author contents tags date time)
    undefined))

#| A |#

; +-----------------------------+------------------------------------
; | Exercise 5: Limiting access |
; +-----------------------------+

#|
One outstanding question that we had with `date` and `date-kernel`
was how we prevent the client from using `date-kernel`, rather than
`date`.  In this exercise, we'll explore one approach.
|#

#|
a. At the end of [the corresponding reading](../readings/structs), we 
described a husk-and-kernel-style technique for ensuring that our dates
had a particular form.  Copy that code into a new DrRacket window.
Make sure to include `date->string` and `string->date`, too.
|#

#|
b. Add the following to the top of the file (after the `#lang racket`).
These commands tell Racket which procedures and values that are defined
in the file are accessible to other programs.

    (provide date
             date?
             date-year
             date-month
             date-day
             date->string
             string->date)
|#

#|
c. Save the file as `/home/username/Desktop/dates.rkt`, substituting
your own username.
|#

#|
d. Open a new DrRacket window and type the following at the
top (after the `#lang racket`) and then click **Run**.  (Once
again, you should substitute your own username.)

    (require (file "/home/username/Desktop/dates.rkt"))
|#

#|
e. What do you expect to get when you type the following in the interactions
pane of the new windows.

    > (define date1 (date 2019 03 01))
    _____
    > date1
    _____
    > (define date2 (date 2019 22 01))
    _____
    > date2
    _____
|#

#|
f. Check your answer experimentally.

    > (define date1 (date 2019 03 01))
    _____
    > date1
    _____
    > (define date2 (date 2019 22 01))
    _____
    > date2
    _____
|#

#|
You should have seen that `date1` is a `date-kernel` structure
and that `date2` does not exist because the second call to `date`
is invalid.  
|#

#|
g.  Since `date1` is valid, we should be able to extract its fields.
What do you expect the results of the following to be?

    > (date-year date1)
    _____
    > (date-month date1)
    _____
    > (date-day date1)
    _____
    > (date? date1)
    _____
|#

#|
h. Check your answer experimentally.

    > (date-year date1)
    _____
    > (date-month date1)
    _____
    > (date-day date1)
    _____
    > (date? date1)
    _____
|#

#|
i. We've seen that `date1` is a `date-kernel` structure.

    > date1
    #<date-kernel>

What do you expect the result of the following to be?

    > (date-kernel? date1)
    _____
    > (date-kernel-year date1)
    _____
    > (date-kernel-month date1)
    _____
    > (date-kernel-day date1)
    _____
|#

#|
j. Check your answer experimentally.

    > (date-kernel? date1)
    _____
    > (date-kernel-year date1)
    _____
    > (date-kernel-month date1)
    _____
    > (date-kernel-day date1)
    _____
|#

#|
k. What do you expect to happen if we try to create a date with
`date-kernel`?

    > (define date3 (date-kernel 2019 03 01))
    > date3
    _____
|#

#|
l. Check your answer experimentally.

    > (define date3 (date-kernel 2019 03 01))
    > date3
    _____
|#

#|
m. What does this exercise suggest?

<TODO: Fill in your answer here.>
|#

#| B |#

; +------------------------------+-----------------------------------
; | Exercise 6: Lists of structs |
; +------------------------------+

#|
Typically, we want more than one of any struct we create.  Consider,
for example, the `chirp` structure we designed earlier.  In effect,
the Chirp service creates something like a list of chirps.  So let's
store them as a list.

    > (define chirp-list
        (list
          (chirp "grinco" "We have installed Chirp on campus."
                 (list "grinnell" "chirp")
                 (date 2019 02 27) (clock 11 03 00))
          (chirp "grinco" "We are so cutting edge!"
                 (list "grinnell" "chirp")
                 (date 2019 02 27) (clock 11 04 00))
          (chirp "rebelsky" "I love CSC 151."  (list "strange" "csc")
                 (date 2019 03 01) (clock 10 00 15))
          (chirp "rebelsky" "That wasn't me."  (list "strange" "other")
                 (date 2019 03 01) (clock 10 01 23))
          (chirp "rebelsky" "I think my son hacked my chirpstream."  
                 (list "strange" "paranoid")
                 (date 2019 03 01) (clock 10 01 55))
          (chirp "presHarris" "Welcome to GrinCo!" (list "grinnell" "grinpres")
                 (date 2019 02 28) (clock 15 02 05))
          (chirp "presHarris" "I love CSC 151." (list "strange" "csc" "grinpres")
                 (date 2019 03 01) (clock 10 01 55))
          (chirp "rebelsky" "I think my son hacked President Harris's chirpstream."  
                 (list "paranoid")
                 (date 2019 03 01) (clock 10 03 00))
          (chirp "grinco" "We are shutting down Chirp on campus"
                 (list "grinnell" "chirp" "its")
                 (date 2019 03 01) (clock 11 03 00))))
|#

#|
a. Write an expression that identifies all of the chirps from `rebelsky`
and creates a list of their contents

(define rebelsky-chirps
  (map chirp-contents (filter ???? chirp-list)))
|#

(define rebelsky-chirps
  undefined)

#|
b. Write an expression that determines how many chirps in the list
are by `presHarris`.  (Recall that `(tally lst pred?)` counts the number
of values in a list that meet a particular predicate.
|#

(define count-of-harris-chirps
  undefined)

; +----------------------------------------+-------------------------
; | Exercise 7: Lists of chirps, revisited |
; +----------------------------------------+

#|
a. Write a procedure, `(chirps-by chirps author)`, that
takes a list of chirps and a string as parameters, and returns a
list of the chirps that have that string in the author field.
|#

(define chirps-by
  (lambda (chirps author)
    undefined))

#|
b. Write a proceudre, `(chirps-tagged-as chirps tag)`, that
takes a list of chirps and a string as parameters, and returns
a list of the chirps that have that tag.

Note that this task is slightly different than the `chirps-by` 
task.
|#

(define chirps-tagged-as
  (lambda (chirps tag)
    undefined))

#| AB |#

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

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

; +-------------------------+----------------------------------------
; | Extra 1: Comparing date |
; +-------------------------+

#|
Write a predicate, `(date-before? date1 date2)`, that determines if
`date1` comes before `date2`.  You can decide whether or not to issue
an error if either value is not a date.
|#

(define date-before?
  (lambda (date1 date2)
    undefined))

; +---------------------------------+--------------------------------
; | Extra 2: Finding chirps by date |
; +---------------------------------+

#|
Write a procedure, `(chirps-from chirps start-date end-date)`, that
takes a list of chirps and two dates as parameters and returns all of
the chirps that come between those two dates.
|#

(define chirps-from
  (lambda (chirps start-date end-date)
    undefined))

; +-------------------------------+----------------------------------
; | Extra 3: Protecting your time |
; +-------------------------------+

#|
Rewrite the `clock` struct using a husk-and-kernel approach that
ensures that the hours, minutes, and seconds are reasonable.
|#

; +--------------------------+---------------------------------------
; | Extra 4: Comparing times |
; +--------------------------+

#|
Write a procedure, `(time-before? time1 time2)`, that returns
true (`#t`) if `time1` comes before `time2` and false otherwise.
|#

; +----------------------------+-------------------------------------
; | Extra 5: Displaying chirps |
; +----------------------------+

#|
Write a procedure `(chirp->string chrp)`, that takes a `chirp` as
a parameter and creates a string that represents it in a useful
way.

    > (define mychirp
        (chirp "rebelsky" "I love CSC 151."  (list "strange" "csc")
               (date 2019 03 01) (clock 10 00 15)))
    > (display (chirp->string mychirp))
    Output! chirp12312: At 10:00:15 on 2019-01-01, rebelsky said "I love CSC 151".
|#

(define chirp->string
  (lambda (chirp)
    undefined))

; +--------------------------+---------------------------------------
; | Extra 6: Hashing structs |
; +--------------------------+

#|
One of the disadvantages of lists is that they are relatively
slow to search.  That's one of the reasons we use hash tables
instead.  For example, we might hash chirps by their unique 
identifier.

Write a procedure, `(store-chirp! hash chrp)` that stores
a chirp in a hash table.

    > (define chirps (hash-new))
    > (define sample (chirp "someone" "something" (list "chirp")
                            (date 2019 02 28) (clock 20 21 22)))
    > (chirp-key sample)
    'chirp108319
    > (hash-has-key? chirps 'chirp108319)
    #f
    > (store-chirp! hash sample)
    > (hash-has-key? chirps 'chirp108319)
    #t
    > (chirp-contents (hash-ref chirps 'chirp108319))
    "something"
|#

