;;; org-ref-helm-cite.el --- Helm interface to insert citations from bibtex files for org-ref  -*- lexical-binding: t; -*-

;; Copyright (C) 2016  John Kitchin

;; Author: John Kitchin <jkitchin@andrew.cmu.edu>
;; Keywords:

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary: This is a competitor of `helm-bibtex'.
;; The main difference is the format of the candidates, which are full citations
;; in this package, and multiline. This package also makes the candidates
;; sortable in different ways, and provides different, context specific actions
;; depending on what buffer you call `org-ref-helm-cite' from, and depending
;; on the properties of the selected candidate.
;;
;; Another significant feature is persistent caching of bibtex files to make
;; startup fast.

;;

(declare-function 'org-ref-find-bibliography "org-ref-core.el")
(declare-function 'org-ref-get-bibtex-key-and-file "org-ref-core.el")

(defvar org-ref-pdf-directory)
(defvar org-ref-notes-directory)
(defvar org-ref-cite-types)
(defvar org-ref-default-citation-link)
(defvar org-ref-insert-link-function)
(defvar org-ref-insert-cite-function)
(defvar org-ref-insert-label-function)
(defvar org-ref-insert-ref-function)
(defvar org-ref-cite-onclick-function)
(defvar org-ref-insert-cite-key)


;;; Code:
(require 'org-ref-helm)
(require 'org-ref-bibtex)


(defun org-ref-helm-cite-completion ()
  "Use helm and org-ref for completion."
  (interactive)
  (setq org-ref-insert-link-function 'org-ref-insert-link
	org-ref-insert-cite-function 'org-ref-helm-cite
	org-ref-insert-label-function 'org-ref-helm-insert-label-link
	org-ref-insert-ref-function 'org-ref-helm-insert-ref-link
	org-ref-cite-onclick-function 'org-ref-helm-cite-click))

(org-ref-helm-cite-completion)

(define-key org-mode-map
  (kbd org-ref-insert-cite-key)
  org-ref-insert-link-function)

;;* Variables
(defvar org-ref-helm-cite-from nil
  "Variable to store the mode `org-ref-helm-cite' was called
  from. This is used to provide some context specific actions.")


(defvar org-ref-helm-cite-help-message
  "* Org-ref helm bibtex.
M-<down> allows you to sort the entries by year or first author
last name.")


(defvar org-ref-helm-cite-map
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map helm-map)
    (define-key map (kbd "M-<down>") 'org-ref-helm-cite-sort)
    map))


(defvar orhc-sort-fn nil
  "Function for sorting the helm entries.")

;;* Helm functions

(defun orhc-helm-cite-sort-alphabetical-a (c1 c2)
  "Sort entries by first author last name from a to z."
  (let* ((a1 (cdr c1))
	 (au1 (cdr (assoc "author" a1)))
	 (a2 (cdr c2))
	 (au2 (cdr (assoc "author" a2)))
	 fa1 fa2)
    (if (or (not (stringp au1))
	    (not (stringp au2)))
	nil
      ;; we need to parse names
      (setq fa1 (car (s-split " and " au1))
	    fa2 (car (s-split " and " au2)))

      (if (string-match "," fa1)
	  ;; last, first
	  (setq fa1 (car (s-split "," fa1)))
	;; first last, ignore von names and jr
	(setq fa1 (car (last (s-split " " fa1)))))

      (if (string-match "," fa2)
	  ;; last, first
	  (setq fa2 (car (s-split "," fa2)))
	;; first last, ignore von names, and jr
	(setq fa2 (car (last (s-split " " fa2)))))
      (string< fa1 fa2))))


(defun orhc-helm-cite-sort-alphabetical-z (c1 c2)
    "Sort entries by first author last name from z to a."
  (let* ((a1 (cdr c1))
	 (au1 (cdr (assoc "author" a1)))
	 (a2 (cdr c2))
	 (au2 (cdr (assoc "author" a2)))
	 fa1 fa2)
    (if (or (not (stringp au1))
	    (not (stringp au2)))
	nil
      ;; we need to parse names to get lastname
      (setq fa1 (car (s-split " and " au1))
	    fa2 (car (s-split " and " au2)))

      (if (string-match "," fa1)
	  ;; last, first
	  (setq fa1 (car (s-split "," fa1)))
	;; first last, ignore von names and jr
	(setq fa1 (car (last (s-split " " fa1)))))

      (if (string-match "," fa2)
	  ;; last, first
	  (setq fa2 (car (s-split "," fa2)))
	;; first last, ignore von names, and jr
	(setq fa2 (car (last (s-split " " fa2)))))
      (string< fa2 fa1))))


(defun org-ref-helm-cite-sort ()
  "Sort interface for `org-ref-helm-cite'."
  (interactive)
  (let ((action (read-char "year↓ (y) year↑ (Y)
1st author↓ (a) 1st author↑ (z)
key↓ (k) key↑ (K): ")))
    (cond
     ;; sort on year
     ((eq action ?y)
      (setq orhc-sort-fn
	    (lambda (c1 c2)
	      (let* ((a1 (cdr c1))
		     (y1 (cdr (assoc "year" a1)))
		     (a2 (cdr c2))
		     (y2 (cdr (assoc "year" a2))))
		(if (or (null y1) (null y2))
		    nil
		  (> (string-to-number  y1) (string-to-number y2)))))))
     ((eq action ?Y)
      (setq orhc-sort-fn
	    (lambda (c1 c2)
	      (let* ((a1 (cdr c1))
		     (y1 (cdr (assoc "year" a1)))
		     (a2 (cdr c2))
		     (y2 (cdr (assoc "year" a2))))
		(if (or (null y1) (null y2))
		    nil
		  (< (string-to-number  y1) (string-to-number y2)))))))
     ;; sort on key
     ((eq action ?k)
      (setq orhc-sort-fn
	    (lambda (c1 c2)
	      (let* ((a1 (cdr c1))
		     (k1 (cdr (assoc "=key=" a1)))
		     (a2 (cdr c2))
		     (k2 (cdr (assoc "=key=" a2))))
		(string< k2 k1)))))
     ((eq action ?K)
      (setq orhc-sort-fn
	    (lambda (c1 c2)
	      (let* ((a1 (cdr c1))
		     (k1 (cdr (assoc "=key=" a1)))
		     (a2 (cdr c2))
		     (k2 (cdr (assoc "=key=" a2))))
		(string< k1 k2)))))
     ;; sort on first author last name
     ((eq action ?a)
      (setq orhc-sort-fn #'orhc-helm-cite-sort-alphabetical-a))
     ((eq action ?z)
      (setq orhc-sort-fn #'orhc-helm-cite-sort-alphabetical-z))
     (t (setq orhc-sort-fn nil)))
    (helm-update)
    (setq orhc-sort-fn nil)))


(defun org-ref-helm-candidate-transformer (candidates _source)
  "Transform CANDIDATES, sorting if needed.
SOURCE is ignored, but required."
  (if orhc-sort-fn
      (-sort orhc-sort-fn candidates)
    candidates))


(defun org-ref-helm-cite-action-transformer (actions candidate)
  "Compute ACTIONS for CANDIDATE."
  ;; Check for pdf and add open or get action.
  (setq actions (append
		 actions
		 '(("insert citation(s)" . org-ref-helm-cite-insert-citation)
		   ("show entry" . org-ref-helm-cite-open-entry))))

  (let ((pdf (expand-file-name
	      (concat (cdr (assoc "=key=" candidate)) ".pdf")
	      org-ref-pdf-directory)))
    (if (file-exists-p pdf)
	(setq actions (append actions
			      (list
			       (cons
				(format "Open %s" pdf)
				(lambda (_candidate)
				  (org-open-file pdf))))))
      (when (assoc "doi" candidate)
	(setq actions
	      (append actions
		      (list
		       (cons
			(format "Get PDF")
			(lambda (candidate)
			  (save-window-excursion
			    (find-file (cdr (assoc "file" candidate)))
			    (goto-char (cdr (assoc "position" candidate)))
			    (doi-utils-get-bibtex-entry-pdf))))))))))

  ;; check for url/doi
  (when (assoc "url" candidate)
    (setq actions (append actions
			  (list
			   (cons (format "Open %s"
					 (cdr (assoc "url" candidate)))
				 (lambda (x)
				   (browse-url (cdr (assoc "url" x)))))))))

  (when (assoc "doi" candidate)
    (setq actions (append actions
			  (list
			   (cons
			    (format "Open doi (%s)"
				    (cdr (assoc "doi" candidate)))
			    (lambda (x)
			      (browse-url
			       (format "http://dx.doi.org/%s"
				       (cdr (assoc "doi" x))))))))))

  ;; Notes, open or create.
  (let ((note-file (expand-file-name
		    (concat (cdr (assoc "=key=" candidate)) ".org")
		    org-ref-notes-directory)))
    (if (file-exists-p note-file)
	(setq actions (append actions (list (cons "Open notes"
						  (lambda (_x)
						    (find-file note-file))))))
      (setq actions (append actions (list (cons "Create notes"
						(lambda (_x)
						  (find-file note-file))))))))

  (setq actions (append
		 actions
		 '(("Add keywords" . org-ref-helm-cite-set-keywords)
		   ("copy to clipboard" . org-ref-helm-cite-copy-entries)
		   ("email" . org-ref-helm-cite-email-entries)
		   ("Insert formatted entries" . (lambda (_)
						   (insert
						    (mapconcat 'identity
							       (cl-loop for key in (helm-marked-candidates)
								        collect (org-ref-format-entry key))
							       "\n\n"))))
		   ("Copy formatted entry" . (lambda (_)
					       (kill-new
						(mapconcat 'identity
							   (cl-loop for key in (helm-marked-candidates)
								    collect (org-ref-format-entry key))
							   "\n\n")))))))

  ;; this is where we could add WOK/scopus functions
  actions)


(defun org-ref-helm-cite-insert-citation (_candidate)
  "Insert selected CANDIDATE as cite link.
This is an action for helm, and it actually works on
`helm-marked-candidates'. Append KEYS if you are on a link.

In the `org-ref-helm-cite' buffer, \\[universal-argument] will give you
a helm menu to select a new link type for the selected entries.

A double \\[universal-argument] \\[universal-argument] will
change the key at point to the selected keys."
  (let* ((keys (cl-loop for entry in (helm-marked-candidates)
			collect (cdr (assoc "=key=" entry))))
	 (object (org-element-context))
         (last-char (save-excursion
                      (when (org-element-property :end object)
                        (goto-char (org-element-property :end object))
                        (unless (bobp)
                          (backward-char))
                        (if (looking-at " ")
                            " "
                          "")))))
    (cond
     ;; case where we are in a link
     ((and (equal (org-element-type object) 'link)
           (-contains?
            org-ref-cite-types
            (org-element-property :type object)))
      (cond
       ;; no prefix. insert or append keys
       ((equal helm-current-prefix-arg nil)
	(cond
	 ;; point after :
	 ((looking-back ":" (- (point) 2))
	  (insert (concat (mapconcat 'identity keys ",") ",")))
	 ;; point on :
	 ((looking-at ":")
	  (forward-char)
	  (insert (concat (mapconcat 'identity keys ",") ",")))
	 ;; point on the cite type
	 ((-contains? org-ref-cite-types (thing-at-point 'word))
	  (re-search-forward ":")
	  (insert (concat (mapconcat 'identity keys ",") ",")))
	 ;; after ,
	 ((looking-back "," (- (point) 2))
	  (insert (concat (mapconcat 'identity keys ",") ",")))
	 ;; on comma
	 ((looking-at ",")
	  (forward-char)
	  (insert (concat (mapconcat 'identity keys ",") ",")))
	 ;; somewhere in the middle or end
	 (t
	  ;; goto next comma or end
	  (re-search-forward
	   ","
	   (org-element-property :end object) t)
	  (skip-chars-backward " ")
	  (insert (mapconcat 'identity keys ","))
	  (unless (looking-at ",") (insert ",")))))
       ;; double prefix, replace key at point
       ((equal helm-current-prefix-arg '(16))
        (setf (buffer-substring
               (org-element-property :begin object)
               (org-element-property :end object))
              (concat
               (replace-regexp-in-string
                (car (org-ref-get-bibtex-key-and-file)) ; key
                (mapconcat 'identity keys ",")		; new keys
                (org-element-property :raw-link object))
               ;; replace space at end to avoid collapsing into next word.
               last-char))
        ;; and we want to go to the end of the new link
        (goto-char
         (org-element-property :end (org-element-context))))
       (t
        (message "Not found"))))

     ;; We are next to a link, and we want to append
     ;; next to a link means one character back is on a link.
     ((save-excursion
        (unless (bobp) (backward-char))
        (and (equal (org-element-type (org-element-context)) 'link)
             (-contains?
              org-ref-cite-types
              (org-element-property :type (org-element-context)))))
      (skip-chars-backward " ")
      (insert (concat "," (mapconcat 'identity keys ","))))

     ;; insert fresh link
     (t
      ;;(message-box "fresh link")
      (insert
       (concat (if (equal helm-current-prefix-arg '(4))
                   (helm :sources `((name . "link types")
                                    (candidates . ,org-ref-cite-types)
                                    (action . (lambda (x) x))))
                 org-ref-default-citation-link)
               ":"
               (s-join "," keys)))))))


(defun org-ref-helm-cite-init ()
  "Initializes the source, setting bibtex files from the
originating buffer, and mode of originating buffer."
  (org-ref-save-all-bibtex-buffers)
  (setq org-ref-bibtex-files (org-ref-find-bibliography))
  ;; save major-mode we came from so we can do context specific things.
  (setq org-ref-helm-cite-from major-mode)
  (message "initialized."))


(defun org-ref-helm-cite-open-entry (entry)
  "Open the selected bibtex entry in its file."
  (find-file (cdr (assoc "bibfile" entry)))
  (goto-char (cdr (assoc "position" entry)))
  (bibtex-beginning-of-entry))


(defun org-ref-helm-cite-copy-entries (_candidate)
  "Copy selected bibtex entries to the clipboard."
  (with-temp-buffer
    (cl-loop for entry in (helm-marked-candidates)
	  do
	  (save-window-excursion
	    (org-ref-helm-cite-open-entry entry)
	    (bibtex-copy-entry-as-kill))
	  (bibtex-yank)
	  (insert "\n"))
    (kill-region (point-min) (point-max))))


(defun org-ref-helm-cite-email-entries (_candidate)
  "Insert selected entries and attach pdf files to an email.
Create email unless called from an email."
  (unless (or (eq org-ref-helm-cite-from 'message-mode)
	      (eq org-ref-helm-cite-from 'mu4e-compose-mode))
    (compose-mail))
  (cl-loop for entry in (helm-marked-candidates)
	do
	(save-window-excursion
	  (org-ref-helm-cite-open-entry entry)
	  (bibtex-copy-entry-as-kill))
	(message-goto-body)
	(insert (pop bibtex-entry-kill-ring))
	(insert "\n")
	if (file-exists-p (expand-file-name
			   (concat (cdr (assoc "=key=" entry)) ".pdf")
			   org-ref-pdf-directory))
	do
	(mml-attach-file (expand-file-name
			  (concat (cdr (assoc "=key=" entry)) ".pdf")
			  org-ref-pdf-directory)))
  (message-goto-to))

(defun org-ref-helm-cite-set-keywords (_candidate)
  "Prompt for keywords, and put them on the selected entries."
  (let ((keywords (read-string "Keyword(s) comma-separated: " ))
	entry-keywords)
    (cl-loop for entry in (helm-marked-candidates)
	  do
	  (save-window-excursion
	    (org-ref-helm-cite-open-entry entry)
	    (setq entry-keywords (bibtex-autokey-get-field "keywords"))
	    (bibtex-set-field
	     "keywords"
	     (if (> (length entry-keywords) 0)
		 (concat entry-keywords ", " keywords)
	       keywords))))))

;;** Helm sources
(defvar  orhc-multiline t
  "Make helm-source multiline if non-nil.
This adds a small separator between the candidates which is a
little more readable.")

(defvar org-ref-helm-user-candidates '()
  "List of user-defined candidates to act when clicking on a cite link.
This is a list of cons cells '((\"description\" . action)). The
action function should not take an argument, and should assume
point is on the cite key of interest.")

(defvar org-ref-helm-cite-source
  (helm-build-sync-source "org-ref Bibtex"
    :init #'org-ref-helm-cite-init
    :candidates #'orhc-bibtex-candidates
    :keymap 'org-ref-helm-cite-map
    :multiline orhc-multiline
    :help-message 'org-ref-helm-cite-help-message
    :filtered-candidate-transformer 'org-ref-helm-candidate-transformer
    :action-transformer 'org-ref-helm-cite-action-transformer
    :action '()))

;; Fallback sources
;; The candidates here are functions that work on `helm-pattern'.
(defvar org-ref-helm-cite-fallback-source
  nil
  "Helm fallback source.")


(setq org-ref-helm-cite-fallback-source
      (helm-build-sync-source "org-ref bibtex Fallbacks"
	:candidates '(("Google" . (lambda ()
				    (browse-url
				     (format "http://www.google.com/search?q=%s"
					     (url-hexify-string helm-pattern)))))
		      ("Google Scholar" . (lambda ()
					    (browse-url
					     (format "http://scholar.google.com/scholar?q=%s"
						     (url-hexify-string helm-pattern)))))
		      ("Crossref" . (lambda ()
				      (browse-url
				       (format
					"http://search.crossref.org/?q=%s"
					(url-hexify-string helm-pattern)))))
		      ("Pubmed" . (lambda ()
				    (browse-url
				     (format
				      "http://www.ncbi.nlm.nih.gov/pubmed/?term=%s"
				      (url-hexify-string helm-pattern)))))
		      ("Arxiv" . (lambda ()
				   (browse-url
				    (format
				     "http://arxiv.org/find/all/1/all:+AND+%s/0/1/0/all/0/1"
				     (url-hexify-string helm-pattern)))))
		      ("WebOfKnowledge" . (lambda ()
					    (browse-url
					     (format
					      "http://gateway.webofknowledge.com/gateway/Gateway.cgi?topic=%s&GWVersion=2&SrcApp=WEB&SrcAuth=HSB&DestApp=UA&DestLinkType=GeneralSearchSummary"
					      (url-hexify-string helm-pattern)))))
		      ("Scopus" . (lambda ()
				    (browse-url
				     (format
				      "http://www.scopus.com//search/submit/basic.url?field1=TITLE-ABS-KEY&searchterm1=%s"
				      (url-hexify-string helm-pattern)))))
		      )
	;; This keeps all the fallback candidates available, by tricking it into
	;; thinking every candidate is a match.
	:match (lambda (_candidate) t)
	:action (lambda (candidate) (funcall candidate))))


(defun org-ref-helm-cite ()
  "Helm interface to bibtex files for `org-ref'."
  (interactive)
  (helm :sources '(org-ref-helm-cite-source
		   org-ref-helm-cite-fallback-source)))


(defalias 'orhc 'org-ref-helm-cite)

;; ** Onclick function
;; These are adapted from org-ref-helm-bibtex, and the dependencies on helm-bibtex removed.

(defun org-ref-helm-cite-candidates ()
  "Generate the list of possible candidates for click actions on a cite link.
Checks for pdf and doi, and add appropriate functions."
  (let* ((results (org-ref-get-bibtex-key-and-file))
         (key (car results))
         (bibfile (cdr results))
	 (pdf-file (funcall org-ref-get-pdf-filename-function key))
         (url (save-excursion
                (with-temp-buffer
                  (insert-file-contents bibfile)
                  (bibtex-set-dialect (parsebib-find-bibtex-dialect) t)
                  (bibtex-search-entry key)
                  (bibtex-autokey-get-field "url"))))
         (doi (save-excursion
                (with-temp-buffer
                  (insert-file-contents bibfile)
                  (bibtex-set-dialect (parsebib-find-bibtex-dialect) t)
                  (bibtex-search-entry key)
                  ;; I like this better than bibtex-url which does not always find
                  ;; the urls
                  (bibtex-autokey-get-field "doi"))))
         (candidates `(("Quit" . org-ref-citation-at-point)
                       ("Open bibtex entry" . org-ref-open-citation-at-point))))
    ;; for some reason, when there is no doi or url, they are returned as "". I
    ;; prefer nil so we correct this here.
    (when (string= doi "") (setq doi nil))
    (when (string= url "") (setq url nil))

    ;; Conditional pdf functions
    ;; try with org-ref first
    (cond ((file-exists-p pdf-file)
    	   (cl-pushnew
    	    '("Open pdf" . (lambda ()
    			     (funcall org-ref-open-pdf-function)))
    	    candidates))
	  
	  ;; try with doi
    	  (t
    	   (cl-pushnew
    	    '("Try to get pdf" . (lambda ()
    				   (save-window-excursion
    				     (org-ref-open-citation-at-point)
    				     (bibtex-beginning-of-entry)
    				     (doi-utils-get-bibtex-entry-pdf))))
    	    candidates)))

    (cl-pushnew
     '("Add/Open notes" . org-ref-open-notes-at-point)
     candidates)

    ;; conditional url and doi functions
    (when (or url doi)
      (cl-pushnew
       '("Open in browser" . org-ref-open-url-at-point)
       candidates))

    (when doi
      (mapc (lambda (x)
              (cl-pushnew x candidates))
            `(("WOS" . org-ref-wos-at-point)
              ("Related articles in WOS" . org-ref-wos-related-at-point)
              ("Citing articles in WOS" . org-ref-wos-citing-at-point)
              ("Google Scholar" . org-ref-google-scholar-at-point)
              ("Pubmed" . org-ref-pubmed-at-point)
              ("Crossref" . org-ref-crossref-at-point))))

    (cl-pushnew
     '("Insert new citation" . (lambda ()
                                 (org-ref-helm-insert-cite-link nil)))
     candidates)

    (cl-pushnew
     '("Delete key at point" . org-ref-delete-key-at-point)
     candidates)

    ;;  This is kind of clunky. We store the key at point. Add the new ref. Get
    ;;  it off the end, and put it in the original position.
    (cl-pushnew
     '("Replace key at point" . org-ref-replace-key-at-point)
     candidates)

    (cl-pushnew
     '("Delete citation at point" . org-ref-delete-cite-at-point)
     candidates)

    (cl-pushnew
     '("Sort keys by year" . org-ref-sort-citation-link)
     candidates)

    (cl-pushnew
     '("Copy formatted citation to clipboard" . org-ref-copy-entry-as-summary)
     candidates)

    (cl-pushnew
     '("Copy key to clipboard" . (lambda ()
                                   (kill-new
                                    (car (org-ref-get-bibtex-key-and-file)))))
     candidates)

    (cl-pushnew
     '("Copy bibtex entry to file" . org-ref-copy-entry-at-point-to-file)
     candidates)

    (cl-pushnew
     '("Email bibtex entry and pdf" . (lambda ()
                                        (save-excursion
                                          (org-ref-open-citation-at-point)
                                          (org-ref-email-bibtex-entry))))
     candidates)

    ;; add Scopus functions. These work by looking up a DOI to get a Scopus
    ;; EID. This may only work for Scopus articles. Not all DOIs are recognized
    ;; in the Scopus API. We only load these if you have defined a
    ;; `*scopus-api-key*', which is required to do the API queries. See
    ;; `scopus'. These functions are appended to the candidate list.
    (when (and (boundp '*scopus-api-key*) *scopus-api-key*)
      (cl-pushnew
       '("Open in Scopus" . (lambda ()
                              (let ((eid (scopus-doi-to-eid (org-ref-get-doi-at-point))))
                                (if eid
                                    (scopus-open-eid eid)
                                  (message "No EID found.")))))
       candidates)

      (cl-pushnew
       '("Scopus citing articles" . (lambda ()
                                      (let ((url (scopus-citing-url
                                                  (org-ref-get-doi-at-point))))
                                        (if url
                                            (browse-url url)
                                          (message "No url found.")))))
       candidates)

      (cl-pushnew
       '("Scopus related by authors" . (lambda ()
                                         (let ((url (scopus-related-by-author-url
                                                     (org-ref-get-doi-at-point))))
                                           (if url
                                               (browse-url url)
                                             (message "No url found.")))))
       candidates)

      (cl-pushnew
       '("Scopus related by references" . (lambda ()
                                            (let ((url (scopus-related-by-references-url
                                                        (org-ref-get-doi-at-point))))
                                              (if url
                                                  (browse-url url)
                                                (message "No url found.")))))
       candidates)

      (cl-pushnew
       '("Scopus related by keywords" . (lambda ()
                                          (let ((url (scopus-related-by-keyword-url
                                                      (org-ref-get-doi-at-point))))
                                            (if url
                                                (browse-url url)
                                              (message "No url found.")))))
       candidates))

    ;; finally return a numbered list of the candidates
    (cl-loop for i from 0
             for cell in (reverse candidates)
             collect (cons (format "%2s. %s" i (car cell))
                           (cdr cell)))))

(defun org-ref-helm-cite-click (_key)
  "Open helm for actions on a cite link.
subtle points.

1. get name and candidates before entering helm because we need
the org-buffer.

2. switch back to the org buffer before evaluating the
action.  most of them need the point and buffer.

KEY is returned for the selected item(s) in helm."
  (interactive)
  (let ((name (org-ref-format-entry (org-ref-get-bibtex-key-under-cursor)))
        (candidates (org-ref-helm-cite-candidates))
        (cb (current-buffer)))

    (helm :sources `(((name . ,name)
                      (candidates . ,candidates)
                      (action . (lambda (f)
                                  (switch-to-buffer ,cb)
                                  (funcall f))))
                     ((name . "User functions")
                      (candidates . ,org-ref-helm-user-candidates)
                      (action . (lambda (f)
                                  (switch-to-buffer ,cb)
                                  (funcall f))))))))


;;* Formatted citations

(defun orhc-formatted-citations (_candidate)
  "Return string containing formatted citations for entries in
`helm-marked-candidates'."
  (load-library
   (completing-read "Style: " '("unsrt" "author-year") nil nil "unsrt"))

  (with-temp-buffer
    (cl-loop for i from 1 to (length (helm-marked-candidates))
	     for entry in (helm-marked-candidates)
	     do
	     (insert (format "%s. %s\n\n" i (orhc-formatted-citation entry))))

    (buffer-string)))


(defun orhc-insert-formatted-citations (candidate)
  "Insert formatted citations at point for selected entries."
  (insert (orhc-formatted-citations candidate)))


(defun orhc-copy-formatted-citations (candidate)
  "Copy formatted citations to clipboard for selected entries."
  (with-temp-buffer
    (orhc-insert-formatted-citations candidate)
    (kill-ring-save (point-min) (point-max))))

(provide 'org-ref-helm-cite)
;;; org-ref-helm-cite.el ends here
