#lang racket
(require xml
         unstable/function
         unstable/text)

;; css/c : FlatContract
;; Recognizes representations of Cascading Style Sheets.
(define css/c (listof (cons/c text/c (listof (list/c text/c text/c)))))

(provide/contract
 [css/c flat-contract?]
 [css? (-> any/c boolean?)]
 [write-css (->* [css/c] [output-port?] void?)])

;; A Cascading Style Sheet (CSS) is a (Listof StyleDefn)
;; A Style Definition (StyleDefn) is a (cons Selectors (Listof PropDefn))
;; A Selectors is a Selector or a (NonEmptyListof Selector)
;; A Selector is a Symbol or String
;; A Property Definition (PropDefn) is a (list PropName PropVal)
;; A Property Name (PropName) is a Symbol or String
;; A Property Value (PropVal) is a Symbol or String

;; css? : Any -> Boolean
;; Reports whether a value is a CSS.
(define css? (flat-contract-predicate css/c))

;; write-css : CSS [OutputPort] -> Void
;; Writes a CSS datastructure as a proper text Cascading Style Sheet.
(define write-css
  (lambda/parameter (css [output #:param current-output-port])
    (for-each write-style-defn css)))

;; write-style-defn : StyleDefn [OutputPort] -> Void
;; Writes a style definition to a Cascading Style Sheet.
(define write-style-defn
  (lambda/parameter (style-defn [output #:param current-output-port])
    (write-selector (car style-defn))
    (display " {")
    (for-each write-prop-defn (cdr style-defn))
    (display " }\n")))

;; write-text : Text [OutputPort] -> Void
;; Writes a text field to a Cascading Style Sheet.
(define write-text
  (lambda/parameter (text [output #:param current-output-port])
    (display (text->string text))))

;; write-selector : Selector [OutputPort] -> Void
;; Writes a selector to a Cascading Style Sheet.
(define write-selector write-text)

;; write-prop-defn : PropDefn [OutputPort] -> Void
;; Writes a property definition to a Cascading Style Sheet.
(define write-prop-defn
  (lambda/parameter (prop-defn [output #:param current-output-port])
    (display " ")
    (write-prop-name (car prop-defn))
    (display " : ")
    (write-prop-val (cadr prop-defn))
    (display ";")))

;; write-prop-name : PropName [OutputPort] -> Void
;; Writes a property name to a Cascading Style Sheet.
(define write-prop-name write-text)

;; write-prop-val : PropVal [OutputPort] -> Void
;; Writes a property value to a Cascading Style Sheet.
(define write-prop-val write-text)

(provide/contract
 [write-xexpr (->* [xexpr/c] [output-port?] void?)])

(define write-xexpr
  (lambda/parameter (xexpr [output #:param current-output-port])
    (write-xml/content (xexpr->xml xexpr))))

(provide/contract
 [create-webpage (string? xexpr/c . -> . void?)]
 [create-stylesheet (string? css/c . -> . void?)])

;; create-stylesheet : String CSS -> Void
;; Writes an individual stylesheet to a file.
(define (create-stylesheet filename css)
  (let* ([out-port (open-output-file filename #:exists 'replace)])
    (write-css css out-port)
    (close-output-port out-port)))

;; create-webpage : String XExpr -> Void
;; Writes an individual webpage to a file.
(define (create-webpage filename xexpr)
  (let* ([out-port (open-output-file filename #:exists 'replace)])
    (write-xexpr xexpr out-port)
    (close-output-port out-port)))
