#!/usr/bin/env python

# MSNCP 0.7.0, a MSN client written on Curses and Python.
# Copyright (C) 2006 Sebastian Santisi <s@ntisi.com.ar>
#
# 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 of the License, 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

#
# WARNING: This is beta code; IT COULD CHANGE (AND WILL CHANGE) for it's
# first stable release.
#

import sys
import select
import string
import os

import curses	# Python 1.6
import time
import urllib
import socket
import thread

import msnlib
import msncb
import windows
import items
import constants

# MSNCP version
version = "0.7.0 Beta"
year= "2006"

def null(s = ''):
	"""From Alberto Bertogli's msnlib"""
	"Null function, useful to void debug ones"
	pass

msnlib.debug = null
msncb.debug = null

# Configuration defaults
config = constants.config_default

# Language defaults
language = constants.language_default

# Lists
openedchats = []
currentchat = None
contactlist = None

# Panel objects
leftmenu = None
centralwin = None
rightmenu = None
bottom = None
topmenu = None
configwin = None

# BETA: Class contactlist
class classcontactlist:
	by_group = 'by group'
	by_status = 'by status'

	def __init__(self, by = None):
		self.reallist = []
		self.list = []

		if not by:
			by = classcontactlist.by_status
		self.by = by

		self.group_gid = []
		self.group_status = [
			items.group(language['group onlines label'], language['group onlines'], 0),
			items.group(language['group offlines label'], language['group offlines'], 1)
			]

		if by == classcontactlist.by_status:
			self.groups = self.group_status
		else:
			self.groups = self.group_gid

		self.list += self.groups

	def setby(self, by):
		self.by = by

		# BETA
		if by == 'by group':
			config['show groups'] = 1
		else:
			config['show groups'] = 0

		for i in range(len(self.list) - 1, -1, -1):
			del self.list[i]
		self.list += self.reallist

		if by == 'by group':
			self.groups = self.group_gid
		else:
			self.groups = self.group_status

		for i in range(len(self.list) - 1, -1, -1):
			contact = self.list[i]
			if not isinstance(contact, items.group):
				self.update(contact)

		self.list += self.groups
		self.sort()
		rightmenu.refreshlist()

		#FIXME:
		rightmenurefresh()

	def addcontact(self, contact):
		self.reallist.append(contact)

		if self.by == 'by status':
			if self.groups[contact.offline].opened:
				self.list.append(contact)
				self.sort()
				# refreshlist?
		else:
#			if self.groups[contact.gid].opened:
				self.list.append(contact)
				self.sort()

	def delcontact(self, contact):
		for i in range(len(self.reallist) - 1, -1, -1):
			if self.reallist[i] == contact:
				del self.reallist[i]
				break

		for i in range(len(self.list) - 1, -1, -1):
			if self.list[i] == contact:
				del self.list[i]
				break

		rightmenu.refreshlist()

	def addgroup(self, group):
		self.group_gid.append(group)

		if self.by == 'by group':
			self.list.append(group)

	def sort(self):
		if self.by == 'by status':
			self.list.sort(items.componline)
		else:
			self.list.sort(items.compgroup)
		self.reallist.sort(items.contact.componline)

	def findemail(self, email):
		for contact in self.reallist:
			if contact.email == email:
				return contact
		return None

	def dellist(self):
		self.reallist = []
		self.group_gid = []

		for i in range(len(self.list) - 1, -1, -1):
			del self.list[i]

		if self.by == 'by status':
			self.list += self.groups
			self.sort()
		rightmenu.refreshlist()

	def update(self, contact):
		if self.by == 'by group':
			if (contact not in self.list) and (self.groups[contact.gid].opened):
				self.list.append(contact)
				self.sort()
				rightmenu.refreshlist()
				return
			if (contact in self.list) and (not self.groups[contact.gid].opened):
				self.list.remove(contact)
				self.sort()
				rightmenu.refreshlist()
				return
		else:
			if (contact not in self.list) and (self.groups[contact.offline].opened):
				self.list.append(contact)
				self.sort()
				rightmenu.refreshlist()
				return
			if (contact in self.list) and (not self.groups[contact.offline].opened):
				self.list.remove(contact)
				self.sort()
				rightmenu.refreshlist()
				return

	def fold(self, group):
		if group.opened:
			group.opened = 0
			for i in range(len(self.list) - 1, -1, -1):
				if isinstance(self.list[i], items.group):
					continue
				if self.by == 'by status':
					if self.list[i].offline == group.gid:
						del self.list[i]
				else:
					if self.list[i].gid == group.gid:
						del self.list[i]
		else:
			group.opened = 1
			for contact in self.reallist:
				if self.by == 'by status':
					if contact.offline == group.gid:
						self.list.append(contact)
				else:
					if contact.gid == group.gid:
						self.list.append(contact)
		self.sort()
		rightmenu.refreshlist()

# LOW LEVEL FUNCTIONS

def decode(str, encoding = None):
	if not encoding:
		encoding = config['encoding']

	stru = str.decode('utf-8')

	ret = []
	for cu in stru:
		try: cu.encode(encoding)
		except: ret.append('?')
		else: ret.append(cu.encode(encoding))
	return ''.join(ret)

def encode(str, encoding):
	stru = str.decode(config['encoding'])

	ret = []
	for cu in stru:
		try: cu.encode(encoding)
		except: ret.append('?')
		else: ret.append(cu.encode(encoding))
	return ''.join(ret)

def parse_configfile(file, config = {}):
	"""From Alberto Bertogli's msnlib"""
	"""Parses a simple config file, and returns a var:value dict"""
	try:
		fd = open(file)
	except:
		return None
	lines = fd.readlines()

	for i in lines:
		i = i.strip()
		if i.find('=') < 0:
			continue
		if i[0] == '#':
			continue
		var, value = i.split('=', 1)
		var = var.strip()
		value = value.strip()
		config[var] = value
	return config

def configure(config):
	booleans = [ 'log history', 'leftmenu scroll', 'rightmenu scroll',
		'reconnect', 'startup connect', 'visual bell', 'sound bell',
		'show sbds', 'log sbds', 'log messages', 'flash sbds',
		'flash messages', 'beep sbds', 'beep messages', 'update nicks',
		'show groups', 'no colors', 'transparent background',
	]

	numerics = [ 'remember password', 'leftmenu width', 'rightmenu width',
		'centralwin heightbox', 'ping frequency', 'wait until reconnect',
		'syncing timeout',
	]

	three = [ 'log connections', 'log status', 'log nickchanges',
		'show connections', 'show status', 'show nickchanges',
		'flash connections', 'flash status', 'flash nickchanges',
		'beep connections', 'beep status', 'beep nickchanges'
	]

	for elem in booleans:
		if config.has_key(elem):
			if config[elem] in (1, '1', 'true', 'True', 'yes', 'Yes', 'enabled'):
				config[elem] = 1
			else:
				config[elem] = 0

	for elem in numerics:
		if config.has_key(elem):
			try:
				config[elem] = int(config[elem])
			except:
				raise 'Numeric', 'Config key [%s] must be integer' % elem

	for elem in three:
		if config.has_key(elem):
			if config[elem] in (2, '2', 'true', 'True', 'yes', 'Yes'):
				config[elem] = 2
			elif config[elem] in (1, '1'):
				config[elem] = 1
			else:
				config[elem] = 0

def makeconfig():
	# FIXME: It checks for permisions, not for existece.
	if os.access(os.environ['HOME'] + '/.msncp/' , os.W_OK):
		if os.access(os.environ['HOME'] + '/.msncp/history/' , os.W_OK):
			return
		os.mkdir(os.environ['HOME'] + '/.msncp/history/', 0700)
		return
	os.mkdir(os.environ['HOME'] + '/.msncp/', 0700)
	os.mkdir(os.environ['HOME'] + '/.msncp/history/', 0700)
	os.mkdir(os.environ['HOME'] + '/.msncp/languages/', 0700)

def now():
	"""From Alberto Bertogli's msnlib"""
	"Returns the current time, in tuple format"
	return time.localtime(time.time())

def beep(visual = 0):
	if visual and config['visual bell']:
		curses.flash()
	if not visual and config['sound bell']:
		sys.stdout.write('\a')


# BETA: Some of this functions does the same.
def openedchatsfindemail(openedchats, email):
	for i in range (0, len(openedchats)):
		if email in openedchats[i].email:
			# FIXME: Multichats
			return i
	return -1

def openedchatsfindemail2(email):
	global openedchats

	for i in range (0, len(openedchats)):
		if len(openedchats[i].email) == 1 and email in openedchats[i].email:
			return i
	return -1

# CONSERVAR
def sendmessage(email, message, sbd = None):
	global currentchat
	i = openedchatsfindemail(openedchats, email)
	if i == -1:
		chat = items.chat(email, m.users[email].nick)
		openedchats.append(chat)
		leftmenurefresh()
	else:
		chat = openedchats[i]

	chat.addmessage(message)

	if currentchat == chat:
		try:
#			centralwin.setchat(currentchat)
			centralwin.writemessage()
			centralwin.noutrefresh()
		except: pass
	elif chat.lines != []: # TOTALLY BETA!!!, PLEASE FIXME NOW!!!
		chat.lines.append(chat.messages[-1].length(centralwin.mens.width - 1))
		chat.totallines += chat.lines[-1]

	if topmenu:
		try: topmenu.noutredraw()
		except: pass
	if configwin:
		try: configwin.noutrefresh()
		except: pass

def sendmessagei(i, message, echo = 1):
	global openedchats, leftmenu, centralwin
	if not i and echo:
		try:
			bottom.write(message)
			bottom.noutrefresh()
		except: pass

	chat = openedchats[i]

	chat.addmessage(message)
				# FIXME
	if chat == currentchat: #leftmenu.current == i:
		try:
#			centralwin.setchat(chat)
			centralwin.writemessage()
			centralwin.noutrefresh()
		except: pass
	else:
		if i:
			chat.unread += 1
			leftmenurefresh()

		# TOTALLY BETA!!!, PLEASE FIXME NOW!!!
		if chat.lines != []:
			chat.lines.append(chat.messages[-1].length(centralwin.mens.width - 1))
			chat.totallines += chat.lines[-1]

	if topmenu:
		try: topmenu.noutredraw()
		except: pass
	if configwin:
		try: configwin.noutrefresh()
		except: pass

