;;; -*- mode: emacs-lisp -*-

;;; Emacs Flashcards
;;; Peter Salvi, 2008

(defvar *flashcard-words*)
(defvar *flashcard-next*)

(defun flashcard-shuffle ()
  (let ((n (length *flashcard-words*)))
    (dotimes (i (1- n))
      (let* ((index (+ i 1 (random (- n i 1))))
             (other (aref *flashcard-words* index)))
        (aset *flashcard-words* index (aref *flashcard-words* i))
        (aset *flashcard-words* i other))))
  (setq *flashcard-next* 0))

(defun flashcard-count-matches (regexp rstart rend)
  "For compliance with version < 22 emacsen."
  (let ((count 0))
    (save-excursion
      (goto-char rstart)
      (while (search-forward-regexp regexp rend t)
        (setq count (1+ count))))
    count))

(defun flashcard-init ()
  (random t)
  (let ((count (flashcard-count-matches ".* = .*" (point-min) (point-max))))
    (when (= count 0)
      (error "There are no usable word pairs in this buffer"))
    (setq *flashcard-words* (make-vector count nil))
    (save-excursion
      (goto-char (point-min))
      (dotimes (i count)
        (search-forward-regexp "\\(.*\\) = \\(.*\\)")
        (aset *flashcard-words* i (list (match-string 1) (match-string 2)))))
    (flashcard-shuffle)))

(defun flashcard-next ()
  (when (>= *flashcard-next* (length *flashcard-words*))
    (flashcard-shuffle))
  (prog1 (aref *flashcard-words* *flashcard-next*)
    (setq *flashcard-next* (1+ *flashcard-next*))))

(defun flashcard-help (&optional message)
  (erase-buffer)
  (insert (format "
Welcome to Emacs Flashcards!

There are %d words in the database.

The definitions appear in the minibuffer one by one.
Pressing a key will show the correct answer.
After the answer is displayed you can press
 - `r' to remove this word (i.e. it won't be asked again)
 - any other key to show the next definition

Press q any time to quit."
                  (length *flashcard-words*)))
  (when message
    (insert (format "\n\n=> %s" message))))

(defun flashcard-write-remaining ()
  (erase-buffer)
  (insert "\nThese words remained:\n\n")
  (dotimes (i (length *flashcard-words*))
    (let ((word (aref *flashcard-words* i)))
      (insert (format "%s = %s\n" (car word) (cadr word))))))

(defun flashcard ()
  "Display flashcards in the minibuffer.

Emacs Flashcards searches the current buffer for lines of the format
`solution = definition' and tests the user if he remembers them."
  (interactive)
  (flashcard-init)
  (switch-to-buffer "*flashcard*")
  (setq cursor-type nil)
  (flashcard-help)
  (message "Press q to quit, and any other key to advance.")
  (let ((status 'start)
        (word nil))
    (while (not (eq status 'end))
      (let ((c (read-char-exclusive)))
        (cond ((char-equal c ?q)
               (setq status 'end))
              ((eq status 'ask)
               (message (concat (cadr word) " = " (car word)))
               (setq status 'show))
              ((member status '(start show))
               (when (and (not (eq status 'start)) (char-equal c ?r))
                 (setq *flashcard-words* (remove word *flashcard-words*)
                       *flashcard-next* (1- *flashcard-next*))
                 (flashcard-help (format "Word `%s' (%s) removed."
                                         (cadr word) (car word))))
               (cond ((= (length *flashcard-words*) 0)
                      (setq status 'end)
                      (message "No more words left. Press a key.")
                      (read-char-exclusive))
                     (t
                      (setq word (flashcard-next))
                      (message (cadr word))
                      (setq status 'ask))))))))
  (if (zerop (length *flashcard-words*))
      (kill-buffer "*flashcard*")
    (flashcard-write-remaining)
    (setq cursor-type t))
  (message "Bye!"))