;;; liece-commands.el --- Interactive commands in command buffer.
;; Copyright (C) 1998-2000 Daiki Ueno

;; Author: Daiki Ueno <ueno@unixuser.org>
;; Created: 1998-09-28
;; Revised: 1999-12-24
;; 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:

(eval-when-compile
   (require 'liece-misc))

(require 'liece-channel)
(require 'liece-nick)
(require 'liece-coding)
(require 'liece-intl)
(require 'liece-minibuf)

(autoload 'liece-dcc-chat-send "liece-dcc")

(autoload 'liece-command-ctcp-action "liece-ctcp" nil t)
(autoload 'liece-command-ctcp-version "liece-ctcp" nil t)
(autoload 'liece-command-ctcp-userinfo "liece-ctcp" nil t)
(autoload 'liece-command-ctcp-userinfo-from-minibuffer "liece-ctcp" nil t)
(autoload 'liece-command-ctcp-help "liece-ctcp" nil t)
(autoload 'liece-command-ctcp-clientinfo "liece-ctcp" nil t)
(autoload 'liece-command-ctcp-ping "liece-ctcp" nil t)
(autoload 'liece-command-ctcp-time "liece-ctcp" nil t)
(autoload 'liece-command-ctcp-x-face "liece-ctcp" nil t)
(autoload 'liece-command-ctcp-x-face-from-xbm-file "liece-ctcp" nil t)
(autoload 'liece-command-ctcp-comment "liece-ctcp" nil t)

(defun liece-command-poll-names ()
  "Handler for polling NAMES."
  (when (liece-server-opened)
    (setq liece-polling
	  (+ liece-polling
	     (length liece-channel-alist)))
    (dolist (chnl liece-channel-alist)
      (liece-send "NAMES %s" (car chnl)))))

(defun liece-command-poll-friends ()
  "Handler for polling ISON."
  (and liece-friends
       (liece-server-opened)
       (liece-send "ISON %s" (mapconcat 'identity liece-friends " "))))

(defun liece-command-find-timestamp ()
  "Find recent timestamp in dialogue buffer."
  (interactive)
  (save-excursion
    (let ((range "")
	  (regexp (concat "^\\(" liece-time-prefix-regexp "\\)?"
			  (regexp-quote liece-timestamp-prefix))))
      (unless (eq 'liece-dialogue-mode (derived-mode-class major-mode))
	(set-buffer liece-dialogue-buffer)
	(goto-char (point-max)))
      (if (re-search-backward regexp (point-min) t)
	  (setq range (concat (buffer-substring (match-end 0)
						(line-end-position))
			      "   ---   ")))
      (if (re-search-forward regexp (point-max) t)
	  (setq range (concat range (buffer-substring (match-end 0)
						      (line-end-position)))))
      (liece-message range))))

(defun liece-command-keepalive ()
  "Handler for polling server connection."
  (if (not (liece-server-opened))
      (liece)
    (liece-ping-if-idle)))

(defvar liece-last-timestamp-time nil "Last time timestamp was inserted.")
(defvar liece-last-timestamp-no-cons-p nil "Last timestamp was no-cons.")

(defun liece-command-timestamp-if-interval-expired (&optional no-cons)
  "If interval timer has expired, insert timestamp into dialogue buffer.
And save variables into `liece-variable-file' if there are variables to save.
Optional argument NO-CONS specifies timestamp format is cons cell."
  (interactive)
  (when (and (not (and no-cons
		       liece-last-timestamp-no-cons-p))
	     (numberp liece-timestamp-interval)
	     (> liece-timestamp-interval 0)
	     (or (null liece-last-timestamp-time)
		 (> (liece-time-difference liece-last-timestamp-time
					    (current-time))
		    liece-timestamp-interval)))
    (if liece-save-variables-are-dirty
	(liece-command-save-vars))
    (liece-command-timestamp)
    (setq liece-last-timestamp-no-cons-p no-cons)))

(defun liece-command-timestamp ()
  "Insert timestamp into dialogue buffer."
  (interactive)
  (let ((stamp (format liece-timestamp-format
		       (funcall liece-format-time-function (current-time))))
	(liece-timestamp-interval 0))
    (liece-insert liece-D-buffer (concat stamp "\n"))
    (setq liece-last-timestamp-time (current-time))))

(defun liece-command-point-back-to-command-buffer ()
  "Set point back to command buffer."
  (interactive)
  (let ((win (liece-get-buffer-window liece-command-buffer)))
    (if win (select-window win))))

(defun liece-command-send-message (message)
  "Send MESSAGE to current chat partner of current channel."
  (if (string-equal message "")
      (progn (liece-message (_ "No text to send")) nil)
    (let ((addr (if (eq liece-command-buffer-mode 'chat)
		    liece-current-chat-partner
		  liece-current-channel))
	  repr method name target)
      (cond
       ((eq liece-command-buffer-mode 'chat)
	(or liece-current-chat-partner
	    (error
	     (substitute-command-keys
	      "Type \\[liece-command-join] to start private conversation")))
	(setq repr (liece-channel-parse-representation
		    liece-current-chat-partner)
	      method (aref repr 0)
	      name (aref repr 1)
	      target (aref repr 2))
	(cond ((eq method 'dcc)
	       (liece-dcc-chat-send target message))
	      ((eq method 'irc)
	       (liece-send "PRIVMSG %s :%s"
			   liece-current-chat-partner message)))
	(liece-own-private-message message))
       (t
	(or liece-current-channel
	    (error
	     (substitute-command-keys
	      "Type \\[liece-command-join] to join a channel")))
	(liece-send
	 "PRIVMSG %s :%s"
	 (liece-channel-real liece-current-channel) message)
	(liece-own-channel-message message))))))

(defun liece-command-enter-message ()
  "Enter the current line as an entry in the IRC dialogue."
  (interactive)
  (beginning-of-line)
  (liece-command-send-message
   (buffer-substring (point)(progn (end-of-line) (point))))
  (liece-next-line 1))

(defun liece-dialogue-enter-message ()
  "Ask for a line as an entry in the IRC dialogue on the current channel."
  (interactive)
  (let (message)
    (while (not (string-equal (setq message (read-string "> ")) ""))
      (liece-command-send-message message))))

(defun liece-command-join-channel (join-channel-var key)
  "Join a JOIN-CHANNEL-VAR with KEY."
  (let ((nicks liece-nick-alist) nick)
    (while (and nicks
                (not (and
                      (car nick)
                      (liece-channel-equal join-channel-var (car nick)))))
      (setq nick (pop nicks)))
    (when nicks
      (setq join-channel-var
            (or (car (get (intern (car nick) liece-obarray) 'chnl))
                join-channel-var)))
    (if (liece-channel-member join-channel-var liece-current-channels)
	(progn
	  (setq liece-current-channel join-channel-var)
	  (liece-switch-to-channel liece-current-channel)
	  (liece-channel-change))
      (liece-send "JOIN %s %s" (liece-channel-real join-channel-var) key))))

(defun liece-command-join-partner (join-channel-var)
  "Join a JOIN-CHANNEL-VAR."
  (if (liece-channel-member join-channel-var liece-current-chat-partners)
      (progn
	(setq liece-current-chat-partner join-channel-var)
	(liece-switch-to-channel liece-current-chat-partner))
    (setq liece-current-chat-partner join-channel-var)
    (liece-channel-join liece-current-chat-partner))
  (liece-channel-change))

(defun liece-command-join (join-channel-var &optional key)
  "Join a JOIN-CHANNEL-VAR with KEY.
If user nickname is given join the same set of channels as the specified user.
If command-buffer is in chat-mode, start private conversation
with specified user."
  (interactive
   (let (join-channel-var key (completion-ignore-case t))
     (setq join-channel-var
	   (if (numberp current-prefix-arg)
	       current-prefix-arg
	     (liece-channel-virtual
	      (if (eq liece-command-buffer-mode 'chat)
		  (liece-minibuffer-completing-read
		   (_ "Start private conversation with: ")
		   liece-nick-alist nil nil nil nil liece-privmsg-partner)
		(liece-minibuffer-completing-read
		 (_ "Join channel: ")
		 (append liece-channel-alist liece-nick-alist)
		 nil nil nil nil liece-default-channel-candidate)))))
     (if (and current-prefix-arg
	      (not (numberp current-prefix-arg)))
	 (setq key
	       (if (eq current-prefix-arg '-)
		   (read-string
		    (format (_ "Key for channel %s: ") join-channel-var))
		 (liece-read-passwd
		  (format (_ "Key for channel %s: ") join-channel-var)))))
     (list join-channel-var key)))
  (let ((real-chnl (liece-channel-real join-channel-var)))
    (if (numberp join-channel-var)
	(liece-switch-to-channel-no join-channel-var)
      (setq liece-default-channel-candidate nil)
      (if (liece-channel-p real-chnl)
	  (liece-toggle-command-buffer-mode 'channel)
	(liece-toggle-command-buffer-mode 'chat))
      (if (eq liece-command-buffer-mode 'chat)
	  (liece-command-join-partner join-channel-var)
	(if (null key)
	    (setq key (get (intern join-channel-var liece-obarray) 'key)))
	(put (intern join-channel-var liece-obarray) 'key key)
	(if (null key)
	    (setq key ""))
	(liece-command-join-channel join-channel-var key))
      (force-mode-line-update))))

(defun liece-command-part (part-channel-var &optional part-msg)
  "Part a PART-CHANNEL-VAR with PART-MSG."
  (interactive
   (let (part-channel-var
	 (completion-ignore-case t)
	 (part-msg "bye..."))
     (setq part-channel-var
	   (liece-channel-virtual
	    (if (eq liece-command-buffer-mode 'chat)
		(liece-minibuffer-completing-read
		 (_ "End private conversation with: ")
		 (list-to-alist liece-current-chat-partners)
		 nil nil nil nil liece-current-chat-partner)
	      (liece-minibuffer-completing-read
	       (_ "Part channel: ")
	       (list-to-alist liece-current-channels)
	       nil nil nil nil liece-current-channel))))
     (when current-prefix-arg
       (setq part-msg (read-string (_ "Part Message: "))))
     (list part-channel-var part-msg)))
  (let ((real-chnl (liece-channel-real part-channel-var)))
    (if (liece-channel-p real-chnl)
	(progn
	  (if (liece-channel-member part-channel-var liece-current-channels)
	      (setq liece-current-channel part-channel-var))
	  (liece-send "PART %s :%s" real-chnl part-msg)
	  (setq liece-default-channel-candidate part-channel-var))
      (setq liece-current-chat-partners
	    (liece-channel-remove part-channel-var
				  liece-current-chat-partners)
	    liece-current-chat-partner
	    (car liece-current-chat-partners))
      (liece-set-channel-indicator)
      (liece-channel-part part-channel-var))))

(defun liece-command-kill (kill-nickname-var &optional timeout silent)
  "Ignore messages from KILL-NICKNAME-VAR.
Username can be given as case insensitive regular expression of form
\".*@.*\.sub.domain\".
If already ignoring him/her, toggle.
If `liece-variables-file' is defined and the file is writable,
settings are updated automatically for future sessions.
Optional argument TIMEOUT says expiration.
If SILENT is non-nil, don't notify current status."
  (interactive
   (let (kill-nickname-var timeout (completion-ignore-case t))
     (setq kill-nickname-var (completing-read
			      (_ "Ignore nickname or regexp: ")
			      (append liece-nick-alist
				      liece-kill-nickname)))
     (or (string-equal "" kill-nickname-var)
	 (string-assoc-ignore-case kill-nickname-var liece-kill-nickname)
	 (setq timeout (string-to-int (read-from-minibuffer
				       (_ "Timeout [RET for none]: ")))))
     (list kill-nickname-var timeout)))
  ;; empty, just list them
  (if (string-equal "" kill-nickname-var)
      (with-current-buffer liece-dialogue-buffer
	(let ((ignores liece-kill-nickname) (time (current-time))
	      buffer-read-only expire expiretime)
	  (goto-char (point-max))
	  (liece-insert-info liece-D-buffer (_ "Currently ignoring:"))
	  (dolist (ignore ignores)
	    (setq expiretime (if (cdr ignore)
				 (/ (liece-time-difference time (cdr ignore))
				    60))
		  expire (cond ((not expiretime) "")
			       ((>= expiretime 0)
				(format (_ " (%d min)") expiretime))
			       ((< expiretime 0)
				(_ " expired"))))
	    (liece-insert liece-D-buffer
			   (concat " " (car ignore) expire "\n")))))
    ;; else not empty, check if exists
    (let ((ignore
	   (string-assoc-ignore-case
	    kill-nickname-var liece-kill-nickname)))
      (if ignore
	  (when (setq ignore (string-assoc-ignore-case
			      (car ignore) liece-kill-nickname))
	    (setq liece-kill-nickname
		  (delq ignore liece-kill-nickname))
	    (liece-insert-info liece-D-buffer
			       (format (_ "No longer ignoring: %s.\n")
				       (car ignore))))
	;; did not find, add to ignored ones
	(let ((expire-time (if (> timeout 0)
			       (liece-time-elapsed (current-time)
						   (* timeout 60)))))
	  (and silent (> timeout 0)
	       (setcar (cdr (cdr expire-time)) -1))
	  (setq liece-kill-nickname
		(cons (cons kill-nickname-var expire-time)
		      liece-kill-nickname))
	  (unless silent
	    (liece-insert-info liece-D-buffer
				(format (_ "Ignoring %s") kill-nickname-var))
	    (liece-insert-info liece-D-buffer
				(if (> timeout 0)
				    (format " for %d minutes.\n" timeout)
				  (format ".\n")))))))
    (setq liece-save-variables-are-dirty t)))

(defun liece-command-kick (nick &optional msg)
  "Kick this NICK out with MSG."
  (interactive
   (let ((completion-ignore-case t)
	 (nicks (liece-channel-get-nicks)) nick msg)
     (setq nick (completing-read
		 (_ "Kick out nickname: ")
		 (list-to-alist nicks)))
     (if current-prefix-arg
	 (setq msg (concat " :" (read-string (_ "Kick Message: ")))))
     (list nick msg)))
  (liece-send "KICK %s %s%s"
	       (liece-channel-real liece-current-channel)
	       nick (or msg "")))

(defun liece-command-ban (ban)
  "BAN this user out."
  (interactive
   (let* ((completion-ignore-case t)
	  (nicks (liece-channel-get-nicks))
	  (uahs (mapcar
		 (function
		  (lambda (nick)
		    (list
		     (concat nick "!" (liece-nick-get-user-at-host nick)))))
		 nicks))
	  ban nick msg)
     (setq ban (liece-minibuffer-completing-read
		(_ "Ban pattern: ") uahs nil nil nil nil
		(concat nick "!" (liece-nick-get-user-at-host nick))))
     (list ban)))
  (liece-send "MODE %s :+b %s"
	       (liece-channel-real liece-current-channel) ban))
   
(defun liece-command-ban-kick (ban nick &optional msg)
  "BAN kick this NICK out with MSG."
  (interactive
   (let* ((completion-ignore-case t)
	  (nicks (liece-channel-get-nicks))
	  (uahs (mapcar
		 (function
		  (lambda (nick)
		    (list
		     (concat nick "!" (liece-nick-get-user-at-host nick)))))
		 nicks))
	  ban nick msg)
     (setq nick (completing-read (_ "Kick out nickname: ")
				 (list-to-alist nicks))
	   ban (liece-minibuffer-completing-read
		(_ "Ban pattern: ") uahs nil nil nil nil
		(concat nick "!" (liece-nick-get-user-at-host nick))))
     (if current-prefix-arg
	 (setq msg (concat " :" (read-string (_ "Kick Message: "))))
       (setq msg ""))
     (list ban nick msg)))
  (liece-send "MODE %s :+b %s"
	       (liece-channel-real liece-current-channel) ban)
  (liece-send "KICK %s %s%s"
	       (liece-channel-real liece-current-channel)
	       nick (or msg "")))

(defun liece-command-list (&optional channel)
  "List the given CHANNEL and its topics.
If you enter only Control-U as argument, list the current channel.
With - as argument, list all channels."
  (interactive
   (if (or current-prefix-arg (null liece-current-channel))
       (if (eq current-prefix-arg '-)
	   (list current-prefix-arg))
     (let ((completion-ignore-case t) channel)
       (setq channel (liece-minibuffer-completing-read
		      (_ "LIST channel: ")
		      liece-channel-alist nil nil nil nil liece-current-channel))
       (unless (string-equal "" channel)
	 (list channel)))))
  
  (cond ((not channel)
	 (if liece-current-channel
	     (liece-send "LIST %s"
			  (liece-channel-real liece-current-channel))))
	((and (eq channel '-)
	      (y-or-n-p (_ "Do you really query LIST without argument?")))
	 (liece-send "LIST"))
	((not (string-equal channel ""))
	 (liece-send "LIST %s" (liece-channel-real channel))
	 )))

(defun liece-command-modec (chnl change)
  "Send a MODE command to this CHNL.
Argument CHANGE ."
  (interactive
   (let ((completion-ignore-case t)
	 (chnl liece-current-channel)
	 liece-minibuffer-complete-function prompt)
     (if current-prefix-arg
	 (setq chnl
	       (liece-minibuffer-completing-read
		(_ "Channel/User: ")
		(append liece-channel-alist liece-nick-alist)
		nil nil nil nil liece-current-channel)))
     (cond
      ((liece-channel-p (liece-channel-real chnl))
       (setq prompt (format
		     (_ "Mode for channel %s [%s]: ")
		     chnl (or (liece-channel-get-modes chnl) ""))
	     liece-minibuffer-complete-function
	     (function liece-minibuffer-complete-channel-modes)))
      (t
       (setq prompt (format
		     (_ "Mode for user %s [%s]: ")
		     chnl (or (liece-nick-get-modes chnl) ""))
	     liece-minibuffer-complete-function
	     (function liece-minibuffer-complete-user-modes))))
     (list chnl (read-from-minibuffer prompt nil liece-minibuffer-map))))
  (liece-send "MODE %s %s" (liece-channel-real chnl) change))

(defun liece-command-qualify-nicks (mode nicks val)
  (liece-send
   "MODE %s %c%s %s"
   (liece-channel-real liece-current-channel)
   (if val ?+ ?-) (make-string (length nicks) mode)
   (string-join nicks " ")))

(defun liece-command-set-operators (nicks &optional arg)
  (interactive
   (let ((opers (liece-channel-get-operators))
	 (completion-ignore-case t)
	 nicks)
     (if current-prefix-arg
	 (setq nicks (liece-minibuffer-completing-read-multiple
		      (_ "Divest operational privilege from: ")
		      (list-to-alist opers)))
       (setq nicks (liece-channel-get-nicks)
	     nicks (filter-elements nick nicks
		     (not (liece-nick-member nick opers)))
	     nicks (liece-minibuffer-completing-read-multiple
		    (_ "Assign operational privilege to: ")
		    (list-to-alist nicks))))
     (list nicks current-prefix-arg)))
  (let (run)
    (unwind-protect
	(dolist (nick nicks)
	  (push nick run)
	  (when (= (length run) liece-compress-mode-length)
	    (liece-command-qualify-nicks ?o run (not arg))
	    (setq run nil)))
      (when run
	(liece-command-qualify-nicks ?o run (not arg))))))

(defun liece-command-set-voices (nicks &optional arg)
  (interactive
   (let ((voices (liece-channel-get-voices))
	 (completion-ignore-case t)
	 nicks)
     (if current-prefix-arg
	 (setq nicks (liece-minibuffer-completing-read-multiple
		      (_ "Forbid to speak: ") (list-to-alist voices)))
       (setq voices (append voices (liece-channel-get-operators))
	     nicks (liece-channel-get-nicks)
	     nicks (filter-elements nick nicks
		     (not (liece-nick-member nick voices)))
	     nicks (liece-minibuffer-completing-read-multiple
		    (_ "Allow to speak: ") (list-to-alist nicks))))
     (list nicks current-prefix-arg)))
  (let (run)
    (unwind-protect
	(dolist (nick nicks)
	  (push nick run)
	  (when (= (length run) liece-compress-mode-length)
	    (liece-command-qualify-nicks ?v run (not arg))
	    (setq run nil)))
      (when run
	(liece-command-qualify-nicks ?v run (not arg))))))

(defun liece-command-message (address message)
  "Send ADDRESS a private MESSAGE."
  (interactive
   (let ((completion-ignore-case t) address)
     (setq address
	   (liece-channel-virtual
	    (liece-minibuffer-completing-read
	     (_ "Private message to: ")
	     (append liece-nick-alist liece-channel-alist)
	     nil nil nil nil liece-privmsg-partner)))
     (list address
	   (read-string
	    (format
	     (_ "Private message to %s: ")
	     address)))))
  (if (funcall liece-message-empty-predicate message)
      (progn (liece-message (_ "No text to send")) nil)
    (let ((chnl (liece-channel-real address)))
      (liece-send "PRIVMSG %s :%s" chnl message)
      (if (liece-channel-p chnl)
	  (liece-own-channel-message message
				     (liece-channel-virtual address))
	(liece-own-private-message message address)))))

(defun liece-command-mta-private (partner)
  "Send a private message (current line) to PARTNER."
  (interactive
   (let ((completion-ignore-case t))
     (setq liece-privmsg-partner
	   (liece-channel-virtual
	    (liece-minibuffer-completing-read
	     (_ "To whom: ")
	     (append liece-nick-alist liece-channel-alist)
	     nil nil nil nil liece-privmsg-partner)))
     (list liece-privmsg-partner)))
  (let ((message (buffer-substring (progn (beginning-of-line) (point))
				   (progn (end-of-line) (point)))))
    (if (> (length message) 0)
	(progn
	  (liece-command-message liece-privmsg-partner message)
	  (liece-next-line 1))
      (liece-message (_ "No text to send")))))

(defun liece-command-names (&optional expr)
  "List the nicknames of the current IRC users on given EXPR.
With an Control-U as argument, only the current channel is listed.
With - as argument, list all channels."
  (interactive
   (if (or current-prefix-arg (null liece-current-channel))
       (if (eq current-prefix-arg '-)
	   (list current-prefix-arg))
     (let ((completion-ignore-case t) expr)
       (setq expr (liece-minibuffer-completing-read
		      (_ "Names on channel: ")
		      liece-channel-alist nil nil nil nil liece-current-channel))
       (unless (string-equal "" expr)
	 (list expr)))))
  (when (or (and (eq expr '-)
		 (y-or-n-p
		  (_ "Do you really query NAMES without argument?")))
	    (not (or expr
		     (if liece-current-channel
			 (setq expr (liece-channel-real
				     liece-current-channel))))))
    (setq expr ""))
  (when expr
    (liece-send "NAMES %s" expr)))

(defun liece-command-nickname (nick)
  "Set your nickname to NICK."
  (interactive "sEnter your nickname: ")
  (let ((nickname (truncate-string-to-width nick liece-nick-max-length)))
    (if (zerop (length nickname))
	(liece-message (_ "illegal nickname \"%s\"; not changed") nickname)
      (liece-send "NICK %s" nick))))
      
(defun liece-command-who (&optional expr)
  "Lists tue users that match the given expression EXPR.
If you enter only Control-U as argument, list the current channel.
With - as argument, list all users."
  (interactive
   (if (or current-prefix-arg (null liece-current-channel))
       (if (eq current-prefix-arg '-)
	   (list current-prefix-arg))
     (let ((completion-ignore-case t) expr)
       (setq expr (completing-read
		   (_ "WHO expression: ")
		   (append liece-channel-alist liece-nick-alist)))
       (unless (string-equal "" expr)
	 (list expr)))))
  (when (or (and (eq expr '-)
		 (y-or-n-p
		  (_ "Do you really query WHO without argument?")))
	    (not (or expr
		     (if liece-current-channel
			 (setq expr (liece-channel-real
				     liece-current-channel))))))
    (setq expr ""))
  (when expr
    (liece-send "WHO %s" expr)
    (setq liece-who-expression expr)))

(defun liece-command-finger (finger-nick-var &optional server)
  "Get information about a specific user FINGER-NICK-VAR.
If called with optional argument SERVER or any prefix argument,
query information to the foreign server."
  (interactive
   (let (finger-nick-var (completion-ignore-case t))
     (setq finger-nick-var
	   (completing-read (_ "Finger whom: ") liece-nick-alist))
     (list finger-nick-var (and current-prefix-arg finger-nick-var))))
  (if server
      (liece-send "WHOIS %s %s" server finger-nick-var)
    (liece-send "WHOIS %s" finger-nick-var)))

(defun liece-command-topic (topic)
  "Change TOPIC of the current channel."
  (interactive
   (list (read-from-minibuffer
	  "Topic: " (cons (or (liece-channel-get-topic) "") 0))))
  (liece-send "TOPIC %s :%s"
	      (liece-channel-real liece-current-channel) topic))

(defun liece-command-invite (&optional invite-nick-var invite-channel-var)
  "Invite INVITE-NICK-VAR to INVITE-CHANNEL-VAR."
  (interactive
   (let ((completion-ignore-case t) invite-channel-var invite-nick-var)
     (if current-prefix-arg
	 (setq invite-channel-var
	       (liece-channel-virtual
		(completing-read
		 (_ "Invite channel: ")
		 (list-to-alist liece-current-channels)))))
     (setq invite-nick-var
	   (completing-read
	    (_ "Invite whom: ")
	    liece-nick-alist))
     (list invite-nick-var invite-channel-var)))
  (or invite-channel-var
      (setq invite-channel-var liece-current-channel))
  (liece-send "INVITE %s %s"
	       invite-nick-var (liece-channel-real invite-channel-var)))

(defun liece-command-away (awaymsg)
  "Mark/unmark yourself as being away.
Leave message AWAYMSG."
  (interactive "sAway message: ")
  (liece-send "AWAY :%s" awaymsg)
  (setq liece-away-message awaymsg))

(defun liece-command-scroll-down (lines)
  "Scroll LINES down dialogue buffer from command buffer."
  (interactive "P")
  (let ((other-window-scroll-buffer
	 (if liece-channel-buffer-mode
	     liece-channel-buffer
	   liece-dialogue-buffer)))
    (when (liece-get-buffer-window other-window-scroll-buffer)
      (condition-case nil
	  (scroll-other-window-down lines)
	(beginning-of-buffer
	 (message "Beginning of buffer"))))))

(defun liece-command-scroll-up (lines)
  "Scroll LINES up dialogue buffer from command buffer."
  (interactive "P")
  (let* ((other-window-scroll-buffer
	  (if liece-channel-buffer-mode
	      liece-channel-buffer
	    liece-dialogue-buffer)))
    (when (liece-get-buffer-window other-window-scroll-buffer)
      (condition-case nil
	  (scroll-other-window lines)
	(end-of-buffer
	 (message "End of buffer"))))))

(defun liece-command-nick-scroll-down (lines)
  "Scroll LINES down nick buffer from command buffer."
  (interactive "P")
  (let ((other-window-scroll-buffer liece-nick-buffer))
    (when (liece-get-buffer-window other-window-scroll-buffer)
      (condition-case nil
	  (scroll-other-window-down lines)
	(beginning-of-buffer
	 (message "Beginning of buffer"))))))

(defun liece-command-nick-scroll-up (lines)
  "Scroll LINES up nick buffer from command buffer."
  (interactive "P")
  (let* ((other-window-scroll-buffer liece-nick-buffer))
    (when (liece-get-buffer-window other-window-scroll-buffer)
      (condition-case nil
	  (scroll-other-window lines)
	(end-of-buffer
	 (message "End of buffer"))))))

(defun liece-command-freeze (&optional arg)
  "Prevent automatic scrolling of the dialogue window.
If prefix argument ARG is non-nil, toggle frozen status."
  (interactive "P")
  (liece-freeze (if liece-channel-buffer-mode
		    liece-channel-buffer
		  liece-dialogue-buffer)
		(if arg (prefix-numeric-value arg))))

(defun liece-command-own-freeze (&optional arg)
  "Prevent automatic scrolling of the dialogue window.
The difference from `liece-command-freeze' is that your messages are hidden.
If prefix argument ARG is non-nil, toggle frozen status."
  (interactive "P")
  (liece-own-freeze (if liece-channel-buffer-mode
			liece-channel-buffer
		      liece-dialogue-buffer)
		    (if arg (prefix-numeric-value arg))))

(defun liece-command-beep (&optional arg)
  "Toggle the automatic beep notice when the channel message is received."
  (interactive "P")
  (liece-set-beep (if liece-channel-buffer-mode
		      liece-channel-buffer
		    liece-dialogue-buffer)
		  (if arg (prefix-numeric-value arg))))

(defun liece-command-quit (&optional arg)
  "Quit IRC.
If prefix argument ARG is non-nil, leave signoff message."
  (interactive "P")
  (when (and (liece-server-opened)
	     (y-or-n-p (_ "Quit IRC? ")))
    (message "")
    (let ((quit-string
	   (if arg (read-string (_ "Signoff message: "))
	     (or liece-signoff-message
		 (product-name (product-find 'liece-version))))))
      (liece-close-server quit-string))))

(defun liece-command-generic (message)
  "Enter a generic IRC MESSAGE, which is sent to the server.
A ? lists the useful generic messages."
  (interactive "sIRC command (? to help): ")
  (if (string-equal message "?")
      (with-output-to-temp-buffer "*IRC Help*"
	(princ "The following generic IRC messages may be of interest to you:
TOPIC <new topic>		set the topic of your channel
INVITE <nickname>		invite another user to join your channel
LINKS				lists the currently reachable IRC servers
SUMMON <user@host>		invites an user not currently in IRC
USERS <host>			lists the users on a host
AWAY <reason>			marks you as not really actively using IRC
				(an empty reason clears it)
WALL <message>			send to everyone on IRC
NAMES <channel>			lists users per channel
"))
    (liece-send "%s" message)))

(defun liece-command-irc-compatible ()
  "If entered at column 0, allow you to enter a generic IRC message."
  (interactive)
  (if (zerop (current-column))
      (call-interactively (function liece-command-generic))
    (self-insert-command 1)))

(defun liece-command-yank-send (&optional arg)
  "Send message from yank buffer.
Prefix argument ARG is regarded as distance from yank pointer."
  (interactive)
  (when (y-or-n-p (_ "Really SEND from Yank Buffer?"))
    (save-restriction
      (narrow-to-region (point) (point))
      (insert (car kill-ring-yank-pointer))
      (goto-char (point-min))
      (while (eobp)
	(liece-command-enter-message)
	(set-buffer liece-command-buffer)))))

(defun liece-command-complete ()
  "Complete word before point from userlist."
  (interactive)
  (let ((completion-ignore-case t)
	(alist (if liece-current-channel
		   (list-to-alist (liece-channel-get-nicks))
		 liece-nick-alist))
	candidate completion all)
    (setq candidate (current-word)
	  completion (try-completion candidate alist)
	  all (all-completions candidate alist))
    (liece-minibuffer-finalize-completion completion candidate all)))

(defun liece-command-load-vars ()
  "Load configuration from liece-variables-file."
  (interactive)
  (let ((nick liece-real-nickname))
    (unwind-protect
	(liece-read-variables-files)
      (setq liece-real-nickname nick)
      (liece-command-reconfigure-windows))))

(defun liece-command-save-vars ()
  "Save current settings to `liece-variables-file'."
  (interactive)
  (let* ((output-buffer
	  (find-file-noselect
	   (expand-file-name liece-variables-file)))
	 output-marker p)
    (save-excursion
      (set-buffer output-buffer)
      (goto-char (point-min))
      (cond ((re-search-forward "^;; Saved Settings *\n" nil 'move)
	     (setq p (match-beginning 0))
	     (goto-char p)
	     (or (re-search-forward
		  "^;; End of Saved Settings *\\(\n\\|\\'\\)" nil t)
		 (error
		  (concat "can't find END of saved state in "
			  liece-variables-file)))
	     (delete-region p (match-end 0)))
	    (t
	     (goto-char (point-max))
	     (insert "\n")))
      (setq output-marker (point-marker))
      (let ((print-readably t)
	    (print-escape-newlines t)
	    (standard-output output-marker))
	(princ ";; Saved Settings\n")
	(dolist (var liece-saved-forms)
	  (if (symbolp var)
	      (prin1 (list 'setq var
			   (let ((val (symbol-value var)))
			     (if (memq val '(t nil))
				 val
			       (list 'quote val)))))
	    (setq var (eval var))
	    (cond ((eq (car-safe var) 'progn)
		   (while (setq var (cdr var))
		     (prin1 (car var))
		     (princ "\n")
		     (if (cdr var) (princ "  "))))
		  (var
		   (prin1 "xx")(prin1 var))))
	  (if var (princ "\n")))
	(princ "\n")
	(princ ";; End of Saved Settings\n")))
    (set-marker output-marker nil)
    (save-excursion
      (set-buffer output-buffer)
      (save-buffer)))
  (setq liece-save-variables-are-dirty nil))

(defun liece-command-reconfigure-windows ()
  "Rearrange window splitting."
  (interactive)
  (let ((command-window (liece-get-buffer-window liece-command-buffer))
	(dialogue-window (liece-get-buffer-window liece-dialogue-buffer))
	(obuffer (current-buffer)))
    (if (and command-window dialogue-window)
	(let ((ch (window-height command-window))
	      (dh (window-height dialogue-window)))
	  (delete-window command-window)
	  (pop-to-buffer liece-dialogue-buffer)
	  (enlarge-window (+ ch dh (- dh))))
      (pop-to-buffer liece-dialogue-buffer))
    (liece-configure-windows)
    (if liece-one-buffer-mode
	(pop-to-buffer liece-dialogue-buffer)
      (pop-to-buffer obuffer))))

(defun liece-command-end-of-buffer ()
  "Get end of the dialogue buffer."
  (interactive)
  (let (buffer window)
    (setq buffer (if liece-channel-buffer-mode
		     liece-channel-buffer
		   liece-dialogue-buffer))
    (or (setq window (liece-get-buffer-window buffer))
	(setq window (liece-get-buffer-window liece-dialogue-buffer)
	      buffer liece-dialogue-buffer))
    (when window
      (save-selected-window
	(select-window window)
	(goto-char (point-max))))))

(defun liece-command-private-conversation (arg)
  "Toggle between private conversation mode and channel mode.
User can then join and part to a private conversation as he would
join or part to a channel.

If there are no private conversations or argument is given user is
prompted the partner/channel (return as partner/channel means toggle
mode, the current channel and current chat partner are not altered)
Argument ARG is prefix argument of toggle status."
  (interactive
   (let ((completion-ignore-case t))
     (list
      (if current-prefix-arg
	  ;; prefixed, ask where to continue
	  (if (eq liece-command-buffer-mode 'chat)
	      (liece-minibuffer-completing-read
	       (_ "Return to channel: ")
	       (append liece-channel-alist liece-nick-alist)
	       nil nil nil nil liece-current-channel)
	    (completing-read
	     (_ "Start private conversation with: ")
	     liece-nick-alist nil nil))
	;; no prefix, see if going to chat
	(if (eq liece-command-buffer-mode 'channel)
	    ;; and if we have chat partner, select that
	    (if liece-current-chat-partner
		liece-current-chat-partner
	      (completing-read
	       (_ "Start private conversation with: ")
	       liece-nick-alist )))))))
  
  (liece-toggle-command-buffer-mode)
  (if (and arg (not (string-equal arg "")))
      (liece-command-join arg))
  (liece-set-channel-indicator)
  ;; refresh mode line
  (force-mode-line-update))

(defun liece-command-next-channel ()
  "Select next channel or chat partner, and *DONT* rotate list."
  (interactive)
  (let ((rest (copy-sequence
	       (if (eq liece-command-buffer-mode 'chat)
		   liece-current-chat-partners
		 liece-current-channels)))
	(chnl (if (eq liece-command-buffer-mode 'chat)
		  liece-current-chat-partner
		liece-current-channel)))
    (liece-switch-to-channel
     (or (cadr (liece-channel-member chnl (delq nil rest)))
	 (car (delq nil rest))
	 chnl))))

(defun liece-command-previous-channel ()
  "Select previous channel or chat partner, and *DONT* rotate list."
  (interactive)
  (let ((rest
	 (reverse
	  (if (eq liece-command-buffer-mode 'chat)
	      liece-current-chat-partners
	    liece-current-channels)))
	(chnl
	 (if (eq liece-command-buffer-mode 'chat)
	     liece-current-chat-partner
	   liece-current-channel)))
    (liece-switch-to-channel
     (or (cadr (liece-channel-member chnl (delq nil rest)))
	 (car (delq nil rest))
	 chnl))))
      
(defun liece-command-unread-channel ()
  "Select unread channel or chat partner."
  (interactive)
  (let ((chnl (car liece-channel-unread-list)))
    (if chnl
	(liece-switch-to-channel chnl)
      (liece-message (_ "No unread channel or chat partner.")))))

(defun liece-command-push ()
  "Select next channel or chat partner, and rotate list."
  (interactive)
  (let* ((rest
	  (if (eq liece-command-buffer-mode 'chat)
	      liece-current-chat-partners
	    liece-current-channels))
	 (temp (car (last rest)))
	 (len (length rest)))
    (unwind-protect
	(while (< 1 len)
	  (setcar (nthcdr (1- len) rest) (nth (- len 2) rest))
	  (decf len))
      (when rest
	(setcar rest temp)))
    (liece-channel-change)))

(defun liece-command-pop ()
  "Select previous channel or chat partner, and rotate list."
  (interactive)
  (let* ((rest
	  (if (eq liece-command-buffer-mode 'chat)
	      liece-current-chat-partners
	    liece-current-channels))
	 (temp (car rest))
	 (len (length rest)))
    (unwind-protect
	(dotimes (i len)
	  (setcar (nthcdr i rest) (nth (1+ i) rest)))
      (when rest
	(setcar (last rest) temp)))
    (liece-channel-change)))

(defvar liece-redisplay-buffer-functions
  '(liece-channel-redisplay-buffer
    liece-nick-redisplay-buffer
    liece-channel-list-redisplay-buffer))

(defun liece-switch-to-channel (chnl)
  "Switch the current channel to CHNL."
  (if (liece-channel-p (liece-channel-real chnl))
      (progn
	(liece-toggle-command-buffer-mode 'channel)
	(setq liece-current-channel chnl)
	(liece-set-channel-indicator))
    (liece-toggle-command-buffer-mode 'chat)
    (setq liece-current-chat-partner chnl)
    (liece-set-channel-indicator))
  (save-excursion
    (run-hook-with-args 'liece-redisplay-buffer-functions chnl)))

(defun liece-switch-to-channel-no (num)
  "Switch the current channel to NUM."
  (let* ((mode liece-command-buffer-mode)
	 (chnls (if (eq mode 'chat)
		    liece-current-chat-partners
		  liece-current-channels)))
    (if (and (integerp num)
	     (stringp (nth num chnls)))
	(let ((chnl (nth num chnls)))
	  (if (eq mode 'chat)
	      (progn
		(liece-toggle-command-buffer-mode 'chat)
		(setq liece-current-chat-partner chnl)
		(liece-set-channel-indicator))
	    (liece-toggle-command-buffer-mode 'channel)
	    (setq liece-current-channel chnl)
	    (liece-set-channel-indicator))
	  (save-excursion
	    (run-hook-with-args 'liece-redisplay-buffer-functions chnl)))
      (message "Invalid channel!"))))

(defun liece-command-ping ()
  "Send PING to server."
  (interactive)
  (if (stringp liece-server-name)
      (liece-send "PING %s" liece-server-name)))

(defun liece-command-ison (nicks)
  "IsON users NICKS."
  (interactive
   (let (nicks (completion-ignore-case t))
     (setq nicks (liece-minibuffer-completing-read-multiple
		  "IsON" liece-nick-alist))
     (list nicks)))
  (liece-send "ISON :%s" (mapconcat #'identity nicks " ")))

(defun liece-command-activate-friends (nicks)
  "Register NICKS to the frinends list."
  (interactive
   (let (nicks (completion-ignore-case t))
     (setq nicks
	   (liece-minibuffer-completing-read-multiple
	    (_ "Friend")
	    (filter-elements nick liece-nick-alist
	      (not (string-list-member-ignore-case
		    (car nick) liece-friends)))))
     (list nicks)))
  (setq liece-friends (append nicks liece-friends)))

(defun liece-command-deactivate-friends ()
  "Clear current friends list."
  (interactive)
  (setq liece-friends nil))

(defun liece-command-display-friends ()
  "Display status of the friends."
  (interactive)
  (with-output-to-temp-buffer " *IRC Friends*"
    (set-buffer standard-output)
    (insert "Friends status: \n\n")
    (dolist (friend liece-friends)
      (if (string-list-member-ignore-case friend liece-friends-last)
	  (insert "+ " friend "\n")
	(insert "- " friend "\n")))))

(defun liece-command-show-last-kill ()
  "Dig last kill from KILL and show it."
  (interactive)
  (liece-insert-info
   (append liece-D-buffer liece-O-buffer)
   (save-excursion
     (set-buffer liece-KILLS-buffer)
     (goto-char (point-max))
     (forward-line -1)
     (concat (buffer-substring (point) (point-max)) "\n"))))

(defun liece-command-toggle-private ()
  "Toggle private mode / channel mode."
  (interactive)
  (case (prog1 liece-command-buffer-mode
	  (liece-toggle-command-buffer-mode))
    (chat
     (if liece-current-channel
	 (liece-switch-to-channel liece-current-channel))
     (setq liece-command-buffer-mode-indicator "Channels"))
    (channel
     (if liece-current-chat-partner
	 (liece-switch-to-channel liece-current-chat-partner))
     (setq liece-command-buffer-mode-indicator "Partners")))
  (liece-channel-change))

(defun liece-command-tag-region (start end)
  "Move current region between START and END to `kill-ring'."
  (interactive
   (if (region-active-p)
       (list (region-beginning)(region-end))
     (list (line-beginning-position)(line-end-position))))
  (static-if (fboundp 'extent-property)
      (kill-ring-save start end)
    (let ((start (set-marker (make-marker) start))
	  (end (set-marker (make-marker) end))
	  (inhibit-read-only t)
	  buffer-read-only
	  buffer-undo-list)
      (liece-remove-properties-region start end)
      (kill-ring-save start end)
      (push nil buffer-undo-list)
      (undo))))

(provide 'liece-commands)

;;; liece-commands.el ends here
