(define-structure surflet surflet-interface
  (open scheme-with-scsh
	surflets
	let-opt
	receiving
	define-record-types
	(subset srfi-13 (string-downcase string-join))
	(subset srfi-1 (find filter-map split-at remove))
	sunet-utilities
	surflet-requests)
(begin

;;; Spaceship components
;;; Size (Class ...)
;;; Arms (Photontorpedos, Phaser)
;;; Shields
;;; Drive (Impuls, Warp)
;;; Extras (Double Casing, Trans Warp Drive, etc.)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DATA
(define-record-type ship-data :ship-data
  (make-ship-data class max-crew med-sections max-shuttles arm-types 
		  max-arms max-shield extras build-time)
  ship-data?
  (class ship-data-class)
  (max-crew ship-data-max-crew)
  (med-sections ship-data-med-sections)
  (max-shuttles ship-data-max-shuttles)
  (arm-types ship-data-arm-types)
  (max-arms ship-data-max-arms)
  (max-shield ship-data-max-shield)
  (extras ship-data-extras)
  (build-time ship-data-build-time))

;;; This are the orderable ships with their data. The following
;;; procedures will refer to this list to get the data for a ship
;;; class.
(define ships
  (map (lambda (data)
	 (apply make-ship-data data))
       ;; class-name	max-crew med-sections	max-shuttles
       ;;               (possible) arm-types 
       ;;		max-arms (TW)   max-shields (TJ)
       ;;		extras
       ;;		build-time (months)
       '(("Constitution" 400	14		10
			(7 torpedo2)	
			17000	729000
			(double-casing tractor shuttle-ramp)
			6)
	 ("Excelsior"	 570	14		#f
			(8 7 torpedo2)	
			41000	2106000
			(double-casing tractor transwarp)
			7)
	 ("Ambassador"	550	15		#f
			(9 8 7 torpedo1)	
			62500	4298000
			(double-casing tractor)
			8)
	 ("Galaxy"	 760	17		25
			(10 9 8 torpedo2 torpedo-M/AM)
			61200	5400000
			(double-casing tractor discus wide-angle-firing
					transporter captains-yacht
					life-maintenance) 
			10))
       ))

;;; All orderable classes extracted from ship data list.
(define classes (map ship-data-class ships))

;;; All orderable drives.
(define drives
  '("Impuls" "Warp"))

;;; All orderable arm types.
(define arm-types
  '((7 . "Phaser Type VII")  (8 . "Phaser Type VIII")
    (9 . "Phaser Type IX") (10 .  "Phaser Type X")
    (torpedo1 . "Class 1 Torpedo")
    (torpedo2 . "Photon-Torpedo-System Class 2")
    (torpedo-M/AM . "Photonen-Torpedo-System M/AM")))

;;; All orderable extras. The ship data contains a list of extras a
;;; ship class may have.
(define extras
  '((double-casing . "Double Casing")
    (tractor . "Tractor Ray")
    (shuttle-ramp . "Shuttle Ramp")
    (transwarp . "Trans Warp Drive (experimental)")
    (discus . "Detachable Discus Section")
    (wide-angle-firing . "300� Fire Angle")
    (transporter . "Extra Transporters (+35)")
    (captains-yacht . "Captain's Yacht")
    (life-maintenance . "Extended Life Maintenance System (ELMS)")
    ))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Calculation

