;;;; This defines the overall look of most or all Yenta pages.

;;; In what follows, "content" is the actual variable part of the page that the user
;;; presumably really cares about.  This is everything that -isn't- the navbar, the
;;; logo, the big icon, the bookmark icons, and the list of bookmarks.  It's assumed
;;; that the two toplevels here (sk:page-preamble and sk:page-postamble) will be called
;;; via {} inclusions around the actual content; if you call one, you must certainly
;;; call the other, since the former starts a table that the latter finishes.

;;; We're using the package prefix "sk:" because this defines the "skeleton" of Yenta pages.

(yreq "Utilities/yenta-utils")		; For join-strings.

;;;; +++ Global enable for links.
;;; sk:wrap-href will be a no-op (returning its input string) if *sk:wrap-href-disable* is non-NIL.
;;; The intent is to allow the page put up after Yenta has been quite to have no active links.
(defvar *sk:wrap-href-disable* nil)	; Do NOT make this a yenta-var!  Otherwise, it'll be saved T and we'll never come back up!

;;;; +++ Global constants.
;;; Colors.
(define *sk:bgcolor-content-active* #f)	; Whether or not to use background colors for the content.
(define *sk:bgcolor-body-active* #f)	; Whether or not to use background colors for the entire page.
(define *sk:bgcolor-body* "gray")
(define *sk:bgcolor-logo* "black")
(define *sk:bgcolor-navbar* "black")
(define *sk:bgcolor-icons* "black")
(define *sk:bgcolor-bookmark-icons* "black")
(define *sk:bgcolor-content* "gray")
(define *sk:bgcolor-lines* "black")
;;; Sizes.
(define *sk:logo-width* 171)
(define *sk:logo-height* 95)
(define *sk:navbar-icon-width* 80)
(define *sk:navbar-icon-height* 95)
(define *sk:main-icon-width* 171)
(define *sk:main-icon-height* 171)
(define *sk:bookmark-icon-width* 57)
(define *sk:bookmark-icon-height* 72)
(define *sk:content-width* 560)
(define *sk:content-cellpadding* 10)	; To keep the content from smashing into the very edges of the table cell.

;;;; +++ Building the actual page.

(define (sk:page-preamble-internal title)
  (format nil
"<html> <head>
<title>~A</title>
</head>
<body~:[~*~; bgcolor=\"~A\"~]>~2&~
<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">
  <tr>
    ~A
    <td>"
	  title *sk:bgcolor-body-active* *sk:bgcolor-body*
	  (sk:make-thin-vertical-line)))

;;; This assumes that images follow a particular naming convention,
;;; namely that an image FOO is really FOO-low.gif and FOO.gif.
;;; The logo fails this in two ways:  it's a JPG, and it doesn't
;;; have a lowsrc.
(define (sk:make-img name alt width height)
  (format nil "<img border=\"0\" width=\"~A\" height=\"~A\" lowsrc=\"~A-low.gif\" src=\"~A.gif\" alt=\"~A\">"
	  width height name name alt))

(define (sk:wrap-href link inside)
  (if *sk:wrap-href-disable*
      inside
      (format nil "<a href=\"~A\">~A</a>"
	      link inside)))

(define (sk:make-td-img name alt width height bgcolor)
  (format nil "<td bgcolor=\"~A\" valign=\"bottom\" width=\"~A\" height=\"~A\">~A</td>"
	  bgcolor width height
	  (sk:make-img name alt width height)))

(define (sk:make-td-href-img name alt width height link bgcolor)
  (format nil "<td bgcolor=\"~A\" valign=\"bottom\" width=\"~A\" height=\"~A\">~A</td>"
	  bgcolor width height
	  (sk:wrap-href link (sk:make-img name alt width height))))

(define (sk:make-thin-vertical-line)
  (format nil "<td bgcolor=\"~A\" valign=\"top\" width=\"1\"><img src=\"1-by-1-pixel-transparent.gif\"></td>"
	  *sk:bgcolor-lines*))

(define (sk:make-thin-horizontal-line)	; Knows that it will span the bottom 3 columns.
  (format nil "<td bgcolor=\"~A\" colspan=\"3\" height=\"1\"><img src=\"1-by-1-pixel-transparent.gif\"></td>"
	  *sk:bgcolor-lines*))

(define (sk:make-navbar-stable name alt link)
  (sk:make-td-href-img 
   name alt *sk:navbar-icon-width* *sk:navbar-icon-height* link *sk:bgcolor-navbar*))

(define (sk:make-navbar-bistable name alt link flag)
  (let ((full-name (string-append name "-" (if flag "open" "closed"))))
    (sk:make-navbar-stable full-name alt link)))

(define (sk:make-navbar-icons)
  (format nil "~A~&    ~A~&    ~A~&    ~A~&    ~A~&    ~A~&    ~A"
	  (sk:make-navbar-bistable "navbar-news" "News" "news.html" *ui:news-new*)
	  (sk:make-navbar-bistable "navbar-messages" "Messages" "messages.html" *ui:messages-new*)
	  (sk:make-navbar-bistable "navbar-interests" "Interests" "interests.html" *ui:interest-new*) ; Yes, it's not plural.
	  (sk:make-navbar-bistable "navbar-attestations" "Attestations" "attestations.html" *ui:attestations-new*)
	  (sk:make-navbar-stable "navbar-requests" "Requests" "request.html")
	  (sk:make-navbar-stable "navbar-tune" "Tune" "params.html")
	  (sk:make-navbar-stable "navbar-help" "Help" "help.html")))

;;; Toplevel for the navbar only.  Emits one complete table.
(define (sk:make-navbar)
  (format nil "
<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">
  <tr>
    <td bgcolor=\"~A\" rowspan=\"2\" width=\"~A\" height=\"~A\"><img width=\"~A\" height=\"~A\" src=\"~A\" alt=\"Yenta logo\"></td>
    ~A
    ~A
  </tr>
</table>~2&"
	  *sk:bgcolor-logo* *sk:logo-width* *sk:logo-height* *sk:logo-width* *sk:logo-height* "logo.jpg"
	  (sk:make-thin-vertical-line)
	  (sk:make-navbar-icons)))

(define (sk:make-icon-stable name alt)
  (sk:make-td-img
   name alt *sk:main-icon-width* *sk:main-icon-height* *sk:bgcolor-icons*))

(define (sk:make-icon-bistable name alt flag)
  (let ((full-name (string-append name "-" (if flag "open" "closed"))))
    (sk:make-icon-stable full-name alt)))

(define (sk:make-icon name alt flag)	; If flag is specified, the icon will be bistable, else stable.
  (if (pair? flag)
      (sk:make-icon-bistable name alt (car flag))
      (sk:make-icon-stable name alt)))

(define (sk:generate-bookmark-list bookmarks)
  (define (inner mark center?)
    (format nil "~&~18T<tr><td~:[~; align=\"center\"~]>~A</td></tr>~&" center? mark))
  (define (once mark)
    (inner mark #f))
  (cond ((null? bookmarks)
	 (inner "<font size= \"-1\"><i>[No bookmarks defined.]</i></font>" #t))
	(t
	 (join-strings (map once bookmarks) ""))))

(define (sk:make-bookmark-img name alt link)
  (sk:make-td-href-img
   name alt
   *sk:bookmark-icon-width* *sk:bookmark-icon-height*
   link
   *sk:bgcolor-bookmark-icons*))			      

;;; Toplevel for everything below the navbar, but above the actual content.
;;; MAIN-ICON-NAME should -not- have "gif" in its name (the ".gif" will be
;;; appended).  If FLAG is specified, then this is a bistable icon, whose
;;; state will reflect the flag.
(define (sk:make-icon-and-bookmarks main-icon-name alt flag)
  (format nil "
<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">
  <tr>
    <td valign=\"top\">
        <table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">
          <tr>~A</tr>
          <tr>
            <td>
                <table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">
                  <tr><td colspan=\"3\" height=\"20\" bgcolor=\"~A\"><img src=\"1-by-1-pixel-transparent.gif\"></td></tr>
                  <tr>
                    ~A
                    ~A
                    ~A
                  </tr>
                </table>
            </td>
          </tr>
          <tr>
            <td>
                <table width=\"~A\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">~A</table>
            </td>
          </tr>
       </table>
    </td>
    ~A
    <td valign=\"top\" width=\"~A\"~:[~*~; bgcolor=\"~A\"~]>
       <table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">
          <tr>
            <td>
              <table cellpadding=\"~A\" cellspacing=\"0\" border=\"0\"~:[~*~; bgcolor=\"~A\"~]>
                <tr>
                  <td valign=\"top\"~:[~*~; bgcolor=\"~A\"~]>"
	  (sk:make-icon main-icon-name alt flag)
	  *sk:bgcolor-lines*
	  (sk:make-bookmark-img "bookmark-add" "Add bookmark" "bookmarks-nyi.html")
	  (sk:make-bookmark-img "bookmark-delete" "Delete bookmark" "bookmarks-nyi.html")
	  (sk:make-bookmark-img (if *ui:bookmarks-are-alphabetic* "bookmark-order-alphabetic" "bookmark-order-chronologic")
				"Reorder bookmarks" "bookmarks-nyi.html")
	  *sk:main-icon-width*
	  (sk:generate-bookmark-list *ui:bookmarks-listing*)
	  (sk:make-thin-vertical-line)
	  *sk:content-width*
	  *sk:bgcolor-content-active* *sk:bgcolor-content*
	  *sk:content-cellpadding*	; The innermost table's cellpadding is how we keep the content from smashing into the enclosing cell's edges.
	  *sk:bgcolor-content-active* *sk:bgcolor-content* *sk:bgcolor-content-active* *sk:bgcolor-content*))

;;;; ++++ Toplevels for use by real pages.
(define (sk:page-preamble title main-icon-name alt . flag)
  (string-append
   (sk:page-preamble-internal title)
   (sk:make-navbar)
   (sk:make-icon-and-bookmarks main-icon-name alt flag)
   (alerts:show)))

(define (sk:page-postamble)
  (format nil "</td>
             </tr>
           </table>
            </td>
          </tr>
       </table>
    </td>
  </tr>
</table>
    </td>
    ~A
  </tr>
  <tr>
    ~A
  </tr>
</table>
</body> </html>
"
    (sk:make-thin-vertical-line)
    (sk:make-thin-horizontal-line)))

;;;; +++ Test function.

(define (sk:test contents)
  (display				; Unslashify strings so we can give this to a browser.
   (string-append
    (sk:page-preamble "Test title" "icon-messages-open" "Open-alt")
    contents
    (sk:page-postamble))))

;;;; The stuff for /index.html

(def-yenta-var *ui:registration-completed* #f) ; Set #t once we're happy with handle, passphrase, and initial search dirs.

(define *ui:bgcolor-required-fields* "lightgreen")
(define *ui:bgcolor-optional-fields* "lightblue")

(define *ui:handle-form-size* 40)
(define *ui:passphrase-form-size* 50)
(define *ui:searchdir-form-cols* 50)
(define *ui:searchdir-form-rows* 4)

;;; Can be one of:
;;;   cold-boot:        We haven't checked for a saved-state file yet.
;;;   brand-new-yenta:  We've checked for a saved-state file, and don't have one.
;;;   envelope-read:    We've read the plaintext envelope of the saved-state file.
;;;   crypto-read:      We've read the crypto portion of the saved-state file.
;;;   registered:       The user has just successfully filled out the first-time registration form.
;;;   scanning:         Scanning and clustering documents after registration.
;;;   idle:             Nothing better to do.  We fall into this after we're done scanning.
(defvar *ui:startup-state* 'cold-boot)

(defvar *ui:saved-passphrase-1* "")	; Only saved so we can reprompt with it if the form gets resubmitted.  Cleared once we set it.
(defvar *ui:saved-passphrase-2* "")	; Separate state, in case the user mistypes it!
(defvar *ui:saved-searchdirs* '())	; This is the temporary list, until validated.  Once that happens, it goes into *interests:search-dirs*.
(defvar *ui:saved-handle* "")		; Also temporary, until validated.

(defvar *ui:saved-email-address* "")
(define *ui:email-address-cols* 30)
(defvar *ui:saved-yenta-announce* 'unset) ; Magic required because the forms processor runs -before- the user first sees the form, ...
(defvar *ui:saved-yenta-discuss* 'unset)  ; ... and because checkboxes yield either "on" or nonexistence, not "off".  What a pain, but that's HTML.

(define (ui:predicted-email-address)
  (format nil "~A@~A" (local-username) (local-host)))

(define (ui:stuff-saved-email-address)
  (unless (and *ui:saved-email-address*
	       (not (equal? *ui:saved-email-address* "")))
    (set! *ui:saved-email-address* (ui:predicted-email-address)))
  *ui:saved-email-address*)

(define (ui:email-looks-valid? s)
  (and (regmatch? "@" s)
       (regmatch? "\\." s)))

(define (ui:email-required-and-looks-valid? s) ; Note that *ui:saved-yenta-discuss* and *ui:saved-yenta-announce* must be set -first-!
  (or (and (not *ui:saved-yenta-announce*) ; If the user wants to sign up for a mailing list, make sure his email actually ...
	   (not *ui:saved-yenta-discuss*)) ; ... looks valid.  Otherwise, don't hassle him about it, since we won't use it.
      (ui:email-looks-valid? *ui:saved-email-address*)))

(define (ui:submit-email-list-registrations)
  (define (sub what)
    (unless (equal? "" (remove-whitespace *ui:saved-email-address*)) ; If the user cleared the field, he "meant" to uncheck the buttons...
      (logger:log-with-ip 0 "SUBSCRIBE yenta-~A ~A" what *ui:saved-email-address*))) ; ... [In theory, we'll cuss him out for having them set ...
  (when *ui:saved-yenta-announce* (sub "announce")) ; ... but if we didn't for some reason, don't submit blank subscription requests.]
  (when *ui:saved-yenta-discuss*  (sub "discuss"))) ; [If he has whitespace in his email, he loses.  I'm not going to bother w/string-trim.]

(define (ui:please-report-bug)
  "Please <a href=\"bug-reports.html\">report this bug.</a>")

(define (ui:index-icon)
  (case *ui:startup-state*
    ((crypto-read idle)
     "icon-requests")			; I don't have any better ideas about this at the moment...
    (else
     "icon-signup")))

(define (ui:index-icon-alt)
  (case *ui:startup-state*
    ((crypto-read idle)
     "Requests")			; I don't have any better ideas about this at the moment...
    (else
     "Sign up")))

;;; This is the very first page that the user sees when starting Yenta for the first time.
;;; (It is also surrounded by the usual icons, etc.)
(define (ui:registration-form)
  (unless (and *ui:saved-searchdirs*
	       (not (null? *ui:saved-searchdirs*)))
    (set! *ui:saved-searchdirs* (list (getenv "HOME")))) ; A reasonable default...
  (format nil "~
<b>In order to start using Yenta, you'll need to choose a handle, choose a 
passphrase, and specify the location of some files that are representative of 
your interests.<p>
<i>Note -- In this version of Yenta, you should probably specify no more than
about three megabytes of documents below.</i><br>
<form action = \"/\" method = \"post\"> 
<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\">
  <tr>
    <td bgcolor=\"~A\">
        <table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\">
          <tr>
            <td align=\"left\"><b>Handle:</b>&nbsp;&nbsp;<font size=\"-1\"><i>[What you want other Yenta users to call you]</i></font></td>
            <td align=\"right\"><b><i>These fields are required</i></b></td>
          </tr>
        </table>
        <input type = \"text\" name = \"handle\" size = \"~A\" value = \"~A\"><br> 
        <b>Passphrase:</b>&nbsp;&nbsp;<font size=\"-1\"><i>[Several unpredictable words---not just one!---that you can remember]</i></font><br>
        <input type = \"password\" name = \"passphrase\" size = \"~A\" value = \"~A\"><br> 
        <b>Passphrase again:</b>&nbsp;&nbsp;<font size=\"-1\"><i>[The same words again; your security depends on other people not guessing them]</i></font><br>
        <input type = \"password\" name = \"passphrase-rep\" size = \"~A\" value = \"~A\"><br>
        <b>Document directories or individual files:</b>&nbsp;&nbsp;<font size=\"-1\"><i>[The directories or files to search for your interests]</i></font><br>
        <textarea name = \"search-dirs\" cols = \"~A\" rows = \"~A\">~A</textarea><br>
    </td>
  </tr>
</table>
<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\">
  <tr>
    <td bgcolor=\"~A\">
        <table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\">
          <tr>
            <td align=\"left\"><b>Email address:</b></td>
            <td align=\"right\"><b><i>These fields are optional, but recommended</i></b></td>
          </tr>
        </table>
        <table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\">
          <tr>
            <td align=\"left\">
              <input type = \"text\" name = \"emailaddr\" size = \"~A\" value=\"~A\"><br>
              <table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">
                <tr>
                  <td><input type = \"checkbox\" ~A name = \"add-to-yenta-announce\"></td>
                  <td>&nbsp;<b>Add me to yenta-announce@media.mit.edu</b></td>
                </tr>
                <tr>
                  <td>&nbsp;</td><td>&nbsp;<i>Very low volume.  Announces new versions only.</i></td>
                </tr>
              </table>
              <table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">
                <tr>
                  <td><input type = \"checkbox\" ~A name = \"add-to-yenta-discuss\"></td>
                  <td>&nbsp;<b>Add me to yenta-discuss@media.mit.edu</b></td>
                </tr>
                <tr>
                  <td>&nbsp;</td><td>&nbsp;<i>General discussion about Yenta; all list member may post.</i></td>
                </tr>
              </table>
            </td>
            <td valign=\"top\" height=\"100%\">
              <table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" height=\"100%\">
                <tr valign=\"top\">
                  <td align=\"left\" valign=\"top\">
                      <font size=\"-1\"><br>Your mailing address is used <i>only</i> to subscribe you to these mailing lists,
                                        and is immediately forgotten again by Yenta.  It is never sent to other Yentas.</font>
                  </td>
                </tr>
                <tr valign=\"bottom\">
                  <td align=\"right\" valign=\"bottom\"><font size=\"-1\"><br><i>Yenta ~A&nbsp;</i></font></td>
                </tr>
              </table>
            </td>
          </tr>
        </table>
    </td>
  </tr>
</table>
<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\">
  <tr>
    <td align=\"center\">
        <input type = \"submit\" value = \"Start Yenta\">
    </td>
  </tr>
</table>    
</form>
"
	  *ui:bgcolor-required-fields*
	  *ui:handle-form-size*
	  (let ((handle (really-safe-list-ref *local-yenta-addr* 1)))
	    (if (or (null? handle)
		    (equal? handle ""))
		*ui:saved-handle*
		handle))
	  *ui:passphrase-form-size* *ui:saved-passphrase-1*
	  *ui:passphrase-form-size* *ui:saved-passphrase-2*
	  *ui:searchdir-form-cols* *ui:searchdir-form-rows*
	  (ui:list->textarea-string *ui:saved-searchdirs*)
	  *ui:bgcolor-optional-fields*
	  *ui:email-address-cols*
	  (ui:stuff-saved-email-address)
	  (ui:on-off *ui:saved-yenta-announce*)
	  (ui:on-off *ui:saved-yenta-discuss*)
	  (ui:yenta-version)))

;;; Runs whenever the initial page is seen, before the actual form is written on the page.
;;; Sets various things and produces a list of various things to be said about it.  If it
;;; has nothing worthwhile to say, returns '().
;;;
;;; Note that these are processed in reverse order from the way they appear on the form.
;;; That's just 'cause we use push! to build the feedback, and I don't want to have to worry
;;; about using reverse on the list, since one item could conceivably want to make more than
;;; one comment, and reverse would then swap them around.
(define (ui:process-registration-form)
  (let ((feedback '())
	(form-yenta-discuss  (ui:form-datum "add-to-yenta-discuss"))
	(form-yenta-announce (ui:form-datum "add-to-yenta-announce"))
	(form-email (ui:form-datum "emailaddr"))
	(form-search-dirs (ui:form-datum "search-dirs"))
	(form-pp1 (ui:form-datum "passphrase"))
	(form-pp2 (ui:form-datum "passphrase-rep"))
	(form-handle (ui:form-datum "handle")))
    (define (barf message)
      (push! (list message) feedback))
    ;; If the button was not checked, we'll get back #f, -not- "off" from ui:form-datum!  But check first to make sure
    ;; that they weren't starting out at their default values---this function runs -before- the form data is ever posted
    ;; back if this is the very first time the user sees the form, and we don't want that to reset the values to off.
    (set! *ui:saved-yenta-discuss*
	  (or (eq? *ui:saved-yenta-discuss* 'unset)
	      (equal? form-yenta-discuss "on")))
    (set! *ui:saved-yenta-announce*
	  (or (eq? *ui:saved-yenta-announce* 'unset)
	      (equal? form-yenta-announce "on")))
    (when form-email			; Note that *ui:saved-yenta-discuss* and *ui:saved-yenta-announce* must be set -first-!
      (cond ((ui:email-required-and-looks-valid? form-email)
	     (set! *ui:saved-email-address* form-email))
	    (t
	     (barf "Your email address does not have both an atsign (@) and at least one dot (.) in it.  This can't be right.<br>"))))
    (when form-search-dirs
      (let ((new-result (map expand-dir (ui:textarea->list form-search-dirs))))
	(set! *ui:saved-searchdirs* new-result) ; Set this, even if it might have errors, so it'll reappear on the next form we present.
	(let ((nonexistent (remove-if file-exists? new-result))) ; May be a file or a directory.
	  (cond ((null? nonexistent)
		 (set! *interests:search-dirs* *ui:saved-searchdirs*)) ; Okay, now we can use it.
		(t
		 (barf (format nil "~A ~:[do~;does~] not exist. ~
                                    Please correct ~:*~:[these~;this~] pathname~P.<br>"
			       (one-two-and-three nonexistent)
			       (= (length nonexistent) 1)
			       (length nonexistent))))))))
    (when form-pp1
      (set! *ui:saved-passphrase-1* form-pp1)
      (set! *ui:saved-passphrase-2* form-pp2)
      (cond ((equal? form-pp1 form-pp2)
	     (cond ((equal? form-pp1 "")
		    (barf "You must set a passphrase.<br>"))
		   ((not (passphrase:passphrase-secure? form-pp1))
		    (barf "That passphrase is insecure.  Please choose a better one, which probably means a longer one.<br>"))
		   (t
		    (passphrase:set-passphrase-unconditionally form-pp1 #f))))
	    (t
	     (barf "You must type the same thing for your passphrase both times.<br>"))))
    (when form-handle
      (cond ((equal? form-handle "")
	     (barf "You must set a handle.<br>"))
	    (t
	     (unless (equal? form-handle (cadr *local-yenta-addr*))
	       (set-car! (cdr *local-yenta-addr*) form-handle)))))
    feedback))

;;; Runs when we've read the envelope and need the passphrase to read the rest.
(define (ui:welcome-back)
  (list (format nil "<title>Authentication for Yenta</title>~&~
                     <h1>Welcome back</h1>~&~
                     This seems to be a new session.  In order to proceed,~&~
                     you'll need to enter your passphrase below.~&~
                     <form action = \"authenticate-response.html\" method = \"post\">~&~
                       <b>Passphrase:</b><br>~&~
                       <input type = \"password\" name = \"passphrase\" size = \"50\">~&")
	(authenticate:pass-request)
	(format nil "</form>~&~
                     <p>You will always be in a new session when you restart either your browser or Yenta.~&~
                     If you visit another site and return, especially if a significant time has passed, your~&~
                     session will also have expired, and you'll be asked for your passphrase again.~&")))

;;; Sets *ui:registration-completed* based on whether enough fields are filled in.
;;; Also saves state immediately if we're happy, so we can preserve the user's private key.
(define (ui:registration-satisfactory?)
; (format-debug 0 "~%ui:registration-satisfactory? entered.~&")
  (unless *ui:registration-completed*
    (set! *ui:registration-completed*
	  (let ((handle (really-safe-list-ref *local-yenta-addr* 1)))
	    (and (not (null? handle))
		 (not (equal? handle ""))
		 *hashed-passphrase*
		 *interests:search-dirs*
		 (not (null? *interests:search-dirs*))
		 (ui:email-required-and-looks-valid? *ui:saved-email-address*))))
;   (format-debug 0 "~%ui:registration-satisfactory? says ~S~&" *ui:registration-completed*)
    (when *ui:registration-completed*
      (set! *ui:saved-passphrase-1* "")
      (set! *ui:saved-passphrase-2* "")
      ;; This puts the local IP somewhere we give out, because we aren't
      ;; running in stealth mode, and otherwise people could never find us,
      ;; or anyone else, for that matter.  They also know our IP address from
      ;; the TCP connection anyway.  [This also registers our handle.]
      (identity:sign-addr)
      ;; The save/load below does more than just checkpoint our state so the user's hard-won entropy & keys are protected.
      ;; It also make sure *vars:successfully-loaded-everything* is set, so a decayed-passphrase reload won't explode.
      ;; See comments at vars:save-and-load-encrypted for more details.
      (vars:save-and-load-encrypted)
      (set! *ui:startup-state* 'registered))) ; Pretend we just read the file back in.
  *ui:registration-completed*)

(define (ui:set-ssl-session-valid)
  (set! *current-authenticated-session* (ssl:session *last-ssl*)) ; *last-ssl* set by check-authentication.
  (ui:note-session-authenticity #t))	; This SSL session is now valid.

(define (ui:yenta-awaits-a-command)
  (wb:start-interyenta-tasks)		; Actually start up the networking, if it hasn't already been started.  Clustering must have finished first.
  (format nil "<b>Yenta is available and may be communicating with other Yentas, ~&~
               but is not executing any particular command of yours.  Feel free to ~&~
               select something from the navigation bar above.</b><p>

	       <b><i>You should follow a link, or reload a page, every so often.</i></b><p>

	       This version of Yenta does <i>not</i> automatically refresh the page
	       when anything changes.  If you don't occasionally do so, you'll never
	       see any changes.<p>

	       If you reload a page and notice that one of the icons above has suddenly
	       opened a pair of doors at you, this means that there is some news it is
	       trying to tell you.  Click on the icon for more information.<p>

	       <b><i>An important note about bookmarks</i></b><p>

	       Please resist the temptation to use your browser's bookmarking features to make a bookmark
	       to any Yenta page.  While it seems like it will work, and actually <i>does</i> work in
	       most cases, it will only cause you grief in the long run&nbsp;--&nbsp;especially if you
	       ever run Yenta on a computer that someone else is already running it on.  An explanation
               appears on <a href=\"help-pitch7.html\">this page</a>, in the section on \"Internal bookmarks.\"<p>
"))

;;; Returns all the HTML we want to serve to the user while scanning is happening,
;;; so he knows what's going on with the animation he's seeing.  Uses ui:animation-html
;;; to actually deliver the image URL, but everything else gets specified here.
(define (ui:scanning-html)
  (format nil "~A

<h1>Yenta is currently scanning documents</h1>

<b>Yenta is scanning files and determining what your interests are.  As
long as the animation to the left is running, it is still scanning.  When
the animation changes to <i>DONE</i>, Yenta is ready to take other
actions, such as accepting user commands or talking to other Yentas.
Until that happens, all commands are inhibited:  attempting to follow any
link will simply land you back on this page.  Just be patient.<p>

You might want to clone your browser window at this point, so that you may continue
to browse the web, but also have access to Yenta at any time.  [You probably want
the \"New Web Browser\" or \"New Window\" command, depending on your browser.]<p>

Once Yenta has finished scanning, you may try out other commands.  You may
want to see what <a href=\"interests.html\">interests</a> Yenta has
computed, for example, or perhaps consult the
<a href=\"help.html\">help</a>.<p>

Yenta will begin asking other Yentas to see if any of them match your
interests as soon as it's done scanning.  You don't have to do anything in
particular for it to start this.<p>

<b><i>Once Yenta has started, you should follow a link, or reload a page, every so often.</i></b><p>

This version of Yenta does <i>not</i> automatically refresh the page
when anything changes.  If you don't occasionally do so, you'll never
see any changes.<p>

If you reload a page and notice that one of the icons above has suddenly
opened a pair of doors at you, this means that there is some news it is
trying to tell you.  Click on the icon for more information.<p>"

	  (ui:animation-html)))

;;; The normal order of states in *ui:startup-state* for a brand-new Yenta is
;;; brand-new-yenta, registered, scanning, idle.  In a Yenta that's being restarted,
;;; the order goes envelope-read, crypto-read, idle.
(define (ui:front-page-contents)
  (ui:registration-satisfactory?)	; Set our state based on the info we've now got.
  (format-debug 0 "~%ui:front-page-contents says *ui:startup-state* is ~S~&" *ui:startup-state*)
  (case *ui:startup-state*
    ((cold-boot)
     (error "ui:front-page-contents can't be in cold-boot.")) ; We should have tried to read saved state and gone one way or the other.
    ((brand-new-yenta)			; May run several iterations, depending on how long it takes the user to fill out the form completely.
     ;; Don't call (ui:set-ssl-session-valid) here, because we're not -really- valid here, even though by definition we
     ;; must let the user fetch -some- pages 'cause he doesn't have a passphrase yet.  Instead, the brand-new-yenta state
     ;; is special-cased in (respond) in start-ui.scm.
     (let ((feedback (ui:process-registration-form)))
       (cond ((ui:registration-satisfactory?)
	      (ui:front-page-contents)) ; Now serve the right front page for an initialized yenta in some other state (presumably 'registered).
	     (t
	      (append			; User didn't fill out enough fields to get us out of 'brand-new-yenta.
	       (list "<table bgcolor=\"pink\"><tr><td><b><i>")
	       feedback
	       (list "</i></b></td></tr></table>")
	       (ui:registration-form))))))
    ((registered)
     (ui:set-ssl-session-valid)		; [Do I need this here any more?]
     (wb:start-yenta-completely)	; Actually start all the rest of Yenta, except interyenta.
     (ui:submit-email-list-registrations) ; Sign the user up for mailing lists.
     (set! *ui:startup-state* 'scanning) ; In case the user hits reload right now, we need to know to continue the animation.
     (interests:run-auto-search-task)	; Scan documents and cluster interests.  Do this in a separate process so we return immediately & serve page.
     (ui:show-animation)		; Start up the animation.  do-find-interests will turn it off when clustering is done.
     (ui:scanning-html))		; Return the HTML the browser needs to start fetching the frames.
    ((scanning)
     (cond (*ui:animation-enable*	; If we're still animating, the user must have clicked reload or something.  Just continue.
	    (ui:show-animation)
	    (ui:animation-html))	; Return the HTML the browser needs to start fetching the frames.
	   (t
	    (set! *ui:startup-state* 'idle)
	    (ui:yenta-awaits-a-command))))
    ((envelope-read)			; We think we just need a passphrase to start up.
     (ui:welcome-back))
    ((crypto-read idle)
     (cond (*interests:at-least-one-scan-completed*	; Clustering has finished.
	    (ui:yenta-awaits-a-command)) ; %%% Need to check here that we have any interests at all.
	   (ui:registration-satisfactory? ; Clustering hasn't happened, but the registration is complete.  Must have crashed/been killed.
	    (set! *ui:startup-state* 'registered) ; Pretend we just finished registering, and try again.
	    (ui:front-page-contents))
	   (t				; Not even done with registration.  I don't think we can be here, but...
	    (format-debug 0 "front-page-contents in state ~S, but unregistered.  ~A" *ui:startup-state* (ui:please-report-bug))
	    (set! *ui:startup-state* 'brand-new-yenta)))) ; Try starting over from the beginning, just in case.
    ))

;;; End of file.
