############################################################################################################################################
#						IrcServerConnection.py
############################################################################################################################################

import socket
import threading
import re
import string
import sys
import time
import types
from qt import *
import Queue

class IrcServerConnection:
	def __init__(self, gui, hoteDistant, port = 6667, nomReel = "Romain GUY", nomUtilisateur = "rguy", serveur = "server"):

		"""Constructeur de la classe IrcServerConnection. Le seul parametre absolument necessaire
		est l'hote distant. Néanmoin il est conseillé de saisir le numéro de port."""

		self.nick = 'Gfx_'
		self.nomReel = nomReel
		self.nomUtilisateur = nomUtilisateur
		self.serveur = serveur
		self.hotePerso = 'localhost'
		self.hoteDistant = hoteDistant
		self.port = port
		self.connectionIRC = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.waitingFor = 0
		self.emetteur = QObject()

		# Create the queue
		self.queue = Queue.Queue()

		# Set up the GUI part
		self.gui = gui
		self.gui.queue = self.queue

		# A timer to periodically call periodicCall :-)
		self.timer = QTimer()
		self.emetteur.connect(self.timer, SIGNAL("timeout()"), self.periodicCall)

		# Start the timer -- this replaces the initial call to periodicCall
		self.timer.start(100)

	def periodicCall(self):
		self.gui.processIncoming()
		#if not self.running:
		#	root.quit()

	def endApplication(self):
		self.running = 0

	def getNick(self):
		return self.nick
	def getNomReel(self):
		return self.nomReel
	def getNomUtilisateur(self):
		return self.nomUtilisateur
	def getServeur(self):
		return self.serveur
	def getHotePerso(self):
		return self.hotePerso
	def getHoteDistant(self):
		return self.hoteDistant
	def getPort(self):
		return self.port

	#Mutateurs

	def setNick(self, nick):
		self.nick = nick
	def setNomReel(self, nomReel):
		self.nomReel = nomReel
	def setNomUtilisateur(nomUtilisateur):
		self.nomUtilisateur = nomUtilisateur
	def setServeur(self, serveur):
		self.serveur = serveur
	def setHote(self, hote):
		self.hote = hote
	def setHoteDistant(self, hoteDistant):
		self.hoteDistant = hoteDistant
	def setPort(self, port):
		self.port = port

	def startIdentdServer(self):
		self.identdServer = threading.Thread(target = self.__identdServer)
		self.identdServer.setDaemon(1)
		self.identdServer.start()

	def __identdServer(self):
		identd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		identd.bind(('', 113))
		identd.listen(1)
		irc, irchost = identd.accept()
		self.queue.put(("IDENTD-REQUEST", irchost))
		match = re.search('(\d+)', irc.recv(512))
		response = ""
		if match is not None:
			response = match.group(1) + ", " + str(self.getPort()) + ' : USERID : UNIX : ' + self.getNomUtilisateur() + '\r\n'
		else:
			str(self.getPort()) + ' : USERID : UNIX : ' + self.getNomUtilisateur() + '\r\n'
		irc.send(response)
		self.queue.put(("IDENTD-RESPONSE", response))
		irc.close()
		identd.close()
			
	def testConnection(self):
		self.connectionTest = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		try:
			self.connectionTest.connect((self.getHoteDistant(), self.getPort()))
			self.connectionTest.close()
			return 1
		except:
			self.connectionTest.close()
			return 0

	def connectIrc(self):
		""""Etablit la connection avec le serveur IRC. Procède à l'identification. """
		# starts the identd server
		
		if not self.testConnection(): return 0
		
		self.startIdentdServer()
			
		self.connectionIRC = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.connectionIRC.connect((self.getHoteDistant(), self.getPort()))

		try:
			commande = "NICK "
			commande = commande + self.getNick()+'\r\n'
			self.connectionIRC.send(commande)
			commande = "USER "
			commande = commande + self.getNomUtilisateur() +' ' + self.getHoteDistant() + ' '
			commande = commande + self.getServeur() + ' ' + ':' + self.getNomReel()     + '\r\n'
			self.connectionIRC.send(commande)
		except:
			self.waitThread= threading.Thread(target = self.__wait)
			self.waitThread.setDaemon(1)
			self.waitThread.start()
			return 0

		#On crée un thread
		self.threadPrincipalLoop()

		return 1

	def __wait(self):
		self.waitingFor = 1
		self.identdServer = None
		#self.connectionIRC.close()
		print "*** start waiting"
		time.sleep(30)
		print "*** end waiting"
		self.waitingFor = 0
		self.queue.put(("END-WAITING", ()))

	def isWaitingFor(self):
		return self.waitingFor

	def threadPrincipalLoop(self):
		""" Création du thread permettant le lancelent de la boucle principale."""
		self.running = 1
		self.threadBoucle = threading.Thread(target = self.__principalLoop)
		self.threadBoucle.setDaemon(1)
		self.threadBoucle.start()

	def closeConnection(self):
		"""Ferme la connection avec le serveur"""
		#self.quit()
		self.connectionIRC.close()

	def getMsgFromBuffer(self, buffer):
		"""Recoit le buffer de messages envoyes par le serveur IRC en parametre.
		Retourne une liste de chaines contenant chacune un message du serveur IRC.
		Si le dernier message envoye par le serveur n'etait pas complet celui ci
		n'est pas retourne dans la liste final, il reste dans le buffer."""

		if buffer == "":
			return ([""],"")

		full = 0

		if buffer[-1] == '\n' and buffer[-2] == '\r':full = 1

		if full :
			l = string.split(buffer,'\r\n')
			buffer = ''
			return (l, buffer)
		else:

			buffer = buffer +'\r\n'
			l = string.split(buffer,'\r\n')
			buffer = l[-2]
			return (l[:-2], buffer)

	def send(self, text):
		try:
			self.connectionIRC.send(text)
		except:
			# erreur de connexion !!!!
			self.queue.put(("ERROR", ))

	def oper(self, psw):
		""" Construit la commande OPER
		paramètre :	- Une chaine correspondant au mot de passe"""
		commande = "OPER " + self.getNick() + " " + psw + "\r\n"
		self.send(commande)

	def who(self, name, listOp = ""):
		""" Construit la commande WHO
		paramètres :	- Une chaine correspondant au nom à rechercher
				- Une chaîne falcultative permettant de limiter la requêtes aux opérateurs"""

		commande = "WHO " + name + " " + listOp + "\r\n"
		self.send(commande)

	def whoIs(self, nick, server=""):
		""" Construit la commande WHOIS
		paramètres :	- Une chaîne correspondant au sujet de la demande d'infos
				- Une chaîne optionnelle permettant de désigner le serveur qui répondra à la requête"""

		commande = "WHOIS " + server + " " + nick + "\r\n"
		self.send(commande)

	def whoWas(self, nick, counter = "", server = ""):
		""" Construit la commande WHOWAS
		paramètres :	- Une chaîne désignant l'utilisateur visé par la requête
				- Une chaine optionnelle numérique correspondant au nombre maximum d'occurences à renvoyer
				- Une chaine optionnelle permettant de désigner le serveur qui répondra à la requête"""

		commande = "WHOWAS " + nick + " " + counter + " " + server + "\r\n"
		self.send(commande)

	def kill(self, nick, comment = ""):
		""" Construit la commande KILL
		paramètres :	- Une chaine désignant l'utilisateur visé par la requête
				- Une chaine optionnelle permettant de spécifier le motif du KILL"""

		commande = "KILL " + nick + " " + comment + "\r\n"
		self.send(commande)

	def quit(self, message = ""):
		""" Construit la commande QUIT
		paramètres :	- Une chaine optionnelle permettant de spécifier le motif du QUIT"""

		commande = "QUIT :" + message + "\r\n"
		self.send(commande)

	def away(self, message = ""):
		""" Construit la commande AWAY
		paramètres :	- Une chaine optionnelle permettant de spécifier le motif du AWAY"""

		commande = "AWAY :" + message + "\r\n"
		self.send(commande)
		
	def back(self):
		""" Construit la commande AWAY, informant au server le retour de lutilisateur
		"""

		commande = "AWAY\r\n"
		self.send(commande) 

	def join(self, chanList, keyList=[""]):
		""" Construit la commande JOIN
		paramètres :	- Une liste de chaines contenant le nom de tous les canaux à rejoindre
				- Une liste optionnelle de chaine contenant les éventuels clées d'accès nescéssaire
					Les clef correspondent respectivement aux canaux"""

		commande = "JOIN "
		i= 0
		nbi = len(chanList)
		for truc in chanList[:-1]:
			commande = commande + truc + ","
		commande = commande + chanList[-1] + " "

		for truc in keyList[:-1]:
			commande = commande + truc + ","
		commande = commande + keyList[-1]

		commande = commande +"\r\n"
		self.send(commande)
