(module dispatch-files mzscheme
  (require (lib "url.ss" "net")
           (lib "xml.ss" "xml")
           (lib "kw.ss")
           (lib "list.ss")
           (lib "plt-match.ss")
           (lib "contract.ss")
           (lib "uri-codec.ss" "net"))
  (require (lib "pretty.ss"))         
  (require "dispatch.ss"
           "../private/configuration.ss"
           "../private/util.ss"
           "../private/mime-types.ss"
           "../private/request.ss"
           "../private/servlet-helpers.ss"
           "../private/response.ss"
           "../response-structs.ss")
  (provide/contract
   [interface-version dispatcher-interface-version?])
  (provide ; XXX contract kw
   make)
  
  (define interface-version 'v1)
  (define/kw (make #:key 
                   [htdocs-path "htdocs"]
                   [mime-types-path "mime.types"]
                   [indices (list "index.html" "index.htm")]
                   [file-not-found-responder 
                    (gen-file-not-found-responder "not-found.html")])
    (define get-mime-type (make-get-mime-type mime-types-path))
    (lambda (conn req)
      (define-values (uri method _path) (decompose-request req))
      ;; ************************************************************
      ;; ************************************************************
      ;; SERVING FILES
      
      ;; serve-file : connection symbol uri host -> void
      ;; to find the file, including searching for implicit index files, and serve it out
      (define path 
        (url-path->path htdocs-path
                        (uri-decode (url-path->string (url-path uri)))))
      (cond
        [(file-exists? path)
         (match (headers-assq #"Range" (request-headers/raw req))
           [#f
            (output-file conn path method (get-mime-type path))]
           [range
            (match (bytes->string/utf-8 (header-value range))
              [(regexp "^bytes=(.*)-(.*)$" (list s start end))
               (define startn
                 (if (string=? "" start)
                     0
                     (string->number start)))
               (define endn
                 (if (string=? "" end)
                     +inf.0
                     (string->number end)))               
               (output-file/partial conn path method (get-mime-type path)
                                    startn endn)]
              [r
               ; XXX: Unhandled range: r
               (output-file conn path method (get-mime-type path))])])]
        [(directory-exists? path)
         (let loop ([dir-defaults indices])
           (cond
             [(pair? dir-defaults)
              (let ([full-name (build-path path (first dir-defaults))])
                (if (file-exists? full-name)
                    (cond
                      [(looks-like-directory? (url-path->string (url-path uri)))
                       (output-file conn full-name method (get-mime-type full-name))]
                      [else
                       (output-slash-message conn method (url-path->string (url-path uri)))])
                    (loop (rest dir-defaults))))]
             [else
              (output-response/method conn (file-not-found-responder uri) method)]))]
        [else
         (output-response/method conn (file-not-found-responder uri) method)])))
  
  ;; looks-like-directory : str -> bool
  ;; to determine if is url style path looks like it refers to a directory
  (define (looks-like-directory? path)
    (eq? #\/ (string-ref path (sub1 (string-length path)))))
  
  ;; output-slash-message: connection symbol string -> void
  ;; basically this is just a special error response
  (define (output-slash-message conn method url-path-str)
    (output-response/method
     conn
     (make-response/full
      301 "Moved Permanently"
      (current-seconds)
      TEXT/HTML-MIME-TYPE
      `([Location . ,(string-append url-path-str "/")])
      (list
       (xexpr->string
        `(html
          (head (title "Add a Slash"))
          (body "Please use "
                (a ([href ,(string-append
                            url-path-str "/")])
                   "this url") " instead.")))))
     method)))