;; Do a C-h v RET ccutils-doc RET to view the documentation nicely
(defconst ccutils-doc nil
"Misc elisp defuns by Erwin S. Andreasen, <erwin@andreasen.org>
New versions may be available at: http://www.andreasen.org/misc.shtml
Version: 1.0 (Feb 13 2002)
This file is hereby placed in the Public Domain.

This file contains the following interactive functions:
make-include-guard (\\[make-include-guard])

when used in a header file, it will insert a standard include guard, i.e.:
#ifndef INCLUDED_FILENAME
#define INCLUDED_FILENAME
...
#endif /* INCLUDED_FILENAME */

The header file will be correctly inserted even if the buffer already
contains some data. With prefix argument, the directory that the file
is located in will also be inserted.

javadoc-skeleton (\\[javadoc-skeleton])

Use in a C++ header file (this might work for Java too) while standing
at a method prototype. It will generate the proper javadoc comment for
all the parameters and results just before it. Example:

int foo(const char *bar, int count);

will have the following generated:
/**
 * X
 * @param bar 
 * @param count 
 * @returns 
 */

X marks the cursor position after javadoc-skeleton has run.

prototype-to-definition (\\[prototype-to-definition])

Used in a C++ header file while standing at a method definition, this
will switch to the corresponding .cc file and, at the end of the file,
insert a skeleton implementation of this method (i.e. the method has
the class prepended, static and void are removed).

If the .cc file does not exist, it is created.

NOTE: This currently does not handle nested classes nor namespaces.

include-read (\\[include-read])

Prompts the user for an include file (with completion), then inserts
an #include <filename> at an appropriate place:
1) if there #include lines already, it is inserted after them
2) If this is a .h file, insertion happens at line 3 (assuming the first 2 lines
   are an include guard)
3) Otherwise, insertion happens at line 1

What directories are search is controlled by the include-paths-alist:
each element in that list consists of the directory to be search and
the prefix to be added in front of it.
")



(require 'cl)

(defun string-join (separator list)
  "Returns a string consisting of list elements separated by separator"
  (reduce (lambda (x y) (concat x separator y)) (or list '(""))))

(defun make-include-guard (levels)
  "Insert a header file guard guarding the contents of this file. The header
guard will be INCLUDED_FILENAME (without the terminating .h or .hh).

If prefix argument is used, the parent directory also included in the guard."
  (interactive "P")
  (let ((guard 
	 (concat "INCLUDED_"
		 (string-join "_"
			      (subseq 
			       (split-string
				(replace-in-string
				 (upcase (buffer-file-name))
				 "\\..+$" "") ; .h / .hh
				  "/")
			      (if levels -2 -1))))))
    ;; Ought to replace an existing included guard here
    (goto-char (point-min))
    (insert-string (format "#ifndef %s\n#define %s\n\n" guard guard))
    (goto-char (point-max))
    (insert-string (format "\n#endif /* %s */\n" guard))))

(defun javadoc-skeleton ()
  "Make a Javadoc skeleton for the function on the current line"
  (interactive)
  (beginning-of-line)
  (unless
      ;; Possible formats:
      ;; void fun(type arg, type arg);
      ;; virtual void fun();
      ;; Class(type arg, type arg);
      ;; void fun(type arg);
      (re-search-forward "[ \t]*\\(.*?\\)[ \t]*[a-zA-Z]+[ \t]*(\\(.*?\\))" (point-at-eol) t)
    (error "Current line doesn't match the function regexp"))
  (let ((args (match-string 2))
	(return-type (match-string 1)))
    (beginning-of-line)
    ;; Header
    (insert-string " /**") (indent-according-to-mode) (newline-and-indent)
    (insert-string " * ") (indent-according-to-mode)
    (save-excursion ;; We'll come back here later
      ;; Arguments
      (unless (equal args "")		; args can be empty if there are no args
	(loop for arg in (split-string args "[ \t]*,[ \t*]") do
	      (insert-string (format 
			      "\n * @param %s "
			      (car (last (split-string arg "[ *&]")))))
	      (indent-according-to-mode)))
      ;; Return type if any. Not that the return-type can be 'virtual void'
      ;; or 'static void', so we need to look only at the last 4 characters
      (unless (or (equal return-type "") (equal (substring return-type -4) "void"))
	(insert-string "\n * @returns ")
	(indent-according-to-mode))
      ;; Footer
      (insert-string "\n */") (indent-according-to-mode)
      (newline-and-indent))))

(defun prototype-to-definition ()
  "Usable only in a .h file. Take the current prototype and convert it into an empty class
in the corresponding .cc file"
  (interactive)
  (unless (equal (substring buffer-file-name -2) ".h")
    (error "This is not a .h file"))
  (beginning-of-line)
  (unless
      (re-search-forward "[ \t]*\\(.*?\\)[ \t]*\\([a-zA-Z]+\\)[ \t]*\\((.*?).*\\);" 
			 (point-at-eol) t)
    (error "Current line doesn't match the function regexp"))
  (let* ((return-type (match-string 1))
	 (function-name (match-string 2))
	 (arguments (match-string 3))	;including function constness etc.
	 (cc-file (concat (substring buffer-file-name 0 -2) ".cc"))
	 (cc-buffer (find-file-noselect cc-file))
	 (class-name))
    (unless 
	(save-excursion
	  (re-search-backward "^[ \t]*class[ \t]+\\([a-zA-Z_]+\\)" nil t))
      (error "Cannot find start of class"))
    (setq class-name (match-string 1))
    (unless cc-buffer
      (error "There is no buffer for file %s" cc-file))
    ;; Remove static and virtual from the return type
    (message "Return type: %s" return-type)
    (while (string-match "\\(virtual\\|static\\)[ \t]+" return-type)
      (setq return-type (replace-match "" nil nil return-type)))
    (switch-to-buffer-other-window cc-buffer)
    (end-of-buffer)
    (insert-string 
     (format "%s %s::%s%s\n{\n\n}\n" return-type class-name function-name arguments))
    (forward-line -2)
    (indent-according-to-mode)))

(defun include-getfiles (path prefix)
  "For all .h files in path, prepend prefix and slash to them if prefix is non-nil"
  (mapcar (lambda (x) 
	    (if prefix
		(concat prefix "/" x)
		x))
	  (directory-files path nil ".h$" nil t)))

(defvar include-paths-alist
    '(
      ("/usr/include" nil)
      ("/usr/include/sys" "sys"))
  "Alist of directories to search for header files and the prefix they'll get")

(defun include-read ()
  "Ask the user about the name of an include file and insert the #include line"
  (interactive)
  (or buffer-file-name
      (error "This isn't a real buffer"))
  (save-excursion
    (let* (
	   (completion-ignore-case t)
	   (filename 
	    (completing-read
	     "Include file: "
	     ;; turn our simple list into the alist that completing-read wants
	     (mapcar (lambda (x) (list x nil)) 
		     (apply #'append
			    (mapcar 
			     (lambda (x) (apply 'include-getfiles x)) 
			     include-paths-alist)))))
	   (line (concat "#include <" filename ">")))
      (beginning-of-buffer)
      (when (search-forward line nil t)
	(error "File %s already included" filename))
      (end-of-buffer)
      (if (re-search-backward "^#include" nil t)
	  ;; An #include directivy already exists in this file
	  (progn
	    (end-of-line)
	    (insert-string (concat "\n" line)))
	  ;; No includes. Are we in a header file? Insert at line 3.
	  ;; Otherwise at line 1
	  (goto-line (if (string-equal (substring buffer-file-name -2) ".h") 3 1))
	  (insert-string (concat line "\n"))))))
