;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; ghc-info.el
;;;

;; Author:  Kazu Yamamoto <Kazu@Mew.org>
;; Created: Nov 15, 2010

;;; Code:

(require 'ghc-func)

(defun ghc-show-info (&optional ask)
  (interactive "P")
  (let* ((modname (or (ghc-find-module-name) "Main"))
	 (expr0 (ghc-things-at-point))
	 (expr (if (or ask (not expr0)) (ghc-read-expression expr0) expr0))
	 (file (buffer-file-name))
	 (cmds (list "info" file modname expr)))
    (ghc-display-information cmds nil)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; type
;;;

(defvar ghc-type-overlay nil)

(make-variable-buffer-local 'ghc-type-overlay)

(defun ghc-type-set-ix (n)
  (overlay-put ghc-type-overlay 'ix n))

(defun ghc-type-get-ix ()
  (overlay-get ghc-type-overlay 'ix))

(defun ghc-type-set-point (pos)
  (overlay-put ghc-type-overlay 'pos pos))

(defun ghc-type-get-point ()
  (overlay-get ghc-type-overlay 'pos))

(defun ghc-type-set-types (types)
  (overlay-put ghc-type-overlay 'types types))

(defun ghc-type-get-types ()
  (overlay-get ghc-type-overlay 'types))

(ghc-defstruct tinfo beg-line beg-column end-line end-column info)

(defun ghc-type-init ()
  (setq ghc-type-overlay (make-overlay 0 0))
  (overlay-put ghc-type-overlay 'face 'region)
  (ghc-type-clear-overlay)
  (setq after-change-functions
	(cons 'ghc-type-clear-overlay after-change-functions))
  (add-hook 'post-command-hook 'ghc-type-post-command-hook))

(defun ghc-type-clear-overlay (&optional beg end len)
  (when (overlayp ghc-type-overlay)
    (ghc-type-set-ix 0)
    (ghc-type-set-point 0)
    (move-overlay ghc-type-overlay 0 0)))

(defun ghc-type-post-command-hook ()
  (when (and (eq major-mode 'haskell-mode)
	     (overlayp ghc-type-overlay)
	     (/= (ghc-type-get-point) (point)))
    (ghc-type-clear-overlay)))

(defun ghc-show-type ()
  (interactive)
  (if (not (executable-find ghc-module-command))
      (message "%s not found" ghc-module-command)
    (let ((modname (or (ghc-find-module-name) "Main")))
      (ghc-show-type0 modname))))

(defun ghc-show-type0 (modname)
  (let* ((buf (current-buffer))
	 (tinfos (ghc-type-get-tinfos modname)))
    (if (null tinfos)
	(progn
	  (ghc-type-clear-overlay)
	  (message "Cannot guess type"))
      (let* ((tinfo (nth (ghc-type-get-ix) tinfos))
	     (type (ghc-tinfo-get-info tinfo))
	     (beg-line (ghc-tinfo-get-beg-line tinfo))
	     (beg-column (ghc-tinfo-get-beg-column tinfo))
	     (end-line (ghc-tinfo-get-end-line tinfo))
	     (end-column (ghc-tinfo-get-end-column tinfo))
	     (left (ghc-get-pos buf beg-line beg-column))
	     (right (ghc-get-pos buf end-line end-column)))
	(move-overlay ghc-type-overlay (- left 1) (- right 1) buf)
	(message type)))))

(defun ghc-type-get-tinfos (modname)
  (if (= (ghc-type-get-point) (point))
      (ghc-type-set-ix
       (mod (1+ (ghc-type-get-ix)) (length (ghc-type-get-types))))
    (ghc-type-set-types (ghc-type-obtain-tinfos modname))
    (ghc-type-set-point (point))
    (ghc-type-set-ix 0))
  (ghc-type-get-types))

(defun ghc-type-obtain-tinfos (modname)
  (let* ((ln (int-to-string (line-number-at-pos)))
	 (cn (int-to-string (current-column)))
	 (cdir default-directory)
	 (file (buffer-file-name)))
    (ghc-read-lisp
     (lambda ()
       (cd cdir)
       (apply 'call-process ghc-module-command nil t nil
	      `(,@(ghc-make-ghc-options) "-l" "type" ,file ,modname ,ln ,cn))
       (goto-char (point-min))
       (while (search-forward "[Char]" nil t)
	 (replace-match "String"))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Expanding Template Haskell
;;;

(defun ghc-expand-th ()
  (interactive)
  (let* ((file (buffer-file-name))
	 (cmds (list "expand" file)))
    (ghc-display-information cmds t)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Display
;;;

(defun ghc-display-information (cmds fontify)
  (interactive)
  (if (not (executable-find ghc-module-command))
      (message "%s not found" ghc-module-command)
    (ghc-display
     fontify
     (lambda (cdir)
       (insert
	(with-temp-buffer
	  (cd cdir)
	  (apply 'call-process ghc-module-command nil t nil
		 (append (ghc-make-ghc-options) cmds))
	  (buffer-substring (point-min) (1- (point-max)))))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Misc
;;;

(defun ghc-get-pos (buf line col)
  (save-excursion
    (set-buffer buf)
    (goto-line line)
    (forward-char col)
    (point)))

(defun ghc-read-expression (default)
  (if default
      (let ((prompt (format "Expression (%s): " default)))
	(read-string prompt default nil))
    (read-string "Expression: ")))

(defun ghc-find-module-name ()
  (save-excursion
    (goto-char (point-min))
    (if (re-search-forward "^module[ ]+\\([^ \n]+\\)" nil t)
	(match-string-no-properties 1))))

(provide 'ghc-info)