# ACT OPENCHAT
def openchatwindow(email, sbd = None, kind = None, message = None, focus = 0):
	global openedchats, leftmenu, currentchat

	if sbd and ocfindsbd(sbd):
		raise 'Trying to open new window and there\'s SBD'

	contact = contactlist.findemail(email)
	if not contact:
		raise 'Unable to find (%s) email' % email

	chat = items.chat(email, contact.nick)
	chat.sbd = sbd
	openedchats.append(chat)

	if focus:
		leftmenu.current = len(openedchats) - 1
		currentchat = chat
		try:
			centralwin.setchat(currentchat)
			centralwin.noutrefresh()
		except: pass

	leftmenurefresh()

	if message:
		sendmessagei(len(openedchats) - 1, message)


# It open a new SBD with the provided e-mail
def opensbd(email):
	sb = msnlib.sbd()
	sb.state = 'xf'
	sb.type = 'invite'
	sb.emails.append(email)
	m.submit_sbd(sb)# no need to connect it yet
			# we set the orig_tid of the sbd to the next tid (that
			# is, the tid the XFR is going to have), in order to
			# be able to identify it later, in cb.cb_xfr()
	sb.orig_tid = str(m.tid)
	m._send('XFR', 'SB')
	return sb

# Send the "is typing"
def mandatyping(sbd):
	me = 'MIME-Version: 1.0\r\nContent-Type: text/x-msmsgscontrol\r\nTypingUser: %s\r\n\r\n\r\n' % m.email
	size = len(me.encode('utf-8'))
	m._send('MSG', 'A ' + str(size) + '\r\n' + me.encode('utf-8'), sbd, raw = 1)

# It finds an chat that matches with the desired SBD.
def ocfindsbd(sbd):
	global openedchats

	for i in range(1, len(openedchats)):
		if openedchats[i].sbd == sbd:
			return i
	return 0
	# The 0 must be _allways_ the SERVER chat.


# It find an inactive chat that matches with the e-mail
def ocfindemail(email):
	global openedchats

	for i in range(0, len(openedchats)):
		if (not openedchats[i].sbd) and openedchats[i].email[0] == email:
			return i
	return 0
	# The 0 must be _always_ the SERVER chat.

def oclistemailsi(email):
	global openedchats

	emaillist = []

	for i in range(0, len(openedchats)):
		if email in openedchats[i].email:
			emaillist.append(i)

	return emaillist


def sendtoemail(email, message, server = 0, echo = 1):
	chatlist = oclistemailsi(email)

	if server:
		chatlist = [0] + chatlist

	for i in chatlist:
		sendmessagei(i, message, echo)

	if len(chatlist) and topmenu:
		topmenu.noutredraw()
	if len(chatlist) and configwin:
		try: configwin.noutrefresh()
		except: pass

def log_msg(email, type, msg, mtime = 0, users = []):
	"""From Alberto Bertogli's msnlib"""
	"""Logs the message or event of the 'type', related to 'email',
	with the content 'msg', to a file in the specified directory.  See
	documentation for more specific details, specially about
	formatting."""

	if not config['log history']:
		return

	if config['profile']:
		prepend = config['profile'] + '::'
	else:
		prepend = ''
	if users:
		# copy and sort the user list, so we log always to the same
		# file regarding the order the users were joined

		# FIXME: sometimes we crash because filename is too long
		usorted = users[:]
		usorted.sort()
		file = config['history directory'] + '/' + prepend + 'M::'
		file += string.join(usorted, ',')
	else:
		file = config['history directory'] + '/' + prepend + email
	file += '.msnlog'
	if not mtime:
		mtime = time.time()
	out = ''
	out += time.strftime('%Y-%m-%d %H:%M:%S ', time.localtime(mtime))
	if type == 'out':
		out += m.email + ' '
	else:
		out += email + ' '
#	out += email + ' '
	if type == 'in':
		out += '<<< '
		lines = msg.split('\n')
		if len(lines) == 1:
			 out += msg + '\n'

		else:
			out += '\n\t'
			out += string.join(lines[:], '\n\t')
			out += '\n'
	elif type == 'out':
		out += '>>> ' + msg + '\n'
	elif type == 'status':
		out += '*** ' + msg + '\n'
	elif type == 'multi':
		out += '+++ ' + msg + '\n'
	elif type == 'realnick':
		out += '--- ' + msg + '\n'

	fd = open(file, 'a')
	fd.write(decode(out, config['log encoding']))
	fd.close()
	del(fd)
	return

def leftmenurefresh():
	global leftmenu, topmenu, centralwin, openedchats

	try:
		leftmenu.refreshlist()
		leftmenu.noutrefresh()
	except: pass

	# FIXME
	try:
#		centralwin.setchat(openedchats[leftmenu.current])
		centralwin.noutrefresh()
	except: pass

	if topmenu:
		try: topmenu.noutredraw()
		except: pass
	if configwin:
		try: configwin.noutrefresh()
		except: pass

def rightmenurefresh():
	global rightmenu, topmenu

	try:
		rightmenu.refreshlist()
		rightmenu.noutrefresh()
	except: pass

	if topmenu:
		try: topmenu.noutredraw()
		except: pass
	if configwin:
		try: configwin.noutrefresh()
		except: pass

#############
# CALLBACKS #
#############

# BETA: For make_menu()
m = msnlib.msnd()

def new_msnd():
	global m

	m = msnlib.msnd()
	m.cb = msncb.cb()
	m.encoding = 'utf-8'

	m.cb.adg = cb_adg
	m.cb.rmg = cb_rmg
	m.cb.reg = cb_reg
	m.cb.lst = cb_lst
	m.cb.lsg = cb_lsg
	m.cb.msg = cb_msg
	m.cb.iln = cb_iln
	m.cb.nln = cb_nln
	m.cb.fln = cb_fln
	m.cb.joi = cb_joi
	m.cb.iro = cb_iro
	m.cb.bye = cb_bye
	m.cb.qng = cb_qng
	m.cb.out = cb_out
	m.cb.add = cb_add
	m.cb.rem = cb_rem
	m.cb.rea = cb_rea
	m.cb.err = cb_err

# BETA: There's no group support

# Add
def cb_adg(md, type, tid, params):
	msncb.cb_adg(md, type, tid, params)

# Rem
def cb_rmg(md, type, tid, params):
	msncb.cb_rmg(md, type, tid, params)

# Ren
def cb_reg(md, type, tid, params):
	msncb.cb_reg(md, type, tid, params)

# When a new user joins the list
def cb_lst(md, type, tid, params):
	p = params.split(' ')
	email = tid
	nick = urllib.unquote(p[0])

	listmask = int(p[1])
        if len(p) == 3:
                groups = p[2]
        else:
                groups = '0'

        # we only use one main group id
        gid = int(groups.split(',')[0])

	contact = contactlist.findemail(email)
	if not contact:
        	# in forward
		if listmask & 1:
			contact = items.contact(email, nick, 'FLN', group = gid)
			contactlist.addcontact(contact)
#			rightmenurefresh()
			if listmask & 4:
				contact.blocked = 1

	        # in reverse
#	        if listmask & 8:
#	                user.lists.append('R')
#	                md.reverse[email] = user

	        # in allow
#	        if listmask & 2:
#	                user.lists.append('A')

	        # in block
#	        if listmask & 4:
#	                user.lists.append('B')

	msncb.cb_lst(md, type, tid, params)

def cb_lsg(md, type, tid, params):
	p = params.split(' ')
	gid = int(tid)
	name, unk = p[0:]
	name = urllib.unquote(name)

	group = items.group(name, name, gid)
	contactlist.addgroup(group)

	msncb.cb_lsg(md, type, tid, params)


def cb_msg(md, type, tid, params, sbd):
	global openedchats, leftmenu

	t = tid.split(' ')
	email = t[0]

        # parse
	lines = params.split('\r\n')
	headers = {}
	eoh = 0
	for i in lines:
                # end of headers
                if not i:
                        break
                tv = i.split(':', 1)
                type = tv[0]
                value = tv[1].strip()
                headers[type] = value
                eoh += 1
	eoh +=1

	if email == 'Hotmail':
		msncb.cb_msg(md, type, tid, params, sbd)
		return

	if headers.has_key('Content-Type') and headers['Content-Type'] == 'text/x-msmsgscontrol':
		mtime = time.time()

		i = ocfindsbd(sbd)
		if i:
			openedchats[i].typing = mtime
			leftmenurefresh()

		if i == leftmenu.current:
			try: centralwin.noutrefresh()
			except: pass

		if topmenu:
			try: topmenu.noutredraw()
			except: pass
		if configwin:
			try: configwin.noutrefresh()
			except: pass

		msncb.cb_msg(md, type, tid, params, sbd)
		return
	elif headers.has_key('Content-Type') and headers['Content-Type'] == 'text/x-clientcaps':
		# ignore the x-clientcaps messages generated from gaim
		pass
        elif headers.has_key('Content-Type') and (headers['Content-Type'] == 'text/x-mms-emoticon'):
		# BETA: Ignore personalized smilies.
		pass
	message = items.message('INC', string.join(lines[eoh:], '\n'), (email))

	i = ocfindsbd(sbd)
	if i:
		openedchats[i].typing = 0
		leftmenurefresh()
		sendmessagei(i, message)
	else:
		sendmessage(email, message, sbd)

	if len(sbd.emails) > 1:
		log_msg(email, 'in', string.join(lines[eoh:], '\n'), users = sbd.emails)
	else:
		log_msg(email, 'in', string.join(lines[eoh:], '\n'))

	msncb.cb_msg(md, type, tid, params, sbd)

def cb_iln(md, type, tid, params):
	global contactlist

	t = params.split(' ')
	status = t[0]
	email = t[1]
	nick = md.users[email].nick
#	realnick = md.users[email].realnick
	realnick = urllib.unquote(t[2])

	contact = contactlist.findemail(email)
	if contact:
		if config['update nicks']:
			m.userren(email, realnick)
			contact.changeall(realnick, status, realnick)
		else:
			contact.changeall(nick, status, realnick)
		contactlist.update(contact)
		contactlist.sort()
		rightmenurefresh()

	log_msg(email, 'status', 'connected as ' + msnlib.reverse_status[status])
	if config['log nickchanges']:
		log_msg(email, 'realnick', realnick)

	msncb.cb_iln(md, type, tid, params)