#--->		#print commande

	def part(self, chanList):
		""" Construit la commande PART
		paramètres :	- Une liste de chaines contenant le nom de tous les canaux à quitter"""

		commande = "PART "

		for truc in chanList[:-1]:
			commande = commande + truc + ","
		commande = commande + chanList[-1] + " "

		commande = commande + "\r\n"
		self.send(commande)
#--->		#print commande

	def modeChan(self, chan, modeList, users, limit = "" , mask = ""):
		""" Construit la commande MODE pour les canaux
		paramètres :	- Une chaine contenant le nom du canal concerné par la requête
				- Une liste de chaines contenant :	- En premier élément sens du changement (+ ou -)
									- Puis les options de mode
				- Une chaine optionnelle numérique s'appliquant au mode l
				- Une chaine optionnelle contenant les utilisateurs concernés par la requête
				- Une chaine optionnelle contenant le masque d'utilisateur éventuel"""

		commande = "MODE " + chan + " "
		for truc in modeList:
			commande = commande + truc

		commande = commande + " " + limit + " "
		for truc in users:
			commande = commande + " " + truc + " "

		commande = commande + mask + "\r\n"
		self.send(commande)
#--->		#print commande

	def modeUser(self, user, modeList):
		""" Construit la commande MODE pour les users
		paramètres :	- Une chaine contenant l'utilisateur concerné
				- Une liste de chaine (CF MODE canaux)"""

		commande = "MODE " + user + " "
		for truc in modeList:
			commande = commande + truc

		commande = commande + "\r\n"
		self.send(commande)
