from mmptypes import *
import utils

import UserDict
import cStringIO
import socket
import struct
import email
from email.Utils import parsedate

wp_request = {}
wp_request_reversed = {}

for k,v in [(key, locals()[key]) for key in locals().keys() if key.startswith('MRIM_CS_WP_REQUEST_PARAM')]:
	wp_request[v] = k

for k,v in wp_request.items():
	wp_request_reversed[v] = k

del k,v

message_flags = tuple([v for k,v in locals().items() if k.startswith('MESSAGE_FLAG')])

class MMPParsingError(Exception):
	def __init__(self, text, packet):
		self.args = text,packet
		self.text = text
		self.packet = packet
	def __str__(self):
		return self.text

class MMPHeader(UserDict.UserDict):

	def __init__(self,typ=0,dlen=0,seq=0,fromip='0.0.0.0',fromport='0',header=''):
		UserDict.UserDict.__init__(self)
		self.header = header
		self.typ = typ
		self.frmt = '5I4s4s16B'
		if not self.header:
			self['magic'] = CS_MAGIC
			self['proto'] = PROTO_VERSION
			self['seq'] = seq
			self['msg'] = typ
			self['from'] = fromip
			self['fromport'] = fromport
			self['dlen'] = dlen
			self['reserved'] = tuple([0 for i in range(16)])
		else:
			try:
				unpacked_header = struct.unpack(self.frmt, self.header)
			except struct.error:
				raise MMPParsingError("Can't unpack header", self.header)
			self['magic'] = unpacked_header[0]
			self['proto'] = unpacked_header[1]
			self['seq'] = unpacked_header[2]
			self['msg'] = unpacked_header[3]
			self['dlen'] = unpacked_header[4]
			self['from'] = socket.inet_ntoa(unpacked_header[5])
			self['fromport'] = socket.inet_ntoa(unpacked_header[6])
			self['reserved'] = unpacked_header[7:]

	def __str__(self):
		if not self.header:
			try:
				new_header = struct.pack(
					self.frmt,
					self['magic'],
					self['proto'],
					self['seq'],
					self['msg'],
					self['dlen'],
					socket.inet_aton(self['from']),
					socket.inet_aton(self['fromport']),
					*self['reserved']
				)
			except (struct.error, KeyError):
				raise MMPParsingError("Can't pack header", self)
			return new_header
		else:
			return self.header

