(defconst +szotar-tones+
  '(("~" . high) ("/" . rising) ("\\" . falling)))
(defconst +szotar-diacritics+
  '((high . "̄") (rising . "́") (falling . "̀")))

(defun tone-class (str)
  "Return NIL for no tone and for tones 3 and 6;
return a list of \(consonants type first-vocal rest) otherwise.
In the case of ng, g takes the role of the vocal."
  (let ((match (string-match (concat "^\\([^aeiou]*\\)\\([aeiou]\\)"
                                     "\\([a-z]*\\)\\([^a-z]\\)\\(.*\\)$")
                             str)))
    (if match
        (let ((consonant (match-string 1 str))
              (vocal (match-string 2 str))
              (rest (match-string 3 str))
              (type (match-string 4 str))
              (post (match-string 5 str)))
          (let ((tone (cdr (assoc type +szotar-tones+))))
            (when tone
              (list consonant tone vocal (concat rest post)))))
      (let ((match (string-match "^ng\\(h?\\)\\([~/\\]\\)\\(.*\\)$"
                                 str)))
        (when match
          (let ((rest (match-string 1 str))
                (type (match-string 2 str))
                (post (match-string 3 str)))
            (let ((tone (cdr (assoc type +szotar-tones+))))
              (when tone
                (list "n" tone "g" (concat rest post))))))))))

(defun format-toned (consonants type first-vocal rest)
  (let ((diacritic (cdr (assoc type +szotar-diacritics+))))
    (concat consonants first-vocal diacritic rest)))

(defun parse-accents (str)
  "For every word, if it ends with ~, / or \\,
mark it with high, rising and falling, respectively."
  (let ((words (split-string str)))
    (with-output-to-string
      (while words
        (let ((toned-word (tone-class (car words))))
          (if toned-word
              (princ (apply #'format-toned toned-word))
            (princ (car words)))
          (when (cdr words)
            (princ " ")))
        (setq words (cdr words))))))

(defun cantonese-htmlize (str)
  (interactive "syuht ping: ")
  (kill-new (parse-accents str)))

(defun szotar-next-line ()
  "Takes the next line and prints its HTML representation."
  (if (re-search-forward "^\\(.*\\) = \\(.*\\)$" (point-max) t)
      (let* ((definition (match-string 2))
             (cantonese (parse-accents (match-string 1))))
        (princ (concat "<tr>\n"
                       "<td><span class=\"cantonese\">" cantonese "</span></td>\n"
                       "<td><span class=\"definition\">" definition "</span></td>\n"
                       "</tr>\n"))
        t)
      nil))

(defun szotar-htmlize ()
  "Generates a CSS-driven HTML file from the current buffer.
The buffer is assumed to be in UTF-8 encoding."
  (interactive)
  (with-output-to-temp-buffer (concat (buffer-name (current-buffer)) ".html")
    (princ (concat "<html>\n"
                   "<head>\n"
                   "<title>" (buffer-name (current-buffer)) "</title>\n"
                   "<link rel=\"stylesheet\" type=\"text/css\""
                   " href=\"szotar.css\" media=\"all\" />\n"
                   "</head>\n"
                   "<meta http-equiv=\"Content-Type\""
                   " content=\"text/html;charset=utf-8\" />\n"
                   "<body>\n"
                   "<table>\n"))
    (save-excursion
      (goto-char (point-min))
      (while (szotar-next-line)))
    (princ "</table>\n</body>\n</html>")))