##
## clirc -- An IRC client in Python.
## user.py - User input handling.
##
## Copyleft 1997-2000 Teemu Kalvas <chery@s2.org>
##

import os
import re
import signal
import socket
import string
import sys
import time

import buffer
import config
import crypt
from event import event
import ircnet
import ircserv
import main
import reloadable
import rnd
import runloop

def child_killed(a, b):
    runloop.signal_queue.append(dcc_exit)

signal.signal(signal.SIGCHLD, child_killed)

def dcc_exit():
    pid, status = os.wait()
    if main.dcc_pid.has_key(pid):
	number, direction = main.dcc_pid[pid]
	del main.dcc_pid[pid]
	if direction:
	    pid, filename, size, nick, nnet = main.dcc_sending[number]
	    event.dcc_send_finish(net = nnet, filename = filename, size = size,
				  nick = nick, id = number)
	    del main.dcc_sending[number]
	else:
	    pid, name, host, port, size, nick, addr, nnet = main.dcc_receiving[
		number]
	    event.dcc_receive_finish(net = nnet, filename = name, host = host,
				     port = port, size = size, nick = nick,
				     addr = addr, id = number)
	    del main.dcc_receiving[number]

class Event:
    def __init__(self, window, screen):
        self.window = window
        self.screen = screen

class KeyEvent(Event):
    def __init__(self, window, screen, key, editor):
        Event.__init__(self, window, screen)
        self.key = key
        self.editor = editor

    def accept(self, str):
        if str:
            encrypted = hasattr(self, 'encrypted')
            if encrypted or str[0] not in config.cmd_chars:
                command_empty(self, str, encrypted)
            else:
                elems = string.split(str, ' ', 1)
                if len(elems) == 1:
                    self.tail = ''
                else:
                    self.tail = elems[1]
                self.command = elems[0][1:]
                command_event(self)

class CommandEvent(Event):
    def __init__(self, window, screen, command, tail):
        Event.__init__(self, window, screen)
        self.command = command
        self.tail = tail

def keypress_event(event):
    handler = globals().get(event.key, keypress_default)
    if callable(handler):
        handler(event)
    elif type(handler) == type(''):
        if hasattr(event.editor, handler):
            getattr(event.editor, handler)(event)

def command_empty(ev, str, encrypted):
    window = ev.window
    net = window.buf.net
    if encrypted:
        cm = crypt.encrypt_message(net.server.nick, str, window.name, 1)
        if cm:
            net.server.out_line('PRIVMSG ' + window.name + ' :' + cm)
        else:
            event.encryption_failed()
            return
    else:
        net.server.out_line('PRIVMSG ' + window.name + ' :' + str)
    event.own_privmsg(net = net, target = window.name, message = str,
                      crpt = encrypted)

def command_event(event):
    handler = globals().get('cmd_' + event.command, None)
    if callable(handler):
        handler(event)

def command_parse(event, narg, *defaults):
    elems = filter(lambda x: x, string.split(event.tail, ' ', narg - 1))
    le = len(elems)
    if le == narg:
        return tuple(elems)
    elif le < narg:
        return tuple(elems) + tuple(defaults[le:])

def keypress_default(event):
    event.editor.insert(event.key)

def keycmd_send_encrypted(event):
    event.encrypted = 1
    event.editor.accept(event)

def keycmd_log_down(event):
    event.window.page_down()

def keycmd_log_up(event):
    event.window.page_up()

def keycmd_prev_window(event):
    event.screen.prev_window()

def keycmd_next_window(event):
    event.screen.next_window()

def keycmd_mark_window(event):
    event.screen.mark_window()

def keycmd_select_marked_window(event):
    event.screen.select_marked_window()

def keycmd_select_active_window(event):
    event.screen.select_active_window()

def keycmd_walk_nicklist(event):
    nicks = event.window.buf.net.msg_nicklist
    if nicks:
	event.editor.set_string('/msg %s ' % nicks[0])
	event.window.buf.net.msg_nicklist = nicks[1:] + [nicks[0]]

def keycmd_resize(event):
    event.screen.resize_event()