def cb_nln(md, type, tid, params):
	global contactlist

	status = tid
	t = params.split(' ')
	email = t[0]
	nick = md.users[email].nick
	realnick = urllib.unquote(t[1])

	contact = contactlist.findemail(email)
	if contact:
		if email == m.email:
			contact.status = status
		else:
			contact.changeall(nick, status, realnick)
		contactlist.update(contact)
		contactlist.sort()
		rightmenurefresh()

	if md.users[email].status == 'FLN' and status != 'FLN':
		if config['show connections']:
			sendtoemail(email, items.message('NLN', 'msg event connection', (email, msnlib.reverse_status[status])), config['show connections'] - 1)
		if config['log connections']:
			log_msg(email, 'status', 'connected as %s' % msnlib.reverse_status[status])
		if config['beep connections']:
			beep(0)
		if config['flash connections']:
			beep(1)
	elif status != md.users[email].status:
		if config['show status']:
			sendtoemail(email, items.message('NLN', 'msg event status', (email, msnlib.reverse_status[status])), config['show status'] - 1)
		if config['log status']:
			log_msg(email, 'status', msnlib.reverse_status[status])
		if config['beep status']:
			beep(0)
		if config['flash status']:
			beep(1)
	if realnick != md.users[email].realnick:
		if config['show nickchanges']:
			sendtoemail(email, items.message('NLN', 'msg event nick', (email, realnick)), config['show nickchanges'] - 1)
		if config['log nickchanges']:
			log_msg(email, 'realnick', realnick)
		if config['beep nickchanges']:
			beep(0)
		if config['flash nickchanges']:
			beep(1)
		if config['update nicks'] and email != m.email:
			m.userren(email, realnick)

	msncb.cb_nln(md, type, tid, params)


def cb_fln(md, type, tid, params):
	email = tid
	nick = md.users[email].nick
#	u = m.users[email]
#	if u.sbd and u.sbd.msgqueue:
#		printl(c.bold + "The following messages for " + nick + " will be discarded:\n")
#		for msg in u.sbd.msgqueue:
#			printl(c.bold + '\t>>> ' + c.normal + msg + '\n')

	contact = contactlist.findemail(email)
	if contact:
		contact.changestatus('FLN')
		contactlist.update(contact)
		contactlist.sort()
		rightmenurefresh()

	if config['show connections']:
		sendtoemail(email, items.message('FLN', 'msg event disconnection',(email)), config['show connections'] - 1)

	if config['log connections']:
		log_msg(email, 'status', 'disconnect')
	if config['beep connections']:
		beep(0)
	if config['flash connections']:
		beep(1)

	msncb.cb_fln(md, type, tid, params)

def cb_joi(md, type, tid, params, sbd):
	global openedchats, currentchat
	email = tid

	i = ocfindsbd(sbd)
	if i:
		openedchats[i].email.append(email)
		nick = params
		if email in m.users.keys():
			nick = m.users[email].nick
		openedchats[i].nicks.append(nick)
		leftmenurefresh()
		message = items.message('JOI', 'msg event joined', (email))
		sendmessagei(i, message)

		log_msg(email, 'multi', 'join', users = sbd.emails + [email])

	else:
		log_msg(email, 'multi', 'opened by you')

		i = ocfindemail(email)
		if i:
			chat = openedchats[i]
		else:
			chat = items.chat(email, m.users[email].nick)
			openedchats.append(chat)
			currentchat = chat
			try:
				centralwin.setchat(currentchat)
				centralwin.noutrefresh()
			except: pass
			i = len(openedchats) - 1
		chat.sbd = sbd
		leftmenurefresh()

		for msg in sbd.msgqueue:
			log_msg(email, 'out', msg)
			if not i:
				message = items.message('OUT', msg)
				sendmessagei(i, message)

	msncb.cb_joi(md, type, tid, params, sbd)
	m.users[email].sbd = None


def cb_iro(md, type, tid, params, sbd):
	global openedchats

	p = params.split(' ')
	uid, ucount, email, realnick = p

	i = ocfindsbd(sbd)
	if i:
		openedchats[i].email.append(email)
		nick = realnick
		if email in m.users.keys():
			nick = m.users[email].nick
		openedchats[i].nicks.append(nick)
		leftmenurefresh()
		message = items.message('IRO', 'msg event joined', (email))
		sendmessagei(i, message)

		log_msg(email, 'multi', 'join', users = sbd.emails + [email])
		if config['beep sbds']:
			beep(0)
		if config['flash sbds']:
			beep(1)
	else:
		i = ocfindemail(email)
		if i:
			chat = openedchats[i]
		else:
			# FIXME Ya hay una funcion para esto
			nick = realnick
			if m.users.has_key(email):
				nick = m.users[email].nick
			chat = items.chat(email, nick)
			openedchats.append(chat)
			i = len(openedchats) - 1
		chat.sbd = sbd
		leftmenurefresh()
		if config['log sbds']:
			log_msg(email, 'multi', 'opened')
		if config['show sbds']:
			sendmessagei(i, items.message('IRO', 'msg event chatopened', (email)))
			sendmessagei(0, items.message('IRO', 'msg event chatopened', (email)))
		if config['beep sbds']:
			beep(0)
		if config['flash sbds']:
			beep(1)

	msncb.cb_iro(md, type, tid, params, sbd)
	m.users[email].sbd = None


def cb_bye(md, type, tid, params, sbd):
	global openedchats
	email = tid

	i = ocfindsbd(sbd)
	if not i:
		raise 'CB_BYE: User leaves chat without chat window opened.'
	chat = openedchats[i]

	if len(sbd.emails) == 1:
		if not params:
			if config['show sbds']:
				sendmessagei(i, items.message('BYE', 'msg event chatclosed', (email)))
				sendmessagei(0, items.message('BYE', 'msg event chatclosed', (email)))
			if config['log sbds']:
				log_msg(email, 'multi', 'closed')
		elif params == '1':
			if config['show sbds']:
				sendmessagei(i, items.message('BYE', 'msg event chatexpired', (email)))
				sendmessagei(0, items.message('BYE', 'msg event chatexpired', (email)))
			if config['log sbds']:
				log_msg(email, 'multi', 'expired')
		if config['beep sbds']:
			beep(0)
		if config['flash sbds']:
			beep(1)
		chat.sbd = None
		leftmenurefresh()
	else:
		# HAY QUE DAR LA OPCION DE LOGUEARLO EN EL SERVER
		sendmessagei(i, items.message('BYE', 'msg event leaves', (email)))
		for i in range (0, len(chat.email)):
			if chat.email[i] == email:
				del chat.email[i]
				del chat.nicks[i]
				break

		leftmenurefresh()

		log_msg(email, 'multi', 'left', users = sbd.emails)

#	msncb.cb_bye(md, type, tid, params, sbd)
	# !!!
	# The msncb have a bug if the initial users leaves the chat, it's
	# our new msncb.cb_bye.

        "Handles a user sb disconnect"
        email = tid
        if len(sbd.emails) > 1:
                msncb.debug('BYE: user %s leaving sbd' % email)
                if email in sbd.emails:
                        sbd.emails.remove(email)
        else:
                msncb.debug('BYE: closing %s' % str(sbd))
                md.close(sbd)

def cb_qng(md, type, tid, params):
	global waitingpong
	waitingpong = 0

def cb_out(md, type, tid, params):
	global isonline, leftmenu

	act_disconnect()

	sendmessagei(0, items.message('SER', 'err logged'))

	msncb.cb_out(md, type, tid, params)

def cb_add(md, type, tid, params):
	t = params.split(' ')
	type = t[0]
	if type == 'RL' or type == 'FL':
		email = t[2]
		nick = urllib.unquote(t[3])
	if type == 'RL':
		# User adds you.
		pass
	elif type == 'FL':
		contact = items.contact(email, nick, 'FLN')
		contactlist.addcontact(contact)
		rightmenurefresh()
	msncb.cb_add(md, type, tid, params)

def cb_rem(md, type, tid, params):
	t = params.split(' ')
	type = t[0]
	if type == 'RL' or type == 'FL':
		email = t[2]
	if type == 'RL':
		# User removes you
		pass
	elif type == 'FL':
		contact = contactlist.findemail(email)
		contactlist.delcontact(contact)
		rightmenurefresh()

	msncb.cb_rem(md, type, tid, params)

def cb_rea(md, type, tid, params):
	t = params.split(' ')
	email = t[1]
	nick = urllib.unquote(t[2])
	if email != m.email:
		contact = contactlist.findemail(email)
		if not contact: raise 'Unknow contact changes the nick.'

		contact.nick = nick
		rightmenurefresh()
	else:
		# The nicks changes.
		if email in m.users.keys() and config['update nicks']:
			contact = contactlist.findemail(email)
			contact.nick = nick
			rightmenurefresh()
		pass
	msncb.cb_rea(md, type, tid, params)

# server errors
def cb_err(md, errno, params):
	if not msncb.error_table.has_key(errno):
		desc = 'Unknown'
	else:
		desc = msncb.error_table[errno]

	sendmessagei(0, items.message('SER', 'err server', (str(errno), desc)))

	msncb.cb_err(md, errno, params)


# HIGH LEVEL ACTIONS

def act_connect(noask = 0):
	global configwin

	if isonline:
		sendmessagei(0, items.message('SER', 'msg client alconn'))
		return

	it = [
		items.config('title', language['config login email'], '', ''),
		items.config('text', '', '', decode(config['email'], config['encoding']), selected = 1),
		items.config('password', language['config login password'], '', decode(config['password'], config['encoding'])),
		items.config('checkbox', language['config login remember'], '', config['remember password']),
	]

	if not (noask and config['password'] and config['email']):
		configwin = windows.configwin(language['config login title'], it, [language['config login ok'], language['config login cancel']], act_connect_cb, maxy, maxx)
		configwin.noutrefresh() # NEEDED?
	else:
		act_connect_cb(it)

def act_connect_cb(configlist):
	new_msnd()
	m.email = encode(configlist[1].default, 'utf-8')
	m.pwd = encode(configlist[2].default, 'utf-8')
	config['remember password'] = configlist[3].default

	if config['remember password']:
		config['password'] = m.pwd
	else:
		config['password'] = ''
	if not m.pwd:
		return

	config['email'] = m.email
	if not m.email:
		return

	sendmessagei(0, items.message('SER', 'msg client logging'))
	curses.doupdate()

	thread.start_new_thread(act_connect_, ())

