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

"""Clase Visita."""

from xml.dom import minidom
import random
import sys
import string
import os

from bloquedialogo import *
from tema import *
from contexto import *
from conversacion import *

class Visita(object):
    """Control de dialogos de los personajes no jugadores.
    
    Representa el estado de la conversación entre el PNJ y un grupo de PJs.
    Contiene los siguientes atributos:
    - _conversacion: objeto de conversacion utilizado durante la visita.
    - _items: secuencia de textos visitados durante el dialogo
    - _items_especiales: secuencia de textos especiales visitados durante el dialogo
    - _hitos: diccionario de variables definidas durante la conversacion
    
    - _estado: tipo de mensaje devuelto en la última pregunta.
    - pnj: personaje que posee la conversacion
    - pj_activo: personaje que realizó la última intervención en el diálogo
    - participantes: diccionario con los personajes jugadores que han escuchado la conversación, y la lista de temas que han oido
    - _comando: último comando solicitado.
    - _nombretema: último tema por el que se preguntó (independientemente de que se haya encontrado o no)
    - _temas_especiales: diccionario con las lista de temas especiales por defecto en este diálogo.
    
	self.activo <-> hay un item de texto activo
    """
        
    ENCONTRADO = "encontrado"
    BUSCANDO = "busqueda"
    AGOTADO = "agotado"
    NO_ENCONTRADO = "no encontrado"
    OFFTOPIC = "offtopic"
    
    def __init__(self, conversacion, personajenojugador, temasEspeciales = None):
	"""Crea un controlador de dialogo para la conversacion a partir de un fichero de descripción de conversacion.

        conversacion: jerarquía de conversación cargada en memoria
        personajenojugador: objeto PNJ que responde durante la conversación
        temasEspeciales: respuestas por defecto en caso de fallo
	
	Requiere:
	  id != None
	  personajenojugador != None
	"""
	
	self._estado = Visita.BUSCANDO
	self.pnj = personajenojugador
	self.pj_activo = None
	self.activo = None
	self._conversacion = conversacion
	self._items = []
	self._items_especiales = []
	self._hitos = {}
        self._comando = None
        if temasEspeciales == None:
            temasEspeciales = temasEspecialesDefault
        self._temas_especiales = temasEspeciales
        
    def asignar_hito(self, nombre, valor="definido"):
        escribir ("Definido hito " + str(nombre) + "=" + str(valor))
        self._hitos[nombre] = valor
	
    def hito(self, nombre):
        if self._hitos.has_key(nombre):
	    return self._hitos[nombre]
        return ""
        
    def activar(self, items):
        """ Establece el item del tema encontrado como último item visitado, y gasta una repetición de todos los items encontrados."""