key_resize = keycmd_resize
key_backspace = 'del_char_left'
key_dc = 'del_char_right'
ctrl_d = 'del_char_right'
key_home = 'line_beg'
ctrl_a = 'line_beg'
key_end = 'line_end'
ctrl_e = 'line_end'
key_enter = 'accept'
ctrl_m = 'accept'
key_left = 'char_left'
ctrl_b = 'char_left'
key_right = 'char_right'
ctrl_f = 'char_right'
key_npage = keycmd_log_down
ctrl_n = keycmd_log_down
key_ppage = keycmd_log_up
ctrl_p = keycmd_log_up
ctrl_c = keycmd_send_encrypted
ctrl_k = 'del_line_end'
ctrl_u = 'reset'
ctrl_l = 'refresh'
ctrl_q = keycmd_prev_window
ctrl_w = keycmd_next_window
key_up = 'history_prev'
key_down = 'history_next'
ctrl_t = 'transpose_char'

def cmd_addkey(event):
    """addkey <address> <key>
Associates <key> to be used with <address> when using IDEA encryption of
IRC messages."""
    addr, key = command_parse(event, 2, None, None)
    if key <> None:
	fingerprint = idea.key_fingerprint(key)
	if not config.known_key.has_key(fingerprint):
	    config.default_key.append((re.compile(addr, re.I), key))
	    config.known_key[fingerprint] = key

def cmd_ban(event):
    """ban <address>
Adds a ban on <address> on the current channel.  Requires channel
operator status."""
    tail = event.tail
    if '@' in tail:
        if '!' in tail:
            addr = tail
        else:
            addr = '*!' + tail
    else:
        addr = '*!*@' + tail
    window = event.window
    window.buf.net.server.out_line('MODE ' + window.name + ' +b ' + addr)

def cmd_bans(event):
    """bans
Lists all bans on the current channel."""
    window = event.window
    window.buf.net.server.out_line('MODE ' + window.name + ' b')

def cmd_ctcp(event):
    """ctcp <channel/nick> <ctcp-data>
Sends an arbitrary CTCP message to <channel> or <nick>."""
    chan, data = command_parse(event, 2, None, None)
    if data <> None:
        event.window.buf.net.server.out_line('PRIVMSG ' + chan + ' :\001'
                                             + data + '\001')