def act_connect_():
	global openedchats, leftmenu, isonline, language, waitingpong
	global m

	try: m.login()
	except 'AuthError', info:
		errno = int(info[0])
		if not language.has_key('err %d' % errno):
			if not msncb.error_table.has_key(errno):
				desc = 'Unknown'
			else:
				desc = msncb.error_table[errno]
		else:
			desc = language['err %d' % errno]
		sendmessagei(0, items.message('SER', 'err server' , (str(errno), desc)))
		return
	except KeyboardInterrupt:
		act_quit()
	except ('SocketError', socket.error), info:
		sendmessagei(0, items.message('SER', 'err network', str(info)))
		return
	except:
		sendmessagei(0, items.message('SER', 'err unklog'))
		return

	# Reset the waitingpong status.
	waitingpong = 0

	isonline = 1
	sendmessagei(0, items.message('SER', 'msg client userlist'))
	try:
		if m.sync():
			sendmessagei(0, items.message('SER', 'msg client waiting'))
		else:
			# FIXME: 'Error syncing users'
			sendmessagei(0, items.message('SER', 'err unklog'))
			act_disconnect()
			return
	except:
		sendmessagei(0, items.message('SER', 'err unklog'))
		act_disconnect()
		return

	start = time.time()
	try:
		while isonline and m.syn_total != m.lst_total:
			time.sleep(0.01)
			if start < time.time() - config['syncing timeout']:
			# Use a config parameter for the timeout.
				sendmessagei(0, items.message('SER', 'msg client synctimeout'))
				act_disconnect()
				return
	except:
		# The exception is only when the client exits and the thread is unfinished.
		return

	if not isonline:
		sendmessagei(0, items.message('SER', 'msg client abortconn'))
		return

	m.change_status('online')
	sendmessagei(0, items.message('SER', 'msg client connected'))
	openedchats[0].sbd = 1
	leftmenurefresh()
	rightmenurefresh()


def act_disconnect():
	global openedchats, rightmenu, leftmenu, contactlist, isonline
	if not isonline:
		sendmessagei(0, items.message('SER', 'msg client aldisc'))
		return

	for chat in openedchats[1:]:
		if chat.sbd:
			m.close(chat.sbd)
			chat.sbd = None
			log_msg(chat.email[0], 'multi', 'closed by you')
			#FIXME --^
			chat.email = [chat.email[0]]
			chat.nicks = [chat.nicks[0]]

	contactlist.dellist()
	rightmenurefresh()

	try: m.disconnect()
	except: pass

	isonline = 0

	openedchats[0].sbd = 0
	leftmenurefresh()

	sendmessagei(0, items.message('SER', 'msg client disconnected'))

def act_quit(errorcode = 0, noexit = 0):
	global isonline, openedchats, topmenu, configwin

	sendmessagei(0, items.message('SER', 'msg client quit'))

	if topmenu:
		topmenu.nouthide()
		topmenu = None

		# BETA:
		leftmenurefresh()
		rightmenurefresh()
		bottom.noutrefresh()

		curses.doupdate()
	configwin = None

	if isonline:
		act_disconnect()

	for i in range (1, len(openedchats)):
		act_closechat(1)

	act_setcurrent(0)

	bye = ''

	sendmessagei(0, items.message('---', bye), echo = 0)
	curses.doupdate()
	time.sleep(1)

	if noexit:
		return errorcode

	sys.exit(errorcode)

# BETA
def act_startcolors(colormap = None):
	cp = {}
	for i in range(0, 6 + 1):
			# curses.CURSES_PAIRS?
		cp[i] = 0

	items.cp = cp
	windows.cp = cp

	if not config['no colors']:
		try:
			curses.start_color()
		except:
			config['no colors'] = 1
			return
	else: return

	bgcolor = 0
	if config['transparent background']:
		try:
			# New on Python 2.4
			curses.use_default_colors()
			bgcolor = -1
		except:
			pass

	# BETA
	curses.init_pair(1, curses.COLOR_RED, bgcolor)
	curses.init_pair(2, curses.COLOR_RED, bgcolor)
	curses.init_pair(3, curses.COLOR_YELLOW, bgcolor)
	curses.init_pair(4, curses.COLOR_BLUE, bgcolor)
	curses.init_pair(5, curses.COLOR_MAGENTA, bgcolor)
#	curses.init_pair(6, curses.COLOR_CYAN, bgcolor)

	# NCurses-Python has no way to know the foreground color, it isn't
	# curses.COLOR_WHITE.
	curses.init_pair(6, curses.COLOR_WHITE, curses.COLOR_RED)

	for i in range(0, 6 + 1):
		cp[i] = curses.color_pair(i)

def act_language(lang = None):
	global language

	language = {}
	for key in constants.language_default.keys():
		language[key] = constants.language_default[key]

	if lang:
		parse_configfile(config['language directory'] + lang, language)

	items.message.language = language

	config['language file'] = lang

	if contactlist:
		# BETA BETA BETA BETA!!!
		contactlist.group_status[0].desc = contactlist.group_status[0].email = language['group onlines label']
		contactlist.group_status[0].name = language['group onlines']
		contactlist.group_status[1].desc = contactlist.group_status[1].email = language['group offlines label']
		contactlist.group_status[1].name = language['group offlines']

	if openedchats:
		openedchats[0].email[0] = language['server title']
		openedchats[0].nicks[0] = language['server title']
		for chat in openedchats:
			chat.lines = []
			chat.totallines = 0
	if leftmenu:
		leftmenu.title = language['leftmenu title']
		leftmenurefresh()
	if rightmenu:
		rightmenu.title = language['rightmenu title']
		rightmenurefresh()
	if centralwin:
		centralwin.setchat(openedchats[leftmenu.current])
		try: centralwin.noutrefresh()
		except: pass
	if bottom:
		try: bottom.noutrefresh()
		except: pass

def listarnoenlacorriente(emails):
	lista = []

	if not leftmenu.current:
		return [items.popupmenuitem(language['menu invite noone'], language['menu invite noone help'])]
	for contact in contactlist.reallist:
		if contact.status == 'FLN':
			continue
		if contact.email not in emails:
			lista.append(items.popupmenuitem('##' + contact.nick, contact.email, act_invite, params = (openedchats[leftmenu.current].sbd, contact.email)))
	if not lista:
		return [items.popupmenuitem(language['menu invite noone'], language['menu invite noone help'])]
	return lista

def listarsinventanaabierta():
	lista = []

	for contact in contactlist.reallist:
		if contact.status == 'FLN':
			continue
		if openedchatsfindemail2(contact.email) == -1:
			lista.append(items.popupmenuitem('##' + contact.nick, contact.email, act_openchat, [contact.email]))
	if not lista:
		return [items.popupmenuitem(language['menu new noone'], language['menu new noone help'])]
	return lista

def listarlasabiertas():
	lista = []

	for i in range(0, len(openedchats)):
		lista.append(items.popupmenuitem('##' + openedchats[i].email[0], 'FIXME', act_setcurrent, [i]))
		if i == leftmenu.current:
			item = lista[len(lista) - 1]
			item.text += ' *'
			item.selected = 1
			item.params = []
			item.action = None
	return lista

def listadelenguajes():
	lista = [items.popupmenuitem(language['menu language default'], language['menu language default help'], act_language, [None])]

	if not config['language file']:
		lista[0].selected = 1
		lista[0].text += ' *'

	try: listita = os.listdir(config['language directory'])
	except: return lista

	for file in listita:
		if file.rfind('.lng') == len(file) - len('.lng'):
			name = file[:-len('.lng')]

			# BETA: I don't need all the keys, only subset.
			lang = parse_configfile(config['language directory'] + file)

			# BETA: Think on languages.
			subset = 'undefined'
			if lang.has_key('subset'):
				subset = lang['subset']

			lista.append(items.popupmenuitem('##' + name, name + ' (' + subset + ')...', act_language, [file]))

			if config['language file'] == file:
				lista[len(lista) - 1].text += ' *'
				lista[len(lista) - 1].selected = 1
	return lista

