def __init__(self):
     
     print "init Cliente..."
     
     self.nombre = "";
     self.ip = "";
     self.puerto = 0;
     
     self.ipServer = "127.0.0.1"; #IP fija del servidor
     self.puertoServer = 4040; #Puerto fijo del servidor
     
     self.usuariosConectados = []; #Lista local de usuarios conectados
     
     self.conversacionesActivas = []; #Lista de conversaciones en las que este usuario participa actualmente
     
     self.qtGuiApp = QtGui.QApplication(sys.argv) #Para crear interfaces gráficas con PyQt4
     
     print "Inicializando Interfaces..."
     
     self.loginUI = LoginUI(); #inicializa la interfaz para solicitar datos del usuario
     self.loginUI.botonLogin.clicked.connect(self.login); 
     
     self.clienteUI = ClienteUI(); #Inicializa la interfaz del cliente del chat
     self.clienteUI.botonActualizarUsuarios.clicked.connect(self.actualizarUsuarios);
     self.clienteUI.botonLogout.clicked.connect(self.desconectar);
     
     self.mostarLoginUI(); #muestra la interfaz para pedir datos del usuario
class Cliente():
    
    def __init__(self):
        
        print "init Cliente..."
        
        self.nombre = "";
        self.ip = "";
        self.puerto = 0;
        
        self.ipServer = "127.0.0.1"; #IP fija del servidor
        self.puertoServer = 4040; #Puerto fijo del servidor
        
        self.usuariosConectados = []; #Lista local de usuarios conectados
        
        self.conversacionesActivas = []; #Lista de conversaciones en las que este usuario participa actualmente
        
        self.qtGuiApp = QtGui.QApplication(sys.argv) #Para crear interfaces gráficas con PyQt4
        
        print "Inicializando Interfaces..."
        
        self.loginUI = LoginUI(); #inicializa la interfaz para solicitar datos del usuario
        self.loginUI.botonLogin.clicked.connect(self.login); 
        
        self.clienteUI = ClienteUI(); #Inicializa la interfaz del cliente del chat
        self.clienteUI.botonActualizarUsuarios.clicked.connect(self.actualizarUsuarios);
        self.clienteUI.botonLogout.clicked.connect(self.desconectar);
        
        self.mostarLoginUI(); #muestra la interfaz para pedir datos del usuario
            
    """
        Función para mostrar la interfaz LoginUI
    """
    def mostarLoginUI(self):
        self.loginUI.show();
        
        os._exit(self.qtGuiApp.exec_());
        
    """
        Función para conectar el cliente con el servidor
        - Obtiene los datos de la interfaz de Login
        - Valida los datos
        - Arranca el servidor del cliente con los datos obtenidos
        - Manda sus datos al servidor
    """
    def login(self):
        
        self.nombre = str(self.loginUI.lineEditNombre.text());
        self.ip = str(self.loginUI.lineEditIP.text());
        self.puerto = self.loginUI.lineEditPuerto.text();
        
        ipValores = self.ip.split("."); #parto la IP donde haya puntos
        
        if(len(ipValores) != 4): #Si la IP está separada con tres puntos
            self.loginUI.mostrarError(u"La dirección IP es inválida");
        else:
            for valor in ipValores:
                try:
                    int(valor); #Si cada valor entre puntos de la IP es numérico
                except ValueError as ve:
                    self.loginUI.mostrarError(u"La dirección IP solo puede contener valores numéricos");
                    return;
            try:
                self.puerto = int(self.puerto); #Valido que el puerto sea numérico
            except ValueError as ve:
                self.loginUI.mostrarError(u"El puerto es inválido");
                return;      
            
            print "Inicializando threadServidorCliente..."
            
            #inicializa el servidor del cliente con estos datos
            self.threadServidorCliente = clientServer(self.nombre,self.ip,self.puerto);
            
            #threadServidorCliente = threading.Thread(target=self.iniciarServidor); 
            #threadServidorCliente.start(); #inicializo un hilo para el servidor del cliente
            
            print "Servidor Escuchando..."
            
            self.clienteUI.setWindowTitle(self.nombre); #La ventana del cliente tendrá mi nombre
            
            """
                Conecto las señales emitidas por el servidor del cliente a la interfaz principal
                del cliente
            """
            self.clienteUI.connect(self.threadServidorCliente,QtCore.SIGNAL("crearChat(QString,QString,QString)"),self.crearChat);
            self.clienteUI.connect(self.threadServidorCliente,QtCore.SIGNAL("recibirMensaje(QString,QString)"),self.recibirMensaje);
            self.clienteUI.connect(self.threadServidorCliente,QtCore.SIGNAL("iniciarLlamada(QString)"),self.iniciarLlamada);
            self.clienteUI.connect(self.threadServidorCliente,QtCore.SIGNAL("reproducirLlamada(QString,PyQt_PyObject)"),self.reproducirLlamada);
            self.clienteUI.connect(self.threadServidorCliente,QtCore.SIGNAL("actualizarUsuarios()"),self.actualizarUsuarios);
            
            #inicializo el servidor del cliente
            self.threadServidorCliente.start();
            
            #Me conecto al servidor con la IP y el puerto fijos que tengo
            try:
                proxy = xmlrpclib.ServerProxy("http://" + str(self.ipServer) + ":" + str(self.puertoServer) + "/",allow_none=True);
                proxy.addCliente(self.nombre,self.ip,self.puerto); #Mando mi información al servidor
                
                self.clienteUI.show(); #Muestro la pantalla del Cliente
                self.actualizarUsuarios(); #Actualizo mi lista de usuarios
            except Exception as e:
                self.clienteUI.mostrarError(u"No se pudo conectar con el servidor. Intente de nuevo más tarde");           
        
            self.loginUI.close(); #cierro la pantalla de Login
            
    
    """
        Función que solicita del servidor la lista actual de usuarios y la guarda en la lista local
    """
    def actualizarUsuarios(self):
        
        print "Actualizando usuarios..."
        
        #abro un proxy al servidor con la IP y el puerto fijos
        try:
            proxy = xmlrpclib.ServerProxy("http://" + str(self.ipServer) + ":" + str(self.puertoServer) + "/",allow_none=True);
            self.usuariosConectados = proxy.getUsuarios(); #sustituyo mi lista por la lista de usuarios del servidor
            
            print "Usuarios conectados: " + str(self.usuariosConectados);
            self.clienteUI.clearUsuarios(); #borro la lista de usuarios de la interfaz de Cliente
            
            #por cada usuario que recibí
            for i,usuario in enumerate(self.usuariosConectados):
                labelUsuario = QtGui.QLabel(); 
                labelUsuario.setText(usuario['nombre']); #creo una etiqueta con su nombre
                self.clienteUI.grid.addWidget(labelUsuario,3+i,0,1,3); #lo añado hasta abajo de ClienteUI
                
                if(not (usuario['ip'] == self.ip and usuario['puerto'] == self.puerto)): #si el usuario no soy yo
                    botonLlamar = QtGui.QPushButton('Llamar'); #creo el botón para llamar
                    botonLlamar.clicked.connect(self.crearFuncionLlamar(usuario['nombre'],usuario['ip'],usuario['puerto'])); #le asigno su función
                    self.clienteUI.grid.addWidget(botonLlamar,3+i,3,1,1); #Lo añado a lado de la etiqueta
        except Exception as e:
            self.clienteUI.mostrarError(u"Error al actualizar los usuarios. No se pudo comunicar con el server. Por favor reinicie la aplicación");
    
    """
        Función para eliminarme de la lista de usuarios conectados del server
    """
    def desconectar(self):
        try:
            proxy = xmlrpclib.ServerProxy("http://" + str(self.ipServer) + ":" + str(self.puertoServer) + "/",allow_none=True);
            proxy.desconectarCliente(self.nombre,self.ip,self.puerto);
                
            os._exit(1);
        except Exception as e:
            self.clienteUI.mostarError(u"Error al desconectarme");
                
    """
        Función que recibe una ip y un puerto y crea una función para contactar a esa IP y a ese puerto
    """
    def crearFuncionLlamar(self,nombre,ip,puerto):       
        def llamar():
            self.iniciarChat(nombre,ip,puerto);
        
        return llamar;
                
    """
        Función que abre una interfaz para mandar mensajes e iniciar
        llamadas con otro usuario dentro de la lista. También abre una
        interfaz en el equipo de la otra persona
        
        @param str El nombre de la persona con la que voy a empezar a hablar
        @param str El ip en donde se encuentra su equipo
        @param int El puerto por donde está escuchando
    """
    def iniciarChat(self,nombre,ipRemoto,puertoRemoto):
        self.crearChat(nombre,ipRemoto,puertoRemoto);
        try:
            proxy = xmlrpclib.ServerProxy("http://" + str(ipRemoto) + ":" + str(puertoRemoto) + "/",allow_none=True);
            proxy.crearChat(self.nombre,self.ip,self.puerto);
        except Exception as e:
            self.clienteUI.mostarError(nombre + " es inalcanzable");
            self.cerrarChat(nombre);
        
    """
        Función que inicializa un chat directamente con otro usuario.        
        También agrega esta conversación a la lista de conversacionesActivas.
        
        @param str nombre El nombre del usuario con el que voy a hablar
        @param str ipRemoto La dirección IP en donde se encuentra el otro usuario
        @param int puertoRemoto El puerto por donde el otro usuario está escuchando
    """
    def crearChat(self,nombre,ipRemoto,puertoRemoto):
        print "Creando chat con " + nombre + " en la ip " + ipRemoto + " en el puerto " + str(puertoRemoto);
        
        #si se llama remotamente, el parámetro puertoRemoto es un QString
        if isinstance(puertoRemoto,QtCore.QString):
            #por lo tanto se parsea a entero
            puertoRemoto,ok = puertoRemoto.toInt();
            
        #inicializo un nuevo chat con mis datos y los datos del otro usuario
        nuevoChat = chatUserUI(self.nombre,nombre,self.ip,ipRemoto,self.puerto,puertoRemoto);
        """
            conecto la interfaz principal del chat con una señal que mandará la interfaz
            del chat directo al cerrarse
        """
        self.clienteUI.connect(nuevoChat,QtCore.SIGNAL("cerrarChat(QString)"),self.cerrarChat);
        
        nuevoChat.show();
        self.conversacionesActivas.append(nuevoChat); #agrego el chat a conversacionesActivas
        
    """
        Función para eliminar un chat de la lista conversacionesActivas.
        
        @param str nombre El nombre del usuario con el que estoy hablando a través de ese chat
    """
    def cerrarChat(self,nombre):
        for chat in self.conversacionesActivas:
            if(chat.nombreRemoto == nombre):
                self.conversacionesActivas.remove(chat);
                print "Removi a " + nombre + " de la lista";
                print str(self.conversacionesActivas);
        
    """
        Función que se ejecuta cuando el servidor del cliente manda una señal
        a la interfaz principal del chat de que se recibió un mensaje
        de texto.
        
        @param str nombre El remitente del mensaje
        @param str mensaje
    """
    def recibirMensaje(self,nombre,mensaje):
        print "Nombre del remitente " + nombre;
        #busco al remitente en mis conversacionesActivas
        for chat in self.conversacionesActivas:
            #si lo encuentro, agrego el mensaje solo a esa vista
            if(chat.nombreRemoto == nombre):
                chat.agregarMensaje(nombre,mensaje);

    """
        Función que se ejecuta cuando el servidor del cliente manda
        una señal de que alguien quiere iniciar una llamada conmigo.
        
        @param str nombreRemoto El nombre del usuario que me está llamando
    """
    def iniciarLlamada(self,nombreRemoto):
        #busco el nombre del usuario en mis conversacionesActivas
        for chat in self.conversacionesActivas:
            if(chat.nombreRemoto == nombreRemoto):
                #si lo encuentro, inicio la llamada con el manejador de ese chat
                chat.manejadorLlamadas.iniciarLlamada();
                
    """
        Función que se ejecuta cuando el servidor del cliente manda
        una señal de que llegó un paquete de audio de una llamada
        
        @param str nombreRemoto El nombre del usuario que me envió el audio
        @param str audio El paquete de audio recibido. Creado con la bilbioteca PyAudio
    """
    def reproducirLlamada(self,nombreRemoto,audio):
        for chat in self.conversacionesActivas:
            if(chat.nombreRemoto == nombreRemoto):
                chat.manejadorLlamadas.reproducirLlamada(audio);
