;;; idris-highlight-input.el --- Compiler-driven highlighting of user input  -*- lexical-binding: t; -*-

;; Copyright (C) 2015  David Raymond Christiansen

;; Author: David Raymond Christiansen <david@davidchristiansen.dk>
;; Keywords: languages

;; 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 file contains routines for highlighting user input with
;; information generated by the Idris elaborator.

;;; Code:

(require 'idris-common-utils)
(require 'idris-settings)

(defun idris-highlight-remove-overlays (&optional buffer)
  "Remove all Idris highlighting overlays from BUFFER.
Use the current buffer if BUFFER is nil."
  (interactive)
  (with-current-buffer (or buffer (current-buffer))
    (save-restriction
      (widen)
      (dolist (overlay (overlays-in (point-min) (point-max)))
        (when (overlay-get overlay 'idris-source-highlight)
          (delete-overlay overlay))))))

(defun idris-highlight-column (idris-col)
  "Compute the Emacs position offset of the Idris column IDRIS-COL.

In particular, this takes bird tracks into account in literate Idris."
  (+ idris-col (if (idris-lidr-p) 1 -1)))

(defun idris-highlight--overlay-modification-hook (&rest args)
  "Delete semantic overlays if they are changed.

See Info node `(elisp)Overlay Properties' to understand how ARGS are used."
  ;; There are 5 args when it's called post-modification
  (when (= (length args) 5)
    (delete-overlay (car args))))

(defun idris-highlight-input-region (start-line start-col end-line end-col highlight)
  "Highlight in BUFFER using an overlay from START-LINE and START-COL to
 END-LINE and END-COL and the semantic properties specified in HIGHLIGHT."
  (save-excursion
    (save-restriction
      (widen)
      (goto-char (point-min))
      (let* ((start-pos (+ (line-beginning-position start-line)
                           (idris-highlight-column start-col)))
             (end-pos (+ (line-beginning-position end-line)
                         (idris-highlight-column end-col)))
             (existing-idris-overlays-in-range (seq-filter
                                                (lambda (overlay)
                                                  (overlay-get overlay 'idris-source-highlight))
                                                (overlays-in start-pos end-pos)))
             (existing-idris-overlay (seq-find (lambda (overlay)
                                                 (and
                                                  (eql start-pos (overlay-start overlay))
                                                  (eql end-pos (overlay-end overlay))
                                                  ;; TODO: overlay properties match
                                                  ))
                                               existing-idris-overlays-in-range)))
        (when (null existing-idris-overlay)
          (mapc #'delete-overlay existing-idris-overlays-in-range)
          (let ((highlight-overlay (make-overlay start-pos end-pos)))
            (overlay-put highlight-overlay 'idris-source-highlight t)
            (idris-add-overlay-properties highlight-overlay (idris-semantic-properties highlight))
            (overlay-put highlight-overlay 'modification-hooks '(idris-highlight--overlay-modification-hook))))))))

(defun idris-highlight-source-file (hs)
  (pcase-dolist
      (`(((:filename ,fn)
          (:start ,start-line-raw ,start-col-raw)
          (:end ,end-line-raw ,end-col-raw))
         ,props)
       hs)
    (when (string= (file-name-nondirectory fn)
                   (file-name-nondirectory (buffer-file-name)))
      (let ((start-line (if (>=-protocol-version 2 1)
                            (1+ start-line-raw)
                          start-line-raw))
            (start-col  (if (>=-protocol-version 2 1)
                            (1+ start-col-raw)
                          start-col-raw))
            (end-line   (if (>=-protocol-version 2 1)
                            (1+ end-line-raw)
                          end-line-raw))
            (end-col    (if (>= idris-protocol-version 1)
                            (1+ end-col-raw)
                          end-col-raw)))
        (idris-highlight-input-region start-line start-col
                                      end-line end-col
                                      props)))))

(defun idris-highlight-input-region-debug (start-line start-col end-line end-col highlight)
  (when (not (or (> end-line start-line)
                 (and (= end-line start-line)
                      (> end-col start-col))))
    (message "Not highlighting absurd span %s:%s-%s:%s with %s"
             start-line start-col
             end-line end-col
             highlight)))

(defun idris-toggle-semantic-source-highlighting ()
  "Turn on/off semantic highlighting.
This is controled by value of `idris-semantic-source-highlighting' variable.
When the value is `debug' additional checks are performed on received data."
  (if idris-semantic-source-highlighting
      (progn
        (if (eq idris-semantic-source-highlighting 'debug)
            (advice-add 'idris-highlight-input-region
                        :before-until
                        #'idris-highlight-input-region-debug)
          (advice-remove 'idris-highlight-input-region
                         #'idris-highlight-input-region-debug))
        (advice-remove 'idris-highlight-source-file #'ignore))
    (advice-add 'idris-highlight-source-file :around #'ignore)))

(defun idris-buffer-semantic-source-highlighting ()
  "Return nil if current buffer size is larger than set limit.
The limit is defined as value of:
`idris-semantic-source-highlighting-max-buffer-size'.
Otherwise return current value of `idris-semantic-source-highlighting'"
  (if (< (buffer-size)
         idris-semantic-source-highlighting-max-buffer-size)
      idris-semantic-source-highlighting
    (message "Semantic source highlighting is disabled for the current buffer. %s"
             "Customize `idris-semantic-source-highlighting-max-buffer-size' to enable it.")
    nil))

(provide 'idris-highlight-input)
;;; idris-highlight-input.el ends here