#        for item in items["especiales"]:
#	     item.decrementar_repeticiones()
#	items["tema"].decrementar_repeticiones()
        self._items.insert(0, items["tema"])
    
    def buscar_items(self, tema):
        """Determina si el tema encontrado tiene algún item presentable. Devuelve el item correspondiente.
        
        Un item es presentable si no ha sido ya utilizado y se cumplen sus requisitos.
        Si el tema tiene algún item presentable, lo devuelve.
        Si no hay ningún item presentable y el tema no se ha usado ninguna vez, es un tema nulo.
        Si no hay items presentables y el tema ya se ha usado alguna vez, devuelve un item de tema agotado.
        
        Requiere:
          tema != None
          comando != None
          
        Asegura:
          tema nulo  <->  return == None
          tema activado  <->  self._items := self._items.insert(0, item) y return == item
          tema agotado   <->  return == itemAgotado
        """
        
        itemEncontrado = None
        # Busca el primer item que cumpla los requisitos y no haya sido ya devuelto en esta conversación.
        for item in tema.items():
            if item.satisfecho_en(self) and (item._comando == "ambos" or self.comando() == item._comando) and not self._item_gastado(item):
                itemEncontrado = item
                self.estado_encontrado()
                return itemEncontrado
        
        # -- Tema sin items activos --
        # Si el tema no está en ningún item de self._items, es un tema nulo
        if not filter(lambda item: item.tema() == tema, self._items): # tema in map(lambda item: item.tema(), self._items):
            return None
        # Tema agotado
	self.estado_agotado()
	# Busca item de tema agotado en el contexto del tema
        return self.item_tema_especial([tema.contexto(), tema.contexto().conversacion(), self], "temaAgotado")

    def _item_gastado(self, item):
        """Indica si el item está repetido."""
        if not item in self._items:
	    # Item no ha sido usado
            return 0
	elif self._item_repeticiones(item) != 0:
	    # Quedan repeticiones por gastar
            return 0
	return 1
	
    def _item_repeticiones(self, item, coleccion = None):
        """Número de repeticiones restantes del item en esta conversación."""
	if item.repeticiones() == item.REPETICIONES_INFINITAS: return item.repeticiones()	
	if coleccion == None: coleccion = self._items
	
	apariciones = filter(lambda i: i == item, coleccion)
        num_usos = len (apariciones)
	if num_usos > item.repeticiones(): num_usos = item.repeticiones()
	return item.repeticiones() - num_usos
	

    def item_tema_especial(self, objetos, tipo):
        """Busca el item de tema especial más próximo y lo gasta"""
        for objeto in objetos:
            itemEncontrado = self._item_tema_especial(objeto, tipo)
            if itemEncontrado:
	        self._items_especiales.insert(0, itemEncontrado)
                return itemEncontrado
	# Error: debería haber encontrado el tema especial
	raise ValueError("Tema especial de tipo " + tipo + " no ha encontrado items." )
	return None

    def _item_tema_especial(self, objeto, tipo):
        """Encuentra un item satisfecho. Si no hay ninguno o el elegido está gastado, devuelve None"""
        # Control de error en el objeto pasado como parámetro
