
(defmacro simbad--safe-command (cmd)
  (condition-case err
      (eval cmd)
    (error)))

(defun simbad--inside-comment-or-string ()
  (let ((p (get-char-property (1- (point)) 'face)))
    (or (eq p 'font-lock-string-face)
        (eq p 'font-lock-comment-face)
        (eq p 'font-lock-doc-string-face)))
  (if (eq p 'font-lock-string-face)
      (message "*** font-lock-string-face" p))
  (message "*** one call to simbad--inside-comment-or-string"))

(defun simbad--kill-empty-lines ()
  (progn
    ;;
    ;;  NOTE: flushes empty lines
    ;;
    (goto-char (point-min))
    (flush-lines "^[ \t]*$"))
  )

(defun simbad--tabify-ampamp ()
  (progn
    ;;
    ;; NOTE: matches &&, ||
    ;;
    (goto-char (point-min))
    (while (re-search-forward "\\(&&\\|||\\)" nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (progn
          (insert " ")
          (backward-char 3)
          (insert " ")
          (forward-char 2))))))

(defun simbad--tabify-plus ()
  (progn
    ;;
    ;; NOTE: matches +
    ;;
    (goto-char (point-min))
    (while (re-search-forward "[^+]\\+[^+=]" nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (progn
          (forward-char -2)
          (insert " ")
          (forward-char 1)
          (insert " ")
          ;;(forward-char -1)
          )))))

(defun simbad--tabify-minus ()
  (progn
    ;;
    ;; NOTE: matches -
    ;;
    (goto-char (point-min))
    (while (re-search-forward "[^-]-[^-=]" nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (progn
          (forward-char -2)
          (insert " ")
          (forward-char 1)
          (insert " "))))))

(defun simbad--tabify-equals ()
  (progn
    ;;
    ;; NOTE: matches =
    ;;
    (goto-char (point-min))
    (while (re-search-forward "[^<>!=+*/-]=[^=]" nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (progn
          (backward-char 1)
          (insert " ")
          (backward-char 2)
          (insert " ")
          (forward-char 2))))))

(defun simbad--tabify-opequals ()
  (progn
    ;;
    ;; NOTE: matches *= etc
    ;;
    (goto-char (point-min))
    (while (re-search-forward "[^=][=!<>+*/]=[^=]" nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (progn
          (backward-char 1)
          (insert " ")
          (backward-char 3)
          (insert " ")
          (forward-char 2))))))

(defun simbad--tabify-comma ()
  (progn
    ;;
    ;; NOTE: matches ,
    ;;
    (goto-char (point-min))
    (while (re-search-forward "," nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (insert " ")))))

(defun simbad--tabify-semi ()

  (progn
    ;;
    ;; NOTE: matches ;
    ;;
    (goto-char (point-min))
    (while (re-search-forward ";" nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (insert " ")))))

(defun simbad--tabify-ket-throws ()
  (progn
    ;;
    ;; NOTE: matches )throws
    ;;
    (goto-char (point-min))
    (while (re-search-forward ")throws" nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (progn
          (forward-sexp -1)
          (insert " "))))))

(defun simbad--splat-tabs-to-spaces ()
  (progn
    ;;
    ;; NOTE: replaces TABs with SPACEs
    ;;
    (goto-char (point-min))
    (while (re-search-forward "\t" nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (backward-delete-char 1)
        (insert " ")))))

(defun simbad--tabify-catchforif-bra ()
  (progn
    ;;
    ;; NOTE: matches catch(, for(, if( -> catch (, for (, if (
    ;;
    (goto-char (point-min))
    (while (re-search-forward "\\(catch\\|for\\|if\\)(" nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (backward-char 1)
        (insert " ")))))

(defun simbad--tabify-tryfor-bra ()
  (progn
    ;;
    ;; NOTE: matches try{ and for (...){ -> try {, for (...) {
    ;;
    (goto-char (point-min))
    (while (re-search-forward "[a-zA-Z0-9)]{" nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (backward-char 1)
        (insert " ")))))

(defun simbad--whitelines-package ()
  (goto-char (point-min))
  (if (re-search-forward "^package.*;" nil t)
      (insert "\n")))

(defun simbad--whitelines-import ()
  (let ((regexp "^\\(//[ \t/]*\\)?import"))
    ;;
    ;; NOTE: puts an extra line after the import statements
    ;;
    (goto-char (point-min))
    (when (re-search-forward regexp nil t)
      (beginning-of-line)
      (while (looking-at regexp)
        (forward-line 1))
      (insert "\n"))))

(defun simbad--move-squiglies ()

  (progn
    (goto-char (point-min))
    (while (re-search-forward ")[ \t]*{" nil t)
    (when (not (simbad--inside-comment-or-string))
      (backward-char 1)
      (insert "\n"))))

  (progn
    (goto-char (point-min))
    (while (re-search-forward "\\(do\\|else\\|try\\)[ \t]*{" nil t)
    (when (not (simbad--inside-comment-or-string))
      (backward-char 1)
      (insert "\n"))))

  (d-quote
    ;;
    ;; NOTE: these three are opposites
    ;;
    (progn
      ;;
      ;; NOTE: moves the class "{" from end of line to beginning of a new line
      ;;
      (let ((regexp "^\\(\\(public\\|abstract\\|final\\)[ \t]+\\)*\\(class\\|interface\\)\\>"))
        (goto-char (point-min))
        (while (re-search-forward regexp nil t)
          (when (re-search-forward "{" (point-at-eol) t)
            (backward-char 1)
            (insert "\n")
            (forward-line 1)))))

    (let (p)
      ;;
      ;; MATCHES: sole line { after catch/for/if/while
      ;;
      (goto-char (point-min))
      (while (setq p (re-search-forward "^[ \t]*{[ \t]*$" nil t))
        (when (and (progn (beginning-of-line)
                          (simbad--safe-command (forward-sexp -1))
                          (looking-at "("))
                   (progn (simbad--safe-command (forward-sexp 1))
                          (not (looking-at "[ \t]*;")))
                   (progn (simbad--safe-command (forward-sexp -2))
                          (looking-at "\\<\\(catch\\|for\\|if\\|while\\)\\>")))
          (goto-char p)
          (beginning-of-line)
          (backward-delete-char 1))
        (goto-char p)
        (forward-line 1)))

    (let (p)
      ;;
      ;; MATCHES: sole line { after else/try
      ;;
      (goto-char (point-min))
      (while (setq p (re-search-forward "^[ \t]*{[ \t]*$" nil t))
        (when (progn (beginning-of-line)
                     (simbad--safe-command (forward-sexp -1))
                     (looking-at "\\<\\(else\\|try\\)\\>"))
          (goto-char p)
          (beginning-of-line)
          (backward-delete-char 1))
        (goto-char p)
        (forward-line 1)))

    (d-quote (progn
             ;;
             ;; MATCHES: } \n catch/else -> } catch/else
             ;;
             (goto-char (point-min))
             (while (setq p (re-search-forward "^[ \t]*}[ \t]*$" nil t))
               (when (progn
                       (simbad--safe-command (forward-sexp 1))
                       (simbad--safe-command (forward-sexp -1))
                       (looking-at "\\<\\(catch\\|else\\)\\>"))
                 (goto-char p)
                 (delete-char 1))
               (goto-char p)
               (forward-line 1))))

    )
  )



(defun simbad--killdups-indent ()
  (let (p1 p2)
    ;;
    ;;  NOTE: kills double spaces
    ;;
    (goto-char (point-min))
    (while (re-search-forward "[ \t][ \t]+" nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (setq p2 (point))
        (skip-chars-backward " \t")
        (setq p1 (point))
        (delete-region p1 p2)
        (insert " "))))

  (progn
    (goto-char (point-min))
    (while (re-search-forward " ;" nil t)
      (if (simbad--inside-comment-or-string)
          (forward-char 1)
        (backward-char 1)
        (backward-delete-char 1))))

  (indent-region (point-min) (point-max) nil))



(defun simbad--is-a-semi-line ()
  (progn;;save-excursion
    (let (done)
      (while (and (not (looking-at "[ \t]*;"))
                  (not (looking-at "[ \t]*="))
                  (not (looking-at "[ \t]*("))
                  (not (eobp))
                  (not done))
        ;;(message "foo %s" (random))
        (condition-case err
            (forward-sexp 1)
          (error (setq done t))))

      (setq done nil)
      (if (looking-at "[ \t]*=")
          (while (and (not (looking-at "[ \t]*;"))
                      (not (eobp))
                      (not done))
            (condition-case err
                (forward-sexp 1)
              (error (setq done t)))))
      (if (looking-at "[ \t]*;")
          (point)))))

(defun simbad--indent-file (file)
  (let ((indent-tabs-mode nil))
      ;;((tab-width 3))
    (save-excursion
      (find-file file)
      (message "Fontifying buffer")
      (font-lock-fontify-buffer)
      (setq c-basic-offset 4)
      ;;(setq tab-width 1000)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; BEGIN: stuff
;;;
      (simbad--kill-empty-lines)
      (simbad--tabify-ampamp)
      (simbad--tabify-plus)
      (simbad--tabify-minus)
      (simbad--tabify-equals)
      (simbad--tabify-opequals)
      (simbad--tabify-comma)
      (simbad--tabify-semi)
      (simbad--tabify-ket-throws)
      (simbad--splat-tabs-to-spaces)
      (simbad--tabify-catchforif-bra)
      (simbad--tabify-tryfor-bra)
      (simbad--whitelines-package)
      (simbad--whitelines-import)
      (simbad--move-squiglies)
;;;
;;; END: stuff
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      (simbad--killdups-indent)
      (let (p)
        ;;
        ;; NOTE: matches { at end of line -> puts at beginning of line
        ;;
        (goto-char (point-min))
        (while (setq p (re-search-forward (concat "^" (make-string c-basic-offset ? )  "[a-zA-Z].*{[ \t]*$") nil t))
          (skip-chars-backward " \t")
          (backward-char)
          (insert "\n")))

      (progn
        ;;
        ;; NOTE: matches end of methods -> adds extra space after each method
        ;;
        (goto-char (point-min))
        (while (re-search-forward (concat "^" (make-string c-basic-offset ? ) "}") nil t)
          (end-of-line)
          (insert "\n")))

      (progn
        ;;
        ;; NOTE: matches end of class/interface -> add extra space after each class/interface
        ;;
        (goto-char (point-min))
        (while (re-search-forward "^}" nil t)
          (end-of-line)
          (insert "\n")))

      (let (p q)
        ;;
        ;; NOTE: matches the end of a list of properties
        ;;
        (goto-char (point-min))
        (while (setq p (re-search-forward (concat "^" (make-string c-basic-offset ? ) "[a-zA-Z]") nil t))
          ;;(debug)
          ;;(message "foo %s" (random))
          (when (and (setq q (simbad--is-a-semi-line))
                     (save-excursion
                       (forward-line 1)
                       (not (simbad--is-a-semi-line))))
            ;;(debug)
            (goto-char q)
            (forward-char 1)
            (insert "\n"))
          ;;(goto-char p)
          ;;(forward-line 1)
          ))

      ;;
      ;; OLD STUFF: matches end of a list of properties
      ;;
      (d-quote (let ((re1 (concat "^" (make-string c-basic-offset ? ) "[a-zA-Z/].*;[ \t]*\\(//.*\\)?$"))
                   (re2 (concat "^" (make-string c-basic-offset ? ) "//.*$")))

               (goto-char (point-min))
               (while (re-search-forward re1 nil t)
                 (if (progn
                       (forward-line 1)
                       (and (not (looking-at re1)) (not (looking-at re2))))
                     (insert "\n")))))

      (simbad--killdups-indent)

      (d-quote (progn
               ;;
               ;; NOTE: moves beginning of method baces to the eol (turned off now)
               ;;
               (let (p)
                 (goto-char (point-min))
                 (while (setq p (re-search-forward "^[ \t]*{[ \t]*$" nil t))
                   (when (and (progn
                                (beginning-of-line)
                                (forward-sexp -1)
                                (looking-at "("))
                              (progn
                                (beginning-of-line)
                                (looking-at "   [a-zA-Z]")))
                     (end-of-line)
                     (delete-char 1))
                   (goto-char p)
                   (forward-line 1)))
               (simbad--kill)))

      (save-buffer)
      )
    )
  )

(defun simbad-indent ()
  (interactive)
  (simbad--indent-file (buffer-file-name)))

;;; (simbad--indent-dir (setq dir "~/workspace/project/"))
;;; (simbad--indent-dir (setq dir "~/tmp-project/"))
(defun simbad--indent-dir (dir)
  (let* ((list  (directory-files-deep dir t "\\.java$"))
         (ptr   list))
         ;;(total (length list))
         ;;(count 0))
    (while ptr
      ;;(message "Count = %s/%s" count total)
      (simbad--indent-file (car ptr))
      (find-file (car ptr))
      (kill-buffer nil)
      ;;(incf count)
      (setq ptr (cdr ptr)))
    (message "Total Files Processed=%s" (length list))))

(defun simbad-indent-a ()
  (let ((file "~/a.java"))
    (simbad--indent-file file))
  )

(defun simbad-indent-project ()
  ;;(simbad--indent-dir "~/workspace/project/")
  (simbad--indent-dir "~/workspace/project/")
  )

(defun test ()
  (interactive)
  (let ()
    (find-file "~/a.java")
    (font-lock-fontify-buffer)
    (goto-char (point-min))
    (while (< (point) (point-max))
      (message "%s" (get-char-property (point) 'face))
      (forward-char 1))))

(load-file "~/dlisp/directory-files-deep.el")
(load-library "cl.el")
(load-library "cl-seq.el")

(provide 'simbad-indent)