def makemenu():
	global leftmenu, rightmenu

	item = items.popupmenuitem

	# Menu connection
	status = [
		item(language['menu status online'], language['menu status online help'], m.change_status, ['online']),
		item(language['menu status away'], language['menu status away help'], m.change_status, ['away']),
		item(language['menu status busy'], language['menu status busy help'], m.change_status, ['busy']),
		item(language['menu status brb'], language['menu status brb help'], m.change_status, ['brb']),
		item(language['menu status phone'], language['menu status phone help'], m.change_status, ['phone']),
		item(language['menu status lunch'], language['menu status lunch help'], m.change_status, ['lunch']),
		item(language['menu status idle'], language['menu status idle help'], m.change_status, ['idle']),
		item(separator = 1),
		item(language['menu status invisible'], language['menu status invisible help'], m.change_status, ['invisible']),
	]

	connection = [
		item(language['menu connection status'], language['menu connection status help'], list = status),
		item(language['menu connection nick'], language['menu connection nick help'], act_changenick, []),
		item(language['menu connection connect'], language['menu connection connect help'], act_connect, []),
		item(language['menu connection disconnect'], language['menu connection disconnect help'], act_disconnect, []),
		item(separator = 1),
		item(language['menu connection quit'], language['menu connection quit help'], act_quit, [0]),
	]

	if isonline:
		connection[2].action = None
		connection[2].params = []
		connection[2].text += ' x'

		for stat in status:
			if msnlib.reverse_status[m.status] in stat.params:
				stat.selected = 1
				stat.text += ' *'
				stat.action = None
				stat.params = []
				break
	else:
		connection[1].action = None
		connection[1].params = []
		connection[1].text += ' x'

		connection[3].action = None
		connection[3].params = []
		connection[3].text += ' x'

		connection[0].list = [item(language['menu status offline'], language['menu status offline help'])]

	# Menu conversation
	invite = listarnoenlacorriente(openedchats[leftmenu.current].email)
	newchat = listarsinventanaabierta()
	jumpto = listarlasabiertas()

	conversation = [
		item(language['menu conversation close'], language['menu conversation close help'], act_closechat, [leftmenu.current]),
		item(separator = 1),
		item(language['menu conversation invite'], language['menu conversation invite help'], list = invite), # List of onlines
		item(separator = 1),
		item(language['menu conversation new'], language['menu conversation new help'], list = newchat), # List of onlines
		item(language['menu conversation jump'], language['menu conversation jump help'], list = jumpto), # List of opened chats
		item(separator = 1),
		item(language['menu conversation scroll'] + ' (F6)', language['menu conversation scroll help'], act_setscroll, []),
		item(language['menu conversation clear'], language['menu conversation clear help'], act_clearchat, []),
		item(separator = 1),
		item(language['menu conversation save'], language['menu conversation save help'], act_savechat, [leftmenu.current]),
		item(language['menu conversation saveas'], language['menu conversation saveas help'], act_savechatas, [leftmenu.current]),
	]

	#BETA
	sort_by = [
		item(language['menu sort status'], language['menu sort status help'], contactlist.setby, ['by status']),
		item(language['menu sort groups'], language['menu sort groups help'], contactlist.setby, ['by group']),
		]

	contact = [
#		item(language['menu contact info'], language['menu contact info help']),
		item(language['menu contact rename'], language['menu contact rename help'], act_renamecontact, [rightmenu.current]),
		item(language['menu contact block'], language['menu contact block help'], act_blockcontact, [rightmenu.current]), # BETA: List of unblockeds
		item(language['menu contact unblock'], language['menu contact unblock help'], act_unblockcontact, [rightmenu.current]), # BETA: List of blockeds
		item(language['menu contact remove'], language['menu contact remove help'], act_removecontact, [rightmenu.current]),
		item(separator = 1),
		item(language['menu contact add'], language['menu contact add help'], act_addcontact, []),

		# BETA
		item(separator = 1),
		item(language['menu contact sort'], language['menu contact sort help'], list = sort_by),
	]
	if not isonline:
		contact = [
			item(language['menu contact offline'], language['menu contact offline help'])
	]

	languag = listadelenguajes()

	encoding = [
		item(language['menu encoding 7bit'], language['menu encoding 7bit help'], act_encoding, ['ascii']),
		item(separator = 1),
		item(language['menu encoding 8859-1'], language['menu encoding 8859-1 help'], act_encoding, ['latin-1']),
		item(language['menu encoding 8859-2'], language['menu encoding 8859-2 help'], act_encoding, ['iso8859-2']),
		item(language['menu encoding 8859-3'], language['menu encoding 8859-3 help'], act_encoding, ['iso8859-3']),
		item(language['menu encoding 8859-4'], language['menu encoding 8859-4 help'], act_encoding, ['iso8859-4']),
		item(language['menu encoding 8859-5'], language['menu encoding 8859-5 help'], act_encoding, ['iso8859-5']),
		item(language['menu encoding 8859-6'], language['menu encoding 8859-6 help'], act_encoding, ['iso8859-6']),
		item(language['menu encoding 8859-7'], language['menu encoding 8859-7 help'], act_encoding, ['iso8859-7']),
		item(language['menu encoding 8859-8'], language['menu encoding 8859-8 help'], act_encoding, ['iso8859-8']),
		item(language['menu encoding 8859-9'], language['menu encoding 8859-9 help'], act_encoding, ['iso8859-9']),
		item(language['menu encoding 8859-10'], language['menu encoding 8859-10 help'], act_encoding, ['iso8859-10']),
		item(language['menu encoding 8859-11'], language['menu encoding 8859-11 help'], act_encoding, ['iso8859-11']),
		item(language['menu encoding 8859-12'], language['menu encoding 8859-12 help'], act_encoding, ['iso8859-12']),
		item(language['menu encoding 8859-13'], language['menu encoding 8859-13 help'], act_encoding, ['iso8859-13']),
		item(language['menu encoding 8859-14'], language['menu encoding 8859-14 help'], act_encoding, ['iso8859-14']),
		item(language['menu encoding 8859-15'], language['menu encoding 8859-15 help'], act_encoding, ['iso8859-15']),
		item(language['menu encoding 8859-16'], language['menu encoding 8859-16 help'], act_encoding, ['iso8859-16']),
		item(separator = 1),
		item(language['menu encoding windows-1250'], language['menu encoding windows-1250 help'], act_encoding, ['cp1250']),
		item(language['menu encoding windows-1251'], language['menu encoding windows-1251 help'], act_encoding, ['cp1251']),
		item(language['menu encoding windows-1252'], language['menu encoding windows-1252 help'], act_encoding, ['cp1252']),
		item(language['menu encoding windows-1253'], language['menu encoding windows-1253 help'], act_encoding, ['cp1253']),
		item(language['menu encoding windows-1254'], language['menu encoding windows-1254 help'], act_encoding, ['cp1254']),
		item(language['menu encoding windows-1255'], language['menu encoding windows-1255 help'], act_encoding, ['cp1255']),
		item(language['menu encoding windows-1256'], language['menu encoding windows-1256 help'], act_encoding, ['cp1256']),
		item(language['menu encoding windows-1257'], language['menu encoding windows-1257 help'], act_encoding, ['cp1257']),
		item(language['menu encoding windows-1258'], language['menu encoding windows-1258 help'], act_encoding, ['cp1258']),
		item(separator = 1),
		item(language['menu encoding ibm437'], language['menu encoding ibm437 help'], act_encoding, ['cp437']),
		item(language['menu encoding ibm850'], language['menu encoding ibm850 help'], act_encoding, ['cp850']),
		item(language['menu encoding ibm852'], language['menu encoding ibm852 help'], act_encoding, ['cp852']),
		item(language['menu encoding ibm855'], language['menu encoding ibm855 help'], act_encoding, ['cp855']),
		item(language['menu encoding ibm857'], language['menu encoding ibm857 help'], act_encoding, ['cp857']),
		item(language['menu encoding ibm861'], language['menu encoding ibm861 help'], act_encoding, ['cp861']),
		item(language['menu encoding ibm862'], language['menu encoding ibm862 help'], act_encoding, ['cp862']),
		item(language['menu encoding ibm864'], language['menu encoding ibm864 help'], act_encoding, ['cp864']),
		item(language['menu encoding ibm866'], language['menu encoding ibm866 help'], act_encoding, ['cp866']),
		item(language['menu encoding ibm869'], language['menu encoding ibm869 help'], act_encoding, ['cp869']),
		item(separator = 1),
		item(language['menu encoding mac-roman'], language['menu encoding mac-roman help'], act_encoding, ['mac-roman']),
		item(language['menu encoding mac-ce'], language['menu encoding mac-ce help'], act_encoding, ['mac-latin2']),
		item(language['menu encoding mac-cyrillic'], language['menu encoding mac-cyrillic help'], act_encoding, ['mac-cyrillic']),
		item(language['menu encoding mac-greek'], language['menu encoding mac-greek help'], act_encoding, ['mac-greek']),
		item(language['menu encoding mac-iceland'], language['menu encoding mac-iceland help'], act_encoding, ['mac-iceland']),
		item(language['menu encoding mac-turkish'], language['menu encoding mac-turkish help'], act_encoding, ['mac-turkish']),
		item(separator = 1),
		item(language['menu encoding koi8-r'], language['menu encoding koi8-r help'], act_encoding, ['koi8-r']),
		item(language['menu encoding ptcp154'], language['menu encoding ptcp154 help'], act_encoding, ['ptcp154']),
		item(language['menu encoding tis-620'], language['menu encoding tis-620 help'], act_encoding, ['tis-620']),
	]

	for elem in encoding:
		if config['encoding'] in elem.params:
			elem.selected = 1
			elem.text += ' *'
			elem.action = None
			elem.params = ()
			break

	configure = [
#		item(language['menu configure options']),
		item(language['menu configure language'], language['menu configure language help'], list = languag),
		item(language['menu configure encoding'], language['menu configure encoding help'], list = encoding),
		item(separator = 1),
		item(language['menu configure history'], language['menu configure history help'], act_confighistory, []),
		item(language['menu configure show'], language['menu configure show help'], act_configshow, []),
		item(language['menu configure beep'], language['menu configure beep help'], act_configbeeps, []),
		item(language['menu configure flash'], language['menu configure flash help'], act_configflashes, []),
		item(separator = 1),
		item(language['menu configure colors'], language['menu configure colors help'], act_configcolors, []),
		item(separator = 1),
		item(language['menu configure save'], language['menu configure save help'], act_saveconfig, []),
	]

	help = [
		item(language['menu help hotkeys'], language['menu help hotkeys help'], act_helpkeys, []),
		item(language['menu help about'], language['menu help about help'], act_helpabout, []),
	]

	firstlevel = [
		item(language['menu firstlevel connection'], language['menu firstlevel connection help'], list = connection),
		item(language['menu firstlevel conversation'], language['menu firstlevel conversation help'], list = conversation),
		item(language['menu firstlevel contact'], language['menu firstlevel contact help'], list = contact),
		item(language['menu firstlevel configure'], language['menu firstlevel configure help'], list = configure),
		item(language['menu firstlevel help'], language['menu firstlevel help help'], list = help),
	]

	return firstlevel

def act_saveconfig():
	try: os.rename(config['config file'], config['config file'] + '.bak')
	except: pass
	fd = open(config['config file'], 'w')
	fd.write('# MSNCP ' + version + ' config file automatic created.\n\n')

	for key in config.keys():
		if key in ('password', 'config file'):
			continue
		if config[key] == None:
			continue
		try:
			value = '%d' % ord(config[key])
		except:
			value = config[key]
		fd.write('%s = %s\n' % (key, value))
	fd.close()
	del(fd)
	pass

def act_setscroll():
	global centralwin, focus
	if focus != 1:
		# BETA: On stable, focus on centralwin.
		return
	centralwin.setscroll()
	centralwin.noutrefresh()
	# FIXME: main.focus = 1

def act_encoding(encoding):
	config['encoding'] = encoding
	windows.encoding = items.encoding = windows.textline.encoding = windows.textbox.encoding = encoding

	leftmenurefresh()
	rightmenurefresh()
	centralwin.setchat(openedchats[leftmenu.current])
	centralwin.noutrefresh()
	bottom.noutrefresh()

