(defcustom ledger-clear-whole-entries nil
  "If non-nil, clear whole entries, not individual transactions."
  :type 'boolean
  :group 'ledger)

(defun ledger-toggle-state (state &optional style)
  (if (not (null state))
      (if (and style (eq style 'cleared))
          'cleared)
    (if (and style (eq style 'pending))
        'pending
      'cleared)))

(defun ledger-entry-state ()
  (save-excursion
    (when (or (looking-at "^[0-9]")
              (re-search-backward "^[0-9]" nil t))
      (skip-chars-forward "0-9./=")
      (skip-syntax-forward " ")
      (cond ((looking-at "!\\s-*") 'pending)
            ((looking-at "\\*\\s-*") 'cleared)
            (t nil)))))

(defun ledger-transaction-state ()
  (save-excursion
    (goto-char (line-beginning-position))
    (skip-syntax-forward " ")
    (cond ((looking-at "!\\s-*") 'pending)
          ((looking-at "\\*\\s-*") 'cleared)
          (t (ledger-entry-state)))))

(defun ledger-toggle-current-transaction (&optional style)
  "Toggle the cleared status of the transaction under point.
Optional argument STYLE may be `pending' or `cleared', depending
on which type of status the caller wishes to indicate (default is
`cleared').
This function is rather complicated because it must preserve both
the overall formatting of the ledger entry, as well as ensuring
that the most minimal display format is used.  This could be
achieved more certainly by passing the entry to ledger for
formatting, but doing so causes inline math expressions to be
dropped."
  (interactive)
  (let ((bounds (ledger-current-entry-bounds))
        clear cleared)
    ;; Uncompact the entry, to make it easier to toggle the
    ;; transaction
    (save-excursion
      (goto-char (car bounds))
      (skip-chars-forward "0-9./= \t")
      (setq cleared (and (member (char-after) '(?\* ?\!))
                         (char-after)))
      (when cleared
        (let ((here (point)))
          (skip-chars-forward "*! ")
          (let ((width (- (point) here)))
            (when (> width 0)
              (delete-region here (point))
              (if (search-forward "  " (line-end-position) t)
                  (insert (make-string width ? ))))))
        (forward-line)
        (while (looking-at "[ \t]")
          (skip-chars-forward " \t")
          (insert cleared " ")
          (if (search-forward "  " (line-end-position) t)
              (delete-char 2))
          (forward-line))))
    ;; Toggle the individual transaction
    (save-excursion
      (goto-char (line-beginning-position))
      (when (looking-at "[ \t]")
        (skip-chars-forward " \t")
        (let ((here (point))
              (cleared (member (char-after) '(?\* ?\!))))
          (skip-chars-forward "*! ")
          (let ((width (- (point) here)))
            (when (> width 0)
              (delete-region here (point))
              (save-excursion
                (if (search-forward "  " (line-end-position) t)
                    (insert (make-string width ? ))))))
          (let (inserted)
            (if cleared
                (if (and style (eq style 'cleared))
                    (progn
                      (insert "* ")
                      (setq inserted t)))
              (if (and style (eq style 'pending))
                  (progn
                    (insert "! ")
                    (setq inserted t))
                (progn
                  (insert "* ")
                  (setq inserted t))))
            (if (and inserted
                     (re-search-forward "\\(\t\\| [ \t]\\)"
                                        (line-end-position) t))
                (cond
                 ((looking-at "\t")
                  (delete-char 1))
                 ((looking-at " [ \t]")
                  (delete-char 2))
                 ((looking-at " ")
                  (delete-char 1))))
            (setq clear inserted)))))
    ;; Clean up the entry so that it displays minimally
    (save-excursion
      (goto-char (car bounds))
      (forward-line)
      (let ((first t)
            (state ? )
            (hetero nil))
        (while (and (not hetero) (looking-at "[ \t]"))
          (skip-chars-forward " \t")
          (let ((cleared (if (member (char-after) '(?\* ?\!))
                             (char-after)
                           ? )))
            (if first
                (setq state cleared
                      first nil)
              (if (/= state cleared)
                  (setq hetero t))))
          (forward-line))
        (when (and (not hetero) (/= state ? ))
          (goto-char (car bounds))
          (forward-line)
          (while (looking-at "[ \t]")
            (skip-chars-forward " \t")
            (let ((here (point)))
              (skip-chars-forward "*! ")
              (let ((width (- (point) here)))
                (when (> width 0)
                  (delete-region here (point))
                  (if (re-search-forward "\\(\t\\| [ \t]\\)"
                                         (line-end-position) t)
                      (insert (make-string width ? ))))))
            (forward-line))
          (goto-char (car bounds))
          (skip-chars-forward "0-9./= \t")
          (insert state " ")
          (if (re-search-forward "\\(\t\\| [ \t]\\)"
                                 (line-end-position) t)
              (cond
               ((looking-at "\t")
                (delete-char 1))
               ((looking-at " [ \t]")
                (delete-char 2))
               ((looking-at " ")
                (delete-char 1)))))))
    clear))

(defun ledger-toggle-current (&optional style)
  (interactive)
  (if (or ledger-clear-whole-entries
          (eq 'entry (ledger-thing-at-point)))
      (progn
        (save-excursion
          (forward-line)
          (goto-char (line-beginning-position))
          (while (and (not (eolp))
                      (save-excursion
                        (not (eq 'entry (ledger-thing-at-point)))))
            (if (looking-at "\\s-+[*!]")
                (ledger-toggle-current-transaction nil))
            (forward-line)
            (goto-char (line-beginning-position))))
        (ledger-toggle-current-entry style))
    (ledger-toggle-current-transaction style)))

(defun ledger-toggle-current-entry (&optional style)
  (interactive)
  (let (clear)
    (save-excursion
      (when (or (looking-at "^[0-9]")
                (re-search-backward "^[0-9]" nil t))
        (skip-chars-forward "0-9./=")
        (delete-horizontal-space)
        (if (member (char-after) '(?\* ?\!))
            (progn
              (delete-char 1)
              (if (and style (eq style 'cleared))
                  (insert " *")))
          (if (and style (eq style 'pending))
              (insert " ! ")
            (insert " * "))
          (setq clear t))))
    clear))

(provide 'ldg-state)