def cmd_dcc(ev):
    """dcc get [<number> [<filename>]]
dcc send <nick> <filename>
dcc quit <number>
dcc list
Initiates reception of a dcc offered file."""
    cmd, a, b = command_parse(ev, 3, None, None, None)
    net = ev.window.buf.net
    if cmd == 'get':
	if a <> None:
	    number = int(a)
	else:
	    number = main.dcc_count
	name, host, port, size, nick, addr, net = main.dcc_hash[number]
	del main.dcc_hash[number]
	if b == None:
	    b = name
	pid = os.fork()
	if pid:
	    main.dcc_pid[pid] = number, 0
	    main.dcc_receiving[
		number] = pid, b, host, port, size, nick, addr, net
	    event.dcc_receive_start(id = number, filename = b, host = host,
				    port = port, size = size, nick = nick,
				    addr = addr, net = net)
	else:
	    os.execl('/bin/sh', 'sh', '-c', 'dcc receive ' + host + ' ' + port
		     + ' ' + size + ' ' + b + ' > /dev/null')
	    sys.exit(1)
    elif cmd == 'send':
	if b <> None:
	    port = repr(rnd.rnd() % 32767 + 32768)
	    size = repr(os.stat(b)[6])
	    pid = os.fork()
	    if pid:
		main.dcc_count = main.dcc_count + 1
		main.dcc_pid[pid] = main.dcc_count, 1
		host = str(long(reduce(lambda x, y: x * 256L + y, map(
		    lambda x: int(x), string.split(socket.gethostbyaddr(
			socket.gethostname())[2][0], '.')), 0)))[:-1]
		ev.window.buf.net.server.out_line(
		    'PRIVMSG ' + a + ' :\001DCC SEND ' + b + ' ' + host + ' '
		    + port + ' ' + size + '\001')
		main.dcc_sending[main.dcc_count] = pid, b, size, a, net
		event.dcc_send_start(net = net, id = main.dcc_count, nick = a,
				     filename = b, size = size)
	    else:
		os.execl('/bin/sh', 'sh', '-c', 'dcc send ' + port + ' '
			 + b + ' > /dev/null')
		sys.exit(1)
    elif cmd == 'kill':
	if a <> None:
	    num = int(a)
	else:
	    if main.dcc_hash:
		keys = main.dcc_hash.keys()
	    elif main.dcc_receiving:
		keys = main.dcc_receiving.keys()
	    elif main.dcc_sending:
		keys = main.dcc_sending.keys()
	    else:
		return
	    keys.sort()
	    num = keys[0]
	if main.dcc_hash.has_key(num):
	    name, host, port, size, nick, addr, nnet = main.dcc_hash[num]
	    event.dcc_request_remove(id = num, filename = name, host = host,
				     port = port, size = size, nick = nick,
				     addr = addr, net = net)
	    del main.dcc_hash[num]
	elif main.dcc_receiving.has_key(num):
	    pid, name, host, port, size, nick, addr, nnet = main.dcc_receiving[
		num]
	    os.kill(pid, signal.SIGHUP)
	    del main.dcc_pid[pid]
	    event.dcc_receive_remove(id = num, filename = name, host = host,
				     port = port, size = size, nick = nick,
				     addr = addr, net = net)
	    del main.dcc_receiving[num]
	elif main.dcc_sending.has_key(num):
	    pid, name, size, nick, nnet = main.dcc_sending[num]
	    os.kill(pid, signal.SIGHUP)
	    del main.dcc_pid[pid]
	    event.dcc_send_remove(id = num, filename = name, size = size,
				  nick = nick, net = net)
	    del main.dcc_sending[num]
    elif cmd == 'list':
	event.dcc_list(net = net)
	for num, (name, host, port, size, nick, addr, nnet) \
	    in main.dcc_hash.items():
	    event.dcc_request_list(id = num, filename = name, host = host,
				   port = port, size = size, nick = nick,
				   addr = addr, net = net)
	for num, (pid, name, host, port, size, nick, addr, nnet) \
	    in main.dcc_receiving.items():
	    event.dcc_receive_list(id = num, filename = name, host = host,
				   port = port, size = size, nick = nick,
				   addr = addr, net = net)
	for num, (pid, name, size, nick, nnet) in main.dcc_sending.items():
	    event.dcc_send_list(id = num, filename = name, size = size,
				nick = nick, net = net)

def cmd_eval(ev):
    """eval <expression>
Evaluates a Python <expression> and prints the result."""
    event.eval_result(expr = ev.tail, result = eval(
        ev.tail, main.__dict__, locals()))

def cmd_exception(event):
    raise AttributeError, 'lamettaa'

def cmd_exit(event):
    """exit <reason>
Quits the connection to the current server and deletes the associated
windows."""
    net = event.window.buf.net
    if net.server <> None:
        net.server.out_line('QUIT :' + event.tail)

def cmd_help(ev):
    """help [<command>]
Prints out the help text associated with <command>, or the main help
if none is given."""
    if ev.tail:
        help = globals().get('cmd_' + ev.tail, None)
        if hasattr(help, '__doc__') and help.__doc__:
            for line in string.split(help.__doc__, '\n'):
                event.help_text(text = line)
            return
        event.error(error = 'No help for ' + ev.tail)
    else:
        event.help_text(text = 'help <command> for help on any command.  The commands defined are:')
        cmds = ''
        glist = globals().items()
        glist.sort()
        for k, v in glist:
            if len(k) >= 4 and k[:4] == 'cmd_' and hasattr(v, '__doc__') \
               and v.__doc__:
                cmds = cmds + k[4:] + ' '
        event.help_text(text = cmds)

def cmd_invite(event):
    """invite <nick>
Invites <nick> to the current channel.  Requires operator status."""
    window = event.window
    window.buf.net.server.out_line('INVITE ' + event.tail + ' ' + window.name)