class MMPBody(UserDict.UserDict):

	def __init__(self, typ=0, dict={}, body=''):
		UserDict.UserDict.__init__(self)
		self.dict = dict
		self.body = body
		self.typ = typ
		if self.body:
			self.io = cStringIO.StringIO(body)
			self.str2dict(body)
		elif self.dict:
			self.io = cStringIO.StringIO()
			self.update(dict)

	def __str__(self):
		if self.body:
			return self.body
		elif self.dict:
			return self.dict2str(self.dict)
		else:
			return ''

	def str2dict(self, body):
		try:
			return self._str2dict(body)
		except struct.error:
			raise MMPParsingError("Can't unpack body", body)

	def dict2str(self, dict):
		try:
			return self._dict2str(dict)
		except (struct.error, KeyError):
			raise MMPParsingError("Can't pack body", dict)

	def _str2dict(self, body):
		if self.typ == MRIM_CS_HELLO_ACK:
			self['ping_period'] = self._read_ul()
		elif self.typ == MRIM_CS_LOGIN_REJ:
			self['reason'] = self._read_lps()
		elif self.typ == MRIM_CS_MESSAGE:
			self['flags'] = self._read_ul()
			self['to'] = self._read_lps()
			self['message'] = self._read_lps()
			self['rtf-message'] = self.readl_lps()
		elif self.typ == MRIM_CS_MESSAGE_ACK:
			self['msg_id'] = self._read_ul()
			self['flags'] = self._read_ul()
			self['from'] = self._read_lps()
			self['message'] = self._read_lps()
			try:
				self['rtf-message'] = self._read_lps()
			except struct.error:
				self['rtf-message'] = ' '
		elif self.typ == MRIM_CS_MESSAGE_RECV:
			self['from'] = self._read_lps()
			self['msg_id'] = self._read_ul()
		elif self.typ == MRIM_CS_MESSAGE_STATUS:
			self['status'] = self._read_ul()
		elif self.typ == MRIM_CS_USER_STATUS:
			self['status'] = self._read_ul()
			self['user'] = self._read_lps()
		elif self.typ == MRIM_CS_LOGOUT:
			self['reason'] = self._read_ul()
		elif self.typ == MRIM_CS_CONNECTION_PARAMS:
			self['ping_period'] = self._read_ul()
		elif self.typ == MRIM_CS_ADD_CONTACT:
			self['flags'] = self._read_ul()
			self['group_id'] = self._read_ul()
			self['email'] = self._read_lps()
			self['name'] = self._read_lps()
			self['phones'] = self._read_ul()
			self['text'] = self._read_lps()
		elif self.typ == MRIM_CS_ADD_CONTACT_ACK:
			self['status'] = self._read_ul()
			current_position = self.io.tell()
			next_char = self.io.read(1)
			if next_char:
				self.io.seek(current_position)
				self['contact_id'] = self._read_ul()
			else:
				return
		elif self.typ == MRIM_CS_MODIFY_CONTACT:
			self['id'] = self._read_ul()
			self['flags'] = self._read_ul()
			self['group_id'] = self._read_ul()
			self['contact'] = self._read_lps()
			self['name'] = self._read_lps()
			self['phones'] = self._read_lps()
		elif self.typ == MRIM_CS_MODIFY_CONTACT_ACK:
			self['status'] = self._read_ul()
		elif self.typ == MRIM_CS_OFFLINE_MESSAGE_ACK:
			self['uidl'] = self._read_uidl()
			self['message'] = self._read_lps()
		elif self.typ == MRIM_CS_DELETE_OFFLINE_MESSAGE:
			self['uidl'] = self._read_uidl()
		elif self.typ == MRIM_CS_AUTHORIZE:
			self['user'] = self._read_lps()
		elif self.typ == MRIM_CS_AUTHORIZE_ACK:
			self['user'] = self._read_lps()
		elif self.typ == MRIM_CS_CHANGE_STATUS:
			self['status'] = self._read_ul()
		elif self.typ == MRIM_CS_GET_MPOP_SESSION_ACK:
			self['status'] = self._read_ul()
			self['session'] = self._read_lps()
		elif self.typ == MRIM_CS_WP_REQUEST:
			current_position = self.io.tell()
			while 1:
				next_char = self.io.read(1)
				if next_char:
					self.io.seek(current_position)
					field = self._read_ul()
					self[field] = self._read_lps()
					current_position = self.io.tell()
				else:
					break
		elif self.typ == MRIM_CS_ANKETA_INFO:
			self['status'] = self._read_ul()
			self['fields_num'] = self._read_ul()
			self['max_rows'] = self._read_ul()
			self['server_time'] = self._read_ul()
			self['fields'] = [self._read_lps() for i in range(self['fields_num'])]
			self['values'] = []
			current_position = self.io.tell()
			while 1:
				next_char = self.io.read(1)
				if next_char:
					self.io.seek(current_position)
					self['values'].append(tuple([self._read_lps() for i in range(self['fields_num'])]))
					current_position = self.io.tell()
				else:
					break
		elif self.typ == MRIM_CS_MAILBOX_STATUS:
			self['count'] = self._read_ul()
			self['sender'] = self._read_lps()
			self['subject'] = self._read_lps()
			self['unix_time'] = self._read_ul()
			self['key'] = self._read_ul()
		elif self.typ == MRIM_CS_MAILBOX_STATUS_OLD:
			self['status'] = self._read_ul()
		elif self.typ == MRIM_CS_CONTACT_LIST2:
			self['status'] = self._read_ul()
			if self['status'] == GET_CONTACTS_OK:
				self['groups_number'] = self._read_ul()
				self['groups_mask'] = self._read_lps()
				self['contacts_mask'] = self._read_lps()
				self['groups'] = [
					self._read_masked_field(self['groups_mask']) \
						for i in range(self['groups_number'])
				]
				self['contacts'] = []
				while 1:
					current_position = self.io.tell()
					next_char = self.io.read(1)
					if next_char:
						self.io.seek(current_position)
						self['contacts'].append(
							self._read_masked_field(self['contacts_mask'])
						)
					else:
						break
			else:
				self['groups_number'] = 0
				self['groups_mask'] = self['contacts_mask'] = ''
				self['groups'] = self['contacts'] = []

		elif self.typ == MRIM_CS_LOGIN2:
			self['login'] = self._read_lps()
			self['password'] = self._read_lps()
			self['status'] = self._read_ul()
			self['user_agent'] = self._read_lps()
		elif self.typ == MRIM_CS_SMS:
			self['UNKNOWN'] = self._read_ul()
			self['number'] = self._read_lps()
			self['text'] = self._read_lps()
		elif self.typ == MRIM_CS_SMS_ACK:
			self['status'] = self._read_ul()

		elif self.typ == MRIM_CS_USER_INFO:
			current_position = self.io.tell()
			while 1:
				next_char = self.io.read(1)
				if next_char:
					self.io.seek(current_position)
					field = self._read_lps()
					if field == 'MESSAGES.TOTAL':
						self['total'] = int(self._read_lps())
					elif field == 'MESSAGES.UNREAD':
						self['unread'] = int(self._read_lps())
					elif field == 'MRIM.NICKNAME':
						self['nickname'] = self._read_lps()
					else:
						self[field] = self._read_lps()
					current_position = self.io.tell()
				else:
					break

	def _dict2str(self, dict):
		self.io = cStringIO.StringIO()
		if self.typ == MRIM_CS_HELLO_ACK:
			self._write_ul(dict['ping_period'])
		elif self.typ == MRIM_CS_LOGIN_REJ:
			self._write_lps(dict['reason'])
		elif self.typ == MRIM_CS_MESSAGE:
			self._write_ul(dict['flags'])
			self._write_lps(dict['to'])
			self._write_lps(dict['message'])
			self._write_lps(dict['rtf-message'])
		elif self.typ == MRIM_CS_MESSAGE_ACK:
			self._write_ul(dict['msg_id'])
			self._write_ul(dict['flags'])
			self._write_lps(dict['from'])
			self._write_lps(dict['message'])
			self._write_lps(dict['rtf-message'])
		elif self.typ == MRIM_CS_MESSAGE_RECV:
			self._write_lps(dict['from'])
			self._write_ul(dict['msg_id'])
		elif self.typ == MRIM_CS_MESSAGE_STATUS:
			self._write_ul(dict['status'])
		elif self.typ == MRIM_CS_USER_STATUS:
			self._write_ul(dict['status'])
			self._write_lps(dict['user'])
		elif self.typ == MRIM_CS_LOGOUT:
			self._write_ul(dict['reason'])
		elif self.typ == MRIM_CS_CONNECTION_PARAMS:
			self._write_ul(dict['ping_period'])
		elif self.typ == MRIM_CS_ADD_CONTACT:
			self._write_ul(dict['flags'])
			self._write_ul(dict['group_id'])
			self._write_lps(dict['email'])
			self._write_lps(dict['name'])
			self._write_lps(dict['phones'])
			self._write_lps(dict['text'])
		elif self.typ == MRIM_CS_ADD_CONTACT_ACK:
			self._write_ul(dict['status'])
			self._write_ul(dict['contact_id'])
		elif self.typ == MRIM_CS_MODIFY_CONTACT:
			self._write_ul(dict['id'])
			self._write_ul(dict['flags'])
			self._write_ul(dict['group_id'])
			self._write_lps(dict['contact'])
			self._write_lps(dict['name'])
			self._write_lps(dict['phones'])
		elif self.typ == MRIM_CS_MODIFY_CONTACT_ACK:
			self._write_ul(dict['status'])
		elif self.typ == MRIM_CS_OFFLINE_MESSAGE_ACK:
			self._write_uidl(dict['uidl'])
			self._write_lps(dict['message'])
		elif self.typ == MRIM_CS_DELETE_OFFLINE_MESSAGE:
			self._write_uidl(dict['uidl'])
		elif self.typ == MRIM_CS_AUTHORIZE:
			self._write_lps(dict['user'])
		elif self.typ == MRIM_CS_AUTHORIZE_ACK:
			self._write_lps(dict['user'])
		elif self.typ == MRIM_CS_CHANGE_STATUS:
			self._write_ul(dict['status'])
		elif self.typ == MRIM_CS_GET_MPOP_SESSION_ACK:
			self._write_ul(dict['status'])
			self._write_lps(dict['session'])
		elif self.typ == MRIM_CS_WP_REQUEST:
			for k,v in [(p,s) for p,s in dict.items() if p != MRIM_CS_WP_REQUEST_PARAM_ONLINE]:
				self._write_ul(k)
				self._write_lps(v)
			if dict.has_key(MRIM_CS_WP_REQUEST_PARAM_ONLINE):
				self._write_ul(MRIM_CS_WP_REQUEST_PARAM_ONLINE)
				self._write_lps(dict[MRIM_CS_WP_REQUEST_PARAM_ONLINE])
		elif self.typ == MRIM_CS_ANKETA_INFO:
			self._write_ul(dict['status'])
			self._write_ul(dict['fields_num'])
			self._write_ul(dict['max_rows'])
			self._write_ul(dict['server_time'])
			for field in dict['fields']:
				self._write_lps(field)
			for value in dict['values']:
				self._write_lps(value)
		elif self.typ == MRIM_CS_MAILBOX_STATUS:
			self._write_ul(dict['status'])
		elif self.typ == MRIM_CS_LOGIN2:
			self._write_lps(dict['login'])
			self._write_lps(dict['password'])
			self._write_ul(dict['status'])
			self._write_lps(dict['user_agent'])
		elif self.typ == MRIM_CS_SMS:
			self._write_ul(dict['UNKNOWN'])
			self._write_lps(dict['number'])
			self._write_lps(dict['text'])
		self.io.seek(0)
		return self.io.read()

	def _read_ul(self):
		return struct.unpack('I', self.io.read(4))[0]

	def _read_lps(self):
		return self.io.read(self._read_ul())

	def _read_uidl(self):
		return self.io.read(8)

	def _write_ul(self, ul):
		self.io.write(struct.pack('I', ul))

	def _write_lps(self, lps):
		self._write_ul(len(lps))
		self.io.write(lps)

	def _write_uidl(self, uidl):
		self.io.write(uidl[:8])

	def _read_masked_field(self, mask):
		group = []
		for i in range(len(mask)):
			symbol = mask[i]
			if symbol == 'u':
				group.append(self._read_ul())
			elif symbol == 's':
				group.append(self._read_lps())
		return tuple(group)

