;;; site-functions.el -*- lexical-binding: t; -*-

;;(require 'eshell)
;;(require 'em-dirs)

(provide 'site-functions)

;;; Code:

(defun kill-whitespace ()
  "Kill the whitespace from here to the next non-whitespace character."
  (interactive "*")
  (save-excursion
    (save-restriction
      (save-match-data
        (progn
          (re-search-forward "[ \t\r\n]+" nil t)
          (replace-match "" nil nil))))))

;; (defun split-line ()
;;   "Split the current line into two at the current point."
;;   (interactive "*")
;;   (save-excursion
;;     (save-restriction
;;       (save-match-data
;;         (progn (newline-and-indent))))))

(defun bind-hooks-to-modes (hooks modes)
  "Takes a list of HOOKS and a list of MODES, add all hooks to all modes."
  (dolist (hook hooks)
    (dolist (mode modes)
      (add-hook mode hook))))

(defun bind-hook-to-modes (hook modes)
  "Bind a single HOOK to many MODES."
  (bind-hooks-to-modes (list hook) modes))

(defun bind-hooks-to-mode (hooks mode)
  "Bind many HOOKS to a single MODE."
  (bind-hooks-to-modes hooks (list mode)))

;; (defun remote-shell (host)
;;   "Open a remote shell on the given host"
;;   (interactive "sRemote host name: \n")
;;   (let ((default-directory (concat "/" host ":/home/" (user-login-name) "/")))
;;     (shell (concat "*" host "*"))))


;; Emacs Lisp doesn’t come with a ‘filter’ function to keep elements that satisfy
;; a conditional and excise the elements that do not satisfy it. One can use ‘mapcar’
;; to iterate over a list with a conditional, and then use ‘delq’ to remove the ‘nil’
;; values.

(defun filter (condp lst)
  "Remove all elements not matching CONDP from LST."
  (delq nil
	      (mapcar (lambda (x) (and (funcall condp x) x)) lst)))

;; Therefore
;;
;; (my-filter 'identity my-list)
;; is equivalent to
;; (delq nil my-list)
;;
;; For example:
;;(let ((num-list '(1 'a 2 "nil" 3 nil 4)))
;;  (my-filter 'numberp num-list))   ==> (1 2 3 4)
;;
;; Actually the package cl-seq contains the functions remove-if and remove-if-not.
;; The latter can be used instead of my-filter.

;;
;;  Never understood why Emacs doesn't have this function.
;;
(defun rename-file-and-buffer (new-name)
  "Renames both current buffer and file it's visiting to NEW-NAME."
  (interactive "sNew name: ")
  (let ((name (buffer-name))
	(filename (buffer-file-name)))
    (if (not filename)
	(message "Buffer '%s' is not visiting a file!" name)
      (if (get-buffer new-name)
	  (message "A buffer named '%s' already exists!" new-name)
	(progn
	  (rename-file name new-name 1)
	  (rename-buffer new-name)
	  (set-visited-file-name new-name)
	  (set-buffer-modified-p nil))))))

;;
;;  Never understood why Emacs doesn't have this function, either.
;;
(defun move-buffer-file (dir)
  "Move both current buffer and file it's visiting to DIR."
  (interactive "DNew directory: ")
  (let* ((name (buffer-name))
	 (filename (buffer-file-name))
	 (dir
	  (if (string-match dir "\\(?:/\\|\\\\)$")
	      (substring dir 0 -1) dir))
	 (newname (concat dir "/" name)))

    (if (not filename)
	(message "Buffer '%s' is not visiting a file!" name)
      (progn
	(copy-file filename newname 1)
	(delete-file filename)
	(set-visited-file-name newname)
	(set-buffer-modified-p nil)
	t))))

(defun join-dirs (&rest strings)
  "Connect a list of STRINGS with a path separator, /."
  (mapconcat 'identity strings "/"))

(defun my-copy-line (&optional arg)
  "Copy ARG lines to kill ring."
  (interactive "P")
  (let ((n (if arg
               arg
             1)))
    (kill-ring-save (line-beginning-position)
                    (line-beginning-position (+ n 1)))
    (message "%d line%s copied" n (if (= 1 n) "" "s"))))

(defvar my-time-format "%k:%M:%S")
(defvar my-date-format "%Y-%m-%d")

(defun my-run-command-on-this-line (cmd)
  "Run the CMD found on this line in an external shell."
  (beginning-of-line-text)
  (let ((start (point)))
    (end-of-line)
    (command start (end-of-line))))

(defun my-kill-this-buffer ()
  "Kill the current buffer."
  (interactive)
  (kill-buffer (buffer-name))
  (set-name))

(defun my-forward-word ()
  "Move one word forward. Leave the pointer at start of word."
  (interactive)
  (forward-char 1)
  (backward-word 1)
  (forward-word 2)
  (backward-word 1)
  (backward-char 1)
  (forward-char 1))

(defun my-backward-word ()
  "Move one word backward. Leave the pointer at start of word."
  (interactive)
  (backward-word 1)
  (backward-char 1)
  (forward-char 1))

(defun smart-tab ()
  (interactive)
  (if (minibufferp)
      (unless (minibuffer-complete)
	(dabbrev-expand nil))
    (if mark-active
	(indent-region (region-beginning)
		       (region-end))
      (indent-for-tab-command))))

(defun copy-line (&optional arg)
  (interactive)
  (save-excursion
    (beginning-of-line-text)
    (let ((start (point)))
      (end-of-line)
      (kill-ring-save start (point)))))

(defun get-point (symbol &optional arg)
  "get the point"
  (funcall symbol arg)
  (point))

(defun copy-thing (begin-of-thing end-of-thing &optional arg)
  "copy thing between beg & end into kill ring"
  (let ((beg (get-point begin-of-thing 1))
        (end (get-point end-of-thing arg)))
    (copy-region-as-kill beg end)))

(defun copy-word (&optional arg)
  "Copy words at point into kill-ring"
  (interactive "P")
  (save-excursion
    (copy-thing 'backward-word 'forward-word arg)))

(defun move-text-internal (arg)
  (cond
   ((and mark-active transient-mark-mode)

    (let ((column (current-column))
          (text (delete-and-extract-region (point) (mark))))
      (forward-line arg)
      (move-to-column column t)
      (set-mark (point))
      (insert text)
      (exchange-point-and-mark)
      (setq deactivate-mark nil)))
   (t
    (beginning-of-line)
    (when (or (> arg 0) (not (bobp)))
      (forward-line)
      (when (or (< arg 0) (not (eobp)))
        (transpose-lines arg))
      (forward-line -1)))))

(defun move-text-down (arg)
  "Move region (transient-mark-mode active) or current line
  arg lines down."
  (interactive "*p")
  (move-text-internal arg))

(defun move-text-up (arg)
  "Move region (transient-mark-mode active) or current line
  arg lines up."
  (interactive "*p")
  (move-text-internal (- arg)))

(defun ido-find-file-in-tag-files ()
  (interactive)
  (save-excursion
    (let ((enable-recursive-minibuffers t))
      (visit-tags-table-buffer))
    (ido-completing-read "Project file "
			 (tags-table-files)
			 nil t)))

(defun untabify-this-file ()
  (interactive)
  (save-excursion
    (end-of-buffer)
    (let ((pos (point)))
      (untabify 0 pos))))

(defun ensime-remote-connect (host port)
  (interactive (list
                (read-from-minibuffer "Host: " ensime-default-server-host)
                (read-from-minibuffer "Port: " (format "%d" ensime-default-port)
                                      nil t)))
  (let ((c (ensime-connect host port))
        (config (ensime-config-load "/Users/whunmr/lab/scala/.ensime")))
    (ensime-set-config c config)
    (setq ensime-buffer-connection c)))

(defun path-join (paths)
  (mapconcat 'identity paths ":"))

(defun strip-dup-paths (path)
  (path-join
   (delete-dups
    (split-string path ":"))))

(defun strip-invalid-paths (path)
  (path-join
   (filter (lambda (p)
             (string-match "^/" p))
           (split-string path ":"))))

(defun get-path ()
  (split-string (getenv "PATH") ":"))

(cl-defun string-join (lst &optional (chr ""))
  (mapconcat 'identity lst chr))

(defun any-p (f lst)
  (reduce
   (lambda (a b) (or a b))
   (mapcar f lst)
   :initial-value '()))

(defun every-p (f lst)
  (reduce
   (lambda (a b) (and a b))
   (mapcar f lst)
   :initial-value t))

(defun odd-p (n)
  (= (% n 2) 1))

(defun even-p (n)
  (not (odd-p n)))

(defun subpath-p (parent child-p)
  (string-match (concat "^" parent ".+") child-p))

(defvar read-only-paths '())

(defun should-be-readonly (filepath)
  (any-p
   (lambda (path)
     (subpath-p path filepath))
   read-only-paths))

(defun goto-match-paren (arg)
  "Go to the matching parenthesis if on parenthesis,
   vi style of % jumping to matching brace."
  (interactive "p")
  (cond ((looking-at "\\s\(") (forward-list 1) (backward-char 1))
        ((looking-at "\\s\)") (forward-char 1) (backward-list 1))
        (t (self-insert-command (or arg 1)))))

(defun yank-buffer-path ()
  "Yank the current buffer's path"
  (interactive)
  (let ((filename (if (equal major-mode 'dired-mode)
                      default-directory
                    (buffer-file-name))))
    (when filename
      (with-temp-buffer
        (insert filename)
        (clipboard-kill-region (point-min) (point-max)))
      (message filename))))

(defun kill-matching-lines (regexp &optional rstart rend interactive)
  "Kill lines containing matches for REGEXP.

See `flush-lines' or `keep-lines' for behavior of this command.

If the buffer is read-only, Emacs will beep and refrain from deleting
the line, but put the line in the kill ring anyway.  This means that
you can use this command to copy text from a read-only buffer.
\(If the variable `kill-read-only-ok' is non-nil, then this won't
even beep.)"
  (interactive
   (keep-lines-read-args "Kill lines containing match for regexp"))
  (let ((buffer-file-name nil)) ;; HACK for `clone-buffer'
    (with-current-buffer (clone-buffer nil nil)
      (let ((inhibit-read-only t))
        (keep-lines regexp rstart rend interactive)
        (kill-region (or rstart (line-beginning-position))
                     (or rend (point-max))))
      (kill-buffer)))
  (unless (and buffer-read-only kill-read-only-ok)
    ;; Delete lines or make the "Buffer is read-only" error.
    (flush-lines regexp rstart rend interactive)))

(defun eshell/x ()
  (insert "exit")
  (eshell-send-input)
  (delete-window))

;; (save-excursion
;;   (save-restriction
;;     (save-match-data
;;       (progn
;;         (re-search-forward "[ \t\r\n]+" nil t)
;;         (replace-match "" nil nil)))))

(defun open-and-indent-line ()
  (interactive)
  (save-restriction
    (save-match-data
      (progn (end-of-line)
             (newline-and-indent)))))

(defun open-and-indent-previous-line ()
  (interactive)
  (save-restriction
    (save-match-data
      (progn (beginning-of-line)
             (newline)
             (forward-line -1)
             (indent-according-to-mode)))))

(defun foreach (f alist)
  (while alist
    (progn (funcall f (car alist))
           (setq alist (cdr alist)))))

(defun bash-env-var (varname)
  (replace-regexp-in-string "\n$" ""
                            (shell-command-to-string
                             (string-join (list ". ~/.bash_profile; echo $"
                                                varname)))))

(defun intern-bash-env-var (varname)                                                                                                                                                                                                                                                                                                                                                   
  (let ((val (bash-env-var varname)))
       (setenv varname val)
       val))

(provide 'site-functions)

;; site-functions.el ends here