class Cliente():
    
    def __init__(self):
        
        print "init Cliente..."
        
        self.nombre = "";
        self.ip = "";
        self.puertoTCP = 0;
        self.puertoUDP = 0;
        
        self.ipServer = "127.0.0.1"; #IP fija del servidor
        self.puertoServer = 4040; #Puerto fijo del servidor
        
        self.usuariosConectados = []; #Lista local de usuarios conectados
        
        self.conversacionesActivas = []; #Lista de conversaciones en las que este usuario participa actualmente
        
        self.qtGuiApp = QtGui.QApplication(sys.argv) #Para crear interfaces gráficas con PyQt4
        
        print "Inicializando Interfaces..."
        
        self.loginUI = LoginUI(); #inicializa la interfaz para solicitar datos del usuario
        self.loginUI.botonLogin.clicked.connect(self.login); 
        
        self.clienteUI = ClienteUI(); #Inicializa la interfaz del cliente del chat
        
        self.mostarLoginUI(); #muestra la interfaz para pedir datos del usuario
            
    """
        Función para mostrar la interfaz LoginUI
    """
    def mostarLoginUI(self):
        self.loginUI.show();
        
        os._exit(self.qtGuiApp.exec_());
        
    """
        Función para conectar el cliente con el servidor
        - Obtiene los datos de la interfaz de Login
        - Valida los datos
        - Arranca el servidor del cliente con los datos obtenidos
        - Manda sus datos al servidor
    """
    def login(self):
        
        self.nombre = str(self.loginUI.lineEditNombre.text());
        self.ip = str(self.loginUI.lineEditIP.text());
        self.puerto = self.loginUI.lineEditPuerto.text();
        
        ipValores = self.ip.split("."); #parto la IP donde haya puntos
        
        if(len(ipValores) != 4): #Si la IP está separada con tres puntos
            self.loginUI.mostrarError(u"La dirección IP es inválida");
        else:
            for valor in ipValores:
                try:
                    int(valor); #Si cada valor entre puntos de la IP es numérico
                except ValueError as ve:
                    self.loginUI.mostrarError(u"La dirección IP solo puede contener valores numéricos");
                    return;
            try:
                self.puerto = int(self.puerto); #Valido que el puerto sea numérico
                self.puertoTCP = self.puerto;
                self.puertoUDP = self.puerto + 1; #hay dos para tener dos tipos de servidores: uno TCP y otro UDP
            except ValueError as ve:
                self.loginUI.mostrarError(u"El puerto es inválido");
                return;
            
            print "Inicializando threadServidorClienteSocket..."
            
            self.threadServidorClienteSocket = clientServerSocket(self.nombre,self.ip,self.puertoTCP);
            
            print "Inicializando threadClientServerUPD..."
            
            self.threadServidorClienteUDP = clientServerUDP(self.nombre,self.ip,self.puertoUDP);
            
            print "Servidor Escuchando..."
            
            self.clienteUI.setWindowTitle(self.nombre); #La ventana del cliente tendrá mi nombre
            
            """
                Conecto las señales emitidas por el servidor del cliente a la interfaz principal
                del cliente
            """
            self.clienteUI.connect(self.threadServidorClienteUDP,QtCore.SIGNAL("crearChat(QString,QString,QString)"),self.crearChat);
            self.clienteUI.connect(self.threadServidorClienteUDP,QtCore.SIGNAL("recibirMensaje(QString,QString)"),self.recibirMensaje);
            self.clienteUI.connect(self.threadServidorClienteUDP,QtCore.SIGNAL("iniciarLlamada(QString)"),self.iniciarLlamada);
            self.clienteUI.connect(self.threadServidorClienteUDP,QtCore.SIGNAL("reproducirLlamada(QString,PyQt_PyObject)"),self.reproducirLlamada);
            self.clienteUI.connect(self.threadServidorClienteSocket,QtCore.SIGNAL("actualizarUsuarios(PyQt_PyObject)"),self.actualizarUsuarios);
            self.clienteUI.connect(self.threadServidorClienteSocket,QtCore.SIGNAL("errorInicioServidorSocket(QString)"),self.loginUI.mostrarError);
            
            self.threadServidorClienteSocket.start();
            
            self.threadServidorClienteUDP.start();
            
            #Me conecto al servidor con la IP y el puerto fijos que tengo
            try:
                #empaqueto mis datos con la operacion que quiero realizar en un JSON
                datos_json = json.dumps({"peticion":"addCliente","nombre":self.nombre,"ip":self.ip,"puerto":self.puertoTCP}).encode("utf-8");
                
                #creo un socket
                serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
                
                #conecto ese socket al socket del servidor
                serverSocket.connect((self.ipServer,self.puertoServer));
                print "Mandando mis datos al servidor... "
                #mando el JSON con mis datos
                serverSocket.send(datos_json);                    
                
                self.clienteUI.show(); #Muestro la pantalla del Cliente
            except socket.error as socket_error:
                self.clienteUI.mostrarError(u"No se pudo conectar con el servidor. Intente de nuevo más tarde");           
        
            self.loginUI.close(); #cierro la pantalla de Login
            
    
    """
        Función que solicita del servidor la lista actual de usuarios y la guarda en la lista local
    """
    def actualizarUsuarios(self,usuarios):
        
        print "Actualizando usuarios..."
        
        self.usuariosConectados = usuarios;
        
        #print "Usuarios conectados: " + str(self.usuariosConectados);
        self.clienteUI.clearUsuarios(); #borro la lista de usuarios de la interfaz de Cliente
        
        #por cada usuario que recibí
        for i,usuario in enumerate(self.usuariosConectados):
            labelUsuario = QtGui.QLabel(); 
            labelUsuario.setText(usuario['nombre']); #creo una etiqueta con su nombre
            self.clienteUI.grid.addWidget(labelUsuario,3+i,0,1,3); #lo añado hasta abajo de ClienteUI
            
            if(not (usuario['ip'] == self.ip and usuario['puerto'] == self.puerto)): #si el usuario no soy yo
                botonLlamar = QtGui.QPushButton('Llamar'); #creo el botón para llamar
                botonLlamar.clicked.connect(self.crearFuncionLlamar(usuario['nombre'],usuario['ip'],usuario['puerto']+1)); #le asigno su función. Es +1 porque así calculo el puerto UDP del otro usuario
                self.clienteUI.grid.addWidget(botonLlamar,3+i,3,1,1); #Lo añado a lado de la etiqueta
   
    """
        Función que recibe una ip y un puerto y crea una función para contactar a esa IP y a ese puerto
    """
    def crearFuncionLlamar(self,nombre,ip,puerto):       
        def llamar():
            self.iniciarChat(nombre,ip,puerto);
        
        return llamar;
                
    """
        Función que abre una interfaz para mandar mensajes e iniciar
        llamadas con otro usuario dentro de la lista. También abre una
        interfaz en el equipo de la otra persona
        
        @param str El nombre de la persona con la que voy a empezar a hablar
        @param str El ip en donde se encuentra su equipo
        @param int El puerto por donde está escuchando
    """
    def iniciarChat(self,nombre,ipRemoto,puertoRemoto):
        self.crearChat(nombre,ipRemoto,puertoRemoto);
        sock = socket.socket(socket.AF_INET, # Internet
                    socket.SOCK_DGRAM) # UDP
        
        datos_json = json.dumps({"peticion":"crearChat","nombreRemoto":self.nombre,"ipRemoto":self.ip,"puertoRemoto":self.puertoUDP});
        
        sock.sendto(datos_json, (ipRemoto, puertoRemoto))
        
    """
        Función que inicializa un chat directamente con otro usuario.        
        También agrega esta conversación a la lista de conversacionesActivas.
        
        @param str nombre El nombre del usuario con el que voy a hablar
        @param str ipRemoto La dirección IP en donde se encuentra el otro usuario
        @param int puertoRemoto El puerto por donde el otro usuario está escuchando
    """
    def crearChat(self,nombre,ipRemoto,puertoRemoto):
        print "Creando chat con " + nombre + " en la ip " + ipRemoto + " en el puerto " + str(puertoRemoto);
        
        #si se llama remotamente, el parámetro puertoRemoto es un QString
        if isinstance(puertoRemoto,QtCore.QString):
            #por lo tanto se parsea a entero
            puertoRemoto,ok = puertoRemoto.toInt();
            
        #inicializo un nuevo chat con mis datos y los datos del otro usuario
        nuevoChat = chatUserUI(self.nombre,nombre,self.ip,ipRemoto,self.puertoUDP,puertoRemoto);
        """
            conecto la interfaz principal del chat con una señal que mandará la interfaz
            del chat directo al cerrarse
        """
        self.clienteUI.connect(nuevoChat,QtCore.SIGNAL("cerrarChat(QString)"),self.cerrarChat);
        
        nuevoChat.show();
        self.conversacionesActivas.append(nuevoChat); #agrego el chat a conversacionesActivas
        
    """
        Función para eliminar un chat de la lista conversacionesActivas.
        
        @param str nombre El nombre del usuario con el que estoy hablando a través de ese chat
    """
    def cerrarChat(self,nombre):
        for chat in self.conversacionesActivas:
            if(chat.nombreRemoto == nombre):
                self.conversacionesActivas.remove(chat);
                print "Removi a " + nombre + " de la lista";
                print str(self.conversacionesActivas);
        
    """
        Función que se ejecuta cuando el servidor del cliente manda una señal
        a la interfaz principal del chat de que se recibió un mensaje
        de texto.
        
        @param str nombre El remitente del mensaje
        @param str mensaje
    """
    def recibirMensaje(self,nombre,mensaje):
        print "Nombre del remitente " + nombre;
        #busco al remitente en mis conversacionesActivas
        for chat in self.conversacionesActivas:
            #si lo encuentro, agrego el mensaje solo a esa vista
            if(chat.nombreRemoto == nombre):
                chat.agregarMensaje(nombre,mensaje);

    """
        Función que se ejecuta cuando el servidor del cliente manda
        una señal de que alguien quiere iniciar una llamada conmigo.
        
        @param str nombreRemoto El nombre del usuario que me está llamando
    """
    def iniciarLlamada(self,nombreRemoto):
        #busco el nombre del usuario en mis conversacionesActivas
        for chat in self.conversacionesActivas:
            if(chat.nombreRemoto == nombreRemoto):
                #si lo encuentro, inicio la llamada con el manejador de ese chat
                chat.manejadorLlamadas.iniciarLlamada();
                
    """
        Función que se ejecuta cuando el servidor del cliente manda
        una señal de que llegó un paquete de audio de una llamada
        
        @param str nombreRemoto El nombre del usuario que me envió el audio
        @param str audio El paquete de audio recibido. Creado con la bilbioteca PyAudio
    """
    def reproducirLlamada(self,nombreRemoto,audio):
        for chat in self.conversacionesActivas:
            if(chat.nombreRemoto == nombreRemoto):
                chat.manejadorLlamadas.reproducirLlamada(audio);