class MMPPacket:

	def __init__(self,typ=0,seq=0,fromip='0.0.0.0',fromport='0',dict={},packet=''):
		self.header = ''
		self.body = ''
		self.typ = typ

		if packet:
			raw_header = packet[:44]
			try:
				magic = struct.unpack('I', raw_header[:4])[0]
			except:
				magic = 0
			if magic == CS_MAGIC:
				self.header = MMPHeader(header=raw_header)
				if self.header:
					self.typ = self.header['msg']
					dlen = self.header['dlen']
					self.body = MMPBody(typ=self.typ,body=packet[44:44+dlen])

		else:
			self.body = MMPBody(self.typ,dict)
			dlen = len(self.body.__str__())
			self.header = MMPHeader(self.typ,dlen,seq,fromip,fromport)
			self.setHeaderAttr('seq', utils.seq())

	def __str__(self):
		return self.header.__str__() + self.body.__str__()

	def getRawVersion(self):
		return self.header['proto']

	def getVersion(self):
		p = self.getRawVersion()
		return '%s.%s' % (utils.get_proto_major(p), utils.get_proto_minor(p))

	def getType(self):
		return self.header['msg']

	def getHeader(self):
		return self.header

	def getBody(self):
		return self.body

	def getBodyAttr(self, attr):
		return self.body[attr]
	
	def getHeaderAttr(self, attr):
		return self.header[attr]
	
	def setHeaderAttr(self, attr, val):
		self.header[attr] = val

	def setBodyAttr(self, attr, val):
		self.body[attr] = val
		self.body = MMPBody(self.getType(),dict=self.body)
		self.setHeaderAttr('dlen', len(self.body.__str__()))

	def setIp(self, ip):
		self.setHeaderAttr('from', ip)
	
	def setPort(self, port):
		self.setHeaderAttr('fromport', port)

	def setType(self, new_typ):
		self.setHeaderAttr['msg'] = new_typ

	def setId(self, _id):
		self.setHeaderAttr('seq', _id)

	def getId(self):
		return self.getHeaderAttr('seq')

	def setMsgId(self, msg_id):
		self.setBodyAttr('msg_id', msg_id)

	def getMsgId(self):
		if self.getBody().has_key('msg_id'):
			return self.getBodyAttr('msg_id')