def act_invite(sbd, email):
	try: m.invite(email, sbd)
	except: beep()

def act_openchat(email):
	i = openedchatsfindemail2(email)
	if i == -1:
		openchatwindow(email, focus = 1)
	else:
		act_setcurrent(i)

def act_clearchat():
	centralwin.chat.clear()

	leftmenurefresh()

	try:
		centralwin.setchat(centralwin.chat)
		centralwin.noutrefresh()
	except: pass

def act_setcurrent(i):
	global openedchats, leftmenu, centralwin, currentchat

	currentchat = openedchats[i] # DELETE
	leftmenu.current = i

	leftmenurefresh()

	try:
		centralwin.setchat(openedchats[i])
		centralwin.noutrefresh()
	except: pass

def act_closechat(i):
	global openedchats, leftmenu, currentchat

	if i == 0:
		act_disconnect()
		return

	chat = openedchats[i]

	email = chat.email[0]
	sbd = chat.sbd
	if sbd:
		if len(sbd.emails) > 1:
			log_msg(email, 'multi', 'closed by you', users = sbd.emails) # Y en un multichat??? FIXME
		else:
			log_msg(email, 'multi', 'closed by you')
		try: m.close(sbd)
		except: pass
		chat.sbd = None
	del openedchats[i]

	if leftmenu.current > i or (leftmenu.current == i and len(openedchats) == i):
		leftmenu.current = len(openedchats) - 1

	leftmenurefresh()
	currentchat = openedchats[leftmenu.current] # DELETE
	try:
		centralwin.setchat(openedchats[leftmenu.current])
		centralwin.noutrefresh()
	except: pass

def act_helpkeys():
	global configwin

	it = [
		items.config('help', 'ESC: ' + language['help keys menu'], '', '', selected = 1),
		items.config('help', 'TAB: ' + language['help keys next'], '', ''),
		items.config('help', 'F1:  ' + language['help keys left'], '', ''),
		items.config('help', 'F2:  ' + language['help keys center'], '', ''),
		items.config('help', 'F3:  ' + language['help keys right'], '', ''),
		items.config('help', 'F6:  ' + language['help keys scroll'], '', ''),
		items.config('help', 'F10: ' + language['help keys quit'], '', ''),
#		items.config('help', ': ' + language['help keys '], '', ''),
	]

	configwin = windows.configwin(language['help keys title'], it, ['', language['help keys close']], None, maxy, maxx)
	configwin.noutrefresh()

def act_helpabout():
	global configwin

	it = [
		items.config('help', 'MSNCP version ' + version, '', '', selected = 1),
		items.config('help', 'http://www.msncp.com.ar', '', ''),
		items.config('help', '(C)2005-' + year + ' by Sebastian Santisi', '', ''),
	]

	configwin = windows.configwin(language['help about title'], it, ['', language['help about close']], None, maxy, maxx)
	configwin.noutrefresh()

def act_confighistory():
	global configwin

	it = [
		items.config('title', language['config history general'], '', ''),
		items.config('checkbox', language['config history enable'], '', config['log history'], selected = 1),
		items.config('text', language['config history directory'], '', config['history directory']),
		items.config('text', language['config history encoding'], '', config['log encoding']),
		items.config('title', language['config history sbds'], '', ''),
		items.config('select', '', '', config['log sbds'], [language['config history anot'], language['config history ayes']]),
		items.config('title', language['config history mess'], '', ''),
		items.config('select', '', '', config['log messages'], [language['config history anot'], language['config history ayes']]),
		items.config('title', language['config history conn'], '', ''),
		items.config('select', '', '', config['log connections'], [language['config history bnot'], language['config history bopen'], language['config history ball']]),
		items.config('title', language['config history stat'], '', ''),
		items.config('select', '', '', config['log status'], [language['config history bnot'], language['config history bopen'], language['config history ball']]),
		items.config('title', language['config history nick'], '', ''),
		items.config('select', '', '', config['log nickchanges'], [language['config history bnot'], language['config history bopen'], language['config history ball']]),
	]

	configwin = windows.configwin(language['config history title'], it, [language['config history ok'], language['config history cancel']], act_confighistory_cb, maxy, maxx)
	configwin.noutrefresh()

def act_confighistory_cb(cl):
	config['log history'] = cl[1].default
	config['history directory'] = cl[2].default # WHAT ABOUT THE ENCODING?
	config['log encoding'] = cl[3].default
	config['log sbds'] = cl[5].default
	config['log messages'] = cl[7].default
	config['log connections'] = cl[9].default
	config['log status'] = cl[11].default
	config['log nickchanges'] = cl[13].default

def act_configflashes():
	global configwin

	it = [
		items.config('checkbox', language['config flashes enable'], '', config['visual bell'], selected = 1),
		items.config('title', language['config flashes sbds'], '', ''),
		items.config('select', '', '', config['flash sbds'], [language['config flashes anot'], language['config flashes ayes']]),
		items.config('title', language['config flashes mess'], '', ''),
		items.config('select', '', '', config['flash messages'], [language['config flashes anot'], language['config flashes ayes']]),
		items.config('title', language['config flashes conn'], '', ''),
		items.config('select', '', '', config['flash connections'], [language['config flashes bnot'], language['config flashes bopen'], language['config flashes ball']]),
		items.config('title', language['config flashes stat'], '', ''),
		items.config('select', '', '', config['flash status'], [language['config flashes bnot'], language['config flashes bopen'], language['config flashes ball']]),
		items.config('title', language['config flashes nick'], '', ''),
		items.config('select', '', '', config['flash nickchanges'], [language['config flashes bnot'], language['config flashes bopen'], language['config flashes ball']]),
	]

	configwin = windows.configwin(language['config flashes title'], it, [language['config flashes ok'], language['config flashes cancel']], act_configflashes_cb, maxy, maxx)
	configwin.noutrefresh()

def act_configflashes_cb(cl):
	config['visual bell'] = cl[0].default
	config['flash sbds'] = cl[2].default
	config['flash messages'] = cl[4].default
	config['flash connections'] = cl[6].default
	config['flash status'] = cl[8].default
	config['flash nickchanges'] = cl[10].default

def act_configbeeps():
	global configwin

	it = [
		items.config('checkbox', language['config bell enable'], '', config['sound bell'], selected = 1),
		items.config('title', language['config bell sbds'], '', ''),
		items.config('select', '', '', config['beep sbds'], [language['config bell anot'], language['config bell ayes']]),
		items.config('title', language['config bell mess'], '', ''),
		items.config('select', '', '', config['beep messages'], [language['config bell anot'], language['config bell ayes']]),
		items.config('title', language['config bell conn'], '', ''),
		items.config('select', '', '', config['beep connections'], [language['config bell bnot'], language['config bell bopen'], language['config bell ball']]),
		items.config('title', language['config bell stat'], '', ''),
		items.config('select', '', '', config['beep status'], [language['config bell bnot'], language['config bell bopen'], language['config bell ball']]),
		items.config('title', language['config bell nick'], '', ''),
		items.config('select', '', '', config['beep nickchanges'], [language['config bell bnot'], language['config bell bopen'], language['config bell ball']]),
	]

	configwin = windows.configwin(language['config bell title'], it, [language['config bell ok'], language['config bell cancel']], act_configbeeps_cb, maxy, maxx)
	configwin.noutrefresh()

def act_configbeeps_cb(cl):
	config['sound bell'] = cl[0].default
	config['beep sbds'] = cl[2].default
	config['beep messages'] = cl[4].default
	config['beep connections'] = cl[6].default
	config['beep status'] = cl[8].default
	config['beep nickchanges'] = cl[10].default

def act_configshow():
	global configwin

	it = [
		items.config('title', language['config notify sbds'], '', ''),
		items.config('select', '', '', config['show sbds'], [language['config notify anot'], language['config notify ayes']], selected = 1),
		items.config('title', language['config notify conn'], '', ''),
		items.config('select', '', '', config['show connections'], [language['config notify bnot'], language['config notify bopen'], language['config notify ball']]),
		items.config('title', language['config notify stat'], '', ''),
		items.config('select', '', '', config['show status'], [language['config notify bnot'], language['config notify bopen'], language['config notify ball']]),
		items.config('title', language['config notify nick'], '', ''),
		items.config('select', '', '', config['show nickchanges'], [language['config notify bnot'], language['config notify bopen'], language['config notify ball']]),
	]

	configwin = windows.configwin(language['config notify title'], it, [language['config notify ok'], language['config notify cancel']], act_configshow_cb, maxy, maxx)
	configwin.noutrefresh()

def act_configshow_cb(cl):
	config['show sbds'] = cl[1].default
	config['show connections'] = cl[3].default
	config['show status'] = cl[5].default
	config['show nickchanges'] = cl[7].default

def act_configcolors():
	global configwin

	it = [
		items.config('title', language['config colors restart'], '', ''),
		items.config('checkbox', language['config colors disable'], '', config['no colors'], selected = 1),
		items.config('title', language['config colors transpalert'], '', ''),
		items.config('checkbox', language['config colors transparent'], '', config['transparent background']),
	]

	configwin = windows.configwin(language['config colors title'], it, [language['config colors ok'], language['config colors cancel']], act_configcolors_cb, maxy, maxx)
	configwin.noutrefresh()

def act_configcolors_cb(cl):
	config['no colors'] = cl[1].default
	config['transparent background'] = cl[3].default

def act_changenick():
	global configwin

	it = [
		items.config('title', language['config changenick insert'], '', ''),
		items.config('text', '', '', decode(m.nick, config['encoding']), selected = 1),
	]

	configwin = windows.configwin(language['config changenick title'], it, [language['config changenick ok'], language['config changenick cancel']], act_changenick_cb, maxy, maxx)
	configwin.noutrefresh()

def act_changenick_cb(cl):
	str = encode(cl[1].default, 'utf-8')

	if not str: return
	m.change_nick(str)

def act_renamecontact(i):
	global configwin
	contact = contactlist.list[i]

	it = [
		items.config('title', language['config renamecontact rename'] % contact.email, '', contact.email), # FIXME
		items.config('text', '', '', decode(contact.nick, config['encoding']), selected = 1),
	]

	configwin = windows.configwin(language['config renamecontact title'], it, [language['config renamecontact ok'], language['config renamecontact cancel']], act_renamecontact_cb, maxy, maxx)
	configwin.noutrefresh()

