#!/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: interfazpnjhablante.py

"""Clase InterfazPNJHablante."""

from xml.dom import minidom
from random import random
import copy

from interfazpnj import *
from mundo import *
from guiontemporizado import GuionTemporizado
import Modulo
import FechaHoraMine
import CronoMine

class InterfazPNJHablante(InterfazPNJ):
    """Interfaz controla directamente a una instancia de clase PersonajeNoJugador con capacidad
    de dialogar.

    El control es automático, no necesita intervención de ningún jugador.
    
    La clase funciona como una máquina de estados, que acepta los eventos de
    Controlador, y reacciona apropiadamente en cada caso.
    
    Contiene los siguientes atributos:
    
     - __id_conversacion: nombre de la conversacion que es capaz de mantener el PNJ
     - __personaje (Personaje): el personaje que controlamos
     - estado (string): el estado en el que se encuentra el interfaz
     - sub_estado (int): algunos estados tienen fases intermedias. Si no, None.
     - crono (CronoMine): cronometro usado opcionalmente
     - oponente (Personaje): otro personaje con quien el nuestro estï¿½ combatiendo     
     - _fragmentos: lista de fragmentos que falta por decir
     - cronos: cronometros para mostrar los fragmentos restantes
    
     Los siguientes atributos provocan ajustes automï¿½ticos al ser modificados:

     - personaje: se modifica tambiï¿½n al personaje para que tenga a este interfaz como
                  controlador
                  
     Invariantes:
        _conversacion != None
        __personaje != None
        len(_visitas) >= 0
        len(cronos) >= 0
        len(_fragmentos) >= 0
        self.visita[i].activo  <--> len(self._fragmentos) > 0
     """
    
    VERSION_DESC_CONVERSACIONES = '2.0.0'
    
    
    DIR_DESC_CONVERSACIONES = 'conversaciones'
    DIR_DESC_GENERICAS = 'generico'
    DIR_DESC_ESPECIALES = 'modulos_especiales'
    EXT_DESC_CONVERSACIONES = '.xml'
    
    TEMAS_ESPECIALES = [ 'iniciativa_propia', 'tema_agotado', 'tema_no_encontrado', 'fuera_de_tema' ]
    
    
    #Constructor
    
    def __init__(self, id_conversacion):
        InterfazPNJ.__init__(self)
        trazar("Probando la funcion trazar()")
        escribir("Cargando el pnj hablante " + id_conversacion)
        #Control de iniciativa
        self.__temporizador = None      # Controla la siguiente activación de la iniciativa
        self.__interlocutor = None      # Último personaje con el que habló
        self.__velocidad = 0.05 # TO DO: parametrizar la velocidad de reaccion del PNJ
        self.__keywords = [] # TO DO: añadir una lista de keywords que el PNJ "entiende" en el diálogo entre PJs
        
        self.id_conversacion = id_conversacion
        self.__guion_activo = None      # Guion del diálogo que está en marcha
        self.__contextos = ['default']
        self.__historia = {}
        
        self.__temas = {}
        self.__temas_especiales = {}
        self.__temas_genericos = {}
        
        for tema in InterfazPNJHablante.TEMAS_ESPECIALES:
                if not self.__encuentra_tema_especial(tema):
                   trazar("No esta definido el tema especial de tipo " + tema, "pnj")
                else:
                   trazar("Inicializado el tema " + tema, "pnj")
                
    def __setattr__(self, name, value):
        """Realiza ajustes automï¿½ticos al modificar cualquier parï¿½metro"""
    
        if name == "personaje":
            # Si es personaje, liberar al anterior y controlar al nuevo.
            if (self.personaje != None): self.personaje.controlador = None
            self.__dict__["personaje"] = value
            if (self.personaje != None): self.personaje.controlador = self
            # Y borrar las visitas que ha mantenido hasta ahora