#--->		#print commande

	def topic(self, chan, newTopic=""):
		""" Construit la commande TOPIC
		paramètres :	- Une chaine contenant le nom du canal concerné
				- Une chaine optionnelle :	- Si définie, permet de spécifier le nouveau topic
								- sinon, la commande renvoi le topic actuel du canal"""
		commande = "TOPIC " + chan + " "

		if newTopic != "":
			commande = commande + ":" + newTopic+ " "

		commande = commande + "\r\n"
		self.send(commande)
#--->		#print commande

	def names(self, chanList = [""]):
		""" Construit la commande NAMES
		paramètres :	- Une liste optionnelle de chaines contenant les noms des canaux dont il faut lister les utilisateurs"""

		commande = "NAMES "

		for truc in chanList[:-1]:
			commande = commande + truc + ","

		commande = commande + chanList[-1] + "\r\n"
		self.send(commande)
#--->		#print commande

	def list(self):
		"""Liste les canaux et leur sujets"""

		commande = "LIST" + "\r\n"
		self.send(commande)
#--->		#print commande

	def privMsg(self, targetList, message):
		"""Envoie un message à une ou plusieur cibles (canaux ou utilisateurs) """

		commande = "PRIVMSG "
		for truc in targetList[:-1]:
			commande = commande + truc + ","

		commande = commande + targetList[-1] + " :" + message + "\r\n"
		self.send(commande)
#--->		print commande

	def notice(self, targetList, message):
		"""Similaire à PrivateMsg sauf qu'il n'a pas de réponse (du serveur ou d'un client)
		à un tel message"""

		commande = "NOTICE "
		for truc in targetList[:-1]:
			commande = commande + truc + ","

		commande = commande + targetList[-1] + " :" + message + "\r\n"
		self.send(commande)
#--->		print commande

	def invite(self, user, chan):
		"""Invite l'utilisateur 'user' à rejoindre le canal 'chan' """

		commande = "INVITE " + user + " " + chan + "\r\n"
		self.send(commande)
#--->		print commande

	def kick(self, chan, user, comment=""):
		"""Déconnecte l'utilisateur du canal 'chan'"""

		commande = "KICK " + chan + " " + user
		if comment != "":
			commande = commande + " :" + comment

		commande = commande + "\r\n"
		self.send(commande)
#--->		print commande

	def version(self, server = ""):
		"""Demande la version d'un serveur , serveur courant si valeur par défaut."""

		commande = "VERSION "
		if server != "":
			commande = commande + server

		commande = commande + "\r\n"
		self.send(commande)
#--->		print commande

	def ctcpVersion(self, target):
		self.send("PRIVMSG " + target + " :" + chr(1) + "VERSION" + chr(1) + '\r\n')
#--->		print commande

	def ctcpTime(self, target):
		self.send("PRIVMSG " + target + " :" + chr(1) + "TIME" + chr(1) + "\r\n")
