# -*- coding: utf-8 -*-
"""serveur chat (UDP)"""
import socket
import threading
import os
import time

#### intialisation des variables ####
hote = ''
port = 6660
connectes = {} #id, adresse
pseudos = {}   #id, pseudo 
taille_paquet = 1024

#fonctions:
class OutilsCo():
    """outils utilises pour gerer la connexion"""
    def __init__(self):
        """initialisation"""
        self.idUtilises = ["00", "mj"]
        
    def genererID(self):
        """génère un id de 2 caractères non utilisé"""
        caracteres = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
                      "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
                      "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
                      "<", ">", "+", "=", "-", "*", "@"]
        fin = False
    
        for c1 in caracteres:
            for c2 in caracteres:
                txt = "{}{}".format(c1,c2)
                if not txt in self.idUtilises:
                    fin = True
                    break
            if fin:
                break
        self.idUtilises.append(str(txt))    
        return txt
    
    def libererID(self, txt):
        """retire un identifiant de la liste des id utilises"""
        self.idUtilises.remove(txt)

#### fils secondaires #### 

class DebitMetre(threading.Thread):
    """debitmetre permettant de mesurer le debit d'emission/reception du serveur"""
    def __init__(self, taille_paquet):
        """création du serveur vocal"""
        threading.Thread.__init__(self)
        self.mesureEnvoi = 0
        self.mesureReception = 0
        self.debitEmission = 0.00
        self.debitReception = 0.00
        #si oui, affiche les resultats chaque seconde:
        self.affichage = False
        
    def envoi(self, taille_paquet):
        """pour signaler l'envoi d'un paquet"""
        self.mesureEnvoi += taille_paquet

    def reception(self, taille_paquet):
        """pour signaler la reception d'un paquet"""
        self.mesureReception += taille_paquet

    def run(self):
        """mesure le volume en octets qui transite chaque seconde"""
        self.lance = True
        t0 = time.time()
        while self.lance:   
            delta = time.time() - t0
            if delta >= 1:
                self.debitEmission = self.mesureEnvoi / delta
                self.mesureEnvoi = 0
                self.debitReception = self.mesureReception / delta
                self.mesureReception = 0
                t0 = time.time()
                if self.affichage:
                    print(self)

    def __repr__(self):
        """affiche les derniers debits mesures (en ko/s)"""
        retour = "** Reception: {} ko/s ** -> Emission: {} ko/s **"\
                  "".format((self.debitReception * 0.001), (self.debitEmission * 0.001))   
        return retour    

    def stop(self):
        """Fermeture du fil debitmetre"""    
        self.lance = False

        
class ServeurVoc(threading.Thread):
    """serveur UDP dedie au chat vocal"""
    def __init__(self, port, taille_paquet):
        """création du serveur vocal"""
        threading.Thread.__init__(self)
        self.serveur_lance = False
        self.connectes = []
        self.taille_paquet = taille_paquet
        self.port = port
        
    def creer(self):
        """creation d'une entree serveur UDP"""
        try:
           self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
           self.socket.bind(('', self.port))
           retour = "Le serveur vocal ecoute a present sur le port {}\n".format(self.port)
           self.serveur_lance = True
           self.start()
        except IOError, e:
            if e.errno==10048:
               retour = "\nUne instance du serveur est déja lancée"
            else:   
               retour = "\nechec de lancement du serveur"     
        finally:
            print(retour)
        return retour

    
    def nouveauClient(self, adresse):
        """ajoute un nouveau client a la liste des connectes"""
        self.connectes.append(adresse)
        retour = "Nouveau client connecte: {}\n)".format(adresse)
        print(retour)
        return retour
    
    def supprimerClient(self, adresse):
        """ajoute un nouveau client a la liste des connectes"""
        self.connectes.remove(adresse)
        retour = "Client deconnecte: {}\n)".format(adresse)
        print(retour)
        return retour


    def run(self):
        """boucle de fonctionnement du serveur"""
        while self.serveur_lance:
            #on recoit les donnees envoyees 
            try:
               data, adresse = self.socket.recvfrom(self.taille_paquet)
               try:
                   dM.reception(len(data))
               except:
                   pass
                
            except IOError, e:
               if e.errno == 10022:  
                 # print("erreur de reception {}".format(adresse))
                 #self.supprimerClient(adresse)
                  time.sleep(0.02)
               else:
                  print("erreur {} - suppression de l'adresse {}".format(e.errno, adresse))
                  os.system("pause")
                  #self.supprimerClient()

            if data:
                if not adresse in self.connectes:
                    #l'adresse n'est pas dans la liste: on l'ajoute aux connectes
                    self.nouveauClient(adresse)
                    
                else:
                    for dest in self.connectes:
                        if dest != adresse:
                            #le client ne recoit pas ses propres donnees
                            self.socket.sendto(data , dest)
                            try:
                               dM.envoi(len(data))
                            except:
                               pass 
                            #tmp = "{} -> {}".format(len(data), adresse)

        
    def stop(self):
        """Fermeture du serveur"""
        self.serveur_lance = False
        self.socket.close()
        retour = "Serveur vocal ferme"
        print("\nFermeture du serveur vocal")
        return retour

        
