;; see file irchat-copyright.el for change log and copyright info
;; irchat.el splitted by kmk@tut.fi Sun Oct 14 18:58:10 EET 1990
;; This is some release, see irchat-client-version-*

(provide 'irchat)
(require 'irchat-commands)
(require 'irchat-handle)

;;;
;;; start of used modifiable variables
;;;
(defvar irchat-command-window-height 4
  "*How large should Command window be on startup.")

(defvar irchat-want-traditional nil
  "*Do we want this to look like IrcII.")

(defvar irchat-variables-file "~/.irchat_vars.el"
  "*Where to look for variables.
Helps to remove clutter from your .emacs .")
 
(defvar irchat-server (getenv "IRCSERVER")
  "*Name of the host running the IRC server.
Initialized from the IRCSERVER environment variable.")

(defvar irchat-service 
  (let ((ircport-env (getenv "IRCPORT")))
    (if ircport-env
	(if (> (string-to-int ircport-env) 0)
	    (string-to-int ircport-env)
	  ircport-env)
      6667))
  "*IRC service name or (port) number.")

(defvar irchat-nickname (or (getenv "IRCNICK") (user-real-login-name))
  "*The nickname you want to use in IRC.
Default is the environment variable IRCNICK, or your login name.")

(defvar irchat-startup-channel nil
  "*The channel to join automagically at start.
If nil, don't join any channel.")

(defvar irchat-format-string ">%s<"
  "*Format string for private messages being sent. Thanks to jvh
for the help with format.")

(defvar irchat-format-string1 "=%s="
  "*Format string for arriving private messages.")

(defvar irchat-format-string2 "<%s>"
  "*Format string for arriving messages to current channel.")

(defvar irchat-format-string3 "<%s:%s>"
  "*Format string for arriving messages to current channel from outside the channel.")

(defvar irchat-format-string4 "(%s)"
  "*Format string for arriving messages to other channel from outside the channel.")

(defvar irchat-format-string5 "(%s:%s)"
  "*Format string for arriving messages to other channel from outside the channel.")

(defvar irchat-nam-suffix ""
  "*String to add to end of line ... like n{{s.")

(defvar irchat-beep-on-bells nil
  "*If non-nil, and the IRC Dialogue buffer is not selected in a window,
an IRC message arriving containing a bell character, will cause you
 to be notified.
If value is 'always, an arriving bell will always cause a beep (or flash).")

(defvar irchat-file-accept nil
  "*Do we accept files.")

(defvar irchat-file-confirm-save nil
  "*Do we want confirmation on saving files.")

(defvar irchat-client-userinfo "No user info given."
  "*Userinfo message given to anyone asking.")

;;;
;;; end of user modifiable variables
;;;

(defconst irchat-client-version-major "2"
  "Major version number.")

(defconst irchat-client-version-minor "2(beta/Jan-6)"
  "Minor version number. alfa beeta gamma delta epsilon zeeta eeta theeta ioota kappa lambda myy nyy ksii omikron pii rhoo sigma tau ypsilon fii khii psii oomega")

(defconst irchat-RCS-revision "$Not Under RCS (mta)$")

(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.")

(defconst irchat-version
;;  (substring irchat-RCS-revision 11 (- (length irchat-RCS-revision) 2))
  (format "%s %s.%s" irchat-client-name irchat-client-version-major
	  irchat-client-version-minor)
  "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
  ""
;;;  "COMMENT :If you get this your client doesn't know about client-to-client communication, just ignore this."
  )

(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-ignore-changes nil
  "*Ignore changes? Good in topic-wars/link troubles.")

(defvar irchat-reconnect-automagic nil
  "*Automatic reconnection, default is disabled (huge kludge).
This was implemented by Kai 'Kaizzu' Kein{nen and others.")

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

(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.")

(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.")

(setq irchat-invited-channel nil) ; not invited anywhere yet

(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-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
    301 311 312 313 321 322 341 351 361 381 401 412 421 441 461
    402 331 391 411 481 371 372 382 431 432 433 332 471 314 403
    451 462 463 464 465 491 315 323 364 365 366 317 318 482
    353 352 204 205 206 209 211 212 213 214 215 216 217 218 219
    367 368 203)
  "A list of the IRC messages and numeric reply codes we are prepared to 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 t 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 you are
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 ""
  "The minute when we last polled the server.")

(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-chat-partner nil
  "The person you are in a private conversation with.")

(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-debug-buffer nil)

(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 ">" 'end-of-buffer)
  (define-key irchat-Dialogue-mode-map "<" 'beginning-of-buffer)
  (define-key irchat-Dialogue-mode-map "f" 'irchat-Dialogue-freeze)
  (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 "o" 'other-window)
  (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 "\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 contact.
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))
	(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)))
  (setq mode-line-modified "--- ")
  (setq major-mode 'irchat-Command-mode)
  (setq mode-name "IRCHAT Commands")
  (setq irchat-privmsg-partner nil)
  (setq irchat-private-indicator nil)
;; Nice to know if one's present or not, some people always leave the /away on
  (setq irchat-away-indicator "-")
  (setq irchat-freeze-indicator "-")
  
  (setq 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 "--- ")
  (setq major-mode 'irchat-Dialogue-mode)
  (setq mode-name "IRCHAT Dialogue")
  (setq 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
        $Id: irchat.el,v 1.25 90/06/21 13:30:00 kmk Exp $


		 Internet Relay Chat interface for GNU Emacs

	If you have any trouble with this software, please let me
	know. I will fix your problems in the next release.

	Comments, suggestions, and bug fixes are welcome.

        This is a beta release. Report bugs to Kaizzu at 
        kmk@cc.tut.fi.

	Tor Lillqvist
	tml@hemuli.atk.vtt.fi"))

(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))
      (kill-buffer irchat-Command-buffer))
  (if (and irchat-Dialogue-buffer (get-buffer irchat-Dialogue-buffer))
      (kill-buffer irchat-Dialogue-buffer))
  (if (and irchat-KILLS-buffer (get-buffer irchat-KILLS-buffer))
      (kill-buffer irchat-KILLS-buffer))
  (if (and irchat-IGNORED-buffer (get-buffer irchat-IGNORED-buffer))
      (kill-buffer irchat-IGNORED-buffer))
  (if (and irchat-WALLOPS-buffer (get-buffer irchat-WALLOPS-buffer))
      (kill-buffer irchat-WALLOPS-buffer))
  (if (and irchat-debug-buffer (get-buffer irchat-debug-buffer))
      (kill-buffer irchat-debug-buffer))
  (setq irchat-debug-buffer nil)
  (setq irchat-channel-indicator "No channel"))

(defun irchat-start-server (&optional confirm)
  "Open network stream to remote chat server.
If optional argument CONFIRM is non-nil, ask you the host that the server
is running on even if it is defined."
  (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 "_")))
    ;; 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"
			      (user-real-login-name)
			      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)
  (setq 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.
It is well-known that the communication speed will be much improved by
defining this function as macro."
  ;; To deal with server process exiting before
  ;;  accept-process-output is called.
  ;; Suggested by Jason Venner <jason@violet.berkeley.edu>.
  ;; This is a copy of `nntp-default-sentinel'.
  (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-sentinel (proc status)
  "Sentinel function for IRC server process."
  (if (and irchat-server-process
	   (not (irchat-server-opened)))
      (if (not irchat-reconnect-automagic)
	  (error "IRCHAT: Connection closed.")
	(if irchat-grow-tail
	    (irchat 'always)
	  (irchat)))))

(defun irchat-filter (process output)
  "Filter function for IRC server process."
  (let ((obuf (current-buffer))
	(data (match-data))
	bol)
    (if (and irchat-debug-buffer (get-buffer irchat-debug-buffer))
	(progn
	  (set-buffer irchat-debug-buffer)
	  (goto-char (point-max))
	  (insert output)
	  (irchat-scroll-if-visible (get-buffer-window irchat-debug-buffer))))
    (set-buffer (process-buffer process))
    (goto-char (point-max))
    (insert output)
    (goto-char (point-min))
    (while (re-search-forward "\n\n" (point-max) t)
      (delete-char -1)) ;;; This hack (from mta) is for 2.4
    (goto-char (point-min))

    (if (string-match "\n" output)
	(irchat-handle-message))
    (set-buffer obuf)
    (store-match-data data)))

(defun irchat-handle-message ()
  "Called when we have at least one line of output from the IRC server."
  (let ((obuf (current-buffer)) beg end prefix message rest-of-line)
    (while (or (looking-at "\\(:[^! \n]*\\)![^ \n]* \\([^ \n]+\\) :?\\(.*\\)\n")
	       (looking-at "\\(:[^ \n]*\\)? *\\([^ \n]+\\) :?\\(.*\\)\n"))
      (setq beg (match-beginning 0)
	    end (match-end 0))
      (setq prefix (if (match-beginning 1)
		       (buffer-substring (1+ (match-beginning 1))
					 (match-end 1))))
      (setq rest-of-line
	    (buffer-substring (match-beginning 3) (match-end 3)))
      (setq message (downcase (buffer-substring
			       (match-beginning 2)
			       (match-end 2))))
      (set-buffer irchat-Dialogue-buffer)
      (if irchat-freeze
	  (save-excursion
	    (irchat-handle-message-2 prefix message rest-of-line))
	(irchat-handle-message-2 prefix message rest-of-line))
      (set-buffer obuf)
      (delete-region beg end))))

(defun irchat-handle-message-2 (prefix message rest-of-line)
  "Helper function for irchat-handle-message."
  (let (buffer-read-only hook fun)
    (goto-char (point-max))
    (if (and (boundp (setq hook
			   (intern (concat "irchat-" message "-hook"))))
	     (eval hook)
	     (eq (eval (list (eval hook) prefix rest-of-line)) t))
	;; If we have a hook, and it returns T, do nothing more
	nil
      ;; else call the handler
      (if (fboundp (setq fun (intern
			      (concat "irchat-handle-" message "-msg"))))
	  (progn
	    (eval (list fun prefix rest-of-line))
	    (if (not irchat-freeze)
		(irchat-scroll-if-visible
		 (get-buffer-window (current-buffer)))))
	(message "IRCHAT: Unknown IRC message \":%s %s %s\"" prefix (upcase message) rest-of-line)
	(insert (format "MESSAGE: %s, %s, %s" prefix message rest-of-line))
	(newline)))))

;; 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 ()
  "At regular intervals, we poll the IRC server, 
checking which users are present."
  (let ((now (substring (current-time-string) 14 16)))
    (if (string= now irchat-last-poll-minute)
	nil
      (setq irchat-last-poll-minute now)
      (setq irchat-polling (or irchat-polling t)) ; figure this out
      (setq irchat-nick-alist nil)
      (setq irchat-channel-alist nil)
      (irchat-send "NAMES")
      (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))
;; we dont clean messages anymore! mta Tue Jan 22 00:28:00 EET 1991
      (irchat-handle-msg-msg nil message))
    (set-window-point (get-buffer-window irchat-Dialogue-buffer)
		      (point-max))))

(defun irchat-send (&rest args)
  (let ((tem (eval (cons 'format args))))
    (process-send-string
     irchat-server-process
;; we dont clean messages anymore! mta Tue Jan 22 00:28:00 EET 1991
     (concat tem "\r"))
    (if (string-match "^away" (downcase tem))
	(if (string-match "^away *$" (downcase tem))
	    (setq irchat-away-indicator "-")
	  (setq irchat-away-indicator "A")))
    (setq foo (downcase tem))
    (if (string-match "^list" (downcase tem))
	(if (string-match "\\(^list\\) \\(.+\\)" foo)
	    (setq irchat-channel-filter (matching-substring foo 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)))))

;;
;; not needed anymore, get rid! mta Tue Jan 22 00:29:45 EET 1991
;;

(defun irchat-clean-string (s)
  (while (string-match "[\C-@-\C-F\C-H-\C-_\C-?]" s)
    (aset s (match-beginning 0) ?.))
  s)

(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))))

(defun irchat-scan-nicklist (str)
  (if (string-match "\\([^ ]+\\) \\(.*\\)" str)
      (let* ((ms1 (matching-substring str 1))
	     (nick 
	      (if (eq (elt ms1 0) 64) ; @
		  (substring ms1 1)
		ms1))
	     (n (intern nick)))
	(if (get n 'irchat-waited-for)
	    (progn
	      (beep t)
	      (message "IRCHAT: %s has entered! (%s)" nick
		       (if (string= chnl "0") "on no channel yet"
			 (concat "on channel " chnl)))
	      (if (get n 'irchat-greeting)
		  (irchat-send "PRIVMSG %s :%s" nick (get n 'irchat-greeting)))
	      (put n 'irchat-waited-for nil)
	      (put n 'irchat-greeting nil)))
	(if (and irchat-greet-author (string= nick irchat-author-nickname))
	    (progn
	      (setq irchat-greet-author nil)
	      (irchat-send "PRIVMSG %s :%s <%s@%s> is using irchat version %s"
			   irchat-author-nickname (user-full-name)
			   (user-login-name) irchat-system-fqdname
			   irchat-version)))	
	(setq irchat-nick-alist (cons (list nick) irchat-nick-alist))
	(irchat-scan-nicklist (matching-substring str 2)))
    nil))

(defun irchat-scan-channels (str)
  (setq irchat-channel-alist (cons (list chnl) irchat-channel-alist)))