#--->		print commande

	def ctcpPing(self, target):
		self.send("PRIVMSG " + target + " :" + chr(1) + "PING " + str(int(time.time())) + chr(1) + "\r\n")
#--->		print commande

	def ctcpSound(self, target, file):
		self.send("PRIVMSG " + target + " :" + chr(1) + "SOUND " + file + chr(1) + "\r\n")
#--->		print commande

	def ctcpImage(self, target, url):
		self.send("PRIVMSG " + target + " :" + chr(1) + "IMAGE " + url + chr(1) + "\r\n")
#--->		print commande

	def ctcpMe(self, target, message):
		self.send("PRIVMSG " + target + " :" + chr(1) + "ACTION " + message + chr(1) + "\r\n")
#--->		print commande

	def stat(self, request, server =""):
		"""Demande les stat d'un serveur , serveur courant si valeur par défaut."""

		commande = "STATS " + request
		if server != "":
			commande = commande + " " + server

		commande = commande + "\r\n"
		self.send(commande)
#-->		print commande

	def link(self, server="", maskServer=""):
		"""Demande la liste des serveur correspondant au masque"""

		commande = "LINKS " + server + " " + maskServer + "\r\n"
		self.send(commande)
#--->		print commande

	def time(self, server=""):
		"""Demande de l'heure et de la date."""

		commande = "TIME " + server + "\r\n"
		self.send(commande)
#--->		print commande

	def admin(self, server=""):
		"""Demande le nom de l'admin d'un serveur, le courant si valeur par défaut. """

		commande = "ADMIN " + server + "\r\n"
		self.send(commande)
#--->		print commande

	def info(self, server=""):
		"""" Demande les infos d'un serveur, le courant si valeur par défaut. """

		commande = "INFO " + server + "\r\n"
		self.send(commande)