#            self._visitas = []
        else:
            # En otro caso, asignar y ya esta
            self.__dict__[name] = value
        
    # Métodos privados
    
    
    # Localizar y cargar temas en la caché
    
    def __ruta_conversacion(self, id_conversacion=None):
        if not id_conversacion: id_conversacion = self.id_conversacion
        return os.path.join(Modulo.DIR_DESC_MODULOS, id_conversacion, InterfazPNJHablante.DIR_DESC_CONVERSACIONES)
    
    def __ruta_especial(self, id_conversacion=None):
        if not id_conversacion: id_conversacion = self.id_conversacion
        return os.path.join(Modulo.DIR_DESC_MODULOS, id_conversacion, InterfazPNJHablante.DIR_DESC_ESPECIALES)
                
    
    def __encuentra_tema(self, nombretema, ruta = None):
        """Localiza un tema por su nombre. Si es necesario lo carga desde disco a la cache de temas."""
        
        tema = self.__temas.get(nombretema, None)
        if tema: return tema
    
        if not ruta: ruta = self.__ruta_conversacion()
        try:
            self.__temas[nombretema] = self.__cargar_modulo(nombretema)
        except IOError:
            return None
        
        return self.__temas.get(nombretema, None)
    
    def __encuentra_tema_especial(self, nombretema, ruta = None):
        """Localiza un tema por su nombre. Si es necesario lo carga desde disco a la cache de temas."""
        
        tema = self.__temas_especiales.get(nombretema, None)
        if tema:
                return tema
    
        if not ruta: ruta = self.__ruta_especial()
        try:
            self.__temas_especiales[nombretema] = self.__cargar_modulo(nombretema, ruta)
        except IOError:
            return None
        
        return self.__temas_especiales.get(nombretema, None)
    
    def __encuentra_tema_generico(self, nombretema, ruta = None):
        """Localiza un tema por su nombre. Si es necesario lo carga desde disco a la cache de temas."""
        
        tema = self.__temas_genericos.get(nombretema, None)
        if tema:
                return tema
        
        if not ruta: ruta = self.__ruta_especial(InterfazPNJHablante.DIR_DESC_GENERICAS)
        try:
            self.__temas_genericos[nombretema] = self.__cargar_modulo(nombretema, ruta)
        except IOError:
            return None
        
        return self.__temas_genericos.get(nombretema, None)
    
        
    def __cargar_modulo(self, id_modulo, ruta=None):
        #archivo = os.path.join(Modulo.DIR_DESC_MODULOS, "prueba" + Modulo.EXT_DESC_MODULOS)
        if not ruta: ruta = self.__ruta_conversacion()
        archivo = os.path.join(ruta, id_modulo + InterfazPNJHablante.EXT_DESC_CONVERSACIONES)
        arbol_xml = minidom.parse(archivo)
        elem_desc_modulo = arbol_xml.documentElement
        macros = [ ("$interlocutor", "Personaje"),
                   ("$pnj", "Personaje"),
                   ("$sala", "Sala")]#,
        #                  ("$tema", "Modulo") ]
                                                        
                                                        
        return Modulo.nuevo_xml(elem_desc_modulo, macros)
        
        if not especial: 
                self.__temas[id_modulo] = Modulo.nuevo_xml(elem_desc_modulo, macros)
        else:
                self.__temas_especiales[id_modulo] = Modulo.nuevo_xml(elem_desc_modulo, macros)
        return
    
    # Controlar la iniciativa del PNJ
    
    def __temporizar_nueva_iniciativa(self):
        """Establece el tiempo necesario para activar la iniciativa"""
        from mundo import Mundo        
        try:
                if self.__temporizador:
                    self.__temporizador.parar()
                activacion = FechaHoraMine.crear_rel(segundos = (random() + 0.5) * 
			                             self.__velocidad * 
						     FechaHoraMine.SEGUNDOS_POR_HORA )
                trazar("Activada iniciativa para dentro de %s horas Mine" % repr(activacion), "pnj", 2)
                self.__temporizador = CronoMine.crear(Mundo.cronoMine_control,
			                              activacion,
						      lambda x: self.__disparar_iniciativa())
        except:
                escribir("Error inesperado al temporizar la iniciativa")
                
            
    def __pausar_iniciativa(self):
        if self.__temporizador:
            try:
                trazar("Pausando la iniciativa del pnj", "pnj", 2)
                # TO DO: comprobar que no se pausa repetidas veces.
                self.__temporizador.pausar()
            except:
                escribir("Error inesperado al pausar la iniciativa")
    
    def __reanudar_iniciativa(self):
        if self.__temporizador:
            try:
                trazar("Reanudando la iniciativa del pnj", "pnj", 2)
                self.__temporizador.reanudar()
            except:
                escribir("Error inesperado al reanudar la iniciativa")
    
    def __disparar_iniciativa(self):
        """Activa el modulo de iniciativa propia de la conversacion"""
        trazar("Activando iniciativa del pnj", "pnj")
        iniciativa = self.__encuentra_tema_especial("iniciativa_propia")
        if iniciativa:
                macros = { "$interlocutor": self.__interlocutor,
                           "$pnj": self.__personaje,
                           "$sala": self.__personaje.sala }
                guion = iniciativa.mejor_guion(self.__contextos)
                if guion:
                    self.__interlocutor = self.__personaje
                    self.__activar_guion(guion, macros)
                self.__temporizador = None
                self.__temporizar_nueva_iniciativa()
        else:
                self.__temporizador = None
                
    # Controlar la temporización del diálogo activo
    
    def __fallar(self, nombretema, macros):
        """Encuentra una respuesta correcta cuando el PJ ha preguntado por un tema no válido."""
        guion_equivocado = None
        if self.__temas_especiales.has_key(nombretema):
            tema = self.__temas_especiales[nombretema]
            guion_equivocado = tema.mejor_guion(self.__contextos)
        if not guion_equivocado:
            tema = self.__encuentra_tema_generico(nombretema)
            guion_equivocado = tema.mejor_guion(self.__contextos)
            if not guion_equivocado:
                raise Exception("Error en modulo de conversacion: no quedan guiones activos en el tema por defecto.")
        self.__activar_guion(guion_equivocado, macros)
        self.__reanudar_iniciativa()
    
    def __activar_guion(self, guion, macros):
        """Comienza la temporización del guion encontrado"""
        self.__guion_activo = guion     # Recordar: es un wrapper, "not the Real Thing"
        self.__contextos = guion.contextos
        for contexto in guion.contextos:
            if not self.__historia.has_key(contexto):
                self.__historia[contexto] = 0
            else:
                self.__historia[contexto] += 1
            trazar(str(contexto) + str(self.__historia[contexto]) + " veces", "pnj", 3)
        guion.ejecutar(macros, finalizar=(lambda guion, cancelado: self.__finalizar_guion(cancelado)))
        
    def __finalizar_guion(self, cancelado=0):
        """Callback que usa el guion activo cuando termina o es cancelado"""
        if self.esta_hablando():
                trazar("Finalizando la conversacion", "pnj")
                self.__guion_activo.cancelar_secuencia()
        self.__guion_activo = None
        self.__temporizar_nueva_iniciativa() 

    # -------------------------------------------------
    # Metodos publicos
    # -------------------------------------------------
    
    def esta_hablando(self):
        return self.__guion_activo != None
    
    def conversa_personaje(self, personaje, comando, nombretema):
        """Metodo llamado cuando otro personaje habla con este PNJ
        
        Devuelve false cuando el PNJ está hablando con otro personaje y no se les puede interrumpir
        """
        
        if self.esta_hablando() and self.__interlocutor:
            #Interrumpiendo una conversacion en curso
            trazar("Esta hablando con " + repr(self.__interlocutor.nombre), "pnj", 2)
            if self.__interlocutor == personaje:
                self.__finalizar_guion()
            elif personaje.competicion(self.__interlocutor, "carisma", "carisma", malus=6):
                #Si el nuevo interlocutor gana la competicion de carisma, distrae al PNJ y cancela la conversacion anterior
                trazar("Cancelando una conversacion por una nueva pregunta que interrumpe la anterior", "pnj")
                self.__finalizar_guion()
            else:
                return 0
        