def act_renamecontact_cb(cl):
	email = cl[0].default
	nick = encode(cl[1].default, 'utf-8')

	if not nick: return
	m.userren(email, nick)

def act_addcontact():
	global configwin

	it = [
		items.config('text', language['config addcontact email'], '', '', selected = 1),
	]

	if not config['update nicks']:
		it.append(items.config('text', language['config addcontact nick'], '', ''))

	configwin = windows.configwin(language['config addcontact title'], it, [language['config addcontact ok'], language['config addcontact cancel']], act_addcontact_cb, maxy, maxx)
	configwin.noutrefresh()

def act_addcontact_cb(cl):
	email = encode(cl[0].default, 'utf-8')
	if not email: return

	if config['update nicks']:
		m.useradd(email)
	else:
		nick = encode(cl[1].default, 'utf-8')
		if not nick:
			nick = email
		m.useradd(email, nick)

def act_removecontact(i):
	m.userdel(contactlist.list[i].email)

def act_blockcontact(i):
	# FIXME: There's not callback for this?
	contactlist.list[i].blocked = 1
	m.userblock(contactlist.list[i].email)
	rightmenurefresh()

def act_unblockcontact(i):
	# FIXME: There's not callback for this?
	contactlist.list[i].blocked = 0
	m.userunblock(contactlist.list[i].email)
	rightmenurefresh()

def act_sendmessage(i, str):
	global openedchats, leftmenu

	chat = openedchats[i]

#	sbd = m.users[chat.email[0]].sbd

	if chat.sbd:
		send = m.sendmsg('NADA', encode(str, 'utf-8'), openedchats[leftmenu.current].sbd)
		if send == 2:
			if len(chat.sbd.emails) > 1:
				log_msg(openedchats[leftmenu.current].email[0], 'out', encode(str, 'utf-8') , users = chat.sbd.emails)
			else:
				log_msg(openedchats[leftmenu.current].email[0], 'out', encode(str, 'utf-8'))
#	elif sbd and len(sbd.emails) == 1:
#		m.sendmsg('NADA', str, sbd)
	else:
		m.sendmsg(chat.email[0], encode(str, 'utf-8'))
#	else:
		# FIXME!!!!!!!!, If you talk to someone that starts a multichat
		# on private and say more than 1 message before the 'IRO' CB
		# comes, then the client crash.
#		sb = opensbd(chat.email[0])
#		sb.msgqueue.append(str)
#		chat.sbd = sb
#		m.sendmsg('NADA', str, sb)

	chat.selftyping = 0
	sendmessagei(i, items.message('OUT', encode(str, 'utf-8')))

#	chat.addhistory()
	if i == leftmenu.current:
		chat.addhistory()
		try:
#			centralwin.setchat(chat)
			centralwin.noutrefresh()
		except: pass
#	else:
#		chat.

def act_savechatas(i):
	global configwin
	chat = openedchats[i]

	it = [
		items.config('title', language['config savechatas filename'], '', chat),
		items.config('text', '', '', decode(chat.filename, config['encoding']), selected = 1),
	]

	configwin = windows.configwin(language['config savechatas title'], it, [language['config savechatas ok'], language['config savechatas cancel']], act_savechatas_cb, maxy, maxx)
	configwin.noutrefresh()

def act_savechatas_cb(cl):
	chat = cl[0].default
	filename = cl[1].default # FIXME: What about encoding?

	chat.filename = filename

	if not filename:
		return

	act_savechat_(chat, filename)


def act_savechat(i):
	global configwin
	contact = contactlist.list[i]

	chat = openedchats[leftmenu.current]
	if not chat.filename:
		act_savechatas(i)
		return

	act_savechat_(chat, chat.filename)

def act_savechat_(chat, filename):
	try: fd = open(filename, 'w')
	except: return

	for message in chat.messages:
		out = message.to_str()
		try:
			fd.write(out + '\n')
		except:
			fd.close()
			return

	fd.close()
	return

def parsecommand(str):
	# TODO: Todo (all)...
	cmd = str.lstrip()

	orig_cmd = cmd
	s = cmd.split()
	if len(s) > 1:
		cmd = s[0]
		params = orig_cmd[orig_cmd.find(s[1]):]

	else:
		if not cmd: return # It's necessary
		cmd = s[0]
		params = ''

	p = params.split()

	openedchats[leftmenu.current].addhistory()
	try:
#		centralwin.setchat(openedchats[leftmenu.current])
		centralwin.noutrefresh()
	except: pass
	sendmessagei(leftmenu.current, items.message('OUT', '/' + encode(str, 'utf-8')))

	out = None

	if cmd == 'quit':
		act_quit()
	elif cmd == 'status':
		if not isonline:
			out = 'Pero estas desconectado'
			beep()
		if not params:
			out = 'Tu estado actual es %s' % msnlib.reverse_status[m.status]
		if not m.change_status(params):
			out = 'El estatus debe ser uno de:\n'
			out += '\tonline, away, busy, brb, phone, lunch, invisible or idle'
			beep()
		else:
			out = 'Status cambiado a: %s' % params

	elif cmd == 'raw':
		try:
			cmd = params[0:3]
			pars = params[4:]
		except:
			return
		try: m._send(cmd, pars)
		except: beep()
	elif cmd == 'close':
		try:
			if not m.users[p[0]].sbd:
				beep()
				return
		except: beep()
		# Use act_closechat()
		m.close(m.users[p[0]].sbd)
		chat = openedchatsfindemail(openedchats, p[0])
		openedchats[chat].sbd = None
		leftmenurefresh()
		log_msg(p[0], 'multi', 'closed by you')
	elif cmd == 'block':
		try: m.userblock(p[0])
		except: beep()
	elif cmd == 'unblock':
		try: m.userunblock(p[0])
		except: beep()
	elif cmd == 'add':
		try: m.useradd(p[0], p[1])
		except: beep()
	elif cmd == 'del':
		try: m.userdel(p[0])
		except: beep()
	elif cmd == 'ren':
		try: m.userren(p[0], p[1])
		except: beep()
	elif cmd == 'invite':
#						sbd = oclistemails(p[2])[0].sbd
		try: m.invite(p[0], sbd)
		except: beep()
	elif cmd == 'nick':
		try: m.change_nick(params)
		except: beep()
	elif cmd == 'sync':
		try: m.sync()
		except: beep()
	elif cmd == 'm':
		try: m.sendmsg(p[0], params[(len(p[0])+1):])
		except: beep()
	elif cmd == 'disc':
		if not isonline:
			beep()
			return
		act_disconnect()
		return
	elif cmd == 'conn':
		if isonline:
			beep()
			return
		act_connect()
	else:
		beep()
#		openedchats[leftmenu.current].addhistory()
		try:
#			centralwin.setchat(openedchats[leftmenu.current])
			centralwin.noutrefresh()
		except: pass
		return

	if out:
		sendmessagei(0, items.message('SER', out))


