(defvar id3el-version "0.04")

(add-hook 'mpg123-hook 'id3-setup-at-mpg123-hook)

(defun id3-setup-at-mpg123-hook ()
  "setup when mpg123 is evaluated."
  (define-key mpg123-mode-map "E" 'id3-edit))

; system identification
(defvar id3*xemacs-p  (string-match "XEmacs" emacs-version))

(defvar id3*put-prog "id3put")

(defvar id3*coding 'sjis)

(defvar id3*title-string   "Title   :")
(defvar id3*artist-string  "Artist  :")
(defvar id3*album-string   "Album   :")
(defvar id3*year-string    "Year    :")
(defvar id3*comment-string "Comment :")
(defvar id3*genre-string   "Genre   :")

(defvar id3*title   "Unknown title")
(defvar id3*artist  "Unknown artist")
(defvar id3*album   "Unknown album")
(defvar id3*year    "1970")
(defvar id3*comment "This is comment")
(defvar id3*genre   "0")

(defvar id3*edit-buffer "*id3 edit*")
(defvar id3*edit-window nil)
(defvar id3*tag-buffer " *id3 tag tmp*")
(defvar id3*completion-buffer " *id3 completion tmp*")

(defvar id3*temp-buffer " *id3 tmp*")

(defvar id3*fname nil)
(defvar id3*next-line-add-newlines nil)

(defvar id3-mode-map nil)

(defun id3*number-to-genre ()
  (let ((n (string-to-number id3*genre))
	(i 0)
	(temp-list id3*genre-list)
	(genre "unknown"))
    (while (and temp-list (< i n))
      (setq i (+ i 1))
      (setq genre (car (car temp-list)))
      (setq temp-list (cdr temp-list))
      )
    genre
    ))

(defun id3*genre-to-number ()
  (let ((genre-str (downcase (id3:get-item id3*genre-string)))
	(i 0)
	(temp-list id3*genre-list)
	(genre nil))
    (while (and temp-list (not (string-equal genre-str genre)))
      (setq i (+ i 1))
      (setq genre (car (car temp-list)))
      (setq temp-list (cdr temp-list))
      )
    (number-to-string i)
    ))

(setq id3*completion-buffer-exist nil)
(defun id3:genre-completion ()
  (interactive)
  (let ((str1 (downcase (id3*kill-cursor-str)))
	compl1 compl2)
    (setq compl1 (try-completion str1 id3*genre-list))
    (setq compl2 (all-completions str1 id3*genre-list))
    (cond
     ((equal 1 (length compl2))
      (message "Sole completion")
      (cond
       (id3*completion-buffer-exist
	(kill-buffer id3*completion-buffer)
	(set-window-configuration id3*completion-window-config)))
      (setq id3*completion-buffer-exist nil)
      (insert-string compl1))
     (compl1
      (setq id3*completion-window-config (current-window-configuration))
      (setq id3*completion-buffer-exist t)
      (with-output-to-temp-buffer id3*completion-buffer
	(display-completion-list compl2))
      (insert-string compl1))
     (t
      (insert-string str1)
      (cond
       (id3*completion-buffer-exist
	(kill-buffer id3*completion-buffer)
	(set-window-configuration id3*completion-window-config)))
      (setq id3*completion-buffer-exist nil)
      (message "No matching %s" str1))
     )
    ))

(defun id3*kill-cursor-str ()
  (let ((str "")
	(end (point)))
    (save-excursion
      (while (and (not (bobp))
		  (string-match "[^:\n]"
				(buffer-substring (- (point) 1) (point))))
	(backward-char))
      (setq str (buffer-substring (point) end))
      (kill-region (point) end)
      str)))

(defun id3*get-cursor-str ()
  (let ((str "")
	(end (point)))
    (save-excursion
      (while (and (not (bobp))
		  (string-match "[^:\n]"
				(buffer-substring (- (point) 1) (point))))
	(backward-char))
      (setq str (buffer-substring (point) end))
      str)))

(defvar id3*genre-list '(
			 ("blues")
			 ("classic rock")
			 ("country")
			 ("dance")
			 ("disco")
			 ("funk")
			 ("grunge")
			 ("hip-hop")
			 ("jazz")
			 ("metal")
			 ("new age")
			 ("oldies")
			 ("other")
			 ("pop")
			 ("r&b")
			 ("rap")
			 ("reggae")
			 ("rock")
			 ("techno")
			 ("industrial")
			 ("alternative")
			 ("ska")
			 ("death metal")
			 ("pranks")
			 ("soundtrack")
			 ("euro-techno")
			 ("ambient")
			 ("trip-hop")
			 ("vocal")
			 ("jazz+funk")
			 ("fusion")
			 ("trance")
			 ("classical")
			 ("instrumental")
			 ("acid")
			 ("house")
			 ("game")
			 ("sound clip")
			 ("gospel")
			 ("noise")
			 ("alternrock")
			 ("bass")
			 ("soul")
			 ("punk")
			 ("space")
			 ("meditative")
			 ("instrumental pop")
			 ("instrumental rock")
			 ("ethnic")
			 ("gothic")
			 ("darkwave")
			 ("techno-industrial")
			 ("electronic")
			 ("pop-folk")
			 ("eurodance")
			 ("dream")
			 ("southern rock")
			 ("comedy")
			 ("cult")
			 ("gangsta")
			 ("top 40")
			 ("christian rap")
			 ("pop/funk")
			 ("jungle")
			 ("native american")
			 ("cabaret")
			 ("new wave")
			 ("psychadelic")
			 ("rave")
			 ("showtunes")
			 ("trailer")
			 ("lo-fi")
			 ("tribal")
			 ("acid punk")
			 ("acid jazz")
			 ("polka")
			 ("retro")
			 ("musical")
			 ("rock & roll")
			 ("hard rock")
			 ("folk")
			 ("folk/rock")
			 ("national folk")
			 ("swing")
			 ("fast-fusion")
			 ("bebob")
			 ("latin")
			 ("revival")
			 ("celtic")
			 ("bluegrass")
			 ("avantgarde")
			 ("gothic rock")
			 ("progressive rock")
			 ("psychedelic rock")
			 ("symphonic rock")
			 ("slow rock")
			 ("big band")
			 ("chorus")
			 ("easy listening")
			 ("acoustic")
			 ("humour")
			 ("speech")
			 ("chanson")
			 ("opera")
			 ("chamber music")
			 ("sonata")
			 ("symphony")
			 ("booty bass")
			 ("primus")
			 ("porn groove")
			 ("satire")
			 ("slow jam")
			 ("club")
			 ("tango")
			 ("samba")
			 ("folklore")
			 ("ballad")
			 ("powder ballad")
			 ("rhythmic soul")
			 ("freestyle")
			 ("duet")
			 ("punk rock")
			 ("drum solo")
			 ("a capella")
			 ("euro-house")
			 ("dance hall")
			 ("goa")
			 ("drum & bass")
			 ("club house")
			 ("hardcore")
			 ("terror")
			 ("indie")
			 ("britpop")
			 ("negerpunk")
			 ("polsk punk")
			 ("beat")
			 ("christian gangsta")
			 ("heavy metal")
			 ("black metal")
			 ("crossover")
			 ("contemporary c")
			 ("christian rock")
			 ("merengue")
			 ("salsa")
			 ("thrash metal")
			 ("anime")
			 ("jpop")
			 ("synthpop")
			 ("unknown")
))

(defun id3:setup-keymap ()
  (setq id3-mode-map (make-sparse-keymap))
  (use-local-map id3-mode-map)
  (define-key id3-mode-map "\C-a" 'id3:beginning-of-item)
;  (define-key id3-mode-map "\t" 'id3:next-item)
  (define-key id3-mode-map "\t" 'id3:genre-completion)
  (define-key id3-mode-map "\C-c\C-c" 'id3:edit-done)
)

(defun id3:get-item (title)
  "Get value of title"
  (goto-char 1)
  (search-forward title nil t)
  (looking-at "\\(.*\\)$")
  (buffer-substring (match-beginning 1) (match-end 1)))

(defun id3:edit-done ()
  "End editing ID3 tag."
  (interactive)
  (let ((b (get-buffer-create id3*tag-buffer))
	(coding-system-for-write id3*coding))
    (setq id3*title   (id3:get-item id3*title-string))
    (setq id3*artist  (id3:get-item id3*artist-string))
    (setq id3*album   (id3:get-item id3*album-string))
    (setq id3*year    (id3:get-item id3*year-string))
    (setq id3*comment (id3:get-item id3*comment-string))
;    (setq id3*genre   (id3:get-item id3*genre-string))
    (setq id3*genre (id3*genre-to-number))

    (save-excursion
      (set-buffer b)
      (erase-buffer)
      (insert-string (concat
		      id3*title "\n"
		      id3*artist "\n"
		      id3*album "\n"
		      id3*year "\n"
		      id3*comment "\n"
		      id3*genre "\n"
		      ))
      (save-excursion
	(switch-to-buffer (get-buffer-create id3*temp-buffer))
	(erase-buffer)
	(insert-string id3*fname)
	(goto-char 1)
	(replace-regexp "'" "'\"'\"'")
	(setq id3*fname (buffer-substring (point-min) (point-max)))
	(kill-buffer id3*temp-buffer))
      (shell-command-on-region (point-min) (point-max)
			       (concat id3*put-prog " '" id3*fname "'"))
      (kill-buffer b)
      )
    (kill-buffer id3*edit-buffer)
    (select-window id3*current-window)
    (if id3*one-window-p
	(delete-other-windows))
    (mpg123-open-new default-directory)
    (setq next-line-add-newlines id3*next-line-add-newlines)
))

(defun id3:beginning-of-item ()
  "Go to beginning of ID3 item"
  (interactive)
  (beginning-of-line)
  (forward-char (length id3*title-string)))

(defun id3:next-item ()
  "Go to next ID3 item"
  (interactive)
  (next-line 1)
  (if (= (point) (point-max))
      (progn (message "End of item")
	     (next-line -1)))
  (id3:beginning-of-item))

(defun id3:disp-item (title value)
  "Display item of ID3"
  (let ((begin (point))
	(end (- (+ (point) (length title)) 1)))
    (insert-string (concat title value "\n"))
    (set-text-properties begin end '(read-only t))))

(defun id3:open-edit-window ()
  "Open window for edit ID3 tag"
;; if there is only one window, open another window.
;; otherwise use next window.

  (setq id3*current-window (selected-window))
  (setq id3*one-window-p (one-window-p))

  (if (eq (one-window-p) nil)
      ;; there are more than one window
      (progn
	(setq id3*edit-window (get-buffer-window (get-buffer-create id3*edit-buffer)))
	(if (eq id3*edit-window nil)
	    ;; edit window is not visible, set other window
	    (progn
	      (other-window 1)
	      (switch-to-buffer (get-buffer-create id3*edit-buffer))
	      (setq id3*edit-window (selected-window)))
	  ;; edit window is visible
	  (select-window id3*edit-window)))
    ;; there is only one window
    (split-window)
    (other-window 1)
    (switch-to-buffer (get-buffer-create id3*edit-buffer))
    (setq id3*edit-window (selected-window))))

(defun id3:prepare-buf ()
  "Prepare buffer for ID3 editing"
  (id3:open-edit-window)
  (setq inhibit-read-only t)
  (set-text-properties 1 (point-max) nil)
  (setq inhibit-read-only nil)
  (erase-buffer)
  (id3:disp-item id3*title-string   id3*title)
  (id3:disp-item id3*artist-string  id3*artist)
  (id3:disp-item id3*album-string   id3*album)
  (id3:disp-item id3*year-string    id3*year)
  (id3:disp-item id3*comment-string id3*comment)
;  (id3:disp-item id3*genre-string   id3*genre)
  (id3:disp-item id3*genre-string   (id3*number-to-genre))

  (goto-char (+ (length id3*title-string) 1))
)

(defun id3-mode ()
  "ID3 tag edit mode."
  (id3:prepare-buf)
  (setq truncate-lines t)
  (setq id3*next-line-add-newlines next-line-add-newlines)
  (setq next-line-add-newlines nil)
  (setq major-mode 'id3-mode
	mode-name "id3")
  (id3:setup-keymap)
  (message "Type \\C-c\\C-c after editing.")
)

(defun id3:get-single-tag (file pos size sq)
  "Get single tag from FILE"
  (let ((sz (nth 7 (file-attributes (file-truename file))))
	(b (get-buffer-create id3*tag-buffer))
	begin end
	)
    (save-excursion
      (set-buffer b)
      (erase-buffer)
      (setq begin (+ (- sz 128) pos))
      (setq end (+ begin size))
      (insert-file-contents file nil begin end)
      (if sq
	  (mpg123:squeeze-spaces-buffer))
      (buffer-substring 1 (point-max)))))

(defun id3:get-tag (file)
  "Try peeking id3tag from FILE"
  (if (string-equal (id3:get-single-tag file 0 3 t) "TAG")
      (progn
	(setq id3*title   (id3:get-single-tag file 3 30 t))
	(setq id3*artist  (id3:get-single-tag file 33 30 t))
	(setq id3*album   (id3:get-single-tag file 63 30 t))
	(setq id3*year    (id3:get-single-tag file 93 4 t))
	(setq id3*comment (id3:get-single-tag file 97 30 t))
	(setq id3*genre
	      (number-to-string
	       (let (tmp)
		 (setq tmp
		       (string-to-char
			(id3:get-single-tag file 127 1 nil)))
		 (cond (id3*xemacs-p
			(char-to-int tmp))
		       (t
			tmp))))
	      )
	)))

(defun id3-edit ()
  (interactive)
  (save-excursion
    (set-buffer (get-buffer-create mpg123*info-buffer))
    (buffer-disable-undo)
    (erase-buffer))
  (beginning-of-line)
  (skip-chars-forward " ")
  (if (or (not (looking-at "[0-9]"))
	  (not (mpg123:in-music-list-p)))
      nil ;;if not on music line, then exit
    (let (file)
      (setq mpg123*cur-music-number (mpg123:get-music-number))
      (setq file (mpg123:get-music-info mpg123*cur-music-number 'filename))
      (if (fboundp 'code-convert-string)
	  (setq file (code-convert-string
		      file mpg123-process-coding-system *internal*)))

      (id3:get-tag file)
      (setq id3*fname file)
      (id3-mode))))