class Message(MMPPacket):

	def __init__(self,to='',body=' ',flags=[],payload=None):
		if not payload:
			d = {}
			flags_sum = 0
			for f in flags:
				flags_sum += f
			d['flags'] = flags_sum & MESSAGE_USERFLAGS_MASK
			d['to'] = to
			d['message'] = body
			if MESSAGE_FLAG_RTF in flags:
				d['rtf-message'] = utils.pack_rtf(body)
			else:
				d['rtf-message'] = ' '
			MMPPacket.__init__(self,typ=MRIM_CS_MESSAGE,dict=d)
			self.setHeaderAttr('seq', utils.seq())
		else:
			MMPPacket.__init__(self,typ=payload.getType(),dict=payload.getBody())

	def getTo(self):
		return self.getBodyAttr('to')

	def getFrom(self):
		return self.getBodyAttr('from')

	def getBodyPayload(self):
		return utils.win2str(self.getBodyAttr('message'))

	def getFlags(self):
		flag_code = self.getBodyAttr('flags')
		flags = []
		for f in message_flags:
			x = flag_code & f
			if x:
				flags.append(x)
		return flags

	def hasFlag(self, flag):
		return flag in self.getFlags()

class OfflineMessage(UserDict.UserDict):

	def __init__(self, data):
		UserDict.UserDict.__init__(self)
		self.msg = email.message_from_string(data)
		self.boundary = self.msg['Boundary']
		self.payload = self.msg.get_payload().split('--%s--' % self.boundary)
		self['from'] = self.msg['From']
		self['date'] = parsedate(self.msg['Date'])
		self['subject'] = self.msg['Subject']
		self['flags'] = eval('0x'+self.msg['X-MRIM-Flags'])
		self['version'] = self.msg['Version']
		self['message'] = utils.win2str(self.payload[0].strip())
		self['rtf-message'] = self.payload[1].strip()

	def buildMessage(self):
		d = {
			'msg_id':0,
			'flags':self['flags'],
			'from':self['from'],
			'message':self.payload[0].strip(),
			'rtf-message':self['rtf-message']
		}
		m = MMPPacket(typ=MRIM_CS_MESSAGE_ACK,dict=d)
		return Message(payload=m)

	def getUTCTime(self):
		return utils.msk2utc(self['date'])

