
;;;
;;; feature-isync.
;;;

(define (prep-str sym str)
  (symbol-append sym '- (string->symbol str)))

(define (isync-channel id local remote)
  `((Channel ,(prep-str id local))
    (Near ,(format #f ":~a-local:~a" id local))
    (Far ,(format #f ":~a-remote:~a" id remote))
    ,#~""))

(define (isync-group-with-channels id isync-mapping)
  (append
   (append-map
    (lambda (x) (isync-channel id (car x) (cdr x)))
    isync-mapping)
   `((Group ,(symbol-append id))
     ,@(map
        (lambda (x) (list 'Channel (prep-str id (car x))))
        isync-mapping)
     ,#~"")))

(define* (generate-isync-serializer
          host folders-mapping
          #:key
          (port #f)
          (auth-mechs #f)
          (subfolders 'Verbatim)
          (cipher-string #f)
          (pipeline-depth #f))
  (ensure-pred symbol? subfolders)

  (define (isync-settings mail-directory mail-account)
    (let* ((id       (mail-account-id mail-account))
           (user     (or (mail-account-user mail-account)
                         (mail-account-fqda mail-account)))
           (pass-cmd (mail-account-get-pass-cmd mail-account)))
      `(,#~(string-append "# Account '" #$(symbol->string id)
                          " starts here")
        (IMAPAccount ,id)
        (Host ,host)
        ,@(if (integer? port) `((Port ,port)) '())
        (User ,user)
        (PassCmd ,pass-cmd)
        ,@(if (symbol? auth-mechs) `((AuthMechs ,auth-mechs)) '())
        (SSLType IMAPS)
        (CertificateFile /etc/ssl/certs/ca-certificates.crt)
        ,@(if (symbol? cipher-string) `((CipherString ,cipher-string)) '())
        ,@(if (integer? pipeline-depth) `((PipelineDepth ,pipeline-depth)) '())
        ,#~""
        (IMAPStore ,(symbol-append id '-remote))
        (Account ,id)
        ,#~""
        (MaildirStore ,(symbol-append id '-local))
        (SubFolders ,subfolders)
        (Path ,(string-append mail-directory "/accounts/" user "/"))
        (Inbox ,(string-append mail-directory "/accounts/" user "/inbox"))
        ,#~""
        ,@(isync-group-with-channels id folders-mapping))))
  isync-settings)

;; Directory names has lowercased spelling to match notmuch tags
(define gmail-folder-mapping
  '(("inbox"   . "INBOX")
    ("sent"    . "[Gmail]/Sent Mail")
    ("drafts"  . "[Gmail]/Drafts")
    ("archive" . "[Gmail]/All Mail")
    ("trash"   . "[Gmail]/Trash")
    ("spam"    . "[Gmail]/Spam")))

(define gandi-folder-mapping
  '(("inbox"   . "INBOX")
    ("sent"    . "Sent")
    ("drafts"  . "Drafts")
    ("archive" . "Archive")
    ("trash"   . "Trash")
    ("spam"    . "Junk")))

(define gmx-fr-folder-mapping
  '(("inbox"   . "INBOX")
    ("sent"    . "Envoy&AOk-s")
    ("drafts"  . "Brouillons")
    ("archive" . "Archive")
    ("trash"   . "Corbeille")
    ("spam"    . "Junk")))

(define outlook-fr-folder-mapping
  '(("inbox"   . "INBOX")
    ("sent"    . "&AMk-l&AOk-ments envoy&AOk-s") ;"Éléments envoyés"
    ("drafts"  . "Brouillons")
    ("archive" . "Notes")
    ("trash"   . "&AMk-l&AOk-ments supprim&AOk-s") ;"Éléments supprimés"
    ("spam"    . "Courrier ind&AOk-sirable"))) ;"Courrier indésirable"

(define mailbox-folder-mapping
  '(("inbox"   . "INBOX")
    ("sent"    . "Sent")
    ("drafts"  . "Drafts")
    ("trash"   . "Trash")
    ("junk"    . "Junk")
    ("archive" . "Archive")))

(define hosteurope-de-folder-mapping
  '(("inbox"   . "INBOX")
    ("sent"    . "Sent")
    ("drafts"  . "Entwurf")
    ("trash"   . "Trash")
    ("spam"    . "Spam")
    ("archive" . "All")))

(define gmx-fr-isync-settings
  (generate-isync-serializer "imap.gmx.net" gmx-fr-folder-mapping))

(define ovh-isync-settings
  (generate-isync-serializer "ssl0.ovh.net" gandi-folder-mapping
                             #:subfolders 'Legacy
                             #:auth-mechs 'LOGIN))

(define gmail-isync-settings
  (generate-isync-serializer "imap.gmail.com" gmail-folder-mapping))

(define gandi-isync-settings
  (generate-isync-serializer "mail.gandi.net" gandi-folder-mapping))

(define mailbox-isync-settings
  (generate-isync-serializer "imap.mailbox.org" mailbox-folder-mapping))

(define hosteurope-de-isync-settings
  (generate-isync-serializer "imap.hosteurope.de" hosteurope-de-folder-mapping))

(define* (get-ovh-pro-isync-settings
          #:key
          (folder-mapping #f)
          (host-number #f))
  (ensure-pred list? folder-mapping)
  (generate-isync-serializer
    (string-append "pro" host-number ".mail.ovh.net")
    folder-mapping
    #:auth-mechs 'LOGIN
    #:subfolders 'Legacy))

(define ovh-pro2-fr-isync-settings
  (get-ovh-pro-isync-settings
   #:host-number "2"
   #:folder-mapping outlook-fr-folder-mapping))

(define (generic-isync-settings mail-directory mail-account)
  (let* ((user     (mail-account-fqda mail-account)))
    `(,#~"# Do not know how to serialize generic accounts :("
      ,#~(format #f "# ~a wasn't configured by rde," #$user)
      ,#~"# Try to set another value for mail-account's type field.")))

(define %default-isync-serializers
  `((gmail . ,gmail-isync-settings)
    (gandi . ,gandi-isync-settings)
    (gmx-fr . ,gmx-fr-isync-settings)
    (ovh . ,ovh-isync-settings)
    (ovh-pro2-fr . ,ovh-pro2-fr-isync-settings)
    (mailbox . ,mailbox-isync-settings)
    (hosteurope-de . ,hosteurope-de-isync-settings)
    (generic . ,generic-isync-settings)))

(define default-isync-global-settings
  `((Create Both)
    (Expunge Both)
    (SyncState *)
    ,#~""))

(define* (feature-isync
          #:key
          (mail-account-ids #f)
          (isync-global-settings default-isync-global-settings)
          (isync-serializers %default-isync-serializers)
          (isync-verbose #f))
  "Setup and configure isync.  If MAIL-ACCOUNT-IDS not provided use all
mail accounts.  ISYNC-VERBOSE controls output verboseness of
@file{mbsync}."
  (ensure-pred maybe-list? mail-account-ids)
  (ensure-pred list? isync-serializers)
  (ensure-pred list? isync-global-settings)

  ;; Sync mail deletion
  ;; https://notmuchmail.org/pipermail/notmuch/2016/023112.html
  ;; http://tiborsimko.org/mbsync-duplicate-uid.html

  ;; https://notmuchmail.org/emacstips/#index25h2

  (define (get-home-services config)
    (require-value 'mail-accounts config
                   "feature-isync can't operate without mail-accounts.")

    (let* ((mail-accounts
            (filter (lambda (x) (eq? (mail-account-synchronizer x) 'isync))
                    (get-value 'mail-accounts config)))
           (mail-directory-fn (get-value 'mail-directory-fn config))
           (mail-directory    (mail-directory-fn config)))

      (define (serialize-mail-acc mail-acc)
        ((assoc-ref isync-serializers (mail-account-type mail-acc))
         mail-directory mail-acc))

      (list
       (simple-service
        'isync-ensure-mail-dirs-exists
        home-activation-service-type
        #~(map mkdir-p
               '#$(map (lambda (acc)
                         (string-append mail-directory "/accounts/"
                                        (mail-account-fqda acc)))
                       mail-accounts)))
       (service
        home-isync-service-type
        (home-isync-configuration
         (config
          (append
           isync-global-settings
           (append-map serialize-mail-acc mail-accounts))))))))

  ;; MAYBE: Wrap it in a program-file to make it possible to call it
  ;; with system*
  (define (isync-synchronize-cmd-fn mail-acc)
    (string-append "mbsync "
                   (if isync-verbose "-V " "")
                   (symbol->string (mail-account-id mail-acc))))

  (feature
   (name 'isync)
   (values `((isync . #t)
             (isync-synchronize-cmd-fn . ,isync-synchronize-cmd-fn)))
   (home-services-getter get-home-services)))