class FilClient(threading.Thread):
    """fil de connexion client au serveur TCP"""
    def __init__(self, outils, cnn):
        """creation du fil"""
        threading.Thread.__init__(self)
        self.cnn = cnn
        self.pseudo = "inconnu"
        self.idClient = "00"
        self.outils = outils
        self.connecte = False

    def creer(self):
        """initialise la connexion avec le client"""       
        self.connecte = True
        #on genere un id pour lui
        self.idClient = self.outils.genererID()
        #on demarre le fil de reception
        self.start()

    def traitement(self, msg):
        """traite le message selon son type et son/ses destinataire(s)"""
        if len(msg) >= 6:
            nature = msg[0:2]
            emet = msg[2:4]
            dest = msg[4:6]
            try:
               contenu = msg[6:]
            except:
               contenu = "" 
        
            if dest == "sa":
                #message pour le serveur, on ne le retransmet pas
                if nature == "ci":
                    #le client informe de sa connexion et demande un identifiant
                    self.pseudo = contenu
                    pseudos[self.idClient] = self.pseudo
                    print("connexion: {} -> {} ({})\n".format(self.getName(),self.pseudo, self.idClient))
                    self.cnn.sendall("cisa"+self.idClient+"#")
                    for cle in connectes:
                        msg = "ccsatc{}{}#".format(self.idClient, self.pseudo)
                        connectes[cle].sendall(msg)
                        try:
                            dM.envoi(len(msg))
                        except:
                            pass
                    for ident in pseudos:
                        #on envoie la liste des id/pseudos des autres clients au nouveau connecte
                        if ident != self.idClient:
                            msg = "ccsa{}{}{}#".format(self.idClient, ident, pseudos[ident])
                            self.cnn.sendall(msg)
                            time.sleep(0.001) #on temporise pour laisser le temps aux infos de s'afficher

                if nature == "cd":
                    #client informe de sa deconnexion
                    for cle in connectes:
                        if cle != self:
                             msg = "cdsaac{}#".format(emet)
                             connectes[cle].sendall(msg)
                             try:
                                 dM.envoi(len(msg))
                             except:
                                 pass
                    self.stop()
                    
                elif nature == "f0":
                    #client demande un identifiant pour un envoi de fichier
                    msg = "f0sa{}{}#".format(self.idClient, self.outils.genererID())
                    self.cnn.sendall(msg)
                    
                elif nature == "f1":
                    #client signale au serveur qu'il peut liberer l'identifiant du fichier
                    self.outils.libererID(contenu)
                
            elif dest == "tc":
                #message pour tous les clients, emetteur compris
                for cle in connectes:
                    connectes[cle].sendall("{}#".format(msg))
                    try:
                       dM.envoi(len(msg)+1)
                    except:
                       pass
                
            elif dest == "ac":
                #message pour tous les clients excepte l'emetteur
                for cle in connectes:
                    if cle != self:
                         connectes[cle].sendall("{}#".format(msg))
                         try:
                             dM.envoi(len(msg)+1)
                         except:
                             pass
                     
            elif dest in connectes:
                #message pour un client unique (mj ou autre)
                print("message pour {}: {}\n".format(dest, contenu))
            
            elif dest == "sv":
                #message pour le serveur vocal
                print("pour serveur vocal: {}".format(contenu)) 

        else:
            print("Erreur : format du message illisible -> {}\n".format(msg))
        
    def run(self):
        """echange avec le client"""
        #try:
        while self.connecte:
               #reception du message client
               recu = self.cnn.recv(1024)
               try:
                   dM.reception(len(recu))
               except:
                   pass
               #on peut recevoir plusieurs messages en meme temps,
               #d'ou le caractere de fin de message: "#"
               
               for msgClient in recu.split("#"):
                   if len(msgClient) > 0:
                       self.traitement(msgClient)
               

       # except:
       #     print("Erreur {}".format(self.getName()))
       #     self.stop()

    def stop(self):
        """ferme le fil client"""
        self.connecte = False
        #fermeture de la connexion avec le client:
        self.cnn.close()
        #on supprime le client de la liste des connectes
        del connectes[self]
        #on libere son identifiant
        self.outils.libererID(self.idClient)
        print("\nClient {} deconnecte ({})".format(self.pseudo, self.getName()))
        
# Le fil se termine ici    

class Serveur(threading.Thread):
    """serveur TCP pour chat, echange de fichiers ou d'objets..."""
    def __init__(self, port):
        """creation du serveur"""
        self.serveur_lance = False
        threading.Thread.__init__(self)
        self.port = port
        self.idUtilises = ["00", "mj"]
        self.o = OutilsCo()
        
    def creer(self):
        """demarre le serveur"""
        try:
           self.cnn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
           self.cnn.bind((hote, self.port))
           self.cnn.listen(5)
           retour = "Le serveur ecoute a present sur le port {}".format(port)
           self.serveur_lance = True
           self.start()
        except:
           retour = "Impossible de demarrer le serveur"
        finally:   
           print(retour)

        return self.serveur_lance, retour

    def run(self):
        """boucle de fonctionnement"""
        while self.serveur_lance:
            try:
               #tant que le serveur est lance, il reste disponible a de nouvelles connexions
               cnnClient, adresse = self.cnn.accept()
               #on cree un nouveau fil pour le nouveau client
               client = FilClient(self.o, cnnClient)
               #client.start()
               
               #et on ajoute son adresse reseau a la liste des connectes
               connectes[client] = cnnClient
               print("Client {} connecte, adresse IP {}, port {}.".format(client.getName(), adresse[0], adresse[1]))
               client.creer()
            except:
               break        
        
    def stop(self):
        """ferme le serveur"""
        self.serveur_lance = False
        for client in connectes.keys():
            client.stop()
        self.cnn.close()
        print("\nFermeture du serveur TCP")
        
#### fil principal ####
if __name__ == "__main__":
   # si lancement direct:
   #debitmetre pour mesurer l'emission

   dM = DebitMetre(taille_paquet)
   dM.affichage = False
 
   s = Serveur(port)
      
   s.creer()
   sVoc = ServeurVoc(port, taille_paquet)
   sVoc.creer()
   dM.start()
   
   os.system("pause")
   s.stop()
   sVoc.stop()
   dM.stop()
   print("- Fin -")   
   time.sleep(1)


   
   
   




    
 