def main(stdscr):
	global openedchats, contactlist, leftmenu, rightmenu, centralwin, currentchat, bottom, isonline, waitingpong, language, config, topmenu, configwin, maxx, maxy, focus

	stdscr.nodelay(1)

	(maxy, maxx) = stdscr.getmaxyx()

	items.message.language = language

	# Create the initial chat list
	openedchats.append(items.chat(language['server title'], language['server title']))

	currentchat = openedchats[0] # DELETE

	if config['show groups']:
		contactlist = classcontactlist('by group')
	else:
		contactlist = classcontactlist('by status')

	# Creates all the objects in the normal MMS
	try:
		leftmenu = windows.sidewin(1, 0, maxy - 4, config['leftmenu width'] - 1, openedchats, language['leftmenu title'], config['leftmenu scroll'])
	except curses.error:
		return
	except windows.sidewin.out_of_range:
		return

	try:
		rightmenu = windows.sidewin(1, maxx - config['rightmenu width'] + 1, maxy - 4, maxx - 1, contactlist.list,  language['rightmenu title'], config['rightmenu scroll'], 1)
	except curses.error:
		return
	except windows.sidewin.out_of_range:
		return

	try:
		centralwin = windows.centerwin(1, config['leftmenu width'], maxy - 4, maxx - config['rightmenu width'], config['centralwin heightbox'], openedchats[leftmenu.current])
	except curses.error:
		return
	except windows.centerwin.out_of_range:
		return

	try:
		bottom = windows.bottombanner(maxy - 3, 0, maxy - 1, maxx -1)
	except curses.error:
		return

	stdscr.nodelay(1)

	# Draw all the objects
	try:
		leftmenu.noutrefresh()
		centralwin.noutrefresh()
		rightmenu.noutrefresh()
		bottom.noutrefresh()
	except curses.error:
		pass
	curses.doupdate()

	license = '' 

	sendmessagei(0, items.message('---', license), echo = 0)
	focus = 1
	centralwin.setfocus()
	try: centralwin.noutrefresh()
	except: pass

	menus = [leftmenu, centralwin, rightmenu]

	isonline = 0

	if config['startup connect']:
		act_connect()
	listcomplete = 0

	lastping = time.time()
	waitingpong = 0

	while 1:
		stdscr.nodelay(1)
		curses.doupdate()
		if topmenu:
			ret = topmenu.read()
		elif configwin:
			ret = [configwin.read()]
		else:
			ret = menus[focus].read()

			# FIXME
			if focus == 1:
				# To update the cursor when spaces were drawn.
				try: centralwin.box.refresh()
				except: pass
		key = ret[0]

		bottom.noutclock()

		menus[focus].setfocus()

		typingchanged = 0
		mtime = time.time()
		for chat in openedchats:
			if chat.typing and mtime - chat.typing > 10:
				chat.typing = 0
				typingchanged = 1
		if typingchanged:
			leftmenurefresh()

		if isonline and not waitingpong and mtime > lastping + config['ping frequency']:
			try: m.ping()
			except: pass
			lastping = mtime
			waitingpong = 1

		if isonline and waitingpong and lastping + 20 < mtime:
			sendmessagei(0, items.message('SER', 'err pong'))
			act_disconnect()
			if config['reconnect']:
				act_connect(1)
			waitingpong = 0

		if not isonline and key == -1:
			time.sleep(0.01)
			continue
		elif key == -1 and isonline:
			# Here we manages the MSN; note that our priority is the
			# keyboard and not the client.
			# FIXME: Make that the precedent commentary will be true :P...
			fds = m.pollable()
			infd = fds[0]
			outfd = fds[1]
			infd.append(sys.stdin)
			try: fds = select.select(infd, outfd, [], 0)
			except select.error: continue
			if not len(fds[0] + fds[1]):
				time.sleep(0.01)
				continue
			for i in fds[0] + fds[1]:
				if i == sys.stdin:
					break
				try:
					m.read(i)
				except ('SocketError', socket.error), err:
					if i != m:
						x = ocfindsbd(i)
						openedchats[x].sbd = None
						leftmenurefresh()
						m.close(i)
						# SENDMESSAGE
					else:
						act_disconnect()
						# ADVISE
				except 'XFRError', err:
					# ALERT
					pass
			continue

		if key == curses.KEY_RESIZE:
			# This is the most important to manage.
			(maxy, maxx) = stdscr.getmaxyx()

			try:
				for chat in openedchats:
					chat.lines = []
					chat.totallines = 0

				leftmenu.resize(1, 0, maxy - 4, config['leftmenu width'] - 1)
				centralwin.resize(1, config['leftmenu width'], maxy - 4, maxx - config['rightmenu width'])
				rightmenu.resize(1, maxx - config['rightmenu width'] + 1, maxy - 4, maxx - 1)
				bottom = windows.bottombanner(maxy - 3, 0, maxy - 1, maxx -1)

				if topmenu:
					# TODO: topmenu.resize()
					topmenu = windows.topmenu(maxy, maxx, makemenu())
				if configwin:
					# TODO: configwin.resize()
					c = configwin
					configwin = windows.configwin(c.title, c.configlist, c.buttons, c.action, maxy, maxx)
			except:
				raise 'The new size of the window is too small.'

			try:
				leftmenu.noutrefresh()
				centralwin.noutrefresh()
				rightmenu.noutrefresh()
				bottom.noutrefresh()
				if topmenu:
					topmenu.noutrefresh()
				if configwin:
					configwin.noutrefresh()
			except curses.error:
				pass



		################################################################
		# MANAGES THE TOPMENU
		################################################################
		if topmenu:
			item = ret[1]

			if key in (27, ord('\t')):			# ESC
				topmenu.nouthide()
				topmenu = None
				try:
					leftmenu.noutrefresh()
					rightmenu.noutrefresh()
					centralwin.noutrefresh()
					bottom.help = ''
					bottom.noutrefresh()
				except: pass
				continue
			if key == ord('\n'):
				topmenu.nouthide()
				topmenu = None
				try:
					leftmenu.noutrefresh()
					rightmenu.noutrefresh()
					centralwin.noutrefresh()

					bottom.help = ''
					bottom.noutrefresh()
				except: pass

				action = item.action
				params = item.params

				if not action:
					curses.flash()
					continue

				if params == ():
					action()
				else:
					action(*params)
				continue
			if key == windows.topmenu.needsredraw:
				bottom.help = item.help
				try:
					bottom.noutrefresh()
					leftmenu.noutrefresh()
					rightmenu.noutrefresh()
					centralwin.noutrefresh()
					topmenu.noutredraw()
				except: pass
				continue
			else:
				bottom.help = item.help
				try:
					bottom.noutrefresh()
					topmenu.noutrefresh()
				except: pass
				continue

		################################################################
		# MANAGES THE CONFIGWIN
		################################################################
		if configwin:
			if key == 27:
				configwin = None
				try:
					leftmenu.noutrefresh()
					rightmenu.noutrefresh()
					centralwin.noutrefresh()
					bottom.help = ''
					bottom.noutrefresh()
				except: pass
				continue
			if key == ord('\n'):
				cw = configwin
				configwin = None
				try:
					leftmenu.noutrefresh()
					rightmenu.noutrefresh()
					centralwin.noutrefresh()
					bottom.help = ''
					bottom.noutrefresh()
				except: pass

				if cw.action:
					cw.action(cw.configlist)

				continue
			else:
				configwin.noutrefresh()
				continue

		################################################################
		# EVENTS IN THE LEFTMENU
		################################################################
		elif focus == 0:
			# Manages the leftmenu
			current = ret[1]

			if key == ord('\n'):
				leftmenu.setfocus(0)
				act_setcurrent(current)
				try:
					centralwin.setfocus()
					centralwin.noutrefresh()
				except: pass
				focus = 1

				continue
			if key == curses.KEY_DC:
				act_closechat(current)
				continue
			if openedchats[leftmenu.current] != centralwin.chat:
				act_setcurrent(current)
				bottom.help = string.join(openedchats[current].email, ', ')
				try: bottom.noutrefresh()
				except: pass
				continue


		################################################################
		# EVENTS IN THE CENTRALWIN
		################################################################
		elif focus == 1:
			# Manages the centralwin
			str = ret[1]

			if centralwin.scrolling:
				if key == 27:
					centralwin.setscroll(0)
					try: centralwin.noutrefresh()
					except: pass
					continue
				elif key == curses.OK:
					try: centralwin.noutrefresh()
					except: pass
					continue
				else:
					continue
			elif key == ord('\n'):
				if len(str) < 1:
					curses.flash()
					continue
				if str[0] == '/':
#					openedchats[leftmenu.current].addhistory()
#					try:
#						centralwin.setchat(openedchats[leftmenu.current])
#						centralwin.refresh()
#					except: pass
					parsecommand(str[1:])
					continue
				if not leftmenu.current:
					curses.flash()
					try: centralwin.noutrefresh()
					except: pass
					continue

				act_sendmessage(leftmenu.current, str)
				continue
			elif key == curses.OK:
				if openedchats[leftmenu.current].sbd and openedchats[leftmenu.current].sbd != 1:
					if not openedchats[leftmenu.current].selftyping or (time.time() - openedchats[leftmenu.current].selftyping) > 5:
						mandatyping(openedchats[leftmenu.current].sbd)
						openedchats[leftmenu.current].selftyping = time.time()
			elif key == curses.KEY_F6:
				centralwin.setscroll()
				centralwin.noutrefresh()
				continue
			elif key == curses.KEY_PPAGE:
				centralwin.movehistory(-1)
				try: centralwin.noutrefresh()
				except: pass
				continue
			elif key == curses.KEY_NPAGE:
				centralwin.movehistory(1)
				try: centralwin.noutrefresh()
				except: pass
				continue

		################################################################
		# EVENTS IN THE RIGHTMENU
		################################################################
		elif focus == 2:
			# Manages the rightmenu
			current = ret[1]

			if key == ord('\n') and contactlist.list:
				if isinstance(contactlist.list[current], items.group):
					contactlist.fold(contactlist.list[current])
					rightmenu.noutrefresh()
					continue
				if contactlist.list[current].status == 'FLN':
#					centralwin.write('User is offline')
					continue
				act_openchat(contactlist.list[current].email)

				try:
					rightmenu.setfocus(0)
					rightmenu.noutrefresh()
					centralwin.setfocus()
					centralwin.noutrefresh()
				except: pass
				focus = 1
				continue
			elif key == curses.OK and contactlist.list:
				bottom.help = contactlist.list[current].email
				try: bottom.noutrefresh()
				except: pass

		################################################################
		# GENERAL EVENTS
		################################################################
		if key == curses.OK:
			if focus == 1:
#				try: menus[focus].refreshbox()
				try: menus[focus].noutrefreshbox()
				except: pass
			else:
				try: menus[focus].noutrefresh()
				except: pass
		elif key == ord('\t'):
			bottom.help = ''
			try: bottom.noutrefresh()
			except: pass

			menus[focus].setfocus(0)
			try: menus[focus].noutrefresh()
			except: pass

			focus = (focus + 1) % 3

			menus[focus].setfocus()
			try: menus[focus].noutrefresh()
			except: pass

			# FIXME: bottom
		elif key == curses.KEY_F10:
			act_quit()
		elif key in [curses.KEY_F1, curses.KEY_F2, curses.KEY_F3]:
			try:
				menus[focus].setfocus(0)
				menus[focus].noutrefresh()
			except: pass

			focus = key - curses.KEY_F1

			try:
				menus[focus].setfocus(1)
				menus[focus].noutrefresh()
			except: pass
		elif key == 27:
			topmenu = windows.topmenu(maxy, maxx, makemenu())
			bottom.help = topmenu.list[0].help	# FIXME
			try:
				bottom.noutrefresh()
				topmenu.noutrefresh()
			except: pass
		else:
#			centralwin.write('%d' % key)
#			centralwin.refresh()
			beep()


def wrapper(func, *rest):
	"""Python's wrapper"""
	res = None
	try:
		stdscr=curses.initscr()

		curses.noecho()
		curses.cbreak()

		stdscr.keypad(1)

		act_startcolors(None) # BETA

		res = apply(func, (stdscr,) + rest)
	except KeyboardInterrupt:
		act_quit(noexit = 1)
		stdscr.keypad(0)
		curses.echo()
		curses.nocbreak()
		curses.endwin()

		return res
	except:
		stdscr.keypad(0)
		curses.echo()
		curses.nocbreak()
		curses.endwin()

		(exc_type, exc_value, exc_traceback) = sys.exc_info()

		raise exc_type, exc_value, exc_traceback
	else:
		stdscr.keypad(0)
		curses.echo()
		curses.nocbreak()
		curses.endwin()

		return res

if len(sys.argv) > 1:
	if sys.argv[1] in ('--version', '-v'):
		print 'MSNCP version ' + version
		sys.exit(0)
	if sys.argv[1] in ('--help', '-h'):
		print 'Not help yet.'
		sys.exit(0)

	# --debug

	parse_configfile(sys.argv[1], config)
        if not config:
		print 'I don\'t understand the "%s" argv' % sys.argv[1]
	else:
		config['config file'] = sys.argv[1]

else:
	makeconfig()
	parse_configfile(config['config file'], config)

if not config:
        sys.stderr.write('Error opening config file, try running "msncpsetup"\n')
        sys.exit(1)

try: configure(config)
except 'Numeric', exc:
	sys.stderr.write(exc + '\n')
	sys.exit(1)

windows.encoding = items.encoding = windows.textline.encoding = windows.textbox.encoding = config['encoding']

if config['language file']:
	act_language(config['language file'])

wrapper(main)

