from __future__ import unicode_literals
# -*- coding: utf-8 -*-
"""serveurs chat et chat vocal (TCP et UDP)"""
from socket import socket, AF_INET, SOCK_DGRAM, SOCK_STREAM
from threading import Thread
from os import system
from time import time, sleep
import logging

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

#parametres deboguage
#logging.basicConfig(level=logging.DEBUG,
#                    format='(%(threadName)-10s) %(message)s',
#                    )

#gestion des erreurs et log
logging.basicConfig(level=logging.DEBUG)
logServeur = logging.getLogger(__name__)
handlerServeur = logging.FileHandler('serveur.log')
handlerServeur.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)s - %(message)s'))  #%(name)s - nom du module
logServeur.addHandler(handlerServeur)
logServeur.debug("    ----------------------    \n")

#classes utilitaires:
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))
        logServeur.info("Id {} attribue".format(txt))
        return txt
    
    def libererID(self, txt):
        """retire un identifiant de la liste des id utilises"""
        try:
           self.idUtilises.remove(txt)
        except:
           logServeur.warning("impossible de liberer l'id {}".format(txt) )

#### fils secondaires #### 

class DebitMetre(Thread):
    """debitmetre permettant de mesurer le debit d'emission/reception du serveur"""
    def __init__(self, taille_paquet):
        """création du serveur vocal"""
        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()
        while self.lance:   
            delta = time() - t0
            if delta >= 1:
                self.debitEmission = self.mesureEnvoi / delta
                self.mesureEnvoi = 0
                self.debitReception = self.mesureReception / delta
                self.mesureReception = 0
                t0 = time()
                if self.affichage:
                    logServeur.info(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(Thread):
    """serveur UDP dedie au chat vocal"""
    def __init__(self, port):
        """création du serveur vocal"""
        Thread.__init__(self)
        self.serveur_lance = False
        self.connectes = []
        self.port = port
        
    def creer(self):
        """creation d'une entree serveur UDP"""
        try:
           self.socket = socket(AF_INET, SOCK_DGRAM)
           self.socket.bind(('', self.port))
           retour = "Le serveur vocal ecoute a present sur le port {}".format(self.port)
           self.serveur_lance = True
           self.start()
        except IOError, e:
            if e.errno==10048:
               retour = "Une instance du serveur est déja lancée"
               logging.error(retour)
            else:   
               retour = "Echec de lancement du serveur"
               logServeur.error(retour)
        finally:
            logServeur.info(retour)
        return retour

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


    def run(self):
        """boucle de fonctionnement du serveur"""
        self.socket.setblocking(0)
        while self.serveur_lance:
            data = ""
            #on recoit les donnees envoyees 
            try:
               data, adresse = self.socket.recvfrom(1024)
               try:
                   dM.reception(len(data))
               except:
                   pass
                
            except IOError, e:
               if e.errno == 10035:
                   #pas de donnee recue
                   pass
               elif e.errno == 10022:  
                  logServeur.debug("erreur de reception {}".format(adresse))
                  sleep(0.02)
               elif e.errno == 10054:
                  self.connectes = []
                  logServeur.warning("reinitialisation de la liste des connectes")
               else:
                  logServeur.error("erreur {}".format(e.errno))
                  #self.supprimerClient()

            if len(data) > 0:
               if not adresse in self.connectes:
                    #l'adresse n'est pas dans la liste: on l'ajoute aux connectes
                    self.nouveauClient(adresse)
                    
               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"
        logServeur.info("Fermeture du serveur vocal")
        return retour

        
class FilClient(Thread):
    """fil de connexion client au serveur TCP"""
    def __init__(self, outils, cnn):
        """creation du fil"""
        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 ajoute son adresse reseau a la liste des connectes
        connectes[self.idClient] = self.cnn
        logServeur.info("Client {} connecte - Id {}".format(self.getName(), self.idClient))

        #on demarre le fil de reception
        self.start()

        
    def run(self):
        """echange avec le client"""
        self.cnn.setblocking(0)
        while self.connecte:
               #reception du message client
               try:
                   msgClient = self.cnn.recv(1024)

                   try:
                       dM.reception(len(msgClient))
                   except:
                       pass
                    
                   tampon.append((self.getName(), msgClient))

               except IOError, e:
                   if e.errno == 10035:
                       #aucun message recu
                       pass
                   else:
                       if self.connecte:
                           logServeur.warning("serveur : erreur reception")
                           self.stop()

    def stop(self):
        """ferme le fil client"""
        self.connecte = False
        #fermeture de la connexion avec le client:
        #on supprime le client de la liste des connectes
        sleep(0.01)
        try:
            del connectes[self.idClient]
        except:
            logServeur.warning("Impossible de retirer {} de la liste des connectes".format(self.idClient))
        #on libere son identifiant
        self.outils.libererID(self.idClient)
        self.cnn.close()   
        logServeur.info("Client {} deconnecte ({})".format(self.pseudo, self.getName()))
        
# Le fil se termine ici    

class Serveur(Thread):
    """serveur TCP pour chat, echange de fichiers ou d'objets..."""
    def __init__(self, port):
        """creation du serveur"""
        self.serveur_lance = False
        Thread.__init__(self)
        self.port = port
        self.outils = OutilsCo()
        self.filsOuverts = {}

    def traitement(self, nomClient, msg):
        """traite le message selon son type et son/ses destinataire(s)"""
        client = self.filsOuverts[nomClient] 

        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
                    client.pseudo = contenu
                    pseudos[client.idClient] = client.pseudo
                    logServeur.info("connexion: {} -> {} ({})".format(client.getName(), client.pseudo, client.idClient))
                    client.cnn.sendall("cisa"+client.idClient)
                    for cnn in connectes.values():
                        if cnn != client.cnn:
                           msg = "ccsatc{}{}".format(client.idClient, client.pseudo)
                           cnn.sendall(msg)
                           sleep(0.001)
                           try:
                               dM.envoi(len(msg))
                           except:
                               pass
                            
                if nature == "cd":
                    #client informe de sa deconnexion
                    for cnn in connectes.values():
                        if cnn != client.cnn:
                             msg = "cdsaac{}".format(emet)
                             cnn.sendall(msg)
                             try:
                                 dM.envoi(len(msg))
                             except:
                                 pass
                    client.stop()
                    
                elif nature == "f0":
                    #client demande un identifiant pour un envoi de fichier
                    msg = "f0sa{}{}".format(client.idClient, self.outils.genererID())
                    client.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 cnn in connectes.values():
                    cnn.sendall(msg)
                    try:
                       dM.envoi(len(msg)+1)
                    except:
                       pass
                
            elif dest == "ac":
                #message pour tous les clients excepte l'emetteur
                for cnn in connectes.values():
                    if cnn != client.cnn:
                         cnn.sendall(msg)
                         try:
                             dM.envoi(len(msg)+1)
                         except:
                             pass
                    
            
            elif dest == "sv":
                #message pour le serveur vocal
                logServeur.debug("pour serveur vocal: {}".format(contenu))

            #else: 
            elif dest in connectes:
                #message pour un client unique (mj ou autre)
                connectes[dest].sendall(msg) 

        else:
            logServeur.warning("Erreur : format du message illisible -> {}".format(msg))

        
    def creer(self):
        """demarre le serveur"""
        """ try:"""
        self.cnn = socket(AF_INET, 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:   
           logServeur.info(retour)
        """
        return self.serveur_lance, retour

    def run(self):
        """boucle de fonctionnement"""
        self.cnn.setblocking(0)
        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.outils, cnnClient)
               #on ajoute ce fil à la liste pour pouvoir le fermer
               self.filsOuverts[client.getName()] = client
               client.creer()
               
            except IOError, e:
               if e.errno == 10035:
                   #si pas de nouvelles connexions, on traite les messages en attente:
                   #le serveur lit les 3 premiers car indiquant la longueur du message ('drapeaux' non  compris)
                   #puis les retire du message avant le traitement
                   try:
                      nomClient, txt = tampon[0]
                      msgClient = txt
                      if len(msgClient) >= 9:
                          while len(msgClient) > 0:
                              lg = int(msgClient[0:3]) + 6
                              self.traitement(nomClient, msgClient[3:lg+3])
                              msgClient = msgClient[lg+3:]     
                      tampon.remove((nomClient, txt))
                      txt = msgClient = ""
                   except IndexError:
                      #la liste tampon est vide 
                      pass     
                   
               else: 
                   logServeur.error("serveur : erreur attente clients") 
                   break        
        
    def stop(self):
        """ferme le serveur"""
        self.serveur_lance = False
        #for client in connectes.keys():
        for client in self.filsOuverts.values():
            if client.connecte:
                client.cnn.sendall("sdsatc")
                client.stop()
        sleep(0.1)
        self.cnn.close()
        logging.info("Fermeture 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)
   sVoc.creer()
   #dM.start()
   
   system("pause")
   s.stop()
   sVoc.stop()
   #dM.stop()
   logServeur.info("- Fin -")   
   sleep(0.1)


   
   
   




    
 

