#lang racket
(require gigls/unsafe)
(provide (all-defined-out))

;;; File:
;;;   files-lab.rkt
;;; Authors:
;;;   Janet Davis
;;;   Samuel A. Rebelsky
;;;   Jerod Weinman
;;;   YOUR NAME HERE
;;; Summary:
;;;   Starting code for the lab on files.

; +---------------------+------------------------------------------------------
; | Procedures Provided |
; +---------------------+

;;; Procedure:
;;;   read-line
;;; Parameters:
;;;   source, an input port
;;; Purpose:
;;;   Read one line of input from a source and return that line
;;;   as a string.
;;; Produces:
;;;   line, a string
;;; Preconditions:
;;;   The source is open for reading. [Unverified]
;;; Postconditions:
;;;   Has read one line of characters from the source (thereby affecting
;;;     future calls to read-char and peek-char).
;;;   line represents the characters in the file from the "current" point 
;;;     at the time read-line was called until the first end-of-line 
;;;     or end-of-file character.
;;;   line does not contain a newline.
(define read-line
  (lambda (source)
    ; Read all the characters remaining on the line and
    ; then convert them to a string.
    (list->string (read-line-of-chars source))))

;;; Procedure:
;;;   read-line-of-chars
;;; Parameters:
;;;   source, an input port
;;; Purpose:
;;;   Read one line of input from a source and return that line
;;;   as a list of characters.
;;; Produces:
;;;   chars, a list of characters.
;;; Preconditions:
;;;   The source is open for reading. [Unverified]
;;; Postconditions:
;;;   Has read characters from the source (thereby affecting
;;;     future calls to read-char and peek-char).
;;;   chars represents the characters in the file from the
;;;     "current" point at the time read-line was called
;;;     until the first end-of-line or end-of-file character.
;;;   chars does not contain a newline.
(define read-line-of-chars
  (lambda (source)
    ; If we're at the end of the line or the end of the file,
    ; then there are no more characters, so return the empty list.
    (cond
      ; If we're at the end of the file, there are no more characters,
      ; so return the empty list.
      [(eof-object? (peek-char source)) 
       null]
      ; If we're at the end of the line, we're done with the line
      ; skip over the end-of-line character and return the empty list.
      [(char=? (peek-char source) #\newline) 
       (read-char source) 
       null]
      ; Otherwise, read the current character, read the remaining
      ; characters, and join them together.
      [else 
       (cons (read-char source) (read-line-of-chars source))])))

;;; Procedure:
;;;   first-line
;;; Parameters:
;;;   file-name, a string that names a file.
;;; Purpose:
;;;   Reads and displays the first line of the file.
;;; Produces:
;;;   Absolutely nothing.
;;; Preconditions:
;;;   There is a file by the given name.
;;;   It is possible to write to the standard output port.
;;; Postconditions:
;;;   Does not affect the file.
;;;   The first line of the named file has been written to
;;;     the standard output.
(define first-line
  (lambda (file-name)
    (let ([source (open-input-file file-name)])
      (display "The first line of '")
      (display file-name)
      (display "' is")
      (newline)
      (display (read-line source))
      (newline)
      (close-input-port source))))

;;; Procedure: 
;;;   sum-of-file
;;; Parameters:
;;;   file-name, a string that names a file.
;;; Purpose:
;;;   Sums the values in the given file.
;;; Produces:
;;;   sum, a number.
;;; Preconditions:
;;;   file-name names a file. [Unverified]
;;;   That file contains only numbers. [Unverified]
;;; Postconditions:
;;;   Returns a number.
;;;   That number is the sum of all the numbers in the file.
;;;   Does not affect the file.
(define sum-of-file
  (lambda (file-name)
    (let kernel ([source (open-input-file file-name)]) ; named let
      (let ([nextval (read source)])
        (cond
          ; Are we at the end of the file?
          ; Then stop and return 0 for "no numbers read".
          ; Here, we're taking advantage of 0 being the arithmetic identity.
          [(eof-object? nextval) 
           (close-input-port source) 
           0]
          ; Have we just read a number?
          ; If so, add it to the sum of the remaining numbers.
          [(number? nextval) 
           (+ nextval (kernel source))]
          ; Hmmm ... not a number.  Skip it.
          [else 
           (kernel source)])))))

;;; Procedure:
;;;   integer-store-divisors
;;; Parameters:
;;;   dividend, a natural number
;;;   file-name, a string that names a file
;;; Purpose:
;;;   Compute all the divisors of dividend and store them
;;;   to the named file.
;;; Produces:
;;;   [None; Called for the side effect of creating a file]
;;; Preconditions:
;;;   It must be possible to open the desired output file.
;;;   dividend must be a non-negative, exact, integer-
;;; Postconditions: 
;;;   The file with name file-name now contains many integers.
;;;   All the values in that file evenly divide dividend.
(define integer-store-divisors
  (lambda (dividend file-name)
    (integer-store-divisors-kernel dividend (open-output-file file-name) 1)))

;;; Helper:
;;;   integer-store-divisors-kernel
;;; Parameters:
;;;   dividend, the number we're working with
;;;   target, an output port
;;;   trial-divisor, the smallest divisor we should try
;;; Purpose:
;;;   Stores all divisors of dividend that are at least as
;;;     large as trial-divisor to target.
;;; Produces:
;;;   Nothing.
;;; Preconditions:
;;;   It is possible to write to the target port.
;;;   Both trial-divisor and dividend are natural numbers.
;;; Postconditions:
;;;   All divisors of dividend that are at least as large as
;;;     trial-divisor have been added to target.
;;;   target is still open for writing
(define integer-store-divisors-kernel
  (lambda (dividend target trial-divisor)
    ; We only continue to work when the trial-divisor is not
    ; larger than the dividend.  Note that I'm using cond because
    ; cond permits multiple operations when the test succeeds.
    (cond 
     [(<= trial-divisor dividend)
      ; Okay, does the current trial-divisor evenly divide dividend?
      (when (zero? (remainder dividend trial-divisor))
        ; It does!  Write it to the file
        (write trial-divisor target)
        (newline target))
      ; Continue with any other potential divisors
      (integer-store-divisors-kernel dividend target (+ 1 trial-divisor))]
      ; If the trial divisor is bigger than the dividend, then we're
      ; done, so close the port and stop.
     [else 
      (close-output-port target)])))

; +-------+--------------------------------------------------------------------
; | Added |
; +-------+