def cmd_join(event):
    """join [<channel> [<key>]]
Joins <channel> and opens a new window for it, or rejoins the channel
associated with the current window if no <channel> is given.  If <key>
is given, uses it as the channel key."""
    chan, key = command_parse(event, 2, event.window.name, None)
    if key <> None:
        event.window.buf.net.server.out_line('JOIN ' + chan + ' :' + key)
    else:
        event.window.buf.net.server.out_line('JOIN ' + chan)

def cmd_kick(event):
    """kick <nick> <reason>
Removes <nick> from the current channel.  Requires channel operator
status."""
    nick, reason = command_parse(event, 2, None, '')
    if nick <> None:
        event.window.buf.net.server.out_line('KICK ' + event.window.name + ' '
					     + nick + ' :' + reason)

def cmd_leave(event):
    """leave <reason>
Leaves the current channel and deletes the associated window."""
    window = event.window
    name = window.name
    buf = window.buf
    net = buf.net
    server = net.server
    if main.buffers[net] == buf:
        if server <> None:
            server.terminate()
        net.delete_buffers()
    else:
        if ircserv.is_channel(name) and server \
           and server.channels.has_key(name):
            server.out_line('PART ' + name + ' :' + event.tail)
        else:
            buf.pre_delete()
            del main.buffers[(net, name)]

def cmd_me(ev):
    """me <message>
Sends a CTCP action to the current channel."""
    window = ev.window
    net = window.buf.net
    net.server.out_line('PRIVMSG ' + window.name + ' :\001ACTION ' + ev.tail
                        + '\001')
    event.own_ctcp_action(net = net, target = window.name, message = ev.tail)

def cmd_mode(event):
    """mode <mode-string>
Sets mode bits on the current channel.  Requires channel operator
status."""
    window = event.window
    window.buf.net.server.out_line('MODE ' + window.name + ' ' + event.tail)

def cmd_msg(ev):
    """msg <channel/nick> <message>
Sends a (private) message to <channel> or <nick>."""
    chan, message = command_parse(ev, 2, None, None)
    if message <> None:
        net = ev.window.buf.net
	if chan == '.':
	    chan = net.last_msg_to
	elif chan == ',':
	    chan = net.last_msg_from
	net.last_msg_to = chan
        net.server.out_line('PRIVMSG ' + chan + ' :' + message)
        event.own_privmsg(net = net, target = chan, message = message)
        if not main.buffers.has_key((net, chan)):
            i = 0
            while i < len(net.msg_nicklist):
                if ircserv.low_equal(chan, net.msg_nicklist[i]):
                    del net.msg_nicklist[i]
                    break
                i = i + 1
            net.msg_nicklist.insert(0, chan)

def cmd_names(event):
    """names [<channel>]
Lists the members of <channel>, or of the current channel if none is
given."""
    window = event.window
    (chan,) = command_parse(event, 1, window.name)
    window.buf.net.server.out_line('NAMES ' + chan)

def cmd_net(event):
    """net <server-name>
Opens a connection to <server-name>.  Does not terminate any existing
connections."""
    name = event.tail
    ircnet.IRCNet(name, [], event.window.buf.net.nicks, [(name, 6667)])

def cmd_nick(ev):
    """nick <nick>
Changes the nickname on the current server to <nick>."""
    server = ev.window.buf.net.server
    if ev.tail in server.nicks:
	server.nicks.remove(ev.tail)
	server.nicks.insert(0, ev.tail)
    server.out_line('NICK ' + ev.tail)

def cmd_nicklist(ev):
    """nicklist
Shows the list of nicks you have exchanged msgs with."""
    net = ev.window.buf.net
    event.show_nicklist(net = net, nicklist = net.msg_nicklist)

def cmd_notice(ev):
    """notice <channel/nick> <data>
Sends an IRC notice to <channel/nick> containing the text <data>."""
    chan, data = command_parse(ev, 2, None, None)
    if data <> None:
        net = ev.window.buf.net
        net.server.out_line('NOTICE ' + chan + ' :' + data)
        event.own_notice(net = net, target = chan, message = data)