#       LOGICA PARA CANCELAR +HABLAR CUANDO HABLA CON OTRO PNJ DE MAYOR CARISMA        
#       if visita.activo:
#          if visita.pj_activo.atrib["carisma"] > personaje.atrib["carisma"]  : #visita.pj_activo.aura > personaje.controlador._DialogoNormal__personaje.aura:
#               personaje.controlador.conn().send(visita.pnj.nombre +" estï¿½ hablando con " + visita.pj_activo.nombre)
#               return
#          else:
#              self.anular_conversacion()
        
        self.__interlocutor = personaje
        self.__pausar_iniciativa()
        macros = { "$interlocutor": personaje,
                   "$pnj": self.__personaje,
                   "$sala": personaje.sala }
        
        if nombretema in InterfazPNJHablante.TEMAS_ESPECIALES or \
           not self.__encuentra_tema(nombretema):
                trazar("No existe el tema %s" % nombretema, "pnj")
                self.__fallar("tema_no_encontrado", macros)
        else:
            tema = self.__temas[nombretema]
            guion = tema.mejor_guion(self.__contextos)
            if guion:
                # Nueva conversacion con exito
                self.__activar_guion(guion, macros)
            else:
                trazar("El tema %s está agotado" % nombretema, "pnj")
                self.__fallar("tema_agotado", macros)
        
        trazar("Ejecutado el tema " + nombretema, "pnj")
        #self._modulo.ejecutar(None)
        
        return 1
        
                                                    
    def anular_conversacion(self):
        self._fragmentos = []
        self._visitas[0].activo = None
        for crono in self.cronos:
          # Anula los cronos que no se han ejecutado aï¿½n, asignandoles la funciï¿½n nula
            crono.vencimiento_func = lambda x: None
    
    
    def controlar_personaje(self, personaje):
        """Pasar a estar controlando un personaje.
        
        Requiere:
          self.__personaje == None
          personaje != None
        """
        self.__personaje = personaje
        
    # -------------------------------------------------
    # Metodos de manejo de eventos (ver controlador.py)
    # -------------------------------------------------
    
    
    def entra_personaje(self, personaje):
        """Crea un temporizador para activar la iniciativa del PNJ"""
        if not self.__temporizador:
            self.__temporizar_nueva_iniciativa()
        
    
    def sale_personaje(self, personaje, nombre_sala_destino):
        """Si no quedan personajes, desactiva el temporizador de iniciativa"""
        
        if personaje == self.__interlocutor:
                trazar("Cancelando la conversacion por abandono del interlocutor", "pnj")
                self.__interlocutor = None
                self.__finalizar_guion()
        
        pjs = [ personaje for personaje in self.__personaje.sala.personajes.values() \
                                         if not isinstance(personaje.controlador, InterfazPNJHablante) ]
        trazar("Quedan %s personajes jugadores en la sala" % len(pjs), "pnj", 2)
        if len(pjs) == 0:
                trazar("Desactivando la iniciativa del pnj", "pnj", 2)
                if self.__temporizador:
                    try:                
                        self.__temporizador.parar()
                        self.__temporizador = None
                    except:
                        escribir("Error inesperado al pausar la iniciativa")
                        
                        
    def habla_personaje(self, personaje, mensaje):
        """ El PNJ intenta entender las conversaciones entre PJs """
        pass
        # To Do: añadir una lista de keywords que el PNJ "entiende" en el diálogo entre PJs 
        for palabra in mensaje.split():
                if palabra in self.__keywords:
                        self.__reaccionar(palabra) # To Do: implementar el metodo __reaccionar



# Cï¿½digo para pruebas unitarias del mï¿½dulo. 

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