;;; alchemist-utils.el --- Common utility functions that don't belong anywhere else -*- lexical-binding: t -*-

;; Copyright © 2014-2015 Samuel Tonini

;; Author: Samuel Tonini <tonini.samuel@gmail.com

;; This file is not 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 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:

;; Common utility functions that don't belong anywhere else

;;; Code:

(require 'cl-lib)
(require 'dash)

(defface alchemist-utils--deprecated-face
  '((t (:inherit font-lock-variable-name-face :bold t :foreground "red")))
  "Face for 'deprecated' word inside deprecated message."
  :group 'alchemist)

(defun alchemist-utils-deprecated-message (function new-function)
  (message "'%s is %s in favor of '%s"
           function (propertize "deprecated"
                                'face 'alchemist-utils--deprecated-face)
           new-function))

(defun alchemist-utils-build-command (command-list)
  "Build the commands list for the runner."
  (let* ((command-list (-flatten (if (stringp command-list)
                                     (split-string command-list)
                                   command-list)))
         (command (-remove (lambda (e) (equal e "")) command-list)))
    (mapconcat 'concat command " ")))

(defun alchemist-utils-count-char-occurence (regexp str)
  "Count occurrence of char with REGEXP inside STR."
  (cl-loop with start = 0
           for count from 0
           while (string-match regexp str start)
           do (setq start (match-end 0))
           finally return count))

(defun alchemist-utils-test-file-p ()
  "Return non-nil `current-buffer' holds an Elixir test file."
  (string-match "_test\\.exs$" (or (buffer-file-name) "")))

(defun alchemist-utils-remove-dot-at-the-end (string)
  "Remove dot character at the end of STRING."
  (replace-regexp-in-string "\\.$" "" string))

(defun alchemist-utils-empty-string-p (string)
  "Return non-nil if STRING is null, blank or whitespace only."
  (or (null string)
      (string= string "")
      (if (string-match-p "^\s+$" string) t)))

(defun alchemist-utils-prepare-aliases-for-elixir (aliases)
  (let* ((aliases (-map (lambda (a)
                            (let ((module (alchemist-utils-remove-dot-at-the-end (car a)))
                                  (alias (alchemist-utils-remove-dot-at-the-end (car (cdr a)))))
                            (if (not (or (alchemist-utils-empty-string-p alias)
                                         (string= alias module)))
                                (format "{%s, %s}"
                                        (if (alchemist-utils-empty-string-p alias)
                                            module
                                          alias)
                                        module)))) aliases))
         (aliases (mapconcat #'identity aliases ",")))
    (format "[%s]" aliases)))

(defun alchemist-utils-prepare-modules-for-elixir (modules)
  (let* ((modules (mapconcat #'identity modules ",")))
    (format "[%s]" modules)))

(defun alchemist-utils--snakecase-to-camelcase (str)
  "Convert a snake_case string STR to a CamelCase string.

This function is useful for converting file names like my_module to Elixir
module names (MyModule)."
  (mapconcat 'capitalize (split-string str "_") ""))

(defun alchemist-utils-add-ext-to-path-if-not-present (path ext)
  "Add EXT to PATH if PATH doesn't already ends with EXT."
  (if (string-suffix-p ext path)
      path
    (concat path ext)))

(defun alchemist-utils-path-to-module-name (path)
  "Convert PATH to its Elixir module name equivalent.

For example, convert 'my_app/my_module.ex' to 'MyApp.MyModule'."
  (let* ((path (file-name-sans-extension path))
         (path (split-string path "/"))
         (path (-remove (lambda (str) (equal str "")) path)))
    (mapconcat #'alchemist-utils--snakecase-to-camelcase path ".")))

(defun alchemist-utils-add-trailing-slash (path)
  "Add trailing slash to PATH if not already contain."
  (if (not (string-match-p "/$" path))
      (format "%s/" path)
    path))

(defun alchemist-utils-occur-in-buffer-p (buffer regex)
  "Return non-nil if BUFFER contains at least one occurrence of REGEX."
  (with-current-buffer buffer
    (save-excursion
      (save-match-data
        (goto-char (point-min))
        (re-search-forward regex nil t)))))

(defun alchemist-utils-jump-to-regex (regex before-fn after-fn search-fn reset-fn)
  "Jump to REGEX using SEARCH-FN to search for it.
A common use case would be to use `re-search-forward' as the SEARCH-FN.
Call RESET-FN if the regex isn't found at the first try. BEFORE-FN is called
before performing the search while AFTER-FN after."
  (when (alchemist-utils-occur-in-buffer-p (current-buffer) regex)
    (save-match-data
      (funcall before-fn)
      (unless (funcall search-fn regex nil t)
        (funcall reset-fn)
        (funcall search-fn regex nil t))
      (funcall after-fn))))

(defun alchemist-utils-jump-to-next-matching-line (regex after-fn)
  "Jump to the next line matching REGEX.
Call AFTER-FN after performing the search."
  (alchemist-utils-jump-to-regex regex 'end-of-line after-fn 're-search-forward 'beginning-of-buffer))

(defun alchemist-utils-jump-to-previous-matching-line (regex after-fn)
  "Jump to the previous line matching REGEX.

Call AFTER-FN after performing the search."
  (alchemist-utils-jump-to-regex regex 'beginning-of-line after-fn 're-search-backward 'end-of-buffer))

(defun alchemist-utils-elixir-version ()
  "Return the current Elixir version on the system."
  (let* ((output (shell-command-to-string (format "%s --version" alchemist-execute-command)))
         (output (split-string output "\n"))
         (output (-remove (lambda (string) (alchemist-utils-empty-string-p string))
                          output))
         (version (-last-item output))
         (version (replace-regexp-in-string "Elixir " "" version)))
    version))

(provide 'alchemist-utils)

;;; alchemist-utils.el ends here
