#lang racket/base
(require ffi/unsafe
         ffi/unsafe/objc
         racket/class
         racket/path
         "../../lock.rkt"
         "utils.rkt"
         "types.rkt"
         "queue.rkt"
         "frame.rkt")

(provide 
 (protect-out file-selector))

(import-class NSOpenPanel NSSavePanel NSURL NSArray)

(define (nsurl->string url)
  (string->path (tell #:type _NSString url path)))

(define (file-selector message directory filename 
                       extension
                       filters style parent)
  (let ([ns (as-objc-allocation-with-retain
             (if (memq 'put style)
                (tell NSSavePanel savePanel)
                (tell NSOpenPanel openPanel)))]
        [parent (and parent
                     (not (send parent get-sheet))
                     parent)])

    (let ([extensions (append
                       (if (and extension 
                                (not (equal? "" extension)))
                           (list extension) 
                           null)
                       (if (memq 'packages style) (list "app") null)
                       (for/list ([e (in-list filters)]
                                  #:when (and (regexp-match #rx"[*][.][^.]+$" (cadr e))
                                              (not (equal? (cadr e) "*.*"))))
                         (car (regexp-match #rx"[^.]+$" (cadr e)))))])
      (unless (null? extensions)
        (when (memq 'put style)
          (tellv ns setCanSelectHiddenExtension: #:type _BOOL #t))
        (let ([a (tell NSArray 
                       arrayWithObjects: #:type (_list i _NSString) extensions
                       count: #:type _NSUInteger (length extensions))])
          (tellv ns setAllowedFileTypes: a))
        (let ([others? (ormap (lambda (e)
                                (equal? (cadr e) "*.*"))
                              filters)])
          (tellv ns setAllowsOtherFileTypes: #:type _BOOL others?))))

    (cond
     [(memq 'multi style)
      (tellv ns setAllowsMultipleSelection: #:type _BOOL #t)]
     [(memq 'dir style)
      (tellv ns setCanChooseDirectories: #:type _BOOL #t)
      (tellv ns setCanChooseFiles: #:type _BOOL #f)])

    (when (or (memq 'put style)
              (memq 'dir style))
      (tellv ns setCanCreateDirectories: #:type _BOOL #t))

    (when message
      (tellv ns setMessage: #:type _NSString message))
    (when directory
      (let ([dir (if (string? directory)
                     directory
                     (path->string directory))])
        (if (version-10.6-or-later?)
            (tellv ns setDirectoryURL: (tell NSURL 
                                             fileURLWithPath: #:type _NSString dir
                                             isDirectory: #:type _BOOL #t))
            (tellv ns setDirectory: #:type _NSString dir))))
    (when filename
      (when (version-10.6-or-later?)
        (tellv ns setNameFieldStringValue: #:type _NSString (path->string
                                                             (file-name-from-path filename)))))
    
    (when (memq 'enter-packages style)
      (tellv ns setTreatsFilePackagesAsDirectories: #:type _BOOL #t))

    (let ([result 
           ;; We run the file dialog completely modally --- shutting out
           ;; all other eventspaces and threads. It would be nice to improve
           ;; on this, but it's good enough.
           (atomically
            (let ([front (get-front)]
                  [parent (and (version-10.6-or-later?)
                               parent)])
              (when parent
                (tellv ns beginSheetModalForWindow: (send parent get-cocoa-window)
                       completionHandler: #f))
              (begin0
               (tell #:type _NSInteger ns runModal)
               (when parent (tell app endSheet: ns))
               (when front (tellv (send front get-cocoa-window)
                                  makeKeyAndOrderFront: #f)))))])
      (begin0
       (if (zero? result)
           #f
           (if (memq 'multi style)
               (let ([urls (tell ns URLs)])
                 (for/list ([i (in-range (tell #:type _NSUInteger urls count))])
                   (nsurl->string (tell urls objectAtIndex: #:type _NSUInteger i))))
               (let ([url (tell ns URL)])
                 (nsurl->string url))))
       (release ns)))))