def cmd_opall(ev):
    """opall <nick>*
Gives operator status to all on the current channel except for the
explicitly listed ones."""
    no_ops = string.split(ev.tail, ' ')
    net = ev.window.buf.net
    channel_name = ev.window.name
    channel = net.server.channels[channel_name]
    nicks = map(lambda v: v[0].nick,
	filter(lambda v, no_ops=no_ops: '@' not in v[1].mode \
		and v[0].nick not in no_ops,
	    channel.hash.values()))
    while nicks:
	n = min(3, len(nicks))
	net.server.out_line('MODE ' + channel_name + ' +' + (n * 'o') + ' '
	    + reduce(lambda x, y: x + ' ' + y, nicks[:n]))
	nicks = nicks[n:]

def cmd_ping(event):
    """ping
Pings the current server."""
    server = event.window.buf.net.server
    server.out_line('PING ' + server.name)
    server.ping_time = time.time()

def cmd_query(event):
    """query [<nick>]
Opens a new window for private conversation with <nick>, or removes
the query window if no <nick> is given."""
    screen = event.screen
    buf = event.window.buf
    net = buf.net
    if event.tail:
        if not main.buffers.has_key((net, event.tail)):
            nbuf = buffer.Buffer(net, event.tail)
            main.buffers[(net, event.tail)] = nbuf
            screen.add_window(net, event.tail, nbuf)
    elif not ircserv.is_channel(event.window.name):
        buf.pre_delete()
        del main.buffers[(net, event.window.name)]

def cmd_quit(event):
    """quit <reason>
Quits the connection to the IRC network."""
    event.window.buf.net.quit(event.tail)

def cmd_quitall(event):
    """quitall <reason>
Quits the connections to all IRC networks."""
    for net in main.nets:
        net.quit(event.tail)

def cmd_quote(event):
    """quote <data>
Sends arbitrary data to the current server."""
    event.window.buf.net.server.out_line(event.tail)

def cmd_reconnect(ev):
    """reconnect
Reconnects to some server of the current IRC net."""
    ev.window.buf.net.reconnect()

def cmd_reload(event):
    """reload
Reloads the client code and the user configuration."""
    main.reload = 1
    runloop.quit = 1

def cmd_server(ev):
    """server <address> [<port>]
Changes the server of the current IRC net to <address> with an
optional <port>."""
    address, port = command_parse(ev, 2, None, 6667)
    if address <> None:
	net = ev.window.buf.net
	if net.server.active:
	    net.server.out_line('QUIT :Changing servers')
	    net.server.sock.shutdown(2)
	    net.server.sock.close()
	ev.window.buf.net.reconnect_server((address, int(port)))

def cmd_swap(ev):
    """swap
Swaps this window with the next window in the window position list."""
    if main.screen:
	main.screen.swap_next()

def cmd_terminate(event):
    """terminate
Exits the program immediately."""
    main.reload = 0
    runloop.quit = 1

def cmd_topic(event):
    """topic [<topic>]
Sets a new topic on the current channel, or displays the current topic
if none is given.  If the channel has mode +t set, setting a new topic
requires channel operator status."""
    window = event.window
    server = window.buf.net.server
    if event.tail:
        server.out_line('TOPIC ' + window.name + ' :' + event.tail)
    else:
        server.out_line('TOPIC ' + window.name)

def cmd_unban(event):
    """unban <address>
Removes a ban on <address> on the current channel.  Requires channel
operator status."""
    tail = event.tail
    if '@' in tail:
        if '!' in tail:
            addr = tail
        else:
            addr = '*!' + tail
    else:
        addr = '*!*@' + tail
    window = event.window
    window.buf.net.server.out_line('MODE ' + window.name + ' -b ' + addr)

def cmd_version(ev):
    """version [<nick>]
Retrieves the version of <nick>s client by means of a ctcp query,
or prints the version of own client if no nick is given."""
    if ev.tail:
	ev.window.buf.net.server.out_line('PRIVMSG ' + ev.tail
					  + ' \001VERSION\001')
    else:
	event.own_version(net = ev.window.buf.net,
			  client = 'clirc ' + main.version)

def cmd_whois(event):
    """whois <nick>
Retrieves user information on <nick>."""
    tail = event.tail
    event.window.buf.net.server.out_line('WHOIS ' + tail + ' ' + tail)

reloadable.register_module(__name__)

## End. ##