#        if not objeto.__dict__.has_key("_temas_especiales"):
#            return none
        
        temas = objeto.temas_especiales()[tipo]
        for tema in temas:
            items = filter (lambda i: i.satisfecho_en(self), tema.items())
            if items:
                item = random.choice(items)
		#Item especial gastado?
                if not item in self._items_especiales:
	            # Item no ha sido usado
                    return item
	        if self._item_repeticiones(item, self._items_especiales)!= 0:
	            # Quedan repeticiones por gastar
                    return item
        return None
        
    def temas_especiales(self):
        return self._temas_especiales
    
    
    def responder(self, personaje, comando, nombretema):
        """Encuentra los items que debe responder el PNJ para responder al tema preguntado"""

        nuevoItem = None
        resultado = {"especiales":[],"tema":None}
	self.activo = "si"
        self.pj_activo = personaje
	self._nombretema = nombretema
	self.estado_buscando()
        if not self._conversacion._sinonimos.has_key(nombretema):
            # Tema no encontrado
	    self.estado_no_encontrado()
            if len(self._items)>0:
                objetos = [self._items[0].tema().contexto()]
            else:
                objetos = []
            objetos.append(self._conversacion)
            objetos.append(self)
	    # Busca item de tema no encontrado en el último contexto visitado
            resultado["especiales"] = [self.item_tema_especial(objetos, "temaNoEncontrado")]
	
        if self.estado() == Visita.BUSCANDO:
            nombretema = self._conversacion._sinonimos[nombretema]
            self.pj_activo = personaje
            self._comando = comando
	    
            # Busca el tema en cada uno de los items pasados
            for i in range(len(self._items)):
                item = self._items[i]
                # Busca si el tema está repetido
                if item.tema().nombre() == nombretema:
                    nuevoItem = self.buscar_items(item.tema())
	            if self.estado() == Visita.AGOTADO:  resultado["especiales"] = [nuevoItem]
		    if self.estado() != Visita.BUSCANDO: break
                # Busca en los temas relacionados del item
                nombrecontexto_relacionado = item.relacionado(nombretema)
                if nombrecontexto_relacionado:
                    nuevoItem = self.buscar_items(self._conversacion.contexto(nombrecontexto_relacionado).tema(nombretema))
	            if self.estado() == Visita.AGOTADO:  resultado["especiales"] = [nuevoItem]
		    if self.estado() != Visita.BUSCANDO: break
	        # Busca en el mismo contexto del item, si el contexto está dentro de la tolerancia a offtopics
                if i < self._conversacion.tolerancia():
                    tema = item.tema().contexto().tema(nombretema)
                    if tema:
                        nuevoItem = self.buscar_items(tema)
        	        if self.estado() == Visita.AGOTADO:  resultado["especiales"] = [nuevoItem]
		        if self.estado() != Visita.BUSCANDO: break
        
        if self.estado() == Visita.BUSCANDO:
            # Busca el tema en toda la conversacion
            for contexto in self._conversacion.contextos():
                tema = contexto.tema(nombretema)
                if tema:
                    nuevoItem = self.buscar_items(tema)
		    if self.estado() == Visita.AGOTADO:
		        resultado["especiales"] = [nuevoItem]
			break
	            # Comprueba si el tema encontrado está offtopic
                    if self.estado() == Visita.ENCONTRADO:
                        if tema.contexto() not in self._conversacion._contextos_iniciales:
			    # Tema offtopic
			    self.estado_offtopic()
                            if len(self._items)>0:
                                objetos = [self._items[0].tema().contexto()]
                            else:
                                objetos = []
                            objetos.append(self._conversacion)
                            objetos.append(self)
			    # Busca item de tema offtopic en el último contexto visitado
                            resultado["especiales"] = [self.item_tema_especial(objetos, "temaOfftopic")]
                            #Test para impedir el cambio de contexto (y desactivar el nuevoItem)
			    #print self._conversacion.probabilidad_cancelacion()
			    dado = tirar_porcentaje()
			    if self._conversacion.probabilidad_cancelacion() > dado:
			        # Si la tirada es menor que el porcentaje de cancelacion, el offtopic cancela el tema
				nuevoItem = None
                        break
	
	if self.estado() == Visita.ENCONTRADO or self.estado() == Visita.OFFTOPIC:
	    resultado["tema"] = nuevoItem
	elif self.estado() == Visita.BUSCANDO:
            # Tema no encontrado
	    self.estado_no_encontrado()
            if len(self._items)>0:
                objetos = [self._items[0].tema().contexto()]
            else:
                objetos = []
            objetos.append(self._conversacion)
            objetos.append(self)
	    # Busca item de tema offtopic en el último contexto visitado
            resultado["especiales"] = [self.item_tema_especial(objetos, "temaNoEncontrado")]
        return resultado
    
    def comando(self):
        return self._comando
	
    def estado(self):
        return self._estado
	
    def estado_encontrado(self):
        self._estado = Visita.ENCONTRADO
	
    def estado_buscando(self):
        self._estado = Visita.BUSCANDO
	
    def estado_agotado(self):
        self._estado = Visita.AGOTADO
	
    def estado_offtopic(self):
        self._estado = Visita.OFFTOPIC
	
    def estado_no_encontrado(self):
        self._estado = Visita.NO_ENCONTRADO
	
    def items(self):
        return self._items

# Inicializa las respuestas por defecto
archivo = os.path.join(BloqueDialogo.DIR_DESC_DIALOGOS, "general", "general-texto_por_defecto" + BloqueDialogo.EXT_DESC_DIALOGOS)
#print archivo
stream = open(archivo)
doc_xml = minidom.parse(stream).documentElement
stream.close()

#temasEspecialesDefault: lista de respuestas por defecto
temasEspecialesDefault = parserXMLconversacion._parsear_temas_especiales(doc_xml)


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

def test():
    conversacion = Conversacion("posadero") 
    c = Visita(conversacion, None)
    while (1):
	texto = raw_input("? +preguntar por ")
	respuesta = c.responder(None, None, texto.encode('ISO-8859-1'))
        for item in respuesta:
            print str(item)
    
if (__name__ == '__main__'):
    test()
