;;; liece-nick.el --- Various facility for nick operation.
;; Copyright (C) 1998-2000 Daiki Ueno

;; Author: Daiki Ueno <ueno@unixuser.org>
;; Created: 1998-09-28
;; Revised: 1998-11-25
;; Keywords: IRC, liece

;; This file is part of Liece.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.


;;; Commentary:
;; 

;;; Code:

(require 'liece-hilit)

(defalias 'liece-nick-set-operator 'liece-channel-set-operator)
(defalias 'liece-nick-set-voice 'liece-channel-set-voice)
(defalias 'liece-nick-equal 'string-equal-ignore-case)

(defun liece-nick-member (nick nicks)
  "Return non-nil if NICK is member of NICKS."
  (member-if
   (lambda (item)
     (and (stringp item) (liece-nick-equal nick item)))
   nicks))

(defvar liece-nick-insert-hook nil)
(defvar liece-nick-replace-hook nil)

(define-widget 'liece-nick-push-button 'push-button
  "A nick button."
  :action 'liece-nick-popup-menu)

(defcustom liece-nick-sort-nicks nil
  "If t, sort nick list in each time."
  :type 'boolean
  :group 'liece-vars)

(defcustom liece-nick-sort-predicate 'string-lessp
  "Function for sorting nick buffers."
  :type 'function
  :group 'liece-vars)

;;; Nick status functions
(defun liece-nick-get-joined-channels (nick)
  "Return channels as list NICK is joined."
  (get (intern (or nick liece-real-nickname) liece-obarray) 'join))

(defun liece-nick-get-user-at-host (nick)
  "Return user-at-host as string NICK is joined."
  (get (intern (or nick liece-real-nickname) liece-obarray) 'user-at-host))

(defun liece-nick-set-user-at-host (nick user-at-host)
  "Set user at host as string NICK is joined."
  (put (intern (or nick liece-real-nickname) liece-obarray)
       'user-at-host user-at-host))

(defun liece-nick-mark-as-part (part &optional nick)
  "Mark NICK is temporary apart."
  (put (intern (or nick liece-real-nickname) liece-obarray) 'part part))

(defun liece-nick-get-modes (&optional nick)
  "Return modes as string NICK is joined."
  (get (intern (or nick liece-real-nickname) liece-obarray) 'mode))

(defun liece-nick-add-mode (mode &optional nick)
  "Add MODE as char to NICK.
MODE is a string splitted into characters one by one."
  (let ((modes
	 (liece-string-to-list
	  (or (liece-nick-get-modes nick) ""))))
    (or (memq mode modes)
	(push mode modes))
    (put (intern (or nick liece-real-nickname) liece-obarray)
	 'mode (mapconcat #'char-to-string modes ""))))

(defun liece-nick-remove-mode (mode &optional nick)
  "Remove MODE from NICK.
MODE is a string splitted into characters one by one."
  (let ((modes
	 (liece-string-to-list
	  (or (liece-nick-get-modes nick) ""))))
    (delq mode modes)
    (put (intern (or nick liece-real-nickname) liece-obarray)
	 'mode (mapconcat #'char-to-string modes ""))))

(defun liece-nick-set-mode (nick mode toggle)
  "Add or remove channel MODE of NICK.
MODE is a string splitted into characters one by one.
If FLAG is non-nil, given modes are added to the user.
Otherwise they are removed from the user."
  (if toggle
      (liece-nick-add-mode mode nick)
     (liece-nick-remove-mode mode nick)))

(defmacro liece-nick-strip (nick)
  `(if (and ,nick (memq (aref ,nick 0) '(?@ ?+ ? )))
       (substring ,nick 1)
     ,nick))

(defmacro liece-nick-normalize (nick)
  `(if (and ,nick (memq (aref ,nick 0) '(?@ ?+ ? )))
       ,nick
     (concat " " ,nick)))

;;; @ display
;;;
(defun liece-nick-insert (nick)
  ;; Find sorted position
  (cond
   ((and (eq liece-nick-sort-nicks t)
	 (liece-functionp liece-nick-sort-predicate))
    (let (nicks found)
      (goto-char (point-min))
      (while (and (not (eobp)) (not found))
	(if (condition-case nil
		(funcall liece-nick-sort-predicate
			 (liece-nick-strip nick)
			 (widget-value (widget-at (1+ (point)))))
	      (void-function nil))
	    (setq found t)
	  (beginning-of-line 2)))))
    ((eq liece-nick-sort-nicks 'reverse)
     (goto-char (point-min)))
    (t (goto-char (point-max))))

  (insert (substring nick 0 1))
  (let ((st (point)) (nick (liece-nick-strip nick)))
    (insert nick)
    (when liece-highlight-mode
      (liece-widget-convert-button
       'liece-nick-push-button st (point) nick))
    (insert "\n")
    (run-hook-with-args 'liece-nick-insert-hook st (point))))

(defun liece-nick-replace (old new &optional limit regexp)
  (if regexp
      (setq old (concat "^\\(" old "\\)$"))
    (setq old (concat "^\\([ @+]\\)\\(" (regexp-quote old) "\\)$")))
  (let (case-fold-search beg end)
    (when (re-search-forward old limit t)
      (unless regexp
	(setq new (concat (match-string 1) new)))
      (if (and (eq liece-nick-sort-nicks t)
	       (liece-functionp liece-nick-sort-predicate))
	  (progn
	    (delete-region (match-beginning 0)
			   (progn (goto-char (match-end 0))
				  (forward-char) (point)))
	    (liece-nick-insert new))
	(condition-case nil
	    (widget-delete (widget-at (1+ (point))))
	  (void-function nil))
	(replace-match new t t)
	(setq end (point)
	      beg (progn (beginning-of-line) (1+ (point))))
	(when liece-highlight-mode
	  (liece-widget-convert-button
	   'liece-nick-push-button beg end (substring new 1)))
	(run-hook-with-args 'liece-nick-replace-hook beg end)))))

;;;###liece-autoload
(defun liece-command-toggle-nick-buffer-mode ()
  (interactive)
  (when (and (eq liece-command-buffer-mode 'channel)
	     (get-buffer liece-nick-buffer))
    (setq liece-nick-buffer-mode (not liece-nick-buffer-mode)))
  (liece-configure-windows))

(defmacro liece-nick-buffer-create (chnl)
  `(with-current-buffer
       (liece-get-buffer-create (format liece-nick-buffer-format ,chnl))
     (unless (eq major-mode 'liece-nick-mode)
       (liece-nick-mode))
     (set-alist 'liece-nick-buffer-alist ,chnl (current-buffer))
     (current-buffer)))

(defun liece-change-nick-of-1 (old new nicks)
  (if new
      (do ((nicks nicks (cdr nicks)))
	  ((or (null nicks)
	       (if (liece-nick-equal (caar nicks) old)
		   (setcar (car nicks) new))))
	nil)
    (delete-if
     `(lambda (nick) (liece-nick-equal (car nick) ,old))
     nicks)))
  
(defun liece-change-nick-of-2 (old new nicks)
  (if new
      (do ((nicks nicks (cdr nicks)))
	  ((or (not nicks)
	       (if (liece-nick-equal (car nicks) old)
		   (setcar nicks new))))
	nil)
    (delete-if
     `(lambda (nick) (liece-nick-equal nick ,old))
     nicks)))

(defun liece-change-nick-of (old new)
  (liece-change-nick-of-1 old new liece-nick-alist)
  (let ((chnls (liece-nick-get-joined-channels old)))
    (dolist (chnl chnls)
      (liece-change-nick-of-2 old new (liece-channel-get-nicks chnl))
      (liece-change-nick-of-2 old new (liece-channel-get-operators chnl))
      (liece-change-nick-of-2 old new (liece-channel-get-voices chnl)))))

(defmacro liece-nick-join-1 (user chnl)
  "Add CHNL to list of channels USER belongs to."
  `(let* ((flag (string-to-char user))
	  (user (liece-nick-strip ,user))
	  (u (intern user liece-obarray))
	  (c (intern ,chnl liece-obarray)))
     (or (string-assoc-ignore-case user liece-nick-alist)
	 (push (list user) liece-nick-alist))
     (cond
      ((char-equal flag ?@)
       (liece-channel-set-operator ,chnl user t))
      ((char-equal flag ?+)
       (liece-channel-set-voice ,chnl user t)))
     (or (string-list-member-ignore-case ,chnl (get u 'join))
	 (put u 'join (cons ,chnl (get u 'join))))
     (or (string-list-member-ignore-case user (get c 'nick))
	 (put c 'nick (cons user (get c 'nick))))))
		
(defmacro liece-nick-part-1 (user chnl)
  "Remove USER information from his CHNL."
  `(let ((u (intern ,user liece-obarray))
	 (c (intern ,chnl liece-obarray)))
     (liece-channel-set-operator ,chnl ,user nil)
     (liece-channel-set-voice ,chnl ,user nil)
     (put u 'join (string-list-remove-ignore-case ,chnl (get u 'join)))
     (put c 'nick (string-list-remove-ignore-case ,user (get c 'nick)))))

;;;###liece-autoload
(defun liece-nick-join (user chnl)
  (liece-nick-join-1 user chnl)
  (let ((nbuf (cdr (string-assoc-ignore-case chnl liece-nick-buffer-alist))))
    (with-current-buffer nbuf
      (let (buffer-read-only)
	(liece-nick-insert (liece-nick-normalize user))))))

;;;###liece-autoload
(defun liece-nick-part (user chnl)
  (let ((nbuf (cdr (string-assoc-ignore-case chnl liece-nick-buffer-alist))))
    (setq user (liece-nick-strip user))
    (with-current-buffer nbuf
      (let ((case-fold-search t) buffer-read-only)
	(goto-char (point-min))
	(when (re-search-forward (concat "^." (regexp-quote user) "$") nil t)
	  (delete-region (match-beginning 0)
			 (progn (goto-char (match-end 0))
				(forward-char) (point)))
	  (liece-nick-part-1 user chnl))))))

;;;###liece-autoload
(defun liece-nick-change (old new)
  (let* ((old (liece-nick-strip old)) (new (liece-nick-strip new))
	 (chnls (get (intern old liece-obarray) 'join)) chnl nbuf)
    (liece-change-nick-of old new)
    (if new
	(put (intern new liece-obarray) 'join chnls))
    (unintern old liece-obarray)
    (dolist (chnl chnls)
      (if (null new)
	  (liece-nick-part old chnl)
	(setq nbuf (cdr (string-assoc-ignore-case
			 chnl liece-nick-buffer-alist)))
	(with-current-buffer nbuf
	  (let (buffer-read-only)
	    (goto-char (point-min))
	    (liece-nick-replace old new)))))))

;;;###liece-autoload
(defun liece-nick-update (chnl users)
  (let ((c (intern chnl liece-obarray))
	(nbuf (cdr (string-assoc-ignore-case chnl liece-nick-buffer-alist))))
    (mapcar (lambda (prop) (put c prop nil)) '(nick oper voice))
    (with-current-buffer nbuf
      (let (buffer-read-only)
	(liece-kill-all-overlays)
	(erase-buffer)))
    (when (and liece-nick-sort-nicks
	       (liece-functionp liece-nick-sort-predicate))
      (setq users (sort users
			(lambda (s1 s2)
			  (funcall liece-nick-sort-predicate
				   (liece-nick-strip s1)
				   (liece-nick-strip s2))))))
    (let (liece-nick-sort-predicate)
      (dolist (user users)
	(liece-nick-join user chnl)))))

(defvar liece-nick-region-nicks nil)

;;;###liece-autoload
(defun liece-nick-update-region ()
  (setq liece-nick-region-nicks nil)
  (save-excursion
    (let (region nick)
      (if (not (region-active-p))
	  (setq region (cons (line-beginning-position)
			     (line-beginning-position 2)))
	(setq region (cons (region-beginning) (region-end)))
	(goto-char (car region))
	(setcar region (line-beginning-position))
	(goto-char (cdr region))
	(if (eobp)
	    (setcdr region (line-beginning-position))
	  (setcdr region (line-beginning-position 2))))
      (save-restriction
	(narrow-to-region (car region) (cdr region))
	(goto-char (point-min))
	(while (not (eobp))
	  (setq nick (widget-value (widget-at (1+ (point)))))
	  (push nick liece-nick-region-nicks)
	  (beginning-of-line 2))))))

(defun liece-nick-add-buttons (start end)
  (save-excursion
    (goto-char start)
    (while (re-search-forward
	    (eval-when-compile
	      (concat "^\\(" liece-time-prefix-regexp "\\)?"
		      "[][=<>(][][=<>(]?\\([^:]*:\\)?\\([^][=<>(]+\\)"))
	    end t)
      (let* ((nick-start (match-beginning 3))
	     (nick-end (match-end 3))
	     (nick (buffer-substring nick-start nick-end)))
	(when liece-highlight-mode
	  (liece-widget-convert-button
	   'liece-nick-push-button nick-start nick-end nick))))))

;;;###liece-autoload
(defun liece-nick-redisplay-buffer (chnl)
  (let ((buffer
	 (cdr (string-assoc-ignore-case
	       chnl liece-nick-buffer-alist)))
	(window (liece-get-buffer-window liece-nick-buffer)))
    (and buffer window
	 (with-current-buffer buffer
	   (set-window-buffer window buffer)
	   (unless (liece-frozen buffer)
	     (set-window-start window (point-min)))
	   (setq liece-nick-buffer buffer)))))

(provide 'liece-nick)

;;; liece-nick.el ends here
