(eval-when-compile (require 'cl))

(defvar clang-executable "clang")
(defvar clang-completion-doc-table (make-hash-table :test 'equal))

;; faces
(defface clang-completion-plain-face
  '((t (:inherit default :family "Verdana")))
  "clang completion hint base font" :group 'clang-completion-faces)

(defface clang-completion-type-face
  '((t (:inherit 'clang-completion-plain-face :foreground "#729FCF" :weight bold :family "Verdana")))
  "clang completion hint font for types" :group 'clang-completion-faces)

(defface clang-completion-variable-face
  '((t (:inherit 'clang-completion-plain-face :foreground "#73D216" :family "Verdana")))
  "clang completion hint font for variables" :group 'clang-completion-faces)

;; extra flags
(defvar clang-completion-pch nil)
(defvar clang-completion-flags nil)
(defvar clang-completion-suppress-error nil)

(defun clang-process-exec (command)
  (with-output-to-string
    (with-current-buffer standard-output
      (unless (or (eq (apply 'call-process (car command) nil '(t ".clang-completion-error") nil (cdr command)) 0) clang-completion-suppress-error)
        (let ((last-command compile-command))
          (compile "cat .clang-completion-error")
          (setq compile-command last-command))))))

(defun clang-parse-completion-line (line)
  (cond ((string-match "^COMPLETION: Pattern" line) nil)  ;; exclude patterns
        ((string-match "^COMPLETION: \\([^ ]*\\)\\(?: : \\([^\"]*\\)\\)$" line)
         (list (match-string 1 line) (match-string 2 line)))
        ((string-match "^OVERRIDE:  \\([^ ]*\\)\\(?: : \\([^\"]*\\)\\)$" line)
         (list (match-string 1 line) (match-string 2 line)))
        (t nil))
  )

(defun clang-process (buffer point)
  (unless (buffer-file-name buffer)
    (return ""))
  (let* ((filename (buffer-file-name buffer))
         (col      (1+ (- point (point-at-bol))))
         (row      (count-lines point (point-min)))
         (cmd      (list clang-executable "-cc1"
                         filename "-fsyntax-only" "-code-completion-at"
                         (format "%s:%s:%s" filename row col))))

    ;; eval the config file under buffer locations
    (let* ((filedir  (file-name-directory filename))
           (config-filename (concat filedir ".clang-completion-config.el")))
      (when (file-readable-p config-filename)
        (with-temp-buffer
          (insert-file-contents config-filename)
          (eval-buffer))))

    (when (listp clang-completion-flags)
      (setq cmd (append cmd clang-completion-flags)))
    (when (stringp clang-completion-pch)
      (setq cmd (append cmd (list "-include-pch" clang-completion-pch))))
    (message (format "complete at %s:%s:%s" filename row col))
    (clang-process-exec cmd)))

(defun clang-get-process-result (string)
  (let* ((completion-lines (split-string string "\n")))
    (delq nil (mapcar 'clang-parse-completion-line completion-lines))))

(defun clang-get-process-completion-result (string)
  (mapcar 'car (clang-get-process-result string)))

(defun clang-get-process-prototype-table (string)
  (let* ((lines (clang-get-process-result string))
         (result-table (make-hash-table :test 'equal)))
    (dolist (line lines)
      (let* ((key (first line))
             (value (gethash key result-table)))
        (setq value (append value (list (second line))))
        (puthash key value result-table))
      )
    (setq clang-completion-doc-table result-table)))

(defun clang-get-completions (&optional buffer point)
  ;; save all modified buffers
  (or buffer (setq buffer (current-buffer)))
  (or point (setq point (point)))
  (save-some-buffers t)
  (let* ((output (clang-process buffer point)))
    (clang-get-process-prototype-table output)
    (clang-get-process-completion-result output)))

(defun filter-doc-buffer ()
  (while (re-search-backward "\\[#.*?::#\\]" nil t)
    (replace-match ""))
  (goto-char (point-max))

  (while (re-search-backward "\\[#\\|#\\]" nil t)
    (replace-match " "))
  (goto-char (point-max))
  (while (re-search-backward "{#\\|#}\\|<#\\|#>" nil t)
    (replace-match ""))
  )

(defun clang-get-doc (symbol)
  ;;(setq symbol (symbol-name (intern-soft symbol)))
  (let ((reslist (gethash symbol clang-completion-doc-table)))
    (with-temp-buffer
      (font-lock-add-keywords nil '(("\\[#\\(.*?\\)#\\]" 1
                                     'clang-completion-type-face t)))
      (font-lock-add-keywords nil '(("<#\\(.*?\\)#>" 1
                                     'clang-completion-variable-face t)))
      (font-lock-add-keywords nil '(("\\(.*\\)" 1
                                     'clang-completion-plain-face t)))
      (font-lock-mode t)

      (insert (reduce '(lambda (x y) (concat x "\n" y)) reslist))
      (font-lock-fontify-buffer)
      (filter-doc-buffer)

      (message (buffer-string))))
  ;;(with-temp-buffer
  ;;  (dolist (proto reslist)
  ;;    (insert proto)
  ;;    (insert "\n\n"))
  ;;  (filter-doc-buffer)
  ;;  (buffer-string))
  ;; display nothing
  (return nil))

(defvar ac-source-clang-complete
  '((candidates . (clang-get-completions nil ac-point))
    (prefix "[^a-zA-Z0-9_]\\(\\(?:[a-zA-Z_][a-zA-Z0-9_]*\\)?\\)" nil 1)
    (document . clang-get-doc)
    (requires . 0)
    (symbol . "C")
    (cache)))

;;(defvar ac-source-clang-static-complete
;;  '((candidates . (clang-get-completions nil ac-point))
;;    (prefix "::\\(\\(?:[a-zA-Z_][a-zA-Z0-9_]*\\)?\\)" nil 1)
;;    ;;(document . 'clang-get-doc)
;;    (requires . 0)
;;    (symbol . "M")
;;    (cache)))

(defun ac-complete-clang ()
  (interactive)
  (auto-complete '(ac-source-clang-complete)))

(provide 'auto-complete-clang)
