from twisted.internet.protocol import Protocol, ClientFactory
from twisted.internet.endpoints import SSL4ClientEndpoint, connectProtocol
from twisted.internet.ssl import ClientContextFactory
from twisted.internet import reactor
import mumble_proto, struct, sys, re

class MumbleResponseError(Exception):
	def __init__(self, value):
		self.value = value

	def __str__(self):
		return str(self.value)

class MumbleUser():
	session = 0
	name = ""
	user_id = 0
	channel_id = 0
	muted = False
	deafened = False
	suppressed = False
	self_muted = False
	self_deafened = False
	priority_speaker = False
	comment = ""

	def __str__(self):
		return "[MumbleUser | name: " + self.name + " user_id: " + str(self.user_id) + " channel_id: " + str(self.channel_id) + " session: " +  str(self.session) + "]"

def placeNew(orig, new):
	if (not new):
		return orig
	else:
		return new

class MumbleProtocol(Protocol):
	remote_version = 0
	local_version = 0
	session = 0
	max_msg_len = 0
	follow_id = 0

	users = {}

	username = "SaaltyBot"
	server_password = ""

	data_expected = False
	data_left = 0
	packet_len = 0
	packet_type = 0
	packet_data = ""
	packet = ""
	
	packets_received = 0
	bad_packets = 0
	good_packets = 0
	
	command_matcher = re.compile("^/(.*)")

	def dataReceived(self, data):
		self.interpretType(data)

		#print mumble_proto.ParseFromString(data)
		#self.remote_version = mumble_proto.Version()
		#self.remote_version.ParseFromString(data)

	def connectionMade(self):
		print "Connection established!"
		self.sendVersion()

	def keepAlive(self):
		print "*badump*"
		ping = mumble_proto.Ping()
		self.writeProtobuf(3, ping)
		reactor.callLater(25, self.keepAlive)

	def connectionLost(self, reason):
		print "Protocol disconnected! Reason:\n ", reason
		#Factory.clients.remove(self)

	def writeProtobuf(self, ptype, proto):
		header = struct.pack("!h", ptype) + struct.pack("!i", proto.ByteSize())
		pstr = header + proto.SerializeToString()
		self.transport.write(pstr)
		print "Sent proto of id:", ptype, "and contents:\n", proto

	def sendVersion(self):
		self.local_version = mumble_proto.Version()
		self.local_version.version = 66053 #Pretend we're 1.2.4
		self.local_version.release = "Initial"
		self.local_version.os = "Python"
		self.local_version.os_version = str(sys.version_info.major) + "." + str(sys.version_info.minor) + "." + str(sys.version_info.micro)#sys.version_info
		self.writeProtobuf(0, self.local_version)

	def authenticate(self):
		print "Attempting to authenticate with server using username: '", self.username, "' and password: '", self.server_password, "'"
		authenticate_packet = mumble_proto.Authenticate()
		authenticate_packet.username = self.username.encode("UTF-8")
		if (len(self.server_password) > 0):
			authenticate_packet.password = self.server_password.encode("UTF-8")
		self.writeProtobuf(2, authenticate_packet)

	def sendMessageToChannel(self, channel, msg):
		msg_packet = mumble_proto.TextMessage()
		msg_packet.actor = self.session
		msg_packet.channel_id.append(channel)
		msg_packet.message = msg
		self.writeProtobuf(11, msg_packet)

	def sendMessageToUser(self, user, msg):
		msg_packet = mumble_proto.TextMessage()
		msg_packet.actor = self.session
		msg_packet.session.append(user)
		msg_packet.message = msg
		self.writeProtobuf(11, msg_packet)

	def storeVersion(self, data):
		self.remote_version = mumble_proto.Version()
		self.remote_version.ParseFromString(data)
		print "Recieved remote version of: "
		print self.remote_version
		self.authenticate()

	def throwRejection(self, data):
		reject_packet = mumble_proto.Reject()
		reject_packet.ParseFromString(data)
		raise MumbleResponseError(reject_packet)

	def setupAuth(self, data):
		auth_packet = mumble_proto.CryptSetup()
		auth_packet.ParseFromString(data)
		self.setState(None, True, True, "I am but a simple bot.")
		self.keepAlive() #Begin heartbeat

	def setState(self, channel, muted, deafened, comment):
		state_packet = mumble_proto.UserState()
		if (channel):
			state_packet.channel_id = channel
		if (muted):
			state_packet.self_mute = muted
		if (deafened):
			state_packet.self_deaf = deafened
		if (comment):
			state_packet.comment = comment
		if (state_packet.ByteSize() <= 0):
			return
		self.writeProtobuf(9, state_packet)

	def parseMessage(self, data):
		msg_packet = mumble_proto.TextMessage()
		msg_packet.ParseFromString(data)
		user = msg_packet.actor
		result = self.command_matcher.match(msg_packet.message)
		print msg_packet
		if (result):
			user = self.users[msg_packet.actor]
			if (re.search("greetme", result.group(1))):
				dstr = "Hello " + user.name + " your id is " + str(user.user_id) + ", the current channel you are in is " + str(user.channel_id) + "."
				if(not msg_packet.channel_id):
					self.sendMessageToUser(msg_packet.actor, dstr)
				else:
					self.sendMessageToChannel(msg_packet.channel_id[0], dstr)
			elif (re.search("cometome", result.group(1))):
				self.setState(user.channel_id, None, None, None)
			elif (re.search("followme", result.group(1))):
				self.follow_id = user.session
			elif (re.search("nofollow", result.group(1))):
				self.follow_id = 0

	def updateUser(self, packet):
		user = self.users[packet.session]
		user.session = packet.session
		user.name = placeNew(user.name, packet.name)
		user.user_id = placeNew(user.user_id, packet.user_id)
		user.channel_id = placeNew(user.channel_id, packet.channel_id)
		user.muted = placeNew(user.muted, packet.mute)
		user.deafened = placeNew(user.deafened, packet.deaf)
		user.suppressed = placeNew(user.suppressed, packet.suppress)
		user.self_muted = placeNew(user.self_muted, packet.self_mute)
		user.self_deafened = placeNew(user.self_deafened, packet.self_deaf)
		user.comment = placeNew(user.comment, packet.comment)
		print self.users[packet.session]

	def checkUser(self, data):
		state_packet = mumble_proto.UserState()
		state_packet.ParseFromString(data)
		try:
			self.users[state_packet.session]
		except KeyError:
			self.users[state_packet.session] = MumbleUser()
		self.updateUser(state_packet)
		if (self.follow_id == state_packet.session):
			self.setState(self.users[state_packet.session].channel_id, None, None, None)

	def removeUser(self, data):
		state_packet = mumble_proto.UserRemove()
		state_packet.ParseFromString(data)
		print state_packet
		del self.users[state_packet.session]
		print self.users

	def acceptConfigs(self, data):
		config_packet = mumble_proto.ServerConfig()
		config_packet.ParseFromString(data)
		self.max_msg_len = config_packet.message_length
		print config_packet.welcome_text

	def syncWithServer(self, data):
		server_packet = mumble_proto.ServerSync()
		server_packet.ParseFromString(data)
		self.session = server_packet.session

	def interpretType(self, data):
		self.packet_type = struct.unpack("!h", data[0:2])[0]
		self.packet_len = struct.unpack("!i", data[2:6])[0]
		self.packet = data[6:6+self.packet_len]

		print "Received packet of id:", self.packet_type, "and length of:", self.packet_len, "got packet size of:", len(self.packet)
		self.packets_received += 1

		if self.packet_type == 0:
			self.storeVersion(self.packet), #Version
		elif self.packet_type == 4:
			self.throwRejection(self.packet) #Reject
		elif self.packet_type == 5:
			self.syncWithServer(self.packet) #ServerSync
		elif self.packet_type == 7:
			pass #ChannelState, don't particularly care about this atm.
		elif self.packet_type == 8:
			self.removeUser(self.packet) #UserRemove
		elif self.packet_type == 9:
			self.checkUser(self.packet) #UserState, gives state of user.
		elif self.packet_type == 11:
			self.parseMessage(self.packet) #TextMessage
		elif self.packet_type == 15:
			self.setupAuth(self.packet) #CryptSetup
		elif self.packet_type == 20:
			pass #PermissionQuery, don't care atm
		elif self.packet_type == 21:
			pass #CodecVersion, notifies client, ignore
		elif self.packet_type == 24:
			self.acceptConfigs(self.packet) #ServerConfig
		else:
			pass

		if (len(data) - (6 + self.packet_len) > 0):
			self.interpretType(data[6+self.packet_len:])


	# def interpretType(self, data):
	# 	self.packets_received = self.packets_received + 1
	# 	if (not self.data_expected):
	# 		self.packet_type = struct.unpack("!h", data[0:2])[0]
	# 		self.packet_len = struct.unpack("!i", data[2:6])[0]
	# 		self.packet_data = data[6:6+self.packet_len]
	# 	#print "Received packet of id:", self.packet_type, "and length of:", self.packet_len, "got packet size of:", len(self.packet_data)

	# 	self.packet = self.packet_data
	# 	if (not self.data_expected and self.packet_len > len(self.packet_data)):
	# 		print "We're expecting more data from this packet!"
	# 		self.data_expected = True
	# 		self.data_left = self.data_left - len(self.packet_data)
	# 		return

	# 	if (self.data_expected):
	# 		print "Adding more data from packet"
	# 		self.data_left = self.data_left - len(data)
	# 		self.packet = self.packet + data
	# 		if (self.data_left <= 0):
	# 			self.data_expected = False
	# 		return

	# 	if self.packet_type == 0:
	# 		self.storeVersion(self.packet),
	# 	elif self.packet_type == 4:
	# 		self.throwRejection(self.packet)
	# 	elif self.packet_type == 11:
	# 		self.parseMessage(self.packet)
	# 	elif self.packet_type == 15:
	# 		self.setupAuth(self.packet)
	# 	else:
	# 		pass

	# 	if (not self.data_expected):
	# 		packet = "" ]]

class MumbleFactory(ClientFactory):
	def startedConnecting(self, connector):
		print "Started to connect."
	
	def buildProtocol(self, addr):
		print "Connected."
		return MumbleProtocol()

	def clientConnectionLost(self, connector, reason):
		print "Client lost connection! Reason:\n ", reason

	def clientConnectionFailed(self, connector, reason):
		print "Client failed connection! Reason:\n ", reason

def gotProtocol(p):
	pass

#reactor.connectSSL("bloods.dyndns.org", 64738, MumbleFactory(), ClientContextFactory())
point = SSL4ClientEndpoint(reactor, "bloods.dyndns.org", 64738, ClientContextFactory())
d = connectProtocol(point, MumbleProtocol())
d.addCallback(gotProtocol)
reactor.run()