#--->		print commande

	def changeNick(self, newNick):
		commande = "NICK"+" "+ newNick+ "\r\n"
		#self.setNick(newNick)
		self.send(commande)

	#Methodes privee
	def __principalLoop(self):
		"""Contient la boucle principale.Est lancée dans un thread.
		Emet des signaux à la reception de message du server"""

		line = ""
		gettingDeeperIn = 0
		tooMany = ""

		while (not gettingDeeperIn):
			line += self.connectionIRC.recv(512)
			tuple = self.getMsgFromBuffer(line)
			line = tuple[1]
			for token in tuple[0]:
				if (gettingDeeperIn):
					tooMany += token + "\r\n"
				else:
					match = re.search(':\S+\s+(\d\d\d)\s+(\S+)\s+:', token)
					if match is not None and match.group(2) != self.getNick():
						self.setNick(match.group(2))
					if re.search(':(\S+)\s+(376|422) ' + self.getNick(), token) != None:
						gettingDeeperIn = 1
					self.queue.put(("WELCOME", token.replace("<", "&lt;").replace(">", "&gt;")))

		buffer = tooMany + line

		regexIRC    = re.compile('(?m)^(PING\s+:.*)|^:([^!:]+)!(\S+)\s+([A-Za-z]+)\s+(#?[^: \t]+)?\s*([^:]+)?:?([^\r]*)')
		regexNames  = re.compile(':(\S+)\s+(\S+)\s+(\S+)\s+(.*)')
		regex353		= re.compile("(=|@) (\S+)\s+:(.*)")
		regex332		= re.compile("(\S+)\s+:(.*)")
		regexEnd		= re.compile(":(.*)")

		while 1:
			#on converti les 512 o recu en msg
			tuple = self.getMsgFromBuffer(buffer)
			liste = tuple[0]	  #liste contient les msg
			buffer = tuple[1]	  #on affecte l'eventuel bout de cmde restant au buffer
				
			for commande in liste:
				print "*** DEBUG :: " + commande

				#on décompose le msg en différente partie :nick, id, command, source, arg, text
				#on utilise les reg exp

				match = regexIRC.search(commande)
				if match is not None:

					# PING PONG
					if match.group(0).find('PING') == 0:
						match = re.search(':(.*)', match.group(1))
						self.queue.put(("PING", match.group(1)))
					else:
						dump, nick, id, command, source, arg, text = match.groups(5)

						if command == 'PRIVMSG':
							if source[0] == '#':
								#ajout d'une commande dans la queue pour affichage d'un msg 'text'msg sur
								#le chanel 'source' envoyé par 'nick'
								self.queue.put(("MESSAGE", nick, source, text))

							else :
								#c'est un msg privé qui nous est adrressé
								#ajout d'une commande dans la queue pour affichge msg 'test' prive venant de 'nick'
								self.queue.put(("PRIVATE", nick, text))

						elif command == 'KICK':
							#ajout d'une commande dans la queue pour kick de arg sur source par nick pour la raison text(facultative)
							#sur le chan source
							self.queue.put(("KICK", nick, source, arg.strip(), text))

						elif command == 'JOIN':
							#ajout d'une commande dans la queue pour joint de nick sur text
							self.queue.put(("JOIN", nick, text))

						elif command == 'QUIT':
							#ajout d'une commande dans la queue pour quit IRC de nick pour la raison text
							self.queue.put(("QUIT", nick, text))

						elif command == 'PART':
							#ajout d'une commande dans la queue pour quit chan c'est nick qui quitte le chan source
							self.queue.put(("PART", nick, source))

						elif command =='MODE':
							self.queue.put(("CHANGEMODE", nick, source, arg))

						elif command =='NICK':
							self.queue.put(("NICK", nick, text))

						elif command == 'NOTICE':
							if source[0]=="#":
								self.queue.put(("CHANNEL-NOTICE", nick, source, text))
							else:
								self.queue.put(("NOTICE", nick, source, text))

						elif command == 'TOPIC':
							self.queue.put(("CHANNEL-TOPIC", nick, source, text))

						else:
							self.queue.put(("DEFAULT", commande))

				else:
					# COMMANDE SERVEUR
					match = regexNames.search(commande)
					if match is not None:
						server, command, nick, argsAndData = match.groups(4)

						if command == '353':
							# début d'une commande /NAMES
							if argsAndData[0] == '=' or argsAndData[0] == '@':
								match = regex353.search(argsAndData)
								if match is not None:
									dump, chan, names = match.groups(3)
									self.queue.put(("NAMES", chan))
									for name in string.split(names, " "):
										self.queue.put(("ADDUSER", chan, name))

						elif command == '366':
							self.queue.put(("END-NAMES", ))
						elif command == '332':
							# titre du canal
							match = regex332.search(argsAndData)
							self.queue.put(("JOIN-TOPIC", match.group(1), match.group(2)))
						elif command == '301':
							match = regex332.search(argsAndData)
							self.queue.put(("AWAY", match.group(1), match.group(2)))
						elif command == '305':
							match = regexEnd.search(argsAndData)
							self.queue.put(("SET-AWAY", match.group(1)))
						elif command == '306':
							match = regexEnd.search(argsAndData)
							self.queue.put(("SET-AWAY", match.group(1)))
						elif command == '333':
							match = re.search("(\S+)\s+(\S+)\s+(\S+)", argsAndData)
							self.queue.put(("SET-BY", match.group(1), match.group(2), match.group(3)))
						elif command == '311':
							match = re.search("(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+:(.*)", argsAndData)
							self.queue.put(("WHOIS-IS", match.group(1), match.group(2), match.group(3), match.group(4), match.group(5)))
						elif command == '319':
							match = regex332.search(argsAndData)
							self.queue.put(("WHOIS-CHANS", match.group(1), match.group(2)))
						elif command == '312':
							match = re.search("(\S+)\s+(\S+)\s+:(.*)", argsAndData)
							self.queue.put(("WHOIS-SERVER", match.group(1), match.group(2), match.group(3)))
						elif command == '317':
							match = re.search("(\S+)\s+(\d+)\s+(\d+)\s+:(.*)", argsAndData)
							self.queue.put(("WHOIS-TIME", match.group(1), match.group(2), match.group(3), match.group(4)))
						elif command == '322':
							match = re.search("(\S+)\s+(\S+)\s+:(.*)", argsAndData)
							self.queue.put(("CHANS-LIST", match.group(1), match.group(2), match.group(3)))
						elif command == '433':
							match = regex332.search(argsAndData)
							self.queue.put(("NICK-TAKEN", match.group(1), match.group(2)))
						elif command == 'NOTICE':
							# server notice
							self.queue.put(("SERVER-NOTICE", server, argsAndData[1:]))

			# RECEPTION D'UNE LIGNE DU SERVEUR
			buffer = buffer + self.connectionIRC.recv(512)
			if buffer == '':
				break

############################################################################################################################################
#								fin de IrcServerConnection.py
############################################################################################################################################