;;; org-ref-bibtex.el -- org-ref-bibtex utilities

;; Copyright(C) 2014 John Kitchin

;; Author: John Kitchin <jkitchin@andrew.cmu.edu>
;; URL: https://github.com/jkitchin/org-ref
;; Version: 0.1
;; Keywords: org-mode, bibtex
;; Package-Requires: ((org-ref) (s) (dash) (doi-utils) (key-chord))

;; This file is not currently part of GNU Emacs.

;; 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 2, 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 ; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:

;; org-ref-bibtex-generate-longtitles
;; org-ref-bibtex-generate-shorttitles
;; org-ref-stringify-journal-name :: replace a journal name with a string in
;; `org-ref-bibtex-journal-abbreviations'
;; org-ref-set-journal-string :: in a bibtex entry run this to replace the
;; journal with a string
;;
;; org-ref-title-case-article :: title case the title in an article or book
;; org-ref-sentence-case-article :: sentence case the title in an article.

;; org-ref-replace-nonascii :: replace nonascii characters in a bibtex
;; entry.  Replacements are in `org-ref-nonascii-latex-replacements'.
;;
;; org-ref-title-case-article
;; org-ref-sentence-case-article
;;
;; org-ref-bibtex-next-entry :: bound to M-n
;; org-ref-bibtex-previous-entry :: bound to M-p
;;
;; Functions to act on a bibtex entry or file
;; org-ref-bibtex-hydra/body gives a hydra menu to a lot of useful functions.
;; org-ref-bibtex-new-entry/body gives a hydra menu to add new bibtex entries.
;; org-ref-bibtex-file/body gives a hydra menu of actions for the bibtex file
;;
;; org-ref-bibtex :: a deprecated menu of actions

