"
Assume and export: 
  (struct dir (name dirs files))
  (struct file (name size content))

A directory is 
  (make-dir symbol (listof directory) (listof file))
A file is 
  (make-file symbol number symbol)

Export: 
  create-dir : string[path-name] -> directory."

(define-signature errorS (check-arg check-arity check-proc))

(define errorU
  (unit/sig errorS
    (import)
    
    ;; check-arg : sym bool str str TST -> void
    (define (check-arg pname condition expected arg-posn given)
      (unless condition
	(error pname "expected <~a> as ~a argument, given: ~e" expected arg-posn given)))

    ;; check-arity : sym num (list-of TST) -> void
    (define (check-arity name arg# args)
      (if (>= (length args) arg#)
	  (void)
	  (error name "expects at least ~a arguments, given ~e" arg# (length args))))

    ;; check-proc : sym (... *->* ...) num (union sym str) (union sym str) -> void
    (define (check-proc proc f exp-arity arg# arg-err)
      (unless (procedure? f)
	(error proc "procedure expected as ~s argument; given ~e" arg# f))
      (unless (and (number? (arity f)) (= (arity f) exp-arity))
	(error proc
	  "procedure of ~a expected as ~s argument; given procedure of ~s arguments" 
	  arg-err arg# (arity f))))))

(define-signature dirS
  ((struct dir (name dirs files))
   (struct file (name size content))
   create-dir get-path-to-file))
  
(define dirU
  (let ((f-s (lambda (x) (if (link-exists? x) 0 (file-size x)))))
    (unit/sig dirS
      (import errorS plt:userspace^)

      (define-struct dir (name dirs files))
  
      (define-struct file (name size content))
  
      ;; create-dir : path -> directory
      (define (create-dir a-path)
	(check-arg 'create-dir (string? a-path) "string" "first" a-path)
	(if (directory-exists? a-path)
	    (car (explore (list a-path)))
	    (error 'create-dir "not a directory: ~e" a-path)))
  
      ;; explore : (listof directory-names) -> (listof directory)
      (define (explore dirs)
	(map (lambda (d) 
	       (let-values ([(fs ds) (pushd d directory-files&directories)]) 
		 (make-dir
		   (string->symbol (my-split-path d))
		   (explore (map (lambda (x) (build-path d x)) ds))
		   (map make-file
		     (map string->symbol fs)
		     (map f-s (map (lambda (x) (build-path d x)) fs))
		     (map (lambda (x) (if (link-exists? x) 'link null)) fs)))))
	  dirs))
  
      (define (my-split-path d)
	(let-values ([(base name mbd?) (split-path d)])
	  (if (string? base) name d)))
	       
      ;; pushd : directory-name ( () -> X ) -> X
      (define (pushd d f)
	(let ((current (current-directory)))
	  (dynamic-wind
	    (lambda () (current-directory d))
	    (lambda () (f))
	    (lambda () (current-directory current)))))
  
      ;; directory-files&directories : 
      ;;  () -> (values (listof file-names) (listof directory-names))
      (define (directory-files&directories)
	(let ((contents (directory-list)))
	  (values
	    (filter (lambda (x) (or (file-exists? x) (link-exists? x))) contents)
	    (filter (lambda (x)
		      (and (directory-exists? x) (not (link-exists? x))))
	            contents))))
  
      ;; filter: (X -> bool) (listof X) -> (listof X)
      ;;  (define (filter p? l)
      ;;    (foldr (lambda (fst rst) (if (p? fst) (cons fst rst) rst)) '() l))

  
      ;; get-file-content : file -> (int -> string)
      ;; to read a file on demand as a string
      ;; option to expand the library ... 
      ;; cache it ... 
      (define (get-file-content f)
        (read-string (file-size f) 
                     (open-input-file (symbol->string (file-name f)))))
      
      (define (get-path-to-file a-file in-dir)
	(local ((define (add-dir to-path)
		  (string-append (symbol->string (dir-name in-dir))
				 "/" to-path)))
	       (cond [(ormap (lambda (f)
			       (symbol=? (file-name f) a-file))
			     (dir-files in-dir))
		      (add-dir (symbol->string a-file))]
		     [else (local ((define found-path
				     (ormap (lambda (a-dir)
					      (get-path-to-file a-file a-dir))
					    (dir-dirs in-dir))))
				  (cond [(string? found-path)
					 (add-dir found-path)]
					[else false]))])))
		     
      ; Test: 
      ; (define G (create-dir "."))
      ; (define foo (assf (lambda (x) (eq? 'dir-test.ss (file-name x))) (dir-files G)))
      ; (get-file-content foo)
      )))

(compound-unit/sig
  (import (PLT : plt:userspace^))
  (link
    (XXX : dirS (dirU ERR PLT))
    (ERR  : errorS (errorU)))
  (export (open XXX)))

(define functionU (require-library-unit/sig "functior.ss"))
(define prettyU (require-library-unit/sig "prettyr.ss"))

(define-signature htmlS
  (make-html-hrule html-hrule?
                   make-html-list html-list-items html-list-numbered? html-list?
                   make-html-paragraph html-paragraph-contents html-paragraph?
                   make-html-section html-section-heading html-section-level
                   html-section-center-heading? html-section-contents html-section?
                   make-html-table html-table-header html-table-rows html-table-grid?
                   html-table?
                   make-html-link html-link-URL html-link-text html-link?
                   make-html-center html-center-contents html-center?
                   make-html-image html-image-file html-image-description html-image?
                   make-html-page html-page-title html-page-bgcolor html-page-contents
                   html-page? 
                   html-to-file category-filename))

(define HtmlU
  (unit/sig htmlS
    (import mzlib:function^ mzlib:pretty-print^)
    (define-struct html-hrule ())
    (define-struct html-list (items numbered?))
    (define-struct html-paragraph (contents))
    (define-struct html-section (heading level center-heading? contents))
    (define-struct html-table (header rows grid?))
    (define-struct html-link (URL text))
    (define-struct html-center (contents))
    (define-struct html-image (file description))
    (define-struct html-page (title bgcolor contents))
    
    (define (category-filename sym)
      (string-append (symbol->string sym) ".html"))

    (define (enclose-in-tag tag str . attr)
      (if (null? attr)
          (format "<~a>~a</~a>
          " tag str tag)
          (format "<~a ~a>~a</~a>
          " tag (car attr) str tag)))
    
    (define (render-lohtml html-elts)
      (apply string-append 
             (map (lambda (item) (format " ~a " (render-html item))) html-elts)))
    
    ;; html-elt or list of html-elt -> string
    (define (render-contents contents . tag)
      (let ([content-str
             (if (list? contents)
                 (render-lohtml contents)
                 (render-html contents))])
        (if (null? tag) content-str (enclose-in-tag (car tag) content-str))))
    
    ;; enclose-each-and-combine : string list-of html-elts -> string
    (define (enclose-each-and-combine tag html-elts)
      (apply string-append
             (map (lambda (elt) (enclose-in-tag tag (render-html elt)))
                  html-elts)))

    (define (render-atom html-elt)
      (cond [(string? html-elt) html-elt]
            [(number? html-elt) (number->string html-elt)]
            [(symbol? html-elt) (symbol->string html-elt)]
            [else (error "Given html-elt ~a is not an html-atom" html-elt)]))
    
    ;; render-html : html-elt -> string
    (define (render-html html-elt)
      (cond [(string? html-elt) html-elt]
            [(number? html-elt) (number->string html-elt)]
            [(symbol? html-elt) (symbol->string html-elt)]
            [(html-hrule? html-elt) "<hr>"]
            [(html-list? html-elt)
             (let ([items (apply string-append
                                 (map (lambda (item)
                                        (format "<li> ~a" (render-html item)))
                                      (html-list-items html-elt)))])
               (if (html-list-numbered? html-elt)
                   (enclose-in-tag "ol" items)
                   (enclose-in-tag "ul" items)))]
            [(html-paragraph? html-elt) 
             (render-contents (html-paragraph-contents html-elt) "p")]
            [(html-section? html-elt) 
             (let ([header (enclose-in-tag (format "h~a" (html-section-level html-elt))
                                           (render-atom (html-section-heading html-elt)))])
               (if (html-section-center-heading? html-elt)
                   (string-append (enclose-in-tag "center" header)
                                  (render-contents (html-section-contents html-elt)))
                   (string-append header 
                                  (render-contents (html-section-contents html-elt)))))]
            [(html-table? html-elt) 
             (let ([table-contents
                    (apply string-append
                           (cons (enclose-in-tag 
                                  "tr" (enclose-each-and-combine 
                                        "th" (map render-atom (html-table-header html-elt))))
                                 (map (lambda (row)
                                        (enclose-in-tag 
                                         "tr" (enclose-each-and-combine "td" row)))
                                      (html-table-rows html-elt))))])
               (if (html-table-grid? html-elt)
                   (enclose-in-tag "table" table-contents "border=3")
                   (enclose-in-tag "table" table-contents)))]
            [(html-link? html-elt) 
             (enclose-in-tag "a" (render-atom (html-link-text html-elt)) 
                             (format "href=~s" (html-link-URL html-elt)))]
            [(html-center? html-elt)
             (render-contents (html-center-contents html-elt) "center")]
            [(html-image? html-elt)
             (format "<IMG SRC=\"~a\" ALT=\"~a\">"
                     (html-image-file html-elt) 
                     (render-atom (html-image-description html-elt)))]
            [(html-page? html-elt) 
             (enclose-in-tag "html"
                             (string-append
                              (enclose-in-tag "title" (html-page-title html-elt))
                              (enclose-in-tag 
                               "body" 
                               (render-contents (html-page-contents html-elt))
                               (format "bgcolor=~a" (html-page-bgcolor html-elt)))))]))
    
;    (define (popup-html html-elt)
;      (let* ([html-string (render-html html-elt)]
;             [port (open-input-string html-string)])
;        (open-url port)
;        (close-input-port port)
;        #t))
    
    (define (html-to-file html-elt filename)
      (let ([html-string (render-html html-elt)]
            [port (open-output-file filename 'replace)])
        (pretty-display html-string port)
        (close-output-port port)
        #t))
    ))

(define-signature hwk2S
  (make-album album-artist album-title album-category album?
              make-format format-type format-copies format-price format?
              make-stock stock-album stock-cd stock-tape stock?
              make-lst lst-first lst-rest lst?
              gen-page/1 gen-page/2 gen-page/3 gen-page/4 gen-page/5
              produce-test-page produce-test-page/list
	      filename-for-stock
              update-db base-inventory
              ))

(define CoreHwk2U
  (unit/sig hwk2S
    (import htmlS plt:userspace^)
    
    (define-struct album (artist title category))
    (define-struct format (type copies price))
    (define-struct stock (album cd tape))
    
    ;; inventory is a list of stock
    
    (define-struct lst (first rest))
    
    (define (make-list->list mlist)
      (cond [(empty? mlist) null]
            [else (cons (lst-first mlist) (make-list->list (lst-rest mlist)))]))
    
    (define (^inca a-stock category)
      (let ([c (lambda (e) (apply (apply compose 
                                         (list album-category stock-album)) 
                                  (cons e '())))])
        (symbol=? category (c a-stock))))
    
    (define (!*appup?* an-update a-stock)
      (printf "in *appup*~n")
      (and (string=?
            (album-artist (stock-album a-stock))
            (with-handlers 
                ([exn:else? 
                  (lambda (exn) (cons exn empty))])
              (update-artist an-update)))
           (string=? 
            (update-title an-update)
            (with-handlers
                ([exn:struct?
                  (lambda (exn) (cons exn empty))])
              (album-title (stock-album a-stock))))))
    
    (define (cohabitators name selector1 a-stock an-update selector2)
      (let ((cohorts (lambda (format-what selector update-what)
                       (+ (format-what (selector a-stock))
                          (update-what an-update)))))
        (make-format name 
                     (cohorts format-copies selector1 update-change-in-copies)
                     (cohorts format-price selector2 update-change-in-price))))
    
    (define (upst** an-update a-stock)
      (cond [(symbol=? (update-type an-update) 'cd)
             (make-stock (stock-album a-stock)
                         (cohabitators 'cd stock-cd a-stock an-update stock-cd)
                         (stock-tape a-stock))]
            [(symbol=? (update-type an-update) 'tape)
             (make-stock (stock-album a-stock)
                         (stock-cd a-stock)
                         (cohabitators 'tape stock-tape a-stock an-update stock-tape))]))
    
    (define (gad++ a-stock)
      (let ([gap (lambda (a-format) 
                   (if a-format (format-price a-format) "N/A"))])
        (let ((lst->list (lambda (l)
                           (let lp ((l l))
                             (if (null? l) l
                                 (cons (lst-first l) (lp (lst-rest l)))))))
              (list->lst (lambda (l)
                           (foldr make-lst empty l))))
          (list->lst
           (apply list (map (lambda (f)
                              (f a-stock))
                            (lst->list
                             (make-lst
                              (lambda (e) (album-artist (stock-album e)))
                              (make-lst
                               (lambda (e) (album-title (stock-album e)))
                               (make-lst 
                                (lambda (e) (gap (stock-cd e)))
                                (make-lst
                                 (lambda (e) (gap (stock-tape e)))
                                 empty)))))))))))

    (define (get-stock type inventory)
      (filter (lambda (a-stock) (in-category? a-stock type))
              inventory))
    
; filename-for-stock : stock -> string

(define (char-converter c)
  (if (char=? c #\space) #\- c))

(define (spaces/dashes s)
  (list->string (map char-converter (string->list s))))

(define (filename-for-stock stock)
  (string-append (spaces/dashes (album-title (stock-album stock)))
                 ".html"))

    (define (update-db apply-update? update-stock an-update inventory)
      (foldr (lambda (a-stock rst)
	       ((lambda (tst tb fb)
		  (if tst (tb a-stock rst) (fb a-stock rst)))
		(apply-update? an-update a-stock)
		(lambda (f r) (cons (update-stock an-update f) r))
		cons))
             () inventory))

    (define (get-table-rows inventory)
      (map (lambda (stock-item)
             ((lambda (data)
                (if (lst? data) (make-list->list data) data))
              (get-album-data stock-item)))
           inventory))

    (define (make-section-table inventory get-table-rows)
      (make-html-table
       (let ([fst "Artist"] [snd "Album"])
	 (append (list fst snd) (list "CD Price") (list "Cassette Price")))
       (get-table-rows inventory)
       #t))
    
    (define (produce-test-page html-item-or-list filename)
       (html-to-file (make-html-page "210 Test Page" "WHITE" html-item-or-list)
                     filename))

    (define produce-test-page/list produce-test-page)

    ;; gen-first-page : inventory -> boolean
    ;; generates web page for an inventory
    (define (gen-first-page inventory get-stock get-table-rows)
      (let ([classical (get-stock 'classical inventory)]
            [rock (get-stock 'rock inventory)]
            [blues (get-stock 'blues inventory)])
        (html-to-file
         (make-html-page 
          "Sammy's Music Prices" "WHITE" 
          (make-html-section 
           "Sammy's Music" 1 #t
           (list
            (make-html-paragraph
             (make-html-center "Check out our Inventory!"))
            (make-html-hrule)
            (make-html-section "Classical" 2 #f (make-section-table classical get-table-rows))
            (make-html-paragraph "")
            (make-html-hrule)
            (make-html-section "Rock" 2 #f (make-section-table rock get-table-rows))
            (make-html-paragraph "")
            (make-html-hrule)
            (make-html-section "Blues" 2 #f (make-section-table blues get-table-rows))
            (make-html-paragraph "")
            (make-html-hrule)
            )))
         "210Music.html")
        #t))

    (define (make-gen-page sub-in-category? sub-apply-update? 
                           sub-update-stock sub-get-album-data
		           get-stock get-table-rows)
      (if sub-in-category?
	  (set! in-category? sub-in-category?)
	  (set! in-category? ^inca))
      (if sub-apply-update?
	  (set! apply-update? sub-apply-update?)
	  (set! apply-update? !*appup?*))
      (if sub-update-stock
	  (set! update-stock sub-update-stock)
	  (set! update-stock upst**))
      (if sub-get-album-data
	  (set! get-album-data sub-get-album-data)
	  (set! get-album-data gad++))
      (lambda (inventory)
        (gen-first-page inventory get-stock get-table-rows)))
    
    (define in-category? #f)
    (define apply-update? #f)
    (define update-stock #f)
    (define get-album-data #f)
    (define update-artist #f)
    (define update-title #f)
    (define update-type #f)
    (define update-change-in-copies #f)
    (define update-change-in-price #f)
    
    (define (gen-page/1 in-category? inventory)
      ((make-gen-page in-category? #f #f #f get-stock get-table-rows) inventory))
    
    (define (gen-page/2 inventory) 
      ((make-gen-page #f #f #f #f get-stock get-table-rows) inventory))
    
    (define (gen-page/3 get-album-data inventory) 
      ((make-gen-page #f #f #f get-album-data get-stock get-table-rows) inventory))
    
    (define (gen-page/4 in-category? get-album-data inventory)
      ((make-gen-page in-category? #f #f get-album-data get-stock get-table-rows)
       inventory))

    (define (gen-page/5 get-stock get-table-data inventory)
      ((make-gen-page #f #f #f #f get-stock get-table-rows) inventory))
           
    ;; inventory data
    
    (define a1 (make-album "Bach" "Brandenburg Concertos" 'classical))
    (define a2 (make-album "Pink Floyd" "The Wall" 'rock))
    (define a3 (make-album "Vivaldi" "The Four Seasons" 'classical))
    (define a4 (make-album "Beatles" "Abbey Road" 'rock))
    (define a5 (make-album "BB King" "The Thrill is Gone" 'blues))
    (define a6 (make-album "Beatles" "Yellow Submarine" 'rock))
    
    (define base-inventory
      (list (make-stock a1 (make-format 'cd 4 8.99)
                        (make-format 'cassette 3 5.99))
            (make-stock a2 (make-format 'cd 8 12.99)
                        (make-format 'cassette 0 7.99))
            (make-stock a3 (make-format 'cd 6 8.99) #f)
            (make-stock a4 #f (make-format 'cassette 9 12.99))
            (make-stock a5 (make-format 'cd 4 11.99) #f)
            (make-stock a6 (make-format 'cd 5 11.99) (make-format 'tape 6 8.99))
            ))

    ))

(define hwk4U
  (compound-unit/sig
    (import (PLT : plt:userspace^))
    (link
     (FUNCTION : mzlib:function^ (functionU))
     (PRETTY : mzlib:pretty-print^ (prettyU))
     (XXX : dirS (dirU ERR PLT))
     (ERR  : errorS (errorU))
     (HTML : htmlS (HtmlU FUNCTION PRETTY))
     (HWK2 : hwk2S (CoreHwk2U HTML PLT)))
    (export (open HTML) (open HWK2) (open XXX))))

hwk4U

