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

"""Clase Conversacion."""


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

from utils import *
from bloquedialogo import *
from contexto import *

class Conversacion(BloqueDialogo):
    """Una coleccion de contextos accesibles durante un mismo dialogo.
    
    Contiene los siguientes atributos:
     - id (string): identifica univocamente la conversacion. No puede contener espacios.
     - version (string): versión del sistema de dialogos.
     - autor (string): nombre del autor de la descripción.
     - pnj (string): nombre del pnj al que pertenece la conversacion.
     - comentario (string): comentario explicativo del fichero de conversacion.

     - _tolerancia_offtopic(int): porcentaje de rechazo de temas fuera de contexto
     - _probabilidad_offtopic_continuar(float): probabilidad de que un offtopic permita o impida cambiar de tema

     - _sinonimos({string: string}): diccionario de conversión de sinonimos a los nombres de tema equivalentes
     - _contextos_iniciales([contexto]): contextos definidos con el atributo inicial='si'
     - _indice_contextos({string: contexto}): acceso a los contextos por identificador
     - _temas_especiales: diccionario con las lista de temas especiales por defecto para la conversación.
     
    

    Invariantes:
      id != None
      version == BloqueDialogo.Version
      autor != None
      pnj != None
      comentario != None
      
      _tolerancia_offtopic >= 0

      _sinonimos != None
      _contextos_iniciales != None
      _indice_contextos != None

      _sinonimos.has_key(s) <-> _sinonimos[s] == tema.nombre() \
                                    para algun tema in contexto.temas() \
                                    para algun contexto in self.contextos()
      -indice_contextos.has_key(n) <-> contexto.id == n para algun contexto in self._contextos
      -item.relacionados().has_key(t) <-> el contexto item.relacionado(nombretema)[t] contiene un tema con nombre t
    """


    def __init__(self, id):
	"""Crea una conversacion nueva a partir de un fichero de descripción de conversacion.
	
	Requiere:
	  id != None
	  id corresponde con un fichero existente
	"""
	
	self.id = id
	self.__inicializar_conversacion()


    def __inicializar_conversacion(self):
	"""Rellena todos los datos de la conversacion a partir de su fichero
	de descripcion.
	"""
	archivo = os.path.join(BloqueDialogo.DIR_DESC_DIALOGOS, self.id.split('-')[0], self.id + BloqueDialogo.EXT_DESC_DIALOGOS)
	stream = open(archivo)
	doc_xml = minidom.parse(stream).documentElement
	stream.close()

	self.__fecha_fichero = os.path.getmtime(archivo)
	self._sinonimos = {}

	self._limpiar_comentarios(doc_xml)
	self.__parsear_conversacion(doc_xml)
	self.__parsear_id(doc_xml)	
	self._tolerancia_offtopic = 1
	self._probabilidad_offtopic_continuar = 100
        
        especiales_xml = doc_xml.getElementsByTagName("temasEspeciales")
        if especiales_xml:
            self._temas_especiales = self._parsear_temas_especiales(especiales_xml[0])
	    offtopic = especiales_xml[0].getElementsByTagName("temaOfftopic")
	    if offtopic and offtopic[0].getAttribute("probabilidad"):
	        self._probabilidad_offtopic_continuar = string.atoi(offtopic[0].getAttribute("probabilidad_continuar"))
	    if offtopic and offtopic[0].getAttribute("tolerancia"):
	        self._tolerancia_offtopic = string.atoi(offtopic[0].getAttribute("tolerancia"))
        else:
            self._temas_especiales = {"temaAgotado": [], "temaNoEncontrado": [], "temaOfftopic" : []}
        
	self._contextos = self._parsear_contextos(doc_xml)
	self._indice_contextos = {}
        self.__crear_indices()
        self.__revisar_integridad(doc_xml)

        #Establece contextos iniciales
        self._contextos_iniciales = []
	for nodo_contexto in doc_xml.getElementsByTagName("contextos")[0].getElementsByTagName("contexto"):
            if nodo_contexto.getAttribute("inicial") == "si":
                self._contextos_iniciales.append(self.contexto(nodo_contexto.getAttribute("id")))
                filter(None, self._contextos_iniciales)
	escribir("Cargada con éxito la conversación " + archivo)

	
    def __parsear_conversacion(self, doc_xml):
	"""Extrae la información del elemento conversacion:
	    pnj: el personaje al que pertenece la conversacion
	    autor: el autor de la conversacion
	    version: la versión del tipo de conversacion"""
	version_desc_conversacion = doc_xml.attributes["version"].value.encode('ISO-8859-1')

        if version_desc_conversacion != BloqueDialogo.VERSION_DESC_DIALOGO:
	    raise ValueError("Version incorrecta del fichero descriptor de conversacion " + self.id + ": " +
	                     version_desc_conversacion + " cuando se esperaba " + BloqueDialogo.VERSION_DESC_DIALOGO)

	self.version = BloqueDialogo.VERSION_DIALOGO
	
	self.autor = doc_xml.attributes["autor"].value.encode('ISO-8859-1')
	self.pnj = doc_xml.attributes["pnj"].value.encode('ISO-8859-1')
	try:
	    self.comentario = doc_xml.attributes["comentario"].value.encode('ISO-8859-1')
	except:
	    self.comentario = "nada"
	

    def __parsear_id(self, doc_xml):
	"""Extrae el id de la conversacion"""
	id_fichero = doc_xml.getElementsByTagName("id")[0].firstChild.nodeValue.encode('ISO-8859-1')
	if id_fichero != self.id:
	    raise ValueError("El id no coincide con el nombre del fichero")
	
    
    def __crear_indices(self):
	"""Crea las estructuras índice de contextos e índice de sinónimos."""
        # Acelera la busqueda de contextos por nombre
	for contexto in self._contextos:
	    self._indice_contextos[contexto.id] = contexto
            
        # Acelera la busqueda y conversion de sinonimos
	for contexto in self._contextos:
	    for tema in contexto.temas():
		self._sinonimos[tema.nombre()] = tema.nombre()
                for sinonimo in tema._sinonimos:
                    self._sinonimos[sinonimo] = tema.nombre()
                
    def __revisar_integridad(self, doc_xml):
	"""Asegura la integridad de los índices y temas relacionados."""
        # Carga sinonimos adicionales definidos a nivel de conversacion, chequeando su integridad
	if doc_xml.getElementsByTagName("sinonimos"):
	    for nodo_tema in doc_xml.getElementsByTagName("sinonimos")[0].getElementsByTagName("tema"):
                nombretema = nodo_tema.getAttribute("nombre").encode('ISO-8859-1')
                #print str(nodo_tema) + " - " + str(nodo_tema.attributes.keys()) #getAttribute("nombre")
                # Comprueba si el tema existe
                if not self._sinonimos.has_key(nombretema):
                    escribir("Error de integridad en conversacion: definido sinonimo para tema '" + nombretema + "' no existente.")
                    continue
                # Completa el índice de sinónimos con los sinónimos definidos en el tema.
                for sinonimo in self._parsear_sinonimos(nodo_tema):
                    self._sinonimos[sinonimo] = nombretema

        # Comprueba la integridad de los temas relacionados de cada item
        for contexto in self._contextos:
            for tema  in  contexto.temas():
                for item   in   tema.items():
                    eliminarEnlaces = []
                    # Por cada enlace de cada item
                    for enlace in item.relacionados():
                        nombrecontexto = item.relacionados()[enlace]
                        if self._indice_contextos.has_key(nombrecontexto):
                            # Si el contexto destino no contiene un tema con el nombre enlazado...
                            if not self._indice_contextos[nombrecontexto].tema(enlace): # not enlace in map(lambda tema: tema.nombre(), self._indice_contextos[nombrecontexto].temas()):
                                eliminarEnlaces.append(enlace)
                                escribir("Error de integridad en conversacion: definido enlace para tema '" \
                                      + enlace + "' que no pertenece al contexto '" + nombrecontexto + "'.")
                            else:
                                pass
                        else:
                            eliminarEnlaces.append(enlace)
                            escribir("Error de integridad en conversacion: definido enlace para tema '" \
                                   + enlace + "' en el contexto '" + nombrecontexto + "' no existente.")
                    # Borra los enlaces incorrectos
                    for enlace in eliminarEnlaces:
                        del (item._relacionados[enlace])
    
    def contexto(self, nombrecontexto):
        """Devuelve el contexto con ese nombre."""
        return self._indice_contextos[nombrecontexto]

    def contextos(self):
        return self._contextos

    def temas_especiales(self):
        return self._temas_especiales

    def tolerancia(self):
        return self._tolerancia_offtopic

    def probabilidad_cancelacion(self):
        return 100 - self._probabilidad_offtopic_continuar
	
    def __str__(self):
        texto = '<Conversacion - "' + self.id
        texto = texto + '"\n\n  Contextos:\n'
        for contexto in self._contextos:
            if contexto in self._contextos_iniciales:
                texto = texto + "  (inicial)\n"
            texto = texto + str(contexto) + "\n"
        texto = texto + '  Temas especiales:\n'
        for tipo in self._temas_especiales:
            texto = texto + "\tTipo: " + tipo + "\n"
            for tema in self._temas_especiales[tipo]:
                tema._nombre = ''
                tema._sinonimos = []
                texto = texto + str(tema) + "\n"
        texto = texto + "/>\n"
        return texto
    
    


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

if (__name__ == '__main__'):
    ejemplo = Conversacion("tutora")
    print "\n" + str(ejemplo) + "\n"
    print ejemplo._contextos_iniciales
#    print "No hay prueba unitaria de módulo. "
