#!/usr/bin/env python
# encoding: iso-8859-15
#
#Minë. Mundo Interactivo-Narrativo en Español [Tierra-Media]
#Copyright (C) 2002  Pablo Ruiz Múzquiz
#
#
#This program is free software; you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation; either version 2 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#
#Fichero: InterfazPJ.py

"""Clase InterfazPJ."""

import logging
import Mundo
import Controlador
import DialogoInicial
from utils import *

log = logging.getLogger("PJ")

class InterfazPJ(Controlador.Controlador):
    """Interfaz que interactúa con el jugador a través de una conexion socket.

    Controla directamente a una instancia de clase PersonajeJugador. Por ello,
    implementa el interfaz Controlador y recibe todos los eventos generados
    por los personajes. También recibe los eventos del socket.
        
    Una vez inicializada, crea un dialogo con el jugador y le pasa el control.
    Todos los eventos recibidos se los pasa al dialogo actual, y éste responde a
    los mismos. La respuesta puede incluir cambiar a otro dialogo distinto.

    También se encarga de recibir los ticks del reloj global de Mundo, y pasárselos
    al diálogo si éste los acepta.
    
    Contiene los siguientes atributos:
    
     - __conn (Connection): la conexion por la cual se comunica
     - __personaje (Personaje): el personaje que controlamos
     - __dialogo (Dialogo): el dialogo actual con el jugador
    
     Invariantes:
       __conn != None
       __dialogo != None
       __personaje != None -> __personaje.controlador == self
    """

    def __init__(self, conn):
        """Crea un interfaz nuevo para una conexión de socket.

	Requiere:
	  conn != None
	"""
        self.__conn = conn
        self.__conn.receive_callback = self.comando_recibido
        self.__conn.error_callback = self.error_de_conexion
        self.__personaje = None
        self.__dialogo = DialogoInicial.DialogoInicial(self, self.__conn)

	Mundo.suscribirse_al_tick(self.tick_recibido)


    def __del__(self):
        """Realiza ajustes automáticos al ser destruido."""
	self.desconectar()
	Mundo.borrarse_del_tick(self.tick_recibido)

    
    def conn(self):
    	return self.__conn


    def personaje(self):
    	return self.__personaje


    def controlar_personaje(self, personaje):
        """Pasar a estar controlando un personaje.
	
	Requiere:
	  self.__personaje == None
	  personaje != None
	"""
        self.__personaje = personaje
	self.__personaje.controlar(self)


    def liberar_personaje(self):
        """Soltar al personaje actual y dejarlo libre.

	Requiere:
	  self.__personaje != None
	"""
	log.info("La conexión desde " + self.__conn.ip + " ha liberado al personaje " + self.__personaje.id)
        self.__personaje.liberar()
	self.__personaje = None
        

    def terminar(self):
        """Soltar al personaje y volver al estado inicial."""
	if self.__personaje != None:
	    self.liberar_personaje()

        if not isinstance(self.__dialogo, DialogoInicial.DialogoInicial):
            self.__dialogo = DialogoInicial.DialogoInicial(self, self.__conn)


    def desconectar(self):
        """Soltar al personaje, desconectar la conexión y destruir el interfaz."""
        try:
                if self.__personaje != None:
                    self.liberar_personaje()

                if self.__dialogo != None:
                    self.__dialogo = None

                if self.__conn.is_open:
                    # Desconectar ordenadamente.
                    self.__conn.send("Adiós, vuelve pronto...")
                    sleep(1)
                    self.__conn.close()
                    log.info("Se ha cerrado normalmente la conexión desde " + self.__conn.ip)
        except:
        	pass

	# En este punto la conexión por socket está cerrada, y el handler
	# se ocupa de destruir el InterfazPJ.


        
    # ---------------------------------------
    # Eventos del controlador de un personaje
    # ---------------------------------------
    
    def entra_personaje(self, personaje):
	self.__dialogo.entra_personaje(personaje)
                        
                        
    def sale_personaje(self, personaje, nombre_sala_destino):
	self.__dialogo.sale_personaje(personaje, nombre_sala_destino)
                        
                        
    def habla_personaje(self, personaje, mensaje):
	self.__dialogo.habla_personaje(personaje, mensaje)

	
    def susurra_personaje(self, personaje, mensaje):
	self.__dialogo.susurra_personaje(personaje, mensaje)

	
    def grita_personaje(self, personaje, mensaje):
	self.__dialogo.grita_personaje(personaje, mensaje)
 

    def actua_personaje(self, personaje, accion):
	self.__dialogo.actua_personaje(personaje, accion)
 

    def actua_individual_personaje(self, personaje, accion):
    	self.__dialogo.actua_individual_personaje(personaje, accion)
                        
			
    def actua_uno_mismo(self, accion):
    	self.__dialogo.actua_uno_mismo(accion)
                        

    def actua_global_personaje(self, personaje, accion):
	self.__dialogo.actua_global_personaje(personaje, accion)
                          
                        
    def suceso_entorno(self, mensaje):
	self.__dialogo.suceso_entorno(mensaje)
       

    def ataca_personaje(self, personaje, result):
	self.__dialogo.ataca_personaje(personaje, result)
         
    
    def movimiento_automatico(self):
	self.__dialogo.movimiento_automatico()

	
    def paga_personaje(self, personaje, monedas):
	self.__dialogo.paga_personaje(personaje, monedas)


    # ------------------------------
    # Tick del reloj global de Mundo
    # ------------------------------
       
    def tick_recibido(self, tick_ahora, tick_anterior):
	"""Responde al tick llamando al diálogo si éste implementa la función."""

	try:

	    # Luego llamamos a la funcion correspondiente segun estado
	    if self.__dialogo != None:
		nombre_func = "tick_" + str(self.__dialogo.estado())
		func = getattr(self.__dialogo, nombre_func, None)
		if func != None:
		    nuevo_dialogo = func(tick_ahora, tick_anterior)

		    # Si hay que cambiar de dialogo, hacerlo
		    if nuevo_dialogo != None:
			self.__dialogo = nuevo_dialogo

	except:
	
	    # No deben ocurrir excepciones no tratadas salvo por un bug del
	    # programa. Aquí se interceptan para que un bug afecte, si es posible,
	    # sólo a la conexión que lo ha hecho saltar, pero no se caiga el
	    # servidor entero.
	    #
	    # Si ocurre esto, desconectamos al jugador de su personaje, pero
	    # mantenemos la conexión abierta por si quiere volverlo a intentar.

	    log.error("Excepción imprevista durante el tick del reloj global.")
	    log.error(" -> conexión desde " + self.__conn.ip)
	    log.error(" -> tick ahora " + str(tick_ahora))
	    log.error(" -> tick anterior " + str(tick_anterior))
	    log.error(" -> dialogo " + str(self.__dialogo.__class__))
	    if self.__personaje != None:
	        log.error(" -> personaje " + self.__personaje.id)
	    log.exception("Datos de la exepción:")

            try:

		self.conn().send("")
		self.conn().send("")
		self.__conn.send("Ha ocurrido un error imprevisto en Mine, probablemente se trate")
		self.__conn.send("de un fallo en el programa. Por favor, ponte en contacto con los")
		self.__conn.send("administadores del juego y explicales, si es posible, toda la")
		self.__conn.send("información que tengas sobre lo que estabas haciendo en el momento")
		self.__conn.send("del error. Gracias, y disculpa por las molestias.")

		self.terminar()

	    except:
	        
		# Si no se puede salir del personaje normalmente, tiramos la
		# conexión del todo.
	        log.critical("Problema gordo: no se puede mantener la conexión. La tiramos...")
		self.desconectar()

        
    # ------------------
    # Eventos del socket
    # ------------------

    def comando_recibido(self, conn):
        """Responde a la recepcion de un mensaje del usuario."""

	cmd_list = []

	try:

	    # Primero lo convertimos en una lista para manejar las palabras
	    # mas comodamente. De paso, nos cargamos el \n del final.
	    cmd_list = conn.receive().split()

	    # Luego llamamos a la funcion correspondiente segun estado
	    nombre_func = "comando_" + str(self.__dialogo.estado())
	    func = getattr(self.__dialogo, nombre_func)
	    nuevo_dialogo = func(cmd_list)

	    # Si hay que cambiar de dialogo, hacerlo
	    if nuevo_dialogo != None:
		self.__dialogo = nuevo_dialogo

	except:
	
	    # No deben ocurrir excepciones no tratadas salvo por un bug del
	    # programa. Aquí se interceptan para que un bug afecte, si es posible,
	    # sólo a la conexión que lo ha hecho saltar, pero no se caiga el
	    # servidor entero.
	    #
	    # Si ocurre esto, desconectamos al jugador de su personaje, pero
	    # mantenemos la conexión abierta por si quiere volverlo a intentar.

	    log.error("Excepción imprevista durante la ejecución de un comando.")
	    log.error(" -> conexión desde " + self.__conn.ip)
	    log.error(" -> comando " + " ".join(cmd_list))
	    log.error(" -> dialogo " + str(self.__dialogo.__class__))
	    if self.__personaje != None:
	        log.error(" -> personaje " + self.__personaje.id)
	    log.exception("Datos de la exepción:")

            try:

		self.conn().send("")
		self.conn().send("")
		self.__conn.send("Ha ocurrido un error imprevisto en Mine, probablemente se trate")
		self.__conn.send("de un fallo en el programa. Por favor, ponte en contacto con los")
		self.__conn.send("administadores del juego y explicales, si es posible, toda la")
		self.__conn.send("información que tengas sobre lo que estabas haciendo en el momento")
		self.__conn.send("del error. Gracias, y disculpa por las molestias.")

		self.terminar()

	    except:
	        
		# Si no se puede salir del personaje normalmente, tiramos la
		# conexión del todo.
	        log.critical("Problema gordo: no se puede mantener la conexión. La tiramos...")
		self.desconectar()


    def error_de_conexion(self, conn):
        """Responde al evento de perdida de conexion por el socket."""
        log.warning("Se ha caido bruscamente la conexion con " + self.__conn.ip)
	self.desconectar()


# Código para pruebas unitarias del módulo.

if (__name__ == '__main__'):
    print "No hay código para pruebas unitarias del módulo."
    print "Para probar, ejecutar mine.py y conectarse via telnet."