(require 'bibtex)
(require 'dash)
(require 'hydra)
(require 'key-chord nil 'no-error)
(require 'message)
(require 's)

(require 'org-ref-citeproc)
(require 'doi-utils)

(defvar org-ref-pdf-directory)
(defvar org-ref-notes-directory)
(defvar org-ref-default-bibliography)

(declare-function reftex-get-bib-field "reftex-cite")
(declare-function key-chord-define-global "key-chord")
(declare-function org-ref-find-bibliography "org-ref-core")
(declare-function org-ref-open-bibtex-pdf "org-ref-core")
(declare-function org-ref-open-bibtex-notes "org-ref-core")
(declare-function org-ref-clean-bibtex-entry "org-ref-core")
(declare-function org-ref-open-in-browser "org-ref-core")
(declare-function org-ref-sort-bibtex-entry "org-ref-core")
(declare-function org-ref-build-full-bibliography "org-ref-core")
(declare-function helm-tag-bibtex-entry "org-ref-helm")
(declare-function bibtex-completion-edit-notes "bibtex-completion")
(declare-function bibtex-completion-get-value "bibtex-completion")
(declare-function bibtex-completion-get-entry "bibtex-completion")
(declare-function parsebib-find-next-item "parsebib")
(declare-function parsebib-read-entry "parsebib")
(declare-function helm-bibtex "helm-bibtex")
(declare-function helm "helm")

;;; Code:

;; This is duplicated from org-ref-core to try to avoid a byte-compile error.
(add-to-list 'load-path
	     (expand-file-name
	      "citeproc"
	      (file-name-directory (or load-file-name (buffer-file-name)))))

(add-to-list 'load-path
	     (expand-file-name
	      "citeproc/csl"
	      (file-name-directory (or load-file-name (buffer-file-name)))))

;;* Custom variables
(defgroup org-ref-bibtex nil
  "Customization group for org-ref-bibtex."
  :group 'org-ref-bibtex)


(defcustom org-ref-bibtex-hydra-key-chord
  nil
  "Key-chord to run `org-ref-bibtex-hydra'.
I like \"jj\""
  :type '(choice (const nil :tag "None")
                 (string))
  :group 'org-ref-bibtex)


(defcustom org-ref-bibtex-hydra-key-binding
  nil
  "Key-binding to run `org-ref-bibtex-hydra'.
I like \"C-c j\"."
  :type '(choice (const nil :tag "No binding")
                 (key-sequence))
  :group 'org-ref-bibtex)


(defcustom org-ref-helm-cite-shorten-authors nil
  "If non-nil show only last names in the helm selection buffer."
  :type 'boolean
  :group 'org-ref-bibtex)


(defcustom org-ref-formatted-citation-formats
  '(("text"  . (("article"  . "${author}, ${title}, ${journal}, ${volume}(${number}), ${pages} (${year}). ${doi}")
		("inproceedings" . "${author}, ${title}, In ${editor}, ${booktitle} (pp. ${pages}) (${year}). ${address}: ${publisher}.")
		("book" . "${author}, ${title} (${year}), ${address}: ${publisher}.")
		("phdthesis" . "${author}, ${title} (Doctoral dissertation) (${year}). ${school}, ${address}.")
		("inbook" . "${author}, ${title}, In ${editor} (Eds.), ${booktitle} (pp. ${pages}) (${year}). ${address}: ${publisher}.")
		("incollection" . "${author}, ${title}, In ${editor} (Eds.), ${booktitle} (pp. ${pages}) (${year}). ${address}: ${publisher}.")
		("proceedings" . "${editor} (Eds.), ${booktitle} (${year}). ${address}: ${publisher}.")
		("unpublished" . "${author}, ${title} (${year}). Unpublished manuscript.")
		(nil . "${author}, ${title} (${year}).")))
    ("org"  . (("article"  . "${author}, /${title}/, ${journal}, *${volume}(${number})*, ${pages} (${year}). ${doi}")
	       ("inproceedings" . "${author}, /${title}/, In ${editor}, ${booktitle} (pp. ${pages}) (${year}). ${address}: ${publisher}.")
	       ("book" . "${author}, /${title}/ (${year}), ${address}: ${publisher}.")
	       ("phdthesis" . "${author}, /${title}/ (Doctoral dissertation) (${year}). ${school}, ${address}.")
	       ("inbook" . "${author}, /${title}/, In ${editor} (Eds.), ${booktitle} (pp. ${pages}) (${year}). ${address}: ${publisher}.")
	       ("incollection" . "${author}, /${title}/, In ${editor} (Eds.), ${booktitle} (pp. ${pages}) (${year}). ${address}: ${publisher}.")
	       ("proceedings" . "${editor} (Eds.), _${booktitle}_ (${year}). ${address}: ${publisher}.")
	       ("unpublished" . "${author}, /${title}/ (${year}). Unpublished manuscript.")
	       (nil . "${author}, /${title}/ (${year})."))))
  "Format strings for formatted bibtex entries for different citation backends.
Used in `org-ref-format-entry'."
  :type '(alist)
  :group 'org-ref-bibtex)

(defcustom org-ref-formatted-citation-backend "text"
  "The backend format for formatted citations.
Should be one of the cars of `org-ref-formatted-citation-formats'."
  :type 'string
  :group 'org-ref-bibtex)

;;* Journal abbreviations
(defvar org-ref-bibtex-journal-abbreviations
  '()
  "List of (string journal-full-name journal-abbreviation). Find
  new abbreviations at http://cassi.cas.org/search.jsp.")

(defcustom org-ref-bibtex-assoc-pdf-with-entry-move-function 'rename-file
  "Function to use when associating pdf files with bibtex entries.
The value should be either `rename-file' or `copy-file'. The former
will move and rename the original file. The latter will leave the
original file in place while creating a renamed copy in
`org-ref-pdf-directory'."
  :type 'function
  :group 'org-ref-bibtex)

(setq org-ref-bibtex-journal-abbreviations
      '(("ACR" "Accounts of Chemical Research" "Acc. Chem. Res.")
        ("ACAT" "ACS Catalysis" "ACS Catal.")
        ("AM" "Acta Materialia" "Acta Mater.")
        ("AMM" "Acta Metallurgica et Materialia" "Acta Metall. Mater.")
        ("AEM" "Advanced Energy Materials" "Adv. Energy Mater.")
        ("AAMI" "ACS Applied Materials \\& Interfaces"
         "ACS Appl. Mater. Interfaces")
        ("AMiner" "American Mineralogist" "Am. Mineral.")
        ("AngC" "Angewandte Chemie-International Edition"
         "Angew. Chem. Int. Edit.")
        ("APLM" "APL Materials" "APL Mat.")
        ("ACBE" "Applied Catalysis B: Environmental" "Appl. Catal. B-Environ.")
        ("APL" "Applied Physics Letters" "Appl. Phys. Lett.")
        ("ASS" "Applied Surface Science" "Appl. Surf. Sci.")
        ("CL" "Catalysis Letters" "Catal. Lett.")
        ("CC" "Catalysis Communications" "Catal. Commun.")
        ("CST" "Catalysis Science & Technology" "Catal. Sci. Technol.")
        ("CT" "Catalysis Today" "Catal. Today")
        ("ChC" "Chemical Communications" "Chem. Commun.")
        ("CPL" "Chemical Physics Letters" "Chem. Phys. Lett")
        ("CR" "Chemical Reviews" "Chem. Rev.")
        ("CSR" "Chemical Society Reviews" "Chem. Soc. Rev.")
        ("CSR" "Chemical Society Reviews" "Chem. Soc. Rev.")
        ("CM" "Chemistry of Materials" "Chem. Mater.")
        ("CSA" "Colloids and Surfaces, A: Physicochemical and Engineering Aspects"
         "Colloids Surf., A")
        ("CF" "Combustion and Flame" "Combust. Flame")
        ("CPMS" "Computational Materials Science" "Comp. Mater. Sci.")
        ("CPC" "Computer Physics Communications" "Comput. Phys. Commun.")
        ("CSE" "Computing in Science \\& Engineering" "Comput. Sci. Eng.")
        ("CGD" "Crystal Growth \\& Design" "Cryst. Growth Des.")
        ("CEC" "CrystEngComm" "CrystEngComm")
        ("EA" "Electrochimica Acta" "Electrochim. Acta")
        ("ECST" "ECS Transactions" "ECS Trans.")
        ("EES" "Energy \\& Environmental Science" "Energy Environ. Sci.")
        ("HPR" "High Pressure Research" "High Pressure Res.")
        ("IC" "Inorganic Chemistry" "Inorg. Chem.")
        ("IECR" "Industrial \\& Engineering Chemistry Research"
         "Ind. Eng. Chem. Res.")
        ("JJAP" "Japanese Journal of Applied Physics" "Jpn. J. Appl. Phys.")
        ("JMatR" "Journal of  Materials Research" "J. Mater. Res.")
        ("JALC" "Journal of Alloys and Compounds" "J. Alloy Compd.")
        ("JAC" "Journal of Applied Crystallography" "J. Appl. Crystallogr.")
        ("JAE" "Journal of Applied Electrochemistry" "J. Appl. Electrochem.")
        ("JAP" "Journal of Applied Physics" "J. Appl. Phys.")
        ("JC" "Journal of Catalysis" "J. Catal.")
        ("JCP" "Journal of Chemical Physics" "J. Chem. Phys.")
        ("JCC" "Journal of Computational Chemistry" "J. Comput. Chem.")
        ("JCG" "Journal of Crystal Growth" "J. Crys. Growth")
        ("JMC" "Journal of Materials Chemistry" "J. Mater. Chem.")
        ("JMC" "Journal of Materials Chemistry" "J. Mater. Chem.")
        ("JMSL" "Journal of Materials Science Letters" "J. Mater. Sci. Lett.")
        ("JMS" "Journal of Membrane Science" "J. Memb. Sci.")
        ("JPE" "Journal of Phase Equilibria" "J. Phase Equilib.")
        ("JPCS" "Journal of Physics and Chemistry of Solids"
         "J. Phys. Chem. Solids")
        ("JPCM" "Journal of Physics: Condensed Matter"
         "J. Phys.: Condens. Matter")
        ("JPS" "Journal of Power Sources" "J. Power Sources")
        ("JSSC" "Journal of Solid State Chemistry" "J. Solid State Chem.")
        ("JACerS" "Journal of the American Ceramic Society" "J. Am. Ceram. Soc.")
        ("JACS" "Journal of the American Chemical Society" "J. Am. Chem. Soc.")
        ("JASIST" "Journal of the American Society for Information Science and Technology"
         "J. Am. Soc. Inf. Sci. Technol.")
        ("JES" "Journal of The Electrochemical Society" "J. Electrochem. Soc.")
        ("JEaC" "Journal of Electroanalytical Chemistry" "J. Electroanal. Chem.")
        ("JMS" "Journal of Membrane Science" "J. Memb. Sci.")
        ("JRS" "Journal of Raman Spectroscopy" "J. Raman Spectrosc.")
        ("JVST" "Journal of Vacuum Science \\& Technology A"
         "J. Vac. Sci. Technol. A")
        ("ML" "Materials Letters" "Mater. Lett.")
        ("MSE-BS" "Materials Science and Engineering B" "Mat. Sci. Eng. B-Solid")
        ("MOLSIM" "Molecular Simulation" "Mol. Sim.")
        ("Nature" "Nature" "Nature")
        ("NM" "Nature Materials" "Nat. Mater.")
        ("NC" "Nature Chemistry" "Nat. Chem.")
        ("PML" "Philosophical Magazine Letters" "Phil. Mag. Lett.")
        ("PMA" "Philosophical Magazine A" "Phil. Mag. A")
        ("PA" "Physica A: Statistical Mechanics and its Applications" "Physica A")
        ("PB" "Physica B-Condensed Matter" "Physica B")
        ("PCCP" "Physical Chemistry Chemical Physics" "Phys. Chem. Chem. Phys.")
        ("PSSB" "physica status solidi (b)" "Phys. Status Solidi B")
        ("PRA" "Physical Review A" "Phys. Rev. A")
        ("PRB" "Physical Review B" "Phys. Rev. B")
        ("PRL" "Physical Review Letters" "Phys. Rev. Lett.")
        ("PCM" "Physics and Chemistry of Minerals" "Phys. Chem. Miner.")
        ("PNAS" "Proceedings of the National Academy of Sciences of the United States of America"
         "Proc. Natl. Acad. Sci. U. S. A.")
        ("PSurfSci" "Progress in Surface Science" "Prog. Surf. Sci.")
        ("Science" "Science" "Science")
        ("SM" "Scripta Materialia" "Scr. Mater.")
        ("SABC" "Sensors and Actuators B: Chemical" "Sensor. Actuat. B-Chem.")
        ("SS" "Surface Science" "Surf. Sci.")
        ("EPJB" "The European Physical Journal B" "Eur. Phys. J. B")
        ("JPC" "The Journal of Physical Chemistry" "J. Phys. Chem.")
        ("JPCB" "The Journal of Physical Chemistry B" "J. Phys. Chem. B")
        ("JPCC" "The Journal of Physical Chemistry C" "J. Phys. Chem. C")
        ("JPCL" "The Journal of Physical Chemistry Letters"
         "J. Phys. Chem. Lett.")
        ("JCP" "The Journal of Chemical Physics" "J. Chem. Phys.")
        ("MSMSE" "Modelling and Simulation in Materials Science and Engineering"
         "Modell. Simul. Mater. Sci. Eng.")
        ("TSF" "Thin Solid Films" "Thin Solid Films")
        ("TC" "Topics in Catalysis" "Top. Catal.")
        ("WR" "Water Research" "Water Res.")))


;;;###autoload
(defun org-ref-bibtex-generate-longtitles ()
  "Generate longtitles.bib which are @string definitions.
The full journal names are in `org-ref-bibtex-journal-abbreviations'."
  (interactive)
  (with-temp-file "longtitles.bib"
    (dolist (row org-ref-bibtex-journal-abbreviations)
      (insert (format "@string{%s=\"%s\"}\n"
                      (nth 0 row)
                      (nth 1 row))))))


;;;###autoload
(defun org-ref-bibtex-generate-shorttitles ()
  "Generate shorttitles.bib which are @string definitions.
The abbreviated journal names in `org-ref-bibtex-journal-abbreviations'."
  (interactive)
  (with-temp-file "shorttitles.bib"
    (dolist (row org-ref-bibtex-journal-abbreviations)
      (insert (format "@string{%s=\"%s\"}\n"
                      (nth 0 row)
                      (nth 2 row))))))


;;;###autoload
(defun org-ref-stringify-journal-name (&optional key start end)
  "Replace journal name in a bibtex entry with a string.
The strings are defined in
`org-ref-bibtex-journal-abbreviations'.  The optional arguments KEY,
START and END allow you to use this with `bibtex-map-entries'"
  (interactive)
  (bibtex-beginning-of-entry)
  (when
      (string= "article"
               (downcase
                (cdr (assoc "=type=" (bibtex-parse-entry)))))
    (let* ((full-names (mapcar
                        (lambda (row)
                          (cons  (nth 1 row) (nth 0 row)))
                        org-ref-bibtex-journal-abbreviations))
           (abbrev-names (mapcar
                          (lambda (row)
                            (cons  (nth 2 row) (nth 0 row)))
                          org-ref-bibtex-journal-abbreviations))
           (journal (s-trim (bibtex-autokey-get-field "journal")))
           (bstring (or
                     (cdr (assoc journal full-names))
                     (cdr (assoc journal abbrev-names)))))
      (when bstring
        (bibtex-set-field "journal" bstring t)
        (bibtex-fill-entry)))))


;;;###autoload
(defun org-ref-helm-set-journal-string ()
  "Helm interface to set a journal string in a bibtex entry.
Entries come from `org-ref-bibtex-journal-abbreviations'."
  (interactive)
  (bibtex-set-field
   "journal"
   (helm :sources
	 `((name . "journal")
	   (candidates . ,(mapcar
			   (lambda (x)
			     (cons (format "%s | %s"  (nth 1 x) (nth 2 x))
				   (car x)))
			   org-ref-bibtex-journal-abbreviations))
	   (action . (lambda (x) (identity x))))
         :input (s-trim (bibtex-autokey-get-field "journal")))
   t)
  (bibtex-fill-entry)
  (bibtex-clean-entry))


;;;###autoload
(defun org-ref-set-journal-string (full-journal-name)
  "Set a bibtex journal name to the string that represents FULL-JOURNAL-NAME.
This is defined in `org-ref-bibtex-journal-abbreviations'."
  (interactive (list
                (completing-read
                 "Journal: "
                 (mapcar
                  (lambda (x)
                    (nth 1 x))
                  org-ref-bibtex-journal-abbreviations))))
  ;; construct data alist for the string lookup.
  (let ((alist (mapcar
                (lambda (x)
                  (cons (nth 1 x) (nth 0 x)))
                org-ref-bibtex-journal-abbreviations)))
    (bibtex-set-field "journal" (cdr (assoc full-journal-name alist)) t)
    (bibtex-fill-entry)
    (bibtex-clean-entry)))

;;* Non-ascii character replacement
;; see https://github.com/fxcoudert/tools/blob/master/doi2bib for more replacements
(defvar org-ref-nonascii-latex-replacements
  '()
  "Cons list of non-ascii characters and their LaTeX representations.")


(setq org-ref-nonascii-latex-replacements
      '(("í" . "{\\\\'i}")
	("æ" . "{\\\\ae}")
	("ć" . "{\\\\'c}")
	("é" . "{\\\\'e}")
	("ä" . "{\\\\\"a}")
	("è" . "{\\\\`e}")
	("à" . "{\\\\`a}")
	("á" . "{\\\\'a}")
	("ø" . "{\\\\o}")
	("ë" . "{\\\\\"e}")
	("ü" . "{\\\\\"u}")
	("ñ" . "{\\\\~n}")
	("ņ" . "{\\\\c{n}}")
	("ñ" . "{\\\\~n}")
	("å" . "{\\\\aa}")
	("ö" . "{\\\\\"o}")
	("Á" . "{\\\\'A}")
	("í" . "{\\\\'i}")
	("ó" . "{\\\\'o}")
	("ó" . "{\\\\'o}")
	("ú" . "{\\\\'u}")
	("ú" . "{\\\\'u}")
	("ý" . "{\\\\'y}")
	("š" . "{\\\\v{s}}")
	("č" . "{\\\\v{c}}")
	("ř" . "{\\\\v{r}}")
	("š" . "{\\\\v{s}}")
	("İ" . "{\\\\.I}")
	("ğ" . "{\\\\u{g}}")
	("δ" . "$\\\\delta$")
	("ç" . "{\\\\c{c}}")
	("ß" . "{\\\\ss}")
	("≤" . "$\\\\le$")
	("≥" . "$\\\\ge$")
	("<" . "$<$")
	("θ" . "$\\\\theta$")
	("μ" . "$\\\\mu$")
	("→" . "$\\\\rightarrow$")
	("⇌" . "$\\\\leftrightharpoons$")
	("×" . "$\\\\times$")
	("°" . "$\\\\deg$")
	("ş" . "{\\\\c{s}}")
	("γ" . "$\\\\gamma$")
	("ɣ" . "$\\\\gamma$")
	("º" . "degC")
	("η" . "$\\\\eta$")
	("µ" . "$\\\\mu$")
	("α" . "$\\\\alpha$")
	("β" . "$\\\\beta$")
	("ɛ" . "$\\\\epsilon$")
	("Ⅵ" . "\\textrm{VI}")
	("Ⅲ" . "\\textrm{III}")
	("Ⅴ" . "\\textrm{V}")
	("λ" . "$\\\\lambda$")
	("π" . "$\\\\pi$")
	("∞" . "$\\\\infty$")
	("χ" . "$\\\\chi$")
	("∼" . "\\\\textasciitilde{}")
	("‑" . "\\\\textemdash{}")
	(" " . " ")
	("…" . "...")
	("•" . "\\\\textbullet ")
	;; I think these are non-ascii spaces. there seems to be more than one.
	(" " . " ")
	(" " . " ")
	(" " . " ")
	("–" . "-")
	("−" . "-")
	("–" . "-")
	("—" . "-")
	("‒" . "\\\\textemdash{}")
	("‘" . "'")
	("’" . "'")
	("’" . "'")
	("“" . "\"")
	("’" . "'")
	("”" . "\"")))


;;;###autoload
(defun org-ref-replace-nonascii ()
  "Hook function to replace non-ascii characters in a bibtex entry."
  (interactive)
  (save-restriction
    (bibtex-narrow-to-entry)
    (goto-char (point-min))
    (dolist (char (mapcar (lambda (x)
			    (car x))
			  org-ref-nonascii-latex-replacements))
      (while (re-search-forward char nil t)
        (replace-match (cdr (assoc char org-ref-nonascii-latex-replacements))))
      (goto-char (point-min)))))

;;* Title case transformations
(defvar org-ref-lower-case-words
  '("a" "an" "on" "and" "for"
    "the" "of" "in")
  "List of words to keep lowercase when changing case in a title.")

(defcustom org-ref-title-case-types '("article" "book")
  "List of bibtex entry types in which the title will be converted to
title-case by org-ref-title-case."
  :type '(repeat string)
  :group 'org-ref-bibtex)

;;;###autoload
(defun org-ref-title-case (&optional key start end)
  "Convert a bibtex entry title and booktitle to title-case.
Convert only if the entry type is a member of the list
`org-ref-title-case-types'. The arguments KEY, START and END are
optional, and are only there so you can use this function with
`bibtex-map-entries' to change all the title entries in articles and
books."
  (interactive)
  (dolist (field '("title" "booktitle"))
    (save-restriction
      (bibtex-narrow-to-entry)
      (bibtex-beginning-of-entry)
      ;; Skip if field is not found in entry
      (when (bibtex-search-forward-field field)
	(let* ((title (bibtex-autokey-get-field field))
	       (words (split-string title))
	       (start 0))
	  (when
	      (member (downcase
		       (cdr (assoc "=type=" (bibtex-parse-entry))))
		      org-ref-title-case-types)
	    (setq words (mapcar
			 (lambda (word)
			   (cond
			    ;; words containing more than one . are probably
			    ;; abbreviations. We do not change those.
			    ((with-temp-buffer
			       (insert word)
			       (goto-char (point-min))
			       (> (count-matches "\\.") 1))
			     word)
			    ;; match words containing {} or \ which are probably
			    ;; LaTeX or protected words, ignore
			    ((string-match "\\$\\|{\\|}\\|(\\|)\\|\\\\" word)
			     word)
			    ;; these words should not be capitalized, unless they
			    ;; are the first word
			    ((-contains? org-ref-lower-case-words
					 (s-downcase word))
			     (s-downcase word))
			    ;; Words that are quoted
			    ((s-starts-with? "\"" word)
			     (concat "\"" (s-capitalize (substring word 1))))
			    (t
			     (s-capitalize word))))
			 words))

	    ;; Check if first word should be capitalized
	    (when (-contains? org-ref-lower-case-words (car words))
	      (setf (car words) (s-capitalize (car words))))

	    (setq title (mapconcat 'identity words " "))

	    ;; Capitalize letters after a dash
	    (while
		(string-match "[a-zA-Z]-\\([a-z]\\)" title start)
	      (let ((char (substring title (match-beginning 1) (match-end 1))))
		(setf (substring title (match-beginning 1) (match-end 1))
		      (format "%s" (upcase char)))
		(setq start (match-end 1))))

	    ;; this is defined in doi-utils
	    (bibtex-set-field
	     field
	     title)
	    (bibtex-fill-entry)))))))

;;;###autoload
(defun org-ref-title-case-article (&optional key start end)
  "Convert a bibtex entry article or book title to title-case.
The arguments KEY, START and END are optional, and are only there
so you can use this function with `bibtex-map-entries' to change
all the title entries in articles and books."
  (interactive)
  (let ((org-ref-title-case-types '("article")))
    (org-ref-title-case)))


;;;###autoload
(defun org-ref-sentence-case-article (&optional key start end)
  "Convert a bibtex entry article title to sentence-case.
The arguments KEY, START and END are optional, and are only there
so you can use this function with `bibtex-map-entries' to change
all the title entries in articles."
  (interactive)
  (bibtex-beginning-of-entry)

  (let* ((title (bibtex-autokey-get-field "title"))
         (words (split-string title))
         (start 0))
    (when
        (string= "article"
		 (downcase
		  (cdr (assoc "=type="
			      (bibtex-parse-entry)))))
      (setq words (mapcar
                   (lambda (word)
                     (if
                         ;; match words containing {} or \ which are probably
                         ;; LaTeX or protected words
                         (string-match "\\$\\|{\\|}\\|\\\\" word)
                         word
                       (s-downcase word)))
                   words))

      ;; capitalize first word
      (setf (car words) (s-capitalize (car words)))

      ;; join the words
      (setq title (mapconcat 'identity words " "))

      ;; capitalize a word after a :, eg. a subtitle, and protect it
      (while
          (string-match "[a-z]:\\s-+\\([A-Z]\\)" title start)
        (let ((char (substring title (match-beginning 1) (match-end 1))))
          (setf (substring title (match-beginning 1) (match-end 1))
                (format "%s" (upcase char)))
          (setq start (match-end 1))))

      ;; this is defined in doi-utils
      (bibtex-set-field
       "title" title)

      ;; clean and refill entry so it looks nice
      (bibtex-clean-entry)
      (bibtex-fill-entry))))

;;* Navigation in bibtex file
;;;###autoload
(defun org-ref-bibtex-next-entry (&optional n)
  "Jump to the beginning of the next bibtex entry.
N is a prefix argument.  If it is numeric, jump that many entries
forward.  Negative numbers do nothing."
  (interactive "P")
  ;; Note if we start at the beginning of an entry, nothing
  ;; happens. We need to move forward a char, and call again.
  (when (= (point) (save-excursion
                     (bibtex-beginning-of-entry)))
    (forward-char)
    (org-ref-bibtex-next-entry))

  ;; search forward for an entry
  (when
      (re-search-forward bibtex-entry-head nil t (and (numberp n) n))
    ;; go to beginning of the entry
    (bibtex-beginning-of-entry)))


;;;###autoload
(defun org-ref-bibtex-previous-entry (&optional n)
  "Jump to beginning of the previous bibtex entry.
N is a prefix argument.  If it is numeric, jump that many entries back."
  (interactive "P")
  (bibtex-beginning-of-entry)
  (when
      (re-search-backward bibtex-entry-head nil t (and (numberp n) n))
    (bibtex-beginning-of-entry)))


(defun org-ref-bibtex-mode-keys ()
  "Modify keymaps used by `bibtex-mode'."
  (local-set-key (kbd "M-n") 'org-ref-bibtex-next-entry)
  (local-set-key (kbd "M-p") 'org-ref-bibtex-previous-entry))

;; add to bibtex-mode-hook
(add-hook 'bibtex-mode-hook 'org-ref-bibtex-mode-keys)

;;* Functions to act on an entry with a doi
;;;###autoload
(defun org-ref-bibtex-entry-doi ()
  "Get doi from entry at point."
  (interactive)
  (save-excursion
    (bibtex-beginning-of-entry)
    (when (not (looking-at bibtex-any-valid-entry-type))
      (error "This entry does not appear to be a valid type."))
    (let ((entry (bibtex-parse-entry t)))
      (when (null entry)
	(error "Unable to parse this bibtex entry."))
      (reftex-get-bib-field "doi" entry))))

;; function that ensures that the url field of a bibtex entry is the
;; properly-formatted hyperlink of the DOI. See
;; http://blog.crossref.org/2016/09/new-crossref-doi-display-guidelines.html
;; for more information.
;;;###autoload
(defun org-ref-bibtex-format-url-if-doi ()
  "Hook function to format url to follow the current DOI conventions."
  (interactive)
  (if (eq (org-ref-bibtex-entry-doi) "") nil
    (let ((front-url "https://doi.org/")
          (doi (org-ref-bibtex-entry-doi)))
      (bibtex-set-field "url"
                        (concat front-url doi)))))


;;;###autoload
(defun org-ref-bibtex-wos ()
  "Open bibtex entry in Web Of Science if there is a DOI."
  (interactive)
  (doi-utils-wos (org-ref-bibtex-entry-doi)))


;;;###autoload
(defun org-ref-bibtex-wos-citing ()
  "Open citing articles for bibtex entry in Web Of Science if
there is a DOI."
  (interactive)
  (doi-utils-wos-citing (org-ref-bibtex-entry-doi)))


;;;###autoload
(defun org-ref-bibtex-wos-related ()
  "Open related articles for bibtex entry in Web Of Science if
there is a DOI."
  (interactive)
  (doi-utils-wos-related (org-ref-bibtex-entry-doi)))


;;;###autoload
(defun org-ref-bibtex-crossref ()
  "Open the bibtex entry in Crossref by its doi."
  (interactive)
  (doi-utils-crossref (org-ref-bibtex-entry-doi)))


;;;###autoload
(defun org-ref-bibtex-google-scholar ()
  "Open the bibtex entry at point in google-scholar by its doi."
  (interactive)
  (let ((doi (org-ref-bibtex-entry-doi)))
    (doi-utils-google-scholar
     (if (string= "" doi)
	 (save-excursion
	   (bibtex-beginning-of-entry)
	   (reftex-get-bib-field "title" (bibtex-parse-entry t)))
       doi))))


;;;###autoload
(defun org-ref-bibtex-pubmed ()
  "Open the bibtex entry at point in Pubmed by its doi."
  (interactive)
  (doi-utils-pubmed (org-ref-bibtex-entry-doi)))


;;;###autoload
(defun org-ref-bibtex-pdf (&optional _)
  "Open the pdf for the bibtex entry at point.
Thin wrapper to get `org-ref-bibtex' to open pdf, because it
calls functions with a DOI argument."
  (interactive)
  (org-ref-open-bibtex-pdf))

(defun org-ref-bibtex-get-file-move-func (prefix)
  "Determine whether to use `rename-file' or `copy-file' for `org-ref-bibtex-assoc-pdf-with-entry'.
When called with a PREFIX argument,
`org-ref-bibtex-assoc-pdf-with-entry-move-function' switches to the
opposite function from that which is defined in
`org-ref-assoc-pdf-with-entry-move-function'."
  (message (format "%s" prefix))
  (if (eq prefix nil)
      org-ref-bibtex-assoc-pdf-with-entry-move-function
    (if (eq org-ref-bibtex-assoc-pdf-with-entry-move-function 'rename-file)
	'copy-file
      'rename-file)))

;;;###autoload
(defun org-ref-bibtex-assoc-pdf-with-entry (&optional prefix)
  "Prompt for pdf associated with entry at point and rename it.
Check whether a pdf already exists in `org-ref-pdf-directory' with the
name '[bibtexkey].pdf'. If the file does not exist, rename it to
'[bibtexkey].pdf' using
`org-ref-bibtex-assoc-pdf-with-entry-move-function' and place it in
`org-ref-pdf-directory'. Optional PREFIX argument toggles between
`rename-file' and `copy-file'."
  (interactive "P")
  (save-excursion
    (bibtex-beginning-of-entry)
    (let* ((file (read-file-name "Select file associated with entry: "))
	   (bibtex-expand-strings t)
           (entry (bibtex-parse-entry t))
           (key (reftex-get-bib-field "=key=" entry))
           (pdf (concat org-ref-pdf-directory (concat key ".pdf")))
	   (file-move-func (org-ref-bibtex-get-file-move-func prefix)))
      (if (file-exists-p pdf)
	  (message (format "A file named %s already exists" pdf))
	(progn
	  (funcall file-move-func file pdf)
	  (message (format "Created file %s" pdf)))))))


;;* Hydra menus
;;** Hydra menu for bibtex entries
;; hydra menu for actions on bibtex entries
(defhydra org-ref-bibtex-hydra (:color blue)
  "
_p_: Open pdf     _y_: Copy key               _N_: New entry            _w_: WOS
_b_: Open url     _f_: Copy formatted entry   _o_: Copy entry           _c_: WOS citing
_r_: Refile entry _k_: Add keywords           _d_: delete entry         _a_: WOS related
_e_: Email entry  _K_: Edit keywords          _L_: clean entry          _P_: Pubmed
_U_: Update entry _N_: New entry              _R_: Crossref             _g_: Google Scholar
_s_: Sort entry   _a_: Remove nonascii        _h_: helm-bibtex          _q_: quit
_u_: Update field _F_: file funcs             _A_: Assoc pdf with entry
_n_: Open notes                               _T_: Title case
                                              _S_: Sentence case
"
  ("p" org-ref-open-bibtex-pdf)
  ("P" org-ref-bibtex-pubmed)
  ("w" org-ref-bibtex-wos)
  ("c" org-ref-bibtex-wos-citing)
  ("a" org-ref-bibtex-wos-related)
  ("R" org-ref-bibtex-crossref)
  ("g" org-ref-bibtex-google-scholar)
  ("N" org-ref-bibtex-new-entry/body)
  ("n" org-ref-open-bibtex-notes)
  ("o" (lambda ()
	 (interactive)
	 (bibtex-copy-entry-as-kill)
	 (message "Use %s to paste the entry"
		  (substitute-command-keys (format "\\[bibtex-yank]")))))
  ("d" bibtex-kill-entry)
  ("L" org-ref-clean-bibtex-entry)
  ("y" (save-excursion
	 (bibtex-beginning-of-entry)
	 (when (looking-at bibtex-entry-maybe-empty-head)
	   (kill-new (bibtex-key-in-head)))))
  ("f" (progn
	 (bibtex-beginning-of-entry)
	 (kill-new
	  (org-ref-format-entry
	   (cdr (assoc "=key=" (bibtex-parse-entry t)))))))
  ("k" helm-tag-bibtex-entry)
  ("K" (lambda ()
         (interactive)
         (org-ref-set-bibtex-keywords
          (read-string "Keywords: "
                       (bibtex-autokey-get-field "keywords"))
          t)))
  ("b" org-ref-open-in-browser)
  ("r" (lambda ()
	 (interactive)
         (bibtex-beginning-of-entry)
         (bibtex-kill-entry)
         (find-file (completing-read
                     "Bibtex file: "
                     (f-entries "." (lambda (f) (f-ext? f "bib")))))
         (goto-char (point-max))
         (bibtex-yank)
         (save-buffer)
         (kill-buffer)))
  ("e" org-ref-email-bibtex-entry)
  ("U" (doi-utils-update-bibtex-entry-from-doi (org-ref-bibtex-entry-doi)))
  ("u" doi-utils-update-field)
  ("F" org-ref-bibtex-file/body)
  ("h" helm-bibtex)
  ("A" org-ref-bibtex-assoc-pdf-with-entry)
  ("a" org-ref-replace-nonascii)
  ("s" org-ref-sort-bibtex-entry)
  ("T" org-ref-title-case-article)
  ("S" org-ref-sentence-case-article)
  ("q" nil))

;; create key-chord and key binding for hydra
(when (and (featurep 'key-chord) org-ref-bibtex-hydra-key-chord)
  (key-chord-define-global
   org-ref-bibtex-hydra-key-chord
   'org-ref-bibtex-hydra/body))


(when org-ref-bibtex-hydra-key-binding
  (define-key bibtex-mode-map org-ref-bibtex-hydra-key-binding 'org-ref-bibtex-hydra/body))

;;** Hydra menu for new bibtex entries
;; A hydra for adding new bibtex entries.
(defhydra org-ref-bibtex-new-entry (:color blue)
  "New Bibtex entry:"
  ("a" bibtex-Article "Article")
  ("b" bibtex-Book "Book")
  ("i" bibtex-InBook "In book")
  ("l" bibtex-Booklet "Booklet")
  ("P" bibtex-Proceedings "Proceedings")
  ("p" bibtex-InProceedings "In proceedings")
  ("m" bibtex-Misc "Misc.")
  ("M" bibtex-Manual "Manual")
  ("T" bibtex-PhdThesis "PhD Thesis")
  ("t" bibtex-MastersThesis "MS Thesis")
  ("R" bibtex-TechReport "Report")
  ("u" bibtex-Unpublished "unpublished")
  ("c" bibtex-InCollection "Article in collection")
  ("q" nil "quit"))


;;** Hydra menu of functions to act on a bibtex file.
(defhydra org-ref-bibtex-file (:color blue)
  "Bibtex file functions: "
  ("v" bibtex-validate "Validate entries")
  ("s" bibtex-sort-buffer "Sort entries")
  ("r" bibtex-reformat "Reformat entries")
  ("c" bibtex-count-entries "Count entries")
  ("p" org-ref-build-full-bibliography "PDF bibliography"))


;;* DEPRECATED bibtex menu
(defvar org-ref-bibtex-menu-funcs '()
  "Functions to run in doi menu.
Each entry is a list of (key menu-name function).  The function
must take one argument, the doi.  This is somewhat deprecated, as
I prefer the hydra interfaces above.")

(setq org-ref-bibtex-menu-funcs
      '(("p" "df" org-ref-bibtex-pdf)
        ("C" "opy" (lambda (doi)
                     (kill-new (org-ref-bib-citation))
                     (bury-buffer)))
        ("w" "os" doi-utils-wos)
        ("c" "iting articles" doi-utils-wos-citing)
        ("r" "elated articles" doi-utils-wos-related)
        ("s" "Google Scholar" doi-utils-google-scholar)
        ("P" "Pubmed" doi-utils-pubmed)
        ("f" "CrossRef" doi-utils-crossref)))

;;;###autoload
(defun org-ref-bibtex ()
  "Menu command to run in a bibtex entry.
Functions from `org-ref-bibtex-menu-funcs'.  They all rely on the
entry having a doi."
  (interactive)
  ;; construct menu string as a message
  (message
   (concat
    (mapconcat
     (lambda (tup)
       (concat "[" (elt tup 0) "]"
               (elt tup 1) " "))
     org-ref-bibtex-menu-funcs "") ": "))
  (let* ((input (read-char-exclusive))
         (choice (assoc
                  (char-to-string input) org-ref-bibtex-menu-funcs)))
    (when choice
      (funcall
       (elt
        choice
        2)
       (org-ref-bibtex-entry-doi)))))

(defalias 'jb 'org-ref-bibtex)


;;;###autoload
(defun org-ref-email-bibtex-entry ()
  "Email current bibtex entry at point and pdf if it exists."
  (interactive)

  (save-excursion
    (bibtex-beginning-of-entry)
    (let* ((key (reftex-get-bib-field "=key=" (bibtex-parse-entry t)))
	   pdf)
      ;; when we have org-ref defined we may have pdf to find.
      (when (boundp 'org-ref-pdf-directory)
	(setq pdf (expand-file-name
		   (concat key ".pdf")
		   org-ref-pdf-directory)))
      (bibtex-copy-entry-as-kill)
      (compose-mail)
      (message-goto-body)
      (insert (pop bibtex-entry-kill-ring))
      (message-goto-subject)
      (insert key)
      (message "%s exists %s" pdf (file-exists-p pdf))
      (when (file-exists-p pdf)
	(mml-attach-file pdf))
      (message-goto-to))))

;;* org-ref bibtex keywords
;; adapted from bibtex-utils.el
;; these are candidates for selecting keywords/tags
(defun org-ref-bibtex-keywords ()
  "Get keywords defined in current bibtex file.
These are in the keywords field, and are comma or semicolon separated."
  (save-excursion
    (goto-char (point-min))
    (let (keywords kstring)
      (while (re-search-forward "^\\s-*keywords.*{\\([^}]+\\)}" nil t)
        ;; TWS - remove newlines/multiple spaces:
        (setq kstring (replace-regexp-in-string
		       "[ \t\n]+" " "
		       (match-string 1)))
        (mapc
         (lambda (v)
           (add-to-list 'keywords v t))
         (split-string kstring "\\(,\\|;\\)[ \n]*\\|{\\|}" t)))
      keywords)))


;;;###autoload
(defun org-ref-set-bibtex-keywords (keywords &optional arg)
  "Add KEYWORDS to a bibtex entry.
If KEYWORDS is a list, it is converted to a comma-separated
string.  The KEYWORDS are added to the beginning of the
field.  Otherwise KEYWORDS should be a string of comma-separate
keywords.  Optional argument ARG prefix arg to replace keywords."
  (interactive
   (list
    (completing-read "Keyword: " (org-ref-bibtex-keywords))
    current-prefix-arg))
  (bibtex-set-field
   "keywords"
   (if arg
       ;; replace with arg
       (if (listp keywords)
           (mapconcat 'identity keywords ", ")
         keywords)
     ;; else concatentate
     (concat
      (if (listp keywords)
          (mapconcat 'identity keywords ", ")
        keywords)
      (when (not (string= "" (bibtex-autokey-get-field "keywords")))
        (concat ", "  (bibtex-autokey-get-field "keywords"))))))
  (when (buffer-file-name)
    (save-buffer)))


(defun org-ref-save-all-bibtex-buffers ()
  "Save all bibtex-buffers."
  (cl-loop for buffer in (buffer-list)
	   do
	   (with-current-buffer buffer
	     (when (and (buffer-file-name) (f-ext? (buffer-file-name) "bib"))
	       (save-buffer)))))


;;* org-ref bibtex cache
(defvar orhc-bibtex-cache-data
  '((hashes . ())
    (candidates . ()))
  "Cache data as an alist.
'hashes is a list of cons cells (bibfile . hash)
'candidates is a list of cons cells (bibfile . candidates).
Stored persistently in `orhc-bibtex-cache-file'.")


(defvar orhc-bibtex-cache-file
  "~/.orhc-bibtex-cache"
  "File to store cached data in.")


(defvar org-ref-bibtex-files nil
  "List of bibtex files to get entries from.
This is set internally.")


(defvar orhc-candidate-formats
  '(("article" . "${pdf}${notes}|${=key=}| ${author}, ${title}, ${journal} (${year}). ${keywords}")
    ("book" . "  |${=key=}| ${author}, ${title} (${year}) ${keywords}.")
    ("inbook" . "  |${=key=}| ${author}, ${chapter} in ${title} (${year}) ${keywords}")
    ("techreport" . "  |${=key=}| ${title}, ${institution} (${year}). ${keywords}")
    ("inproceedings" . "  |${=key=}| ${author}, ${title} in ${booktitle} (${year}). ${keywords}")
    ("incollection" . "  |${=key=}| ${author}, ${title} in ${booktitle} (${year}). ${keywords}")
    ("phdthesis" . "  |${=key=}| ${author}, ${title}, ${school} (${year}). Phd thesis. ${keywords}")
    ("mastersthesis" . "  |${=key=}| ${author}, ${title}, ${school} (${year}). MS thesis. ${keywords}")
    ("misc" . "  |${=key=}| ${author}, ${title}")
    ("unpublished" . "  |${=key=}| ${author}, ${title}"))
  "Formats for candidates.
It is an alist of (=type= . s-format-string).")


;; when you load, we should check the hashes and files
(defun orhc-load-cache-file ()
  "Load the cache file to set `orhc-bibtex-cache-data'."
  (when (file-exists-p orhc-bibtex-cache-file)
    (with-current-buffer (find-file-noselect orhc-bibtex-cache-file)
      (goto-char (point-min))
      (setq orhc-bibtex-cache-data (read (current-buffer))))
    (when (find-buffer-visiting orhc-bibtex-cache-file)
      (kill-buffer (find-buffer-visiting orhc-bibtex-cache-file)))))


(defun orhc-clear-cache ()
  "Clear the cache and delete `orhc-bibtex-cache-file'."
  (interactive)
  (setq orhc-bibtex-cache-data '((hashes . nil)
				 (candidates . nil)))
  (when (find-buffer-visiting orhc-bibtex-cache-file)
    (kill-buffer (find-buffer-visiting orhc-bibtex-cache-file)))
  (when (file-exists-p orhc-bibtex-cache-file)
    (delete-file orhc-bibtex-cache-file))
  (message "org-ref-helm-cite cache cleared."))


(defun orhc-bibtex-cache-up-to-date ()
  "Return if bibtex caches are up to date.
This means the hash of each bibfile is equal to the one for it in
the cache."
  (-all? 'identity
	 (cl-loop
	  for bibfile in org-ref-bibtex-files
	  collect
	  (string= (with-temp-buffer
		     (insert-file-contents bibfile)
		     (secure-hash 'sha256 (current-buffer)))
		   (or (cdr (assoc
			     bibfile
			     (cdr (assoc 'hashes orhc-bibtex-cache-data)))) "")))))

(defun orhc-bibtex-field-formatter (field entry)
  "Format FIELD in a bibtex parsed ENTRY.
A few fields are treated specially, e.g. authors are replaced by
comma-separated list, and I put :: around keywords to make it
easier to search specifically for them."
  (let ((s (replace-regexp-in-string
	    "^{\\|}$" ""
	    (replace-regexp-in-string
	     "[\n\t\s]+" " "
	     (or (cdr (assoc field entry))
		 (and (string= field "author")
		      (cdr (assoc "editor" entry)))
		 "")))))
    (cond
     ((string= field "author")
      (if org-ref-helm-cite-shorten-authors
	  ;; copied from `helm-bibtex-shorten-authors'
	  (cl-loop for a in (s-split " and " s)
		   for p = (s-split "," a t)
		   for sep = "" then ", "
		   concat sep
		   if (eq 1 (length p))
		   concat (-last-item (s-split " +" (car p) t))
		   else
		   concat (car p))
	(mapconcat 'identity (s-split " and " s) ", ")))
     ((string= field "keywords")
      (if (> (length s) 0)
	  (mapconcat (lambda (keyword)
		       (concat ":" (s-trim keyword) ":"))
		     (s-split "," s)
		     " ")
	""))
     ((string= field "pdf")
      (if (file-exists-p (expand-file-name
			  (concat (cdr (assoc "=key=" entry)) ".pdf")
			  org-ref-pdf-directory))
	  "⌘"
	" "))
     ((string= field "notes")
      (if (file-exists-p (expand-file-name
			  (concat (cdr (assoc "=key=" entry)) ".org")
			  org-ref-notes-directory))
	  "✎"
	" "))
     ;; catch all the other fields and just return them.
     (t
      s))))

(defun orhc-update-bibfile-cache (bibfile)
  "Update cache for BIBFILE.
This generates the candidates for the file. Some of this code is
adapted from `helm-bibtex-parse-bibliography'. This function runs
when called, it resets the cache for the BIBFILE."
  ;; check if the bibfile is already open, and preserve this state. i.e. if it
  ;; is not open close it, and if it is leave it open.
  (let ((bibfile-open (find-buffer-visiting bibfile)))
    (with-current-buffer (find-file-noselect bibfile)
      (bibtex-beginning-of-first-entry)
      (message "Updating cache for %s" bibfile)
      (let ((hash (secure-hash 'sha256 (current-buffer)))
	    (entries
	     (cl-loop
	      for entry-type = (parsebib-find-next-item)
	      while entry-type
	      unless (member-ignore-case entry-type
					 '("preamble" "string" "comment"))
	      collect
	      (let* ((entry (cl-loop for cons-cell in (parsebib-read-entry entry-type)
				     ;; we remove all properties too. they
				     ;; cause errors in reading/writing.
				     collect
				     (cons (substring-no-properties
					    (downcase (car cons-cell)))
					   (substring-no-properties
					    ;; clumsy way to remove surrounding
					    ;; brackets
					    (let ((s (cdr cons-cell)))
					      (if (or (and (s-starts-with? "{" s)
							   (s-ends-with? "}" s))
						      (and (s-starts-with? "\"" s)
							   (s-ends-with? "\"" s)))
						  (substring s 1 -1)
						s))))))
		     ;; (key (cdr (assoc "=key=" entry)))
		     )
		(cons
		 ;; this is the display string for helm. We try to use the formats
		 ;; in `orhc-candidate-formats', but if there isn't one we just put
		 ;; all the fields in.
		 (s-format
		  (or (cdr (assoc (downcase entry-type) orhc-candidate-formats))
		      (format "%s: %s" (cdr (assoc "=key=" entry)) entry))
		  'orhc-bibtex-field-formatter
		  entry)
		 ;; this is the candidate that is returned, the entry a-list +
		 ;; file and position.
		 (append entry (list (cons "bibfile" (buffer-file-name))
				     (cons "position" (point)))))))))

	;; Now update the cache variables for hash and entries
	(if (assoc bibfile (cdr (assoc 'candidates orhc-bibtex-cache-data)))
	    (setf (cdr (assoc bibfile
			      (cdr (assoc 'candidates orhc-bibtex-cache-data))))
		  entries)
	  (cl-pushnew (cons bibfile entries)
		      (cdr (assoc 'candidates orhc-bibtex-cache-data))))
	(if (assoc bibfile (cdr (assoc 'hashes orhc-bibtex-cache-data)))
	    (setf (cdr (assoc
			bibfile
			(cdr (assoc 'hashes orhc-bibtex-cache-data))))
		  hash)
	  (cl-pushnew (cons bibfile hash)
		      (cdr (assoc 'hashes orhc-bibtex-cache-data))))

	;; And save it to disk for persistent use
	(with-temp-file orhc-bibtex-cache-file
	  (print orhc-bibtex-cache-data (current-buffer)))))
    (unless bibfile-open (kill-buffer (find-buffer-visiting bibfile)))))

(defun orhc-update-bibtex-cache ()
  "Conditionally update cache for all files in `org-ref-bibtex-files'.
Files that have the same hash as in the cache are not updated."
  (cl-loop for bibfile in org-ref-bibtex-files
	   unless (string= (with-temp-buffer
			     (insert-file-contents bibfile)
			     (secure-hash 'sha256 (current-buffer)))
			   (or (cdr
				(assoc bibfile
				       (cdr
					(assoc 'hashes orhc-bibtex-cache-data))))
			       ""))
	   do
	   (orhc-update-bibfile-cache bibfile)))


(defun orhc-helm-cite-describe-cache ()
  "Show what is in the cache."
  (interactive)
  (let ((hash-cache (cdr (assoc 'hashes orhc-bibtex-cache-data)))
	(candidates-cache (cdr (assoc 'candidates orhc-bibtex-cache-data))))
    (message "%s\n\n%s"
	     (mapconcat (lambda (h)
			  (format "%s - %s" (car h) (cdr h)))
			hash-cache "\n")
	     (mapconcat (lambda (c)
			  (format "%s - %s entries" (car c) (length (cdr c))))
			candidates-cache "\n"))))


(defun orhc-bibtex-candidates ()
  "Return the candidates from cache for files listed in `org-ref-bibtex-files'.
Update the cache if necessary."
  ;; this only does something when the cache is out of date
  (org-ref-save-all-bibtex-buffers)
  (orhc-update-bibtex-cache)
  (let ((candidates (cdr (assoc 'candidates orhc-bibtex-cache-data))))
    (apply 'append
	   (cl-loop for bibfile in org-ref-bibtex-files
		    collect (cdr (assoc bibfile candidates))))))


;; Now load files and update them if needed. We do this when you load the
;; library so they are available later.
(orhc-load-cache-file)
(orhc-update-bibtex-cache)


;;* org-ref bibtex formatted citation

(defun org-ref-format-bibtex-entry (entry)
  "Return a formatted citation for the bibtex entry at point.
Formats are from `org-ref-formatted-citation-formats'. The
variable `org-ref-formatted-citation-backend' determines the set
of format strings used."
  (save-excursion
    (bibtex-beginning-of-entry)
    (let* ((formats (cdr (assoc org-ref-formatted-citation-backend  org-ref-formatted-citation-formats)))
	   (format-string)
	   (ref))
      (if (null entry)
          "!!! No entry found !!!"
        (setq format-string (cdr (or (assoc (downcase (bibtex-completion-get-value "=type=" entry)) formats)
                                     (assoc nil formats))))
        (setq ref (s-format format-string 'bibtex-completion-apa-get-value entry))
        (replace-regexp-in-string "\\([.?!]\\)\\." "\\1" ref)))))


(defun org-ref-format-entry (key)
  "Returns a formatted bibtex entry for KEY."
  (let* ((bibtex-completion-bibliography (org-ref-find-bibliography)))
    (org-ref-format-bibtex-entry (ignore-errors (bibtex-completion-get-entry key)))))


(defun org-ref-format-bibtex-entry-at-point ()
  "Return a formatted citation for the bibtex entry at point."
  (save-excursion
    (bibtex-beginning-of-entry)
    (org-ref-format-bibtex-entry (bibtex-parse-entry t))))


;; ** using citeproc
(defun orhc-formatted-citation (entry)
  "Get a formatted string for ENTRY."
  (require 'unsrt)
  (let* ((adaptive-fill-function '(lambda () "    "))
	 (indent-tabs-mode nil)
	 (entry-type (downcase
		      (cdr (assoc "=type=" entry))))
	 (entry-styles (cdr (assoc 'entries bibliography-style)))
	 (entry-fields
	  (progn
	    (if (cdr (assoc (intern entry-type) entry-styles))
		(cdr (assoc (intern entry-type) entry-styles))
	      (warn "%s not found. Using default." entry-type)
	      (cdr (assoc 't entry-styles)))))
	 (funcs (mapcar
		 (lambda (field)
		   (if (fboundp (intern
				 (format "orcp-%s" field)))
		       (intern
			(format "orcp-%s" field))
		     ;; No formatter found. just get the data
		     `(lambda (entry)
			(orcp-get-entry-field
			 ,(symbol-name field) entry))))
		 entry-fields)))

    ;; this is the entry. We do this in a buffer to make it
    ;; easy to indent, fill, etc...
    (with-temp-buffer
      (insert (mapconcat (lambda (field-func)
			   (funcall field-func entry))
			 funcs
			 ""))
      (buffer-string))))

;; * Extract bibtex blocks from an org-file
;;;###autoload
(defun org-ref-extract-bibtex-blocks (bibfile)
  "Extract all bibtex blocks in buffer to BIBFILE.
If BIBFILE exists, append, unless you use a prefix arg (C-u), which
will clobber the file."
  (interactive
   (list (read-file-name "Bibfile: " nil nil nil
			 (file-name-nondirectory
			  (concat (file-name-sans-extension
				   (buffer-file-name))
				  ".bib")))))

  (let ((contents ""))
    (when (and (file-exists-p bibfile)
	       (not current-prefix-arg))
      (setq contents (with-temp-buffer
		       (insert-file-contents bibfile)
		       (buffer-string))))

    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward "#\\+BEGIN_SRC bibtex" nil t)
	(setq contents
	      (concat
	       contents
	       (org-element-property :value (org-element-at-point))))))

    (with-temp-file bibfile
      (insert contents))))

(defun org-ref-bibtex-key-from-doi (doi &optional bib)
  "Return a bibtex entry's key from a DOI.
BIB is an optional filename to get the entry from. Defaults to
the first entry of `org-ref-default-bibliography'."
  (let ((bibfile (if bib bib (car org-ref-default-bibliography))))
    (with-temp-buffer
      (insert-file-contents (expand-file-name bibfile))
      (search-forward doi)
      (bibtex-beginning-of-entry)
      (cdr (assoc "=key=" (bibtex-parse-entry))))))

;;* The end
(provide 'org-ref-bibtex)

;;; org-ref-bibtex.el ends here
