(module sendurl mzscheme
  (require (lib "process.ss")
           (lib "file.ss")
           (lib "etc.ss")
           (lib "port.ss")
           (lib "sendevent.ss"))
  
  (provide send-url unix-browser-list browser-preference? external-browser)
  
  (define separate-by-default?
    (get-preference 'new-browser-for-urls (lambda () #t)))

  ; : any -> bool
  (define (browser-preference? x)
    (or (not x) (eq? 'plt x) (memq x unix-browser-list) (custom-browser? x)
        (procedure? x)))

  (define external-browser
    (make-parameter
     #f ; #f means "consult the preferences file"
     (lambda (x)
       (if (browser-preference? x)
           x
           (error 'external-browser "~a is not a valid browser preference" x)))))
  
  ; send-url : str [bool] -> void
  (define send-url
    (opt-lambda (url-str [separate-window? separate-by-default?])
      (cond
       [(procedure? (external-browser))
	((external-browser) url-str)]
       [(eq? (system-type) 'macos)
	(if (regexp-match "Blue Box" (system-type #t))
	    ;; Classic inside OS X:
	    (let loop ([l '("MSIE" "NAVG")])
	      (if (null? l)
		  (error 'send-url "couldn't start Internet Explorer or Netscape")
		  (with-handlers ([exn:fail? (lambda (x) (loop (cdr l)))])
		    (subprocess #f #f #f "by-id" (car l))
		    (let loop ([retries 2]) ;; <<< Yuck <<<
		      (if (zero? retries)
			  (error "enough already") ; caught above
			  (with-handlers ([exn:fail? (lambda (x)
						       (loop (sub1 retries)))])
			    (let ([t (thread (lambda ()
					       (send-event (car l) "GURL" "GURL" url-str)))])
			      (sync/timeout 1 t) ;; <<< Yuck (timeout) <<<
			      (when (thread-running? t)
				(kill-thread t)
				(error "timeout")))))))))
	    ;; Normal OS Classic:
	    (send-event "MACS" "GURL" "GURL" url-str))]
       [(or (eq? (system-type) 'macosx)
	    (equal? "ppc-darwin" (system-library-subpath)))
	;; not sure what changed, but this is wrong now.... -robby
	;;(browser-process (format "osascript -e 'open location \"~a\"'" (regexp-replace* "%" url-str "%25")))	
	(browser-process (format "osascript -e 'open location \"~a\"'" url-str))]
       [(eq? (system-type) 'windows)
	(shell-execute #f url-str "" (current-directory) 'SW_SHOWNORMAL)]
       [(eq? (system-type) 'unix)
	(let ([preferred (or (external-browser) (get-preference 'external-browser))])
	  (cond
	   [(use-browser 'opera preferred)
	    =>
	    (lambda (browser-path) 
	      ;; opera may not return -- always open asyncronously
	      ;; opera starts a new browser automatically, if it can't find one
	      (browser-process* browser-path "-remote"
				(format "openURL(~a)"
					(if separate-window?
					    (format "~a,new-window" url-str)
					    url-str))))]
	   [(use-browser 'galeon preferred)
	    =>
	    (lambda (browser-path)
	      (browser-process* browser-path
				(if separate-window? "-w" "-x")
				url-str))]
	   [(or (use-browser 'netscape preferred)
		(use-browser 'mozilla preferred))
	    =>
	    (lambda (browser-path)
	      ;; netscape's -remote returns with an error code, if no
	      ;; netscape is around. start a new netscape in that case.
	      (or (system* browser-path "-remote"
			   (format "openURL(~a)"
				   (if separate-window?
				       (format "~a,new-window" url-str)
				       url-str)))
		  (browser-process* browser-path url-str)))]
	   [(use-browser 'dillo preferred)
	    =>
	    (lambda (browser-path)
	      (browser-process* browser-path url-str))]
	   [(custom-browser? preferred)
	    (let ([cmd (string-append (car preferred)
				      url-str
				      (cdr preferred))])
	      (browser-process cmd))]
	   [else
	    (error 'send-url "Couldn't find ~a to open URL: ~e" (orify unix-browser-list) url-str)]))]
       [else (error 'send-url "don't know how to open URL on platform: ~s" (system-type))])))
  
  ; : tst -> bool
  (define (custom-browser? x)
    (and (pair? x) (string? (car x)) (string? (cdr x))))
  
  (define unix-browser-list '(opera galeon netscape mozilla dillo))
  
  ; : (cons tst (listof tst)) -> str
  (define (orify l)
    (cond
      [(null? (cdr l)) (format "~a" (car l))]
      [(null? (cddr l)) (format "~a or ~a" (car l) (cadr l))]
      [else 
    (let loop ([l l])
      (cond
        [(null? (cdr l)) (format "or ~a" (car l))]
        [else (string-append (format "~a, " (car l)) (loop (cdr l)))]))]))
  
  ; : sym sym -> (U #f str)
  ; to find the path for the named browser, unless another browser is preferred
  (define (use-browser browser-name preferred)
    (and (or (not preferred)
	     (eq? preferred browser-name))
	 (find-executable-path (symbol->string browser-name) #f)))
    
  ;; run-browser : process-proc list-of-strings -> void
  (define (run-browser process*/ports args)
    (let-values ([(stdout stdin pid stderr control)
		  (apply values (apply process*/ports
				       (open-output-nowhere)
				       #f
				       (current-error-port)
				       args))])
      (close-output-port stdin)
      (thread (lambda ()
		(control 'wait)
		(when (eq? 'done-error (control 'status))
		  (error 'run-browser "process execute failed: ~e" args))))
      (void)))

  (define (browser-process* . args)
    (run-browser process*/ports args))

  (define (browser-process cmd)
    (run-browser process/ports (list cmd))))