;;; Main entry point. Shows a welcome message, and invites to order a
;;; spaceship.
(define (main req . update-text+class+armed?+shields?+drive)
  (let-optionals update-text+class+armed?+shields?+drive
      ((update-text #f)
       (def-class #f)
       (def-armed? #f)
       (def-shields? #f)
       (def-drive #f))
    (let* ((class-radios (make-radios
			  (checked-radio classes def-class)))
	   (drive-radios (make-radios
			  (checked-radio drives def-drive)))
	   (armed-checkbox (make-checkbox def-armed?))
	   (shield-checkbox (make-checkbox def-shields?))
	   (req (send-html/suspend
		 (lambda (new-url)
		   (generate-main-page new-url update-text
				       class-radios drive-radios 
				       armed-checkbox shield-checkbox))))
	   (bindings (get-bindings req))
	   (class (input-field-value (car class-radios) bindings))
	   (armed? (input-field-value armed-checkbox bindings))
	   (shields? (input-field-value shield-checkbox bindings))
	   (drive (input-field-value (car drive-radios) bindings)))
      (cond
       ((not class)
	(main req "Please specifiy a class." class armed? shields? drive))
       ((not drive)
	(main req "Please specifiy a drive." class armed? shields? drive))
       (else
	(specify-components req #f class armed? shields? drive))))))

;;; Step 2ff: Let the customer specify the crew size, the arming, the
;;; shields and the extras, showing a result page at the end.
(define (specify-components req update-text class armed? shields? drive)
  (receive (size med-beds)
      (get-size req class)
    (receive (weapons energy shield)
	(get-armed+shields req class armed? shields?)
      (order-page req
		  class
		  weapons energy
		  shield
		  drive
		  size
		  med-beds
		  (get-extras req class)))))

;;; Ask the customer about details for the arming and the shield of
;;; his spaceship. The selectable components are taken from the ship
;;; data list.
(define (get-armed+shields req class armed? shields? . 
			   maybe-update-text+weapons+energy+shield)
  (let-optionals maybe-update-text+weapons+energy+shield
      ((update-text #f)
       (def-weapons #f)
       (def-energy #f)
       (def-shield #f))
    (let* ((checkboxes+text
	    (and armed?
		 (map (lambda (type)
			(let ((text (cdr (assoc type arm-types))))
			  (cons (make-annotated-checkbox
				 text
				 (and def-weapons (member? text def-weapons)))
				text)))
		      (ship-data-arm-types (ship-ref class)))))
	   (energy-input (and armed?
			      (if def-energy 
				  (make-number-field def-energy)
				  (make-number-field))))
	   (shield-input (and shields?
			      (if def-shield
				  (make-number-field def-shield)
				  (make-number-field))))
	   (req (send-html/suspend
		 (lambda (new-url)
		   (generate-armed+shield-page new-url update-text
					       checkboxes+text energy-input
					       shield-input))))
	   (bindings (get-bindings req))
	   (weapons (and armed?
			 (filter-map (lambda (checkbox+text)
				       (input-field-value (car checkbox+text) bindings))
				     checkboxes+text)))
	   (energy (and armed?
			(input-field-value energy-input bindings)))
	   (shield (and shields?
			(input-field-value shield-input bindings)))
	   (complains 
	    (remove not
		    (list
		     (and armed?
			  (null? weapons)
			  need-weapons)
		     (and armed?
			  (check-bounded-number-field class energy positive-energy 
						      ship-data-max-arms 
						      arms-boundary))
		     (and shields?
			  (check-bounded-number-field class shield positive-shield 
						      ship-data-max-shield 
						      shield-boundary))))))
      (if (null? complains)
	  (values weapons energy shield)
	  (get-armed+shields req class armed? shields?
			     `(p ,@(map (lambda (complain) `(,complain (br)))
					complains))
			     weapons energy shield)))))

;;; Ask the customer about extras he want for his ship. The selectable
;;; items are taken from the ship data list.
(define (get-extras req class)
  (let* ((checkboxes+text (map (lambda (extra)
				 (cons (make-annotated-checkbox extra)
				       (cdr (assoc extra extras))))
			       (ship-data-extras (ship-ref class))))
	 (req (send-html/suspend
	       (lambda (new-url)
		 (generate-extras-page new-url class checkboxes+text))))
	 (bindings (get-bindings req)))
    (filter-map (lambda (checkbox+text)
		  (and (input-field-value (car checkbox+text) bindings)
		       (cdr checkbox+text)))
		checkboxes+text)))

;;; Show the selected components of the customers ship and ask him for
;;; ordering the whole thing (without telling him, how long this will
;;; take, of course ;-) )
(define (order-page req class weapons arms-energy shield-energy drive 
		    size med-beds extras)
  (send-html/suspend
   (lambda (new-url)
     (generate-order-page new-url class weapons arms-energy 
			  shield-energy drive size med-beds
			  extras)))
  (send-html/finish
   (generate-finish-page (calculate-build-time class weapons arms-energy 
					       shield-energy drive size
					       extras)
			 req)))

;;; This returns the number of months that are probably necessary to
;;; build the ship. The data are taken from experience of the last
;;; five years :-)
(define (calculate-build-time class weapons arms-energy shield-energy 
			      drive size extras)
  (+ (ship-data-build-time (ship-ref class))
     (if weapons
	 (+ (length weapons)
	    (if (> arms-energy 40000) 2 1))
	 0)
     (if shield-energy
	 (if (> shield-energy 2200000)
	     3
	     2)
	 0)
     4					; for impulse drive
     (if (string=? drive "Warp") 2 0)	; extra for warp drive
     (if (> size 300) 3 2)		; This includes the med-beds.
     (length extras)
     ))

;;; This asks the customer to specify how many crew members his ship
;;; will have. We only check that there is at least one crew member
;;; and the maximum crew member for a class is not exceeded.
(define (get-size req class . maybe-update-text)
  (let* ((update-text (:optional maybe-update-text #f))
	 (size-input (make-number-field))
	 (req (send-html/suspend
	       (lambda (new-url)
		 (generate-size-page new-url update-text
				     class size-input))))
	 (bindings (get-bindings req))
	 (size (input-field-value size-input bindings)))
    (if (or (not size)
	    (<= size 0))
	(get-size req class positive-size)
	(let* ((ship (ship-ref class))
	       (max-size (ship-data-max-crew ship) ))
	  (if (<= size max-size)
	      (values size (ship-data-med-sections ship))
	      (get-size req class (complain-size class max-size)))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Page and text generating

;;; Title of each HTML page.
(define (make-title)
  '(title "Spaceship Builder"))

;;; The following procedure do the actual HTML composing for the
;;; different steps. Nothing exciting here.

;;; Main page HTML.
(define (generate-main-page new-url update-text
			    class-radios drive-radios
			    armed-checkbox shield-checkbox)
  `(html 
    ,(make-title)
    (body
     (h1 "Welcome to the Spaceship Builder Web Site!")
     (p "Here you can build your own space ship.")
     (h2 "Step 1 -- Selecting components")
     ,(and update-text `(font (@ (color "red")) ,update-text))
     (surflet-form ,new-url
       GET
       (table
	(tr (@ (valign "top"))
	    (td "My spaceship is of class")
	    (td (table ,@(table-radios class-radios classes))))
	(tr (td (table (tr (td ,armed-checkbox) (td "My spaceship is armed.")))))
	(tr (td (table (tr (td ,shield-checkbox) (td "My spaceship has shields.")))))
	(tr (@ (valign "top"))
	    (td "My spaceship's drive is ")
	    (td (table ,@(table-radios drive-radios drives))))
	(tr))
       ,(make-submit-button "Submit choices"))
     ,(return-links main-return-link))))

;;; Size page HTML.
(define (generate-size-page new-url update-text class size-input)
  `(html 
    ,(make-title)
    (body 
     (h2 "Step 2 -- Specify crew size")
     (p "Please specify how many crew members your ship of class " ,class " 
will have. The builder will add as many treatment beds and accomodations as necessary to 
fullfill UFP Spaceship Crew's Rights Act 023/1000285.0/AB")
     ,(print-update update-text)
     (surflet-form ,new-url
       GET
       (table
	(tr (td "My ship is for a crew of ")
	    (td ,size-input)
	    (td "people"))
	(tr (td ,(make-submit-button)))))
     ,(return-links first-page-return-link main-return-link))))

;;; Text displayed if crew size is too big for the spaceship's class.
(define (complain-size class size)
  (format #f "Spaceships of the ~a class can only have 
up to ~a crew members. Please adjust the selected size or choose another 
spaceship class" class size))

;;; Text displayed if size is not positive.
(define positive-size
  "According to UFP Spaceship Crew Consistence Act 
025/100030.2/BX there must be at least one person on each spaceship. Thus, please 
specify a positive number.")

;;; HTML page generator for Step 3 and 4: Arming and shields.
;;; Shows the possible arming for selection and asks about the amount
;;; of energy for arming and shields.
(define (generate-armed+shield-page new-url update-text 
				    checkboxes+text energy-input
				    shield-input)
  `(html 
    ,(make-title)
    (body
     (surflet-form ,new-url
       GET
       ,(print-update update-text)
       ,(if (and checkboxes+text energy-input)
	    `((h2 "Step 3 -- Specify arming")
	      (p "Please select one or more arm types for your ship and amount of energy you want to spent for it or them, respectively.")
	      (p
	       (table ,@(map (lambda (checkbox+text)
			       `(tr (td ,(car checkbox+text))
				    (td ,(cdr checkbox+text))))
			     checkboxes+text)))
	      (p
	       (table (tr (td "Use") (td ,energy-input) (td "TW for weapons.")))))
	    '(h2 "Step 3 -- Done: No Arming"))
       ,(if shield-input
	    `((h2 "Step 4 -- Specify shields")
	      (p "Please specify the amount of energy you want to spent for your shields:")
	      (table (tr (td ,shield-input) (td "TJ"))))
	    '(h2 "Step 4 -- Done: No shields"))
       ,(make-submit-button "OK"))
     ,(return-links first-page-return-link main-return-link))))

;;; Text displayed, if arms' energy is not positive.
(define positive-energy 
  "Please specify a positive number for the amount of arms energy.")

;;; Text displayed if shield's energy is not positive.
(define positive-shield 
  "Please specify a positive number for the amount of shield energy.")

;;; Text displayed if no weapons are selected, though the customer
;;; wished to have an armed spaceship.
(define need-weapons
  "Please specify at least one weapon or turn back to main selection page and deselect arming.")

;;; Text displayed if arms' energy is too high for the spaceship class.
(define (arms-boundary class max-energy)
  (format #f "Spaceships of class ~a cannot spend more than ~a TW for their arming."
	  class max-energy))

;;; Text displayed if shield's energy is too high for the spaceship class.
(define (shield-boundary class max-shield)
  (format #f "Spaceships of class ~a cannot spend more than ~a TJ for their shields."
	  class max-shield))

;;; HTML page generator for the summary (order) page.
;;; Shows alle the details chosen for construction.
(define (generate-order-page new-url class weapons arms-energy
			     shield-energy drive size med-beds extras)
  `(html ,(make-title)
	 (body 
	  (h2 "Ordering")
	  (p "This are the data of your spaceship:")
	  (ul
	   (li "Class \"" ,class "\"")
	   (li ,size " crew members")
	   (li ,med-beds " treatment beds")
	   (li ,(if weapons
		    (format #f "Armed with ~a, powered with ~a TW"
			    (text-enumerate weapons) arms-energy)
		    "No arms"))
	   (li ,(if shield-energy 
		    (format #f "~a TJ of shield energy" shield-energy)
		    "No shields"))
	   (li ,drive " drive")
	   ,@(map (lambda (extra-text)
		    `(li ,extra-text))
		  extras))
	  (surflet-form ,new-url
	    POST
	    ,(make-submit-button "Order now"))
	  ,(return-links first-page-return-link main-return-link))))

;;; HTML page generator for the extras page.
;;; Shows a list of possible extras of this spaceship class for selection.
(define (generate-extras-page new-url class checkboxes+text)
  `(html 
    ,(make-title)
    (body
     (h2 "Step 5 -- Extras")
     (p "Select one or more extras that are available for
spaceships of class " ,class ":")
     (surflet-form ,new-url
       GET
       (table ,@(map (lambda (checkbox+text)
		       `(tr (td ,(car checkbox+text))
			    (td ,(cdr checkbox+text))))
		     checkboxes+text))
       ,(make-submit-button "OK"))
     ,(return-links first-page-return-link main-return-link))))
			      
;;; HTML page generator. 
;;; Shows the final page with a "Thank you" and an estimate for the
;;; construction time. Furthermore, it shows the customers host-name
;;; or its IP-adress.
(define (generate-finish-page months req)
  `(html ,(make-title)
	 (body (h2 "Ordered")
	       (p "Thank you for your ordering.")
	       (p "Your order has been registered. "
		  "We will contact you (" 
		  ,(host-name-or-ip (socket-remote-address 
				     (surflet-request-socket req)))
		  ") as soon as the ship is built.")
	       (p "This will take about " ,months " months.")
	       ,(return-links first-page-return-link main-return-link))))

(define main-return-link
  '(url "/" "Return to main menu."))

(define (previous-page-return-link prev)
  `(url ,prev "Return to previous page."))

(define first-page-return-link
  '(url "/surflet/spaceship.scm" "Return to spaceship builder entry page."))

(define (return-links . links)
  `(p
    (hr)
    ,@(map (lambda (link) (list link '(br)))
	   links)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Helping functions

;;; Creates HTML-table rows, putting a radio in front of a text.
(define (table-radios radios texts)
  (map (lambda (radio text)
	 `(tr (td ,radio) (td ,text)))
       radios texts))

;;; Adds the 'checked attribute to a radio button, if its value
;;; (stored in LIST) equals to CHECK-THIS. With this, the selected
;;; value of a radio list can be restored if the page is redisplayed.
(define (checked-radio list check-this)
  (map (lambda (elem)
	 (if (equal? elem check-this)
	     (cons elem `(@ (checked)))
	     elem))
       list))

;;; Returns the ship-data structure for the class of name NAME.
(define (ship-ref name)
  (find (lambda (ship) (string=? (ship-data-class ship) name))
	ships))

;;; "Prints" UPDATE-TEXT in red color, i.e. in an HTML paragraph
;;; block.
(define (print-update update-text)
  `(p ,(and update-text `(font (@ (color "red")) ,update-text))))

;;; Same as R5RS member, except that it returns either #t or #f.
(define (member? thing list)
  (if (member thing list) #t #f))

;;; Makes an enumeration of the strings in TEXT-LIST:
;;; (text-enumerate '("John", "Bill", "Juliet")
;;; => "John, Bill and Juliet"
;;; with reasonable results if the list's length is smaller than 2.
(define (text-enumerate text-list)
  (let ((len (length text-list)))
    (case len
      ((0) "")
      ((1) (car text-list))
      ((2) (string-append (car text-list) " and " (cadr text-list)))
      (else
       (receive (head last)
	   (split-at text-list (- len 1))
	 (string-append (string-join head ", ")
			" and "
			(car last)))))))

;;; Does a check on the value of a number-input-field. Abstraction
;;; over two cases occured above. Best explained by the use above.
(define (check-bounded-number-field class input positiv selector boundary)
  (if (or (not input)
	  (<= input 0))
      positiv
      (let ((max (selector (ship-ref class))))
	(if (<= input max)
	    #f
	    (boundary class max)))))

  ))