class Anketa(MMPPacket):

	def __init__(self, data):
		MMPPacket.__init__(self,packet=data)

	def getStatus(self):
		return self.getBodyAttr('status')

	def getFields(self):
		return self.getBodyAttr('fields')

	def getVCards(self):
		vcards = []
		fields = self.getFields()
		for card in self.getBodyAttr('values'):
			card_dict = {}
			for n in range(self.getBodyAttr('fields_num')):
				card_dict[fields[n]] = utils.win2str(card[n])
			vcards.append(card_dict)
		return vcards

class ContactList:

	def __init__(self, packet=None):
		self.cids = {}
		self.users = {}
		self.group = {}
		if packet:
			self.packet = packet
			self.users = self.getUsers()
			self.groups = self.getGroups()
			i = 0
			for u in self.packet.getBodyAttr('contacts'):
				_id = 20+i
				if (u[0] & CONTACT_FLAG_SMS):
					self.cids[u[6]] = _id
				else:
					self.cids[u[2]] = _id
				i += 1

	def getGroups(self):
		d = {}
		for g in self.packet.getBodyAttr('groups'):
			d[g[0]] = {'name':utils.win2str(g[1])}
		return d

	def getUsers(self):
		d = {}
		for u in self.packet.getBodyAttr('contacts'):
			contact = {
					'flags':u[0],
					'group':u[1],
					'nick':utils.win2str(u[3]),
					'server_flags':u[4],
					'status':u[5],
					'phones':u[6]
			}
			if (u[0] & CONTACT_FLAG_SMS):
				d[u[6]] = contact
			else:
				d[u[2]] = contact
		return d

	def getEmails(self):
		return self.users.keys()

	def getUserFlags(self, mail):
		return self.users[mail]['flags']

	def isValidUser(self, mail):
		return not (self.isIgnoredUser(mail) or self.isRemovedUser(mail) or self.isSMSNumber(mail))

	def isIgnoredUser(self, mail):
		flags = self.getUserFlags(mail)
		return bool(flags & CONTACT_FLAG_IGNORE)

	def isRemovedUser(self, mail):
		flags = self.getUserFlags(mail)
		return bool(flags & CONTACT_FLAG_REMOVED)

	def isSMSNumber(self, phone):
		return not utils.is_valid_email(phone)

	def getUserId(self, mail):
		return self.cids[mail]

	def setUserId(self, mail, _id):
		self.cids[mail] = _id

	def getUserStatus(self, mail):
		status = 1
		if utils.is_valid_email(mail):
			status = self.users[mail]['status']
		return status

	def setUserStatus(self, mail, status):
		self.users[mail]['status'] = status

	def getAuthFlag(self, mail):
		return self.users[mail]['server_flags']

	def setAuthFlag(self, mail, flag):
		self.users[mail]['server_flags'] = flag

	def isAuthorized(self, mail):
		return not bool(self.getAuthFlag(mail) & 0x1)

	def getUserGroup(self, mail):
		return self.users[mail]['group']

	def setUserGroup(self, mail, gid):
		self.users[mail]['group'] = gid

	def getUserNick(self, mail):
		return self.users[mail]['nick']

	def setUserNick(self, mail, nick):
		self.users[mail]['nick'] = nick

	def delUser(self, mail):
		return self.users.pop(mail)

	def delGroup(self, gid):
		return self.groups.pop(gid)

	def getGroupName(self, gid):
		name = 'unknown'
		try:
			name = self.groups[gid]
		except KeyError:
			pass
		return name

	def setGroupName(self, gid, name):
		self.groups[gid] = name

	def getGroupMembers(self, gid):
		members = []
		for u in self.users:
			if self.getUserGroup(u) == gid:
				members.append(u)
		return members

	def getPhones(self, mail):
		phones = self.users[mail]['phones']
		if phones:
			return phones.split(',')
		else:
			return []

	def setPhones(self, mail, phones):
		self.users[mail]['phones'] = ','.join(phones[:3])
