#lang racket/base
(require ffi/unsafe
         ffi/unsafe/define
         racket/draw/unsafe/bstr
         net/base64
	 "../common/queue.rkt"
         "types.rkt"
         "utils.rkt")

(provide 
 (protect-out do-single-instance))

(define unique-lib-name "libunique-1.0")

(define unique-lib
  (with-handlers ([exn:fail? (lambda (exn) #f)])
    (ffi-lib unique-lib-name '("0"))))

(define-ffi-definer define-unique unique-lib
  #:default-make-fail make-not-available)

(define _gsize _ulong)

(define UNIQUE_RESPONSE_OK 1)

(define _UniqueApp _GtkWidget) ; not a widget, but we want to connect a signal
(define _UniqueMessageData (_cpointer 'UniqueMessageData))

(define-unique unique_app_new (_fun _string _string -> _UniqueApp)
  #:fail (lambda () (lambda args
                      (unless unique-lib
                        (log-error "~s not found; single-instance mode disabled"
                                   unique-lib-name))
                      #f)))
(define-unique unique_app_add_command (_fun _UniqueApp _string _int -> _void))
(define-unique unique_app_is_running (_fun _UniqueApp -> _gboolean))
(define-unique unique_app_send_message (_fun _UniqueApp _int _UniqueMessageData -> _int))

(define-unique unique_message_data_new (_fun -> _UniqueMessageData))
(define-unique unique_message_data_free (_fun _UniqueMessageData -> _void))
(define-unique unique_message_data_set (_fun _UniqueMessageData _pointer _gsize -> _void))
(define-unique unique_message_data_get (_fun _UniqueMessageData (len : (_ptr o _gsize))
                                             -> (data : _bytes)
                                             -> (scheme_make_sized_byte_string
                                                 data
                                                 len
                                                 0)))

(define-signal-handler connect-message-received "message-received"
  (_fun _UniqueApp _int _UniqueMessageData _uint -> _int)
  (lambda (app cmd data time)
    (let ([d (unique_message_data_get data)])
      (with-handlers ([exn:fail? (lambda (exn)
				   (log-error 
				    (format "error handling single-instance message: ~s"
					    (exn-message exn))))])
	(let* ([p (open-input-bytes d)]
	       [vec (read p)])
	  (for-each
	   queue-file-event
	   (map (lambda (s) (if (bytes? s)
                                (bytes->path s)
                                (string->path s)))
                (vector->list vec))))))
    UNIQUE_RESPONSE_OK))

(define-mz gethostname (_fun _pointer _long -> _int)
  #:fail (lambda () #f))

(define HOSTLEN 256)

(define (build-app-name)
  (let-values ([(path) (simplify-path
                        (path->complete-path
                         (or (find-executable-path (find-system-path 'run-file) #f)
                             (find-system-path 'run-file))
                         (current-directory)))]
               [(host) (or (and gethostname
                                (let ([b (make-bytes HOSTLEN)])
                                  (and (zero? (gethostname b HOSTLEN))
                                       (bytes->string/utf-8 (car (regexp-match #rx#"^[^\0]*" b)) #\?))))
                           "")])
    (string->bytes/utf-8
     (format "org.racket-lang.~a"
             (encode
              (format "~a~a~a" host path (version)))))))

(define (encode s)
  (regexp-replace* #rx"=|\r\n" (base64-encode (string->bytes/utf-8 s)) ""))

(define (send-command-line app)
  (let ([msg (unique_message_data_new)]
        [b (let ([o (open-output-bytes)])
             (write (for/vector ([p (in-vector (current-command-line-arguments))])
                      (define cp (path->complete-path p))
                      (define s (path->string cp))
                      (if (equal? cp (string->path s))
                          s
                          ;; can't represent as string; use bytes
                          (path->bytes cp)))
                    o)
             (get-output-bytes o))])
    (unique_message_data_set msg b (bytes-length b))
    (unique_app_send_message app 42 msg)))

(define (do-single-instance)
  (let ([app (unique_app_new (build-app-name) #f)])
    (when app
      (unique_app_add_command app "startup" 42)
      (when (unique_app_is_running app)
        (when (= (send-command-line app)
                 UNIQUE_RESPONSE_OK)
          (exit 0)))
      (void (connect-message-received app)))))
