;;;
;;; $Id: irchat.el,v 1.15 1993/07/15 17:31:37 tmo Exp $
;;;
;;; see file irchat-copyright.el for change log and copyright info

(require 'defsubst)
(require 'irchat-commands)
(require 'irchat-handle)
(require 'irchat-filter)
(require 'irchat-vars)

(provide 'irchat)

(defconst irchat-client-version-major "2"
  "Major version number. Major functional changes change this number.")

(defconst irchat-client-version-minor "3"
  "Minor version number. Protocol changes and bug fixes change this number.")

(defconst irchat-client-version-release "$Date: 1993/07/18 17:31:37 $"
  "version release date")

(defconst irchat-client-name "Irchat"
  "*Name of this program.")

(defconst irchat-client-error-msg "Unrecognized command: '%s'"
  "*Error message given to anyone asking wrong CLIENT data.")

(defun matching-substring (string arg)
  (substring string (match-beginning arg) (match-end arg)))

(defconst irchat-version
  (format "%s %s.%s (%s)" 
	  irchat-client-name 
	  irchat-client-version-major
	  irchat-client-version-minor
	  (progn 
	    (string-match ".*: \\([^ ]*\\).*" irchat-client-version-release)
	    (matching-substring irchat-client-version-release 1)))
  "*The version of irchat you are using.")

(defvar irchat-query-client-lastcommand nil
  "*Place to keep last entered command")

(defvar irchat-query-client-nick nil
  "*Place to keep last queried nick")

(defvar irchat-query-client-alist
  (list (list "VERSION")
	(list "CLIENTINFO")
	(list "HELP")
	(list "USERINFO")))

(defconst irchat-query-client-insert-to-generic
  "")

(defconst irchat-query-client-version
  (concat "VERSION" irchat-query-client-insert-to-generic))

(defconst irchat-query-client-userinfo
  (concat "USERINFO" irchat-query-client-insert-to-generic))

(defconst irchat-query-client-help
  (concat "HELP" irchat-query-client-insert-to-generic))

(defconst irchat-query-client-clientinfo
  (concat "CLIENTINFO" irchat-query-client-insert-to-generic))

(defvar irchat-system-fqdname (system-name)
  "*The fully qualified domain name of the system.
Default is what (system-name) returns.")

(if irchat-want-traditional
    (defvar irchat-command-window-on-top nil
      "*If non-nil, the Command window will be put at the top of the screen.
Else it is put at the bottom.")
  (defvar irchat-command-window-on-top t
    "*If non-nil, the Command window will be put at the top of the screen.
Else it is put at the bottom."))
  
(defvar irchat-use-full-window t
  "*If non-nil, IRCHAT will use whole emacs window. Annoying for GNUS-
users, therefore added by nam.")

(defvar irchat-change-prefix ""
  "*String to add before any change msg, used for customisation of
IRCHAT to suit old users of the irc-loser-client.")

(defvar irchat-notice-prefix "*** Notice: "
  "*String to add before any notice msg.")

(defvar irchat-ignore-changes nil
  "*Ignore changes? Good in topic-wars/link troubles.")

(defvar irchat-reconnect-automagic nil
  "*Automatic reconnection, default is disabled")

(defvar irchat-ask-for-nickname nil
  "*Ask for nickname if irchat was entered with C-u.")

(defvar irchat-blink-parens t
  "*Should we blink matching parenthesis in irchat command buffer?")

(defvar irchat-one-buffer-mode nil
  "*When non-nil, irchat will put up only a dialogue-buffer (on the
screen). Useful for those (perverts) who use 24 line terminals.")

(defvar irchat-channel-filter)
(setq irchat-channel-filter "")
;;  *Enables use of C-u argument with NAMES and TOPIC."

(defvar irchat-grow-tail "_"
  "*Add irchat-grow-tail to nick when reconnecting. Otherwise you might get
killed again if automagic reconnect is too fast.")

(defvar irchat-ignore-extra-notices t
  "*Don't show NOTICEs with \"as being away\" unless they come from
the local server.")

(defvar irchat-shorten-kills t
  "*Shorten KILL messages to about one line.")

(defvar irchat-invited-channel)
(setq irchat-invited-channel nil)

(defvar irchat-Command-mode-hook nil
  "*A hook for IRCHAT Command mode.")

(defvar irchat-Dialogue-mode-hook nil
  "*A hook for IRCHAT Dialogue mode.")

(defvar irchat-Exit-hook nil
  "*A hook executed when signing off IRC.")

(defvar irchat-kill-nickname nil
  "*A list of nicknames, as symbols, to ignore.  Messages from these people
won't be displayed.")

(defvar irchat-kill-realname nil
  "*A list of real names of people to ignore. Messages from them
won't be displayed.")

(defvar irchat-away-indicator)
(defvar irchat-freeze-indicator)

(defvar irchat-kill-logon nil
  "*A list of logon names (user@host.dom.ain). Messages from them
won't be displayed.")

(defvar irchat-old-nickname nil
  "A place to keep old nickname in case it returs thas
we cannot change to new nick")

;; Define hooks for each IRC message the server might send us.
;; The newer IRC servers use numeric reply codes instead of words.

(defvar irchat-msg-list
  '(channel error invite linreply msg namreply nick ping pong
    privmsg quit topic wall whoreply kill wallops mode kick part join
    200 203 204 205 206 209 211 212 213 214 215 216 217 218 219
    301 311 312 313 314 315 317 318 321 322 323 331 332 341 
    351 352 353 361 364 365 366 367 368 371 372 381 382 391
    401 402 403 411 412 421 431 432 433 441 451 461 462 463 
    464 465 471 472 473 474 481 482 491)
  "A list of the IRC messages and numeric reply codes irchat can handle.")


(mapcar (function
	 (lambda (sym)
	   (eval (list 'defvar (intern (concat "irchat-"
					       (prin1-to-string sym)
					       "-hook"))
		       nil
		       (concat "*A hook that is executed when the IRC "
				 "message \"" (upcase (prin1-to-string sym))
				 "\" is received.
The hook function is called with two arguments, PREFIX and REST-OF-LINE.
It should return non NIL if no further processing of the message is to be
carried out.")))))
	irchat-msg-list)

(defvar irchat-current-channel nil
  "The channel you currently have joined.")

(defvar irchat-current-channels nil
  "The channels you have currently joined.")

(defvar irchat-channel-indicator "No channel"
  "The current channel, \"pretty-printed.\"")

(defvar irchat-private-indicator nil
  "A string displayed in the mode line indicating that user is
currently engaged in a one-to-one conversation.")

(defvar irchat-polling 'start
  "T when we are automatically polling the server.")

(defvar irchat-last-poll-minute nil
  "The minute when we last polled the server.")

(defvar irchat-last-timestamp 0
  "The minute we inserted the previous timestamp")

(defvar irchat-freeze nil
  "If non-nil the Dialogue window will not be scrolled automatically to bring
new entries into view.")

(defvar irchat-privmsg-partner nil
  "The person who got your last private message.")

(defvar irchat-current-chat-partner nil
  "The person you are in a private conversation with.")

(defvar irchat-current-chat-partners nil
  "An list containing nics user is chatting with.")

(defvar irchat-chat-partner-alist nil
  "An alist containing nics user is chatting with.")

(defvar irchat-command-buffer-mode 'channel
  "symbol chat or channel depending on which is current mode at 
command buffer.")

(defvar irchat-nick-alist nil
  "An alist containing the nicknames of users known to currently be on IRC.
Each element in the list is a list containing a nickname.")

(defvar irchat-channel-alist nil 
  "An alist containing the channels on IRC.  Each element in the list is 
a list containing a channel name.")

(defvar irchat-greet-author t
  "T until we notice that the author of IRCHAT is present, and send him
a greeting telling what version of IRCHAT this is.")

(defconst irchat-author-nickname "tml"
  "The nickname used by the author of IRCHAT.")

(defvar irchat-userathost ""
  "The user@host for the current line.")

(defvar irchat-debug-buffer nil)
(defvar irchat-server-buffer)
(defvar irchat-server-name)

(defvar irchat-Command-buffer "*IRC Commands*")
(defvar irchat-Dialogue-buffer "*IRC Dialogue*")
(defvar irchat-KILLS-buffer "*IRC KILLS*")
(defvar irchat-IGNORED-buffer "*IRC IGNORED*")
(defvar irchat-WALLOPS-buffer "*IRC WALLOPS*")
(defvar irchat-show-wallops t)

(defvar irchat-Command-mode-map nil)
(defvar irchat-Dialogue-mode-map nil)
(defvar irchat-Client-query-map nil)

(defvar irchat-server-process nil)
(defvar irchat-status-message-string nil)

(put 'irchat-Command-mode 'mode-class 'special)
(put 'irchat-Dialogue-mode 'mode-class 'special)

(if irchat-Command-mode-map
    nil
  (setq irchat-Command-mode-map (make-sparse-keymap))
  (define-key irchat-Command-mode-map "\C-m" 'irchat-Command-enter-message)
  (define-key irchat-Command-mode-map "\C-j" 'irchat-Command-enter-message)
  (define-key irchat-Command-mode-map "\C-cF" 'irchat-Command-send-file)
  (define-key irchat-Command-mode-map "\C-c\C-c" 'irchat-Client-query-prefix)
  (define-key irchat-Command-mode-map "\C-c\C-d" 'irchat-Command-debug)
  (define-key irchat-Command-mode-map "\C-c\C-i" 'irchat-Command-ison)
  (define-key irchat-Command-mode-map "\C-c\C-l" 'irchat-Command-redisplay)
  (define-key irchat-Command-mode-map "\C-c\C-n" 'irchat-Command-names)
  (define-key irchat-Command-mode-map "\C-c\C-r" 'irchat-Command-caesar-line)
  (define-key irchat-Command-mode-map "\C-c\C-u" 'irchat-Command-userhost)
  (define-key irchat-Command-mode-map "\C-c\C-y" 'irchat-Command-yank-send)
  (define-key irchat-Command-mode-map "\C-c\C-?" 'irchat-Command-scroll-down)
  (define-key irchat-Command-mode-map "\C-c " 'irchat-Command-scroll-up)
  (define-key irchat-Command-mode-map "\C-c!" 'irchat-Command-exec)
  (define-key irchat-Command-mode-map "\C-c2" 'irchat-Command-private-conversation)
  (define-key irchat-Command-mode-map "\C-ca" 'irchat-Command-away)
  (define-key irchat-Command-mode-map "\C-cc" 'irchat-Command-inline)
  (define-key irchat-Command-mode-map "\C-cf" 'irchat-Command-finger)
  (define-key irchat-Command-mode-map "\C-c\C-f" 'irchat-Command-freeze)
  (define-key irchat-Command-mode-map "\C-ci" 'irchat-Command-invite)
  (define-key irchat-Command-mode-map "\C-cj" 'irchat-Command-join)
  (define-key irchat-Command-mode-map "\C-c\C-p" 'irchat-Command-part)
  (define-key irchat-Command-mode-map "\C-ck" 'irchat-Command-kill)
  (define-key irchat-Command-mode-map "\C-c\C-k" 'irchat-Command-kick)
  (define-key irchat-Command-mode-map "\C-cl" 'irchat-Command-list)
  (define-key irchat-Command-mode-map "\C-cL" 'irchat-Command-load-vars)
  (define-key irchat-Command-mode-map "\C-cm" 'irchat-Command-message)
  (define-key irchat-Command-mode-map "\C-c\C-m" 'irchat-Command-modec)
  (define-key irchat-Command-mode-map "\C-cn" 'irchat-Command-nickname)
  (define-key irchat-Command-mode-map "\C-cp" 'irchat-Command-mta-private)
  (define-key irchat-Command-mode-map "\C-cq" 'irchat-Command-quit)
  (define-key irchat-Command-mode-map "\C-cr" 'irchat-Command-reconfigure-windows)
  (define-key irchat-Command-mode-map "\C-ct" 'irchat-Command-topic)
  (define-key irchat-Command-mode-map "\C-cu" 'irchat-Command-lusers)
  (define-key irchat-Command-mode-map "\C-cw" 'irchat-Command-who)
  (define-key irchat-Command-mode-map "\C-cW" 'irchat-Command-wait)
  (define-key irchat-Command-mode-map "\C-c|" 'irchat-Command-show-last-kill)
  (define-key irchat-Command-mode-map "\C-c/" 'irchat-Command-generic)
  (define-key irchat-Command-mode-map "\C-i" 'irchat-Command-complete)
  (define-key irchat-Command-mode-map "\C-[\C-i" 'lisp-complete-symbol)
  (define-key irchat-Command-mode-map "\C-c$" 'irchat-Command-eod-buffer)
  (define-key irchat-Command-mode-map "\C-c>" 'irchat-Command-push)
  (define-key irchat-Command-mode-map "\C-c<" 'irchat-Command-pop)
  (if irchat-want-traditional
      (define-key irchat-Command-mode-map "/" 'irchat-Command-irc-compatible))
  )

(if irchat-Client-query-map
    nil
  (define-prefix-command 'irchat-Client-query-map)
  (setq irchat-Client-query-map (make-keymap))
  (fset 'irchat-Client-query-prefix irchat-Client-query-map)
  (define-key irchat-Client-query-map "v" 'irchat-Command-client-version)
  (define-key irchat-Client-query-map "u" 'irchat-Command-client-userinfo)
  (define-key irchat-Client-query-map "h" 'irchat-Command-client-help)
  (define-key irchat-Client-query-map "c" 'irchat-Command-client-clientinfo)
  (define-key irchat-Client-query-map "g" 'irchat-Command-client-generic)
  (define-key irchat-Client-query-map "U" 
    'irchat-Command-client-userinfo-from-minibuffer)
  (define-key irchat-Client-query-map "\C-u" 
    'irchat-Command-client-userinfo-from-commandbuffer)
  )


(if irchat-Dialogue-mode-map
    nil
  (setq irchat-Dialogue-mode-map (make-keymap))
  (suppress-keymap irchat-Dialogue-mode-map)
  ;; mta@tut.fi wants these
  (define-key irchat-Dialogue-mode-map "\C-?" 'scroll-down)
  (define-key irchat-Dialogue-mode-map " " 'scroll-up)
  (define-key irchat-Dialogue-mode-map "$" 'end-of-buffer)
  (define-key irchat-Dialogue-mode-map "/" 'irchat-Command-generic)
  (define-key irchat-Dialogue-mode-map ">" 'end-of-buffer)
  (define-key irchat-Dialogue-mode-map "<" 'beginning-of-buffer)
  (define-key irchat-Dialogue-mode-map "a" 'irchat-Command-away)
  (define-key irchat-Dialogue-mode-map "f" 'irchat-Dialogue-freeze)
  (define-key irchat-Dialogue-mode-map "i" 'irchat-Command-invite)
  (define-key irchat-Dialogue-mode-map "j" 'irchat-Command-join)
  (define-key irchat-Dialogue-mode-map "l" 'irchat-Command-load-vars)
  (define-key irchat-Dialogue-mode-map "m" 'irchat-Dialogue-enter-message)
  (define-key irchat-Dialogue-mode-map "n" 'irchat-Command-names)
  (define-key irchat-Dialogue-mode-map "o" 'other-window)
  (define-key irchat-Dialogue-mode-map "p" 'irchat-Command-part)
  (define-key irchat-Dialogue-mode-map "r" 'irchat-Command-reconfigure-windows)
  (define-key irchat-Dialogue-mode-map "t" 'irchat-Dialogue-tag-line)
  (define-key irchat-Dialogue-mode-map "w" 'irchat-Command-who)
  (define-key irchat-Dialogue-mode-map "\C-m" 'irchat-Command-message)
  )

;;;
;;;
;;;
(defun matching-substring (string arg)
  (substring string (match-beginning arg) (match-end arg)))


(defun irchat (&optional confirm)
  "Connect to the IRC server and start chatting.
If optional argument CONFIRM is non-nil, ask which IRC server to connect.
If already connected, just pop up the windows."
  (interactive "P")
  (if (file-exists-p (expand-file-name irchat-variables-file))
      (load (expand-file-name irchat-variables-file)))
  (if (irchat-server-opened)
      (irchat-configure-windows)
    (unwind-protect
	(progn
	  (switch-to-buffer (get-buffer-create irchat-Command-buffer))
	  (irchat-Command-mode)
	  (irchat-start-server confirm))
      (if (not (irchat-server-opened))
	  (irchat-Command-quit)
	;; IRC server is successfully open. 
	(setq mode-line-process (format " {%s}" irchat-server))
	(let ((buffer-read-only nil))
	  (erase-buffer)
	  (sit-for 0))
	(irchat-Dialogue-setup-buffer)
	(irchat-KILLS-setup-buffer)
	(irchat-IGNORED-setup-buffer)
	(irchat-WALLOPS-setup-buffer)
	(irchat-configure-windows)
	(setq irchat-current-channels nil)
	(if irchat-current-channel
	    (irchat-Command-join irchat-current-channel)
	  (if irchat-startup-channel
	      (irchat-Command-join irchat-startup-channel)))
	(run-hooks 'irchat-Startup-hook)
	(irchat-Command-describe-briefly)))))


(defun irchat-Command-mode ()
  "Major mode for IRCHAT.  Normal edit function are available.
Typing Return or Linefeed enters the current line in the dialogue.
The following special commands are available:
For a list of the generic commands type \\[irchat-Command-generic] ? RET.
\\{irchat-Command-mode-map}"
  (interactive)
  (kill-all-local-variables)

  (setq irchat-nick-alist (list (list irchat-nickname))
	mode-line-modified "--- "
	major-mode 'irchat-Command-mode
	mode-name "IRCHAT Commands"
	irchat-privmsg-partner nil
	irchat-private-indicator nil
	irchat-away-indicator "-"
	irchat-freeze-indicator "-"
	mode-line-format
	'("--- IRCHAT: Commands " irchat-private-indicator
	  "{"irchat-channel-indicator "} "irchat-away-indicator irchat-freeze-indicator "-- " irchat-server " %-"))
  (use-local-map irchat-Command-mode-map)
  (if irchat-blink-parens
      nil
    (make-variable-buffer-local 'blink-matching-paren)
    (set-default 'blink-matching-paren t)
    (setq blink-matching-paren nil))
  (run-hooks 'irchat-Command-mode-hook))
  

(defun irchat-Dialogue-mode ()
  "Major mode for displaying the IRC dialogue.
All normal editing commands are turned off.
Instead, these commands are available:
\\{irchat-Dialogue-mode-map}"
  (kill-all-local-variables)
  (setq mode-line-modified "--- "
	major-mode 'irchat-Dialogue-mode
	mode-name "IRCHAT Dialogue"
	mode-line-format
	'("--- IRCHAT: Dialogue " 
	  "{"irchat-channel-indicator "} "irchat-away-indicator irchat-freeze-indicator "--" (-3 . "%p") "-%-"))
  (use-local-map irchat-Dialogue-mode-map)
  (buffer-flush-undo (current-buffer))
  (erase-buffer)
  (set-buffer irchat-Dialogue-buffer)
  (setq buffer-read-only t)
  (run-hooks 'irchat-Dialogue-mode-hook))


(defun irchat-startup-message ()
  (insert "\n\n\n\n
				IRCHAT
				   
	     Internet Relay CHAT interface for GNU Emacs

        This is a production release. Comments, suggestions, and bug 
        fixes are welcome. Address them to Kim Nyberg <kny@cs.hut.fi>

	Authors: Tor Lillqvist, Kai Keinänen, Markku Järvinen, Jukka Partanen
                 Kim Nyberg"))


(defun irchat-configure-windows ()
  "Configure Command mode and Dialogue mode windows.
One is for entering commands and text, the other displays the IRC dialogue."
  (if (or (one-window-p t)
	  (null (get-buffer-window irchat-Command-buffer))
	  (null (get-buffer-window irchat-Dialogue-buffer)))
      (progn
	(if irchat-command-window-on-top
	    (progn
	      (switch-to-buffer irchat-Command-buffer)
	      (if irchat-use-full-window
		  (delete-other-windows))
	      (if irchat-one-buffer-mode
		  (switch-to-buffer irchat-Dialogue-buffer)
		(split-window-vertically (max window-min-height 
					      irchat-command-window-height))
		(other-window 1)
		(switch-to-buffer irchat-Dialogue-buffer)
		(other-window 1)))
	  ;; mta@tut.fi wants it like this
	  (switch-to-buffer irchat-Dialogue-buffer)
	  (if irchat-use-full-window
	      (delete-other-windows))
	  (if irchat-one-buffer-mode
	      nil
	    (split-window-vertically
	     (- (window-height) (max window-min-height 
				     irchat-command-window-height)))
	    (other-window 1)
	    (switch-to-buffer irchat-Command-buffer))))))

(fset 'irchat-Dialogue-freeze 'irchat-Command-freeze)


(defun irchat-Dialogue-tag-line ()
  "Move current line to kill-ring."
  (interactive)
  (save-excursion
    (let (start)
      (beginning-of-line)
      (setq start (point))
      (end-of-line)
      (kill-ring-save start (point)))))


(defun irchat-Dialogue-setup-buffer ()
  "Initialize Dialogue mode buffer."
  (or (get-buffer irchat-Dialogue-buffer)
      (save-excursion
	(set-buffer (get-buffer-create irchat-Dialogue-buffer))
	(irchat-Dialogue-mode))))


(defun irchat-KILLS-setup-buffer ()
  "Initialize KILLS buffer."
  (or (get-buffer irchat-KILLS-buffer)
      (save-excursion
	(set-buffer (get-buffer-create irchat-KILLS-buffer)))))


(defun irchat-IGNORED-setup-buffer ()
  "Initialize IGNORED buffer."
  (or (get-buffer irchat-IGNORED-buffer)
      (save-excursion
	(set-buffer (get-buffer-create irchat-IGNORED-buffer)))))


(defun irchat-WALLOPS-setup-buffer ()
  "Initialize WALLOPS buffer."
  (or (get-buffer irchat-WALLOPS-buffer)
      (save-excursion
	(set-buffer (get-buffer-create irchat-WALLOPS-buffer)))))


(defun irchat-clear-system ()
  "Clear all IRCHAT variables and buffers."
  (interactive)
  (if (and irchat-Command-buffer (get-buffer irchat-Command-buffer))
      (bury-buffer irchat-Command-buffer))
  (if (and irchat-Dialogue-buffer (get-buffer irchat-Dialogue-buffer))
      (bury-buffer irchat-Dialogue-buffer))
  (if (and irchat-KILLS-buffer (get-buffer irchat-KILLS-buffer))
      (bury-buffer irchat-KILLS-buffer))
  (if (and irchat-IGNORED-buffer (get-buffer irchat-IGNORED-buffer))
      (bury-buffer irchat-IGNORED-buffer))
  (if (and irchat-WALLOPS-buffer (get-buffer irchat-WALLOPS-buffer))
      (bury-buffer irchat-WALLOPS-buffer))
  (if (and irchat-debug-buffer (get-buffer irchat-debug-buffer))
      (bury-buffer irchat-debug-buffer))
  (setq irchat-debug-buffer nil
	irchat-channel-indicator "No channel"))


(defun irchat-start-server (&optional confirm)
  "Open network stream to remote irc server.
If optional argument CONFIRM is non-nil, ask the host that the server
is running on."
  (if (irchat-server-opened)
      ;; Stream is already opened.
      nil
    ;; Open IRC server.
    (if (or
	 (and confirm
	      (not (eq confirm 'always)))
	 (null irchat-server))
	(setq irchat-server
	      (read-string "IRC server: " irchat-server)))
    (if (and confirm
	     (not (eq confirm 'always))
	     irchat-ask-for-nickname)
	(setq irchat-nickname
	      (read-string "Enter your nickname: " irchat-nickname)))
    (if (eq confirm 'always)
	(setq irchat-nickname (concat irchat-nickname irchat-grow-tail)))
    ;; If no server name is given, local host is assumed.
    (if (string-equal irchat-server "")
	(setq irchat-server (system-name)))
    (message "Connecting to IRC server on %s..." irchat-server)
    (cond ((irchat-open-server irchat-server irchat-service))
	  ((and (stringp irchat-status-message-string)
		(> (length irchat-status-message-string) 0))
	   ;; Show valuable message if available.
	   (error irchat-status-message-string))
	  (t (error "Cannot open IRC server on %s" irchat-server)))))


(defun irchat-open-server (host &optional service)
  "Open chat server on HOST.
If HOST is nil, use value of environment variable \"IRCSERVER\".
If optional argument SERVICE is non-nil, open by the service name."
  (let ((host (or host (getenv "IRCSERVER")))
	(status nil))
    (setq irchat-status-message-string "")
    (cond ((and host (irchat-open-server-internal host service))
	   (irchat-send "PING %s" host)
	   (if (setq status (irchat-wait-for-response ".*PONG.*"))
	       (progn
		 (set-process-sentinel irchat-server-process
				       'irchat-sentinel)
		 (set-process-filter irchat-server-process
				     'irchat-filter)
		 (irchat-send "USER %s %s %s :%s"
			      (or (getenv "IRCUSER")
				  (user-real-login-name))
			      (or (getenv "IRCHOST")
				  irchat-system-fqdname)
			      irchat-server
			      (or (getenv "IRCNAME")
				  (getenv "NAME")
				  (user-full-name)))
		 (setq irchat-old-nickname irchat-nickname)
		 (irchat-send "NICK %s" irchat-nickname)
		 (irchat-maybe-poll))
	     ;; We have to close connection here, since the function
	     ;;  `irchat-server-opened' may return incorrect status.
	     (irchat-close-server-internal)))
	  ((null host)
	   (setq irchat-status-message-string "IRC server is not specified.")))
    status))


(defun irchat-close-server ()
  "Close chat server."
  (unwind-protect
      (progn
	;; Un-set default sentinel function before closing connection.
	(and irchat-server-process
	     (eq 'irchat-sentinel
		 (process-sentinel irchat-server-process))
	     (set-process-sentinel irchat-server-process nil))
	;; We cannot send QUIT command unless the process is running.
	(if (irchat-server-opened)
	    (irchat-send "QUIT")))
    (irchat-close-server-internal)))


(defun irchat-server-opened ()
  "Return server process status, T or NIL.
If the stream is opened, return T, otherwise return NIL."
  (and irchat-server-process
       (memq (process-status irchat-server-process) '(open run))))


(defun irchat-open-server-internal (host &optional service)
  "Open connection to chat server on HOST by SERVICE (default is irc)."
  (condition-case err 
      (save-excursion
	;; Initialize communication buffer.
	(setq irchat-server-buffer (get-buffer-create " *IRC*"))
	(set-buffer irchat-server-buffer)
	(kill-all-local-variables)
	(buffer-flush-undo (current-buffer))
	(erase-buffer)
	(setq irchat-server-process
	      (open-network-stream "IRC" (current-buffer)
				   host (or service "irc")))
	(setq irchat-server-name host)
	(run-hooks 'irchat-server-hook)
	;; Return the server process.
	irchat-server-process)
    (error (message (car (cdr err)))
	   nil)))


(defun irchat-close-server-internal ()
  "Close connection to chat server."
  (if irchat-server-process
      (delete-process irchat-server-process))
  (if irchat-server-buffer
      (kill-buffer irchat-server-buffer))
  (setq irchat-server-buffer nil
	irchat-server-process nil))


(defun irchat-wait-for-response (regexp)
  "Wait for server response which matches REGEXP."
  (save-excursion
    (let ((status t)
	  (wait t))
      (set-buffer irchat-server-buffer)
      (irchat-accept-response)
      (while wait
	(goto-char (point-min))
	(cond ((looking-at "ERROR")
	       (setq status nil)
	       (setq wait nil))
	      ((looking-at ".")
	       (setq wait nil))
	      (t (irchat-accept-response))))
      ;; Save status message.
      (end-of-line)
      (setq irchat-status-message-string
	    (buffer-substring (point-min) (point)))
      (if status
	  (progn
	    (setq wait t)
	    (while wait
	      (goto-char (point-max))
	      (forward-line -1)		;(beginning-of-line)
	      (if (looking-at regexp)
		  (setq wait nil)
		(message "IRCHAT: Reading...")
		(irchat-accept-response)
		(message "")))
	    ;; Successfully received server response.
	    t)))))


(defun irchat-accept-response ()
  "Read response of server. Only used at startup time"
  ;; To deal with server process exiting before accept-process-output is called.
  (or (memq (process-status irchat-server-process) '(open run))
      (if (not irchat-reconnect-automagic)
	  (error "IRCHAT: Connection closed.")
	(if irchat-grow-tail
	    (irchat 'always)
	  (irchat))))
  (condition-case errorcode
      (accept-process-output irchat-server-process)
    (error
     (cond ((string-equal "select error: Invalid argument" (nth 1 errorcode))
	    ;; Ignore select error.
	    nil)
	   (t (signal (car errorcode) (cdr errorcode)))))))

(defun irchat-send-poll (now)
  (setq irchat-last-poll-minute now
	irchat-polling (or irchat-polling t)
	irchat-nick-alist nil
	irchat-channel-alist nil)
  (irchat-send "NAMES"))

;;; As the server PINGs us regularly, we can be sure that we will 
;;; have the opportunity to poll it with a NAMES as often.
;;; We do this so that we can keep the irchat-nick-alist up-to-date.
;;; We send a PING after the NAMES so that we notice when the final
;;; NAMREPLY has come.
(defun irchat-maybe-poll ()
  (let* ((now (substring (current-time-string) 14 16))
	 (nowint (string-to-int now))
	 (stampint (mod (+ irchat-last-timestamp
			   (or irchat-timestamp-interval 0))
			60)))

    ;; Print timestamp, this should use timer.el or something
    (if (or (not irchat-timestamp-interval)
	    (> stampint nowint)
	    (> (- nowint stampint) 30))
	nil
      (let ((obuf (current-buffer)))
	(if (get-buffer irchat-Dialogue-buffer)
	    (progn
	      (set-buffer irchat-Dialogue-buffer)
	      (setq irchat-last-timestamp stampint)
	      (insert (format "%s\n" (format irchat-timestamp-format 
					     (current-time-string))))
	      (set-buffer obuf)))))

    ;; Update names lists
    (if (or (not irchat-global-names)
	    (string-equal "A" irchat-away-indicator))
	;; not polling or away, forget it
	nil
      ;; polling, see if enoung time has  passed since last poll, or this is 
      ;; the first poll
      (if (not irchat-last-poll-minute)
	  (irchat-send-poll nowint)
	(if (and (numberp irchat-global-names) 
		 (> irchat-global-names 0))
	    (if (>= (mod (+ irchat-last-poll-minute irchat-global-names) 60)
		    nowint)
		(irchat-send-poll nowint))
	  (if (not (= nowint irchat-last-poll-minute))
	      (irchat-send-poll nowint)))))

    (irchat-send "PING %s" (system-name))))
  
  
(defun irchat-own-message (message)
  (let ((obuf (current-buffer)))
    (set-buffer irchat-Dialogue-buffer)
    (let (buffer-read-only)
      (goto-char (point-max))
      (irchat-handle-msg-msg nil message))
    (set-window-point (get-buffer-window irchat-Dialogue-buffer)
		      (point-max))))


(defun replace-in-string (string from to)
  (let ((newstring "")
	(pos 0))
    (while (string-match from string pos)
      (setq newstring (concat newstring
			      (substring string pos (match-beginning 0))
			      to))
      (setq pos (match-end 0)))
    (concat newstring (substring string pos (length string)))))


(defun irchat-send (&rest args)
  (let ((item (apply 'format args)) ditem)
    (let ((conv-list irchat-send-convert-list))
      (while conv-list
	(setq item (replace-in-string item (car (car conv-list))
				      (car (cdr (car conv-list)))))
	(setq conv-list (cdr conv-list))))
    (process-send-string irchat-server-process
			 (concat item "\r"))
    (setq ditem (downcase item))
    (if (string-match "^list" (downcase ditem))
	(if (string-match "\\(^list\\) \\(.+\\)" ditem)
	    (setq irchat-channel-filter (matching-substring ditem 2))
	  (setq irchat-channel-filter "")))))    


(defun irchat-change-nick-of (old new)
  (let ((pair (assoc old irchat-nick-alist)))
    (if pair
	(rplaca pair new)
      (setq irchat-nick-alist (cons (cons new nil) irchat-nick-alist)))))


(defun irchat-clean-hostname (hostname)
  "Return the arg HOSTNAME, but if is a dotted-quad, put brackets around it."
  (let ((data (match-data)))
    (unwind-protect
	(if (string-match "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+" hostname)
	    (concat "[" hostname "]")
	  hostname)
      (store-match-data data))))


(defun irchat-scroll-if-visible (window)
  (if window (set-window-point window (point-max))))

;;;
;;; this function handles default arguments more user friendly
;;;
(defun irchat-completing-default-read 
  (prompt table predicate require-match initial-input)
  "completing-read w/ default argument like in 'kill-buffer'"
  (let ((default-read
	  (completing-read
	   (if initial-input
	       (format "%s(default %s) "
		       prompt initial-input)
	     (format "%s" prompt))
	   table predicate require-match nil)))
    (if (and (string= default-read "") initial-input)
	initial-input
      default-read)))
;;;
;;; eof